Validate

Once an issue has been parsed, it can be validated against any rules that you require. When used in public repositories, issue form templates do enforce some validation rules such as required fields, selection options, and more. However, you may have additional needs that apply to your specific use case. For example, if you are creating an IssueOps workflow for users to request membership to GitHub teams, the issue form template is not able to validate if a value provided by a user is in fact a team in your organization.e. When used in public repositories, issue form templates do enforce someThe issue-ops/validator action takes the parsed output of the issue body and validates it against the issue form template and any custom rules you define.

Basic validation

The most basic validation compares each input field to the rules specified in your issue form template and, if any are violated, comments with an error message.
1- name: Validate Issue
2  id: validate
3  uses: issue-ops/validator@vX.X.X
4  with:
5    issue-form-template: example-request.yml
6    parsed-issue-body: ${{ steps.parse.outputs.json }}
For example, if you have an input field for users to select the visibility of their new repository, you can specify that the field is required and only one of a list of options can be chosen.
1- type: dropdown
2  id: visibility
3  attributes:
4    label: Repository Visibility
5    description: The visibility of the repository.
6    multiple: false
7    options:
8      - private
9      - public
10  validations:
11    required: true
When run against an issue submitted with this template, the validator will comment on the issue with an error message if any of the following occur:
  • The field is empty
  • The field is missing from the issue body
  • An option other than private or public is present

Custom validation

For each form field, you can also specify custom validation logic. This is done using several files in your repository:
  • The validator configuration file (.github/validator/config.yml)
  • One or more validator scripts (.github/validator/<script-name>.js)

Configuration file

This file defines the mapping of validator scripts to form fields. For example, if your issue form has input fields named Read Team and Write Team, you can specify a validator script (check_team_exists.js) to run against those fields.
1validators:
2  - field: read_team
3    script: check_team_exists
4  - field: write_team
5    script: check_team_exists

Validator scripts

Validator scripts are run on the associated fields in the configuration file. The script must specify a default export of a function with the following behavior:
  • Accept inputs of the following types:
    • Input and Textarea: string
    • Dropdown: string[]
    • Checkboxes: { label: string; required: boolean }
  • Return 'success' for successful validation
  • Return an error message (string) for unsuccessful validation
The following is an example of a validator script that checks if a team exists. This can also be found in the issue-ops/validator repository.
1module.exports = async (field) => {
2  if (typeof field !== 'string') return 'Field type is invalid'
3
4  const { getOctokit } = require('@actions/github')
5  const core = require('@actions/core')
6  const octokit = getOctokit(core.getInput('github-token', { required: true }))
7
8  try {
9    await octokit.rest.teams.getByName({
10      org: process.env.ORGANIZATION ?? '',
11      team_slug: field
12    })
13
14    core.info(`Team '${field}' exists`)
15    return 'success'
16  } catch (error) {
17    if (error.status === 404) {
18      core.error(`Team '${field}' does not exist`)
19      return `Team '${field}' does not exist`
20    }
21  }
22}

New repository request

Recall from the issue form template that the new repository request expects the following inputs:
InputRequiredOptions
Repository Name
Visibilityprivate, public
Read Team
Write Team
Auto Inittrue, false
Topics
Since the Visibility and Auto Init inputs must be one of several predefined values, they can be handled by basic validation. The other fields, however, must meet additional requirements:
FieldRequirement
Repository NameMust not be an existing repository
Read TeamMust be a team in the organization
Write TeamMust be a team in the organization
TopicsMust be a list of 20 or fewer
Must be lowercase
Must be 50 or fewer characters
Must contain only letters, numbers, and hyphens

Create a configuration file

In order to configure custom validation, first create a configuration file in the repository.
1# File Path: .github/validator/config.yml
2
3validators:
4  - field: repository_name
5    script: repo_doesnt_exist
6  - field: read_team
7    script: team_exists
8  - field: write_team
9    script: team_exists
10  - field: topics
11    script: topics_valid

Create validator scripts

