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 someTheissue-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.User Edits
After a request is initially validated, there is nothing stopping a user from editing the issue and submitting it with invalid inputs. You should run your validation logic any time the following events occur:
- The issue is opened
- The issue body is edited
- The issue is reopened after being closed
- The request is submitted for provisioning/creation
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
orpublic
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 namedRead 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
GitHub Token
If you want to run custom validators that access GitHub APIs, you will need to provide a value for the
github-token
input. This is a good scenario for GitHub App authentication!- 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
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:Input | Required | Options |
---|---|---|
Repository Name | ||
Visibility | private , public | |
Read Team | ||
Write Team | ||
Auto Init | true , false | |
Topics |
Field | Requirement |
---|---|
Repository Name | Must not be an existing repository |
Read Team | Must be a team in the organization |
Write Team | Must be a team in the organization |
Topics | Must 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 theValidated
state. Next, we're going to submit the request for approval.Continue to the next section