The following scripts can be used to validate the new repository request.First, create a script to check if a repository already exists.
1// File Path: .github/validator/repo_doesnt_exist.js
2
3module.exports = async (field) => {
4  if (typeof field !== 'string') return 'Field type is invalid'
5
6  const { getOctokit } = require('@actions/github')
7  const core = require('@actions/core')
8  const octokit = getOctokit(core.getInput('github-token', { required: true }))
9
10  try {
11    // This should throw a 404 error
12    await octokit.rest.repos.get({
13      org: '<org-name>',
14      repo: field
15    })
16
17    core.error(`Repository '${field}' already exists`)
18    return `Repository '${field}' already exists`
19  } catch (error) {
20    if (error.status === 404) {
21      core.info(`Repository '${field}' does not exist`)
22      return 'success'
23    }
24  }
25}
Next, create a script to check if a team exists.
1// File Path: .github/validator/team_exists.js
2
3module.exports = async (field) => {
4  if (typeof field !== 'string') return 'Field type is invalid'
5
6  const { getOctokit } = require('@actions/github')
7  const core = require('@actions/core')
8  const octokit = getOctokit(core.getInput('github-token', { required: true }))
9
10  try {
11    await octokit.rest.teams.getByName({
12      org: process.env.ORGANIZATION ?? '',
13      team_slug: field
14    })
15
16    core.info(`Team '${field}' exists`)
17    return 'success'
18  } catch (error) {
19    if (error.status === 404) {
20      core.error(`Team '${field}' does not exist`)
21      return `Team '${field}' does not exist`
22    }
23  }
24}
Finally, create a script to check if the topics are valid.
1// File Path: .github/validator/topics_valid.js
2
3module.exports = async (field) => {
4  if (typeof field !== 'string') return 'Field type is invalid'
5
6  const topics = field.split(/[\r\n]+/)
7
8  if (topics.length > 20)
9    return `There are ${request.topics.length} topics (max: 20)`
10
11  const invalidTopics = []
12  for (const topic of topics) {
13    if (
14      topic !== topic.toLowerCase() ||
15      topic.length > 50 ||
16      !topic.match(/^[a-z0-9-]+$/)
17    )
18      invalidTopics.push(topic)
19  }
20
21  if (invalidTopics.length > 0)
22    return `The following topics are invalid: ${JSON.stringify(invalidTopics)}`
23}

Update the workflow

Now that issue validation has been configured, you can add it as a step to your workflow. Additional updates are noted with comments.
1name: Issue Opened/Edited/Reopened
2
3on:
4  issues:
5    types:
6      - opened
7      - edited
8      - reopened
9
10jobs:
11  new-repository-request:
12    name: New Repository Request
13    runs-on: ubuntu-latest
14
15    if: contains(github.event.issue.labels.*.name, 'issueops:new-repository')
16
17    # Since the validation step involves adding comments to issues, you will
18    # need to give it write permissions. If you are using a GitHub App to call
19    # other GitHub APIs, you will also need to add the appropriate permissions.
20    permissions:
21      contents: read
22      id-token: write
23      issues: write
24
25    steps:
26      # Always remove the validated/submitted labels first! This ensures that
27      # the validation logic runs any time the issue body is edited. It also
28      # ensures the issue must be re-submitted after editing.
29      - name: Remove Labels
30        id: remove-label
31        uses: issue-ops/labeler@vX.X.X
32        with:
33          action: remove
34          issue_number: ${{ github.event.issue.number }}
35          labels: |
36            issueops:validated
37            issueops:submitted
38
39      # If your validation script checks things beyond the scope of the
40      # repository it is running in, you will need to create a GitHub App and
41      # generate an installation access token in your workflow.
42      - name: Get App Token
43        id: token
44        uses: actions/create-github-app-token@vX.X.X
45        with:
46          app_id: ${{ secrets.MY_GITHUB_APP_ID }}
47          private_key: ${{ secrets.MY_GITHUB_APP_PEM }}
48          owner: ${{ github.repository_owner }}
49
50      - name: Checkout
51        id: checkout
52        uses: actions/checkout@vX.X.X
53
54      # Install Node.js so the workflow can add npm packages that are used by
55      # the custom validator scripts (e.g. '@octokit/rest').
56      - name: Setup Node.js
57        id: setup-node
58        uses: actions/setup-node@vX.X.X
59        with:
60          node-version-file: .node-version
61          cache: npm
62
63      # Install NPM packages needed by the validator scripts.
64      - name: Install Packages
65        id: npm
66        run: npm ci
67
68      - name: Parse Issue
69        id: parse
70        uses: issue-ops/parser@vX.X.X
71        with:
72          body: ${{ github.event.issue.body }}
73          issue-form-template: new-repository-request.yml
74
75      # Add a step to validate the issue.
76      - name: Validate Issue
77        id: validate
78        uses: issue-ops/validator@vX.X.X
79        with:
80          issue-form-template: new-repository-request.yml
81          github-token: ${{ steps.token.outputs.token }}
82          parsed-issue-body: ${{ steps.parse.outputs.json }}
83
84      # Add a label to mark the request as validated.
85      - if: ${{ steps.validate.outputs.result == 'success' }}
86        name: Add Validated Label
87        id: add-label
88        uses: issue-ops/labeler@vX.X.X
89        with:
90          action: add
91          issue_number: ${{ github.event.issue.number }}
92          labels: |
93            issueops:validated

Next steps

Congratulations! Your request has been successfully transitioned to the Validated state. Next, we're going to submit the request for approval.Continue to the next section