Validate
- Alpha
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.
The 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.
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 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.
- name: Validate Issue
id: validate
uses: issue-ops/validator@vX.X.X
with:
issue-form-template: example-request.yml
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 option can be chosen.
- type: dropdown
id: visibility
attributes:
label: Repository Visibility
description: The visibility of the repository.
multiple: false
options:
- private
- public
validations:
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:
private
or public
is presentFor each form field, you can also specify custom validation logic. This is done using several files in your repository:
.github/validator/config.yml
).github/validator/<script-name>.js
)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.
validators:
- field: read_team
script: check_team_exists
- field: write_team
script: check_team_exists
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 another good scenario
for GitHub App authentication!
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:
string
(Input and Textarea)string[]
(Dropdown){ label: string; required: boolean }
(Checkboxes)'success'
for successful validationstring
) for unsuccessful validationThe 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.
module.exports = async (field) => {
if (typeof field !== 'string') return 'Field type is invalid'
const { getOctokit } = require('@actions/github')
const core = require('@actions/core')
const octokit = getOctokit(core.getInput('github-token', { required: true }))
try {
await octokit.rest.teams.getByName({
org: process.env.ORGANIZATION ?? '',
team_slug: field
})
core.info(`Team '${field}' exists`)
return 'success'
} catch (error) {
if (error.status === 404) {
core.error(`Team '${field}' does not exist`)
return `Team '${field}' does not exist`
}
}
}
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 | ❌ |
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:
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 |
Each topic must be lowercase | |
Each topic must be 50 or fewer characters | |
Each topic must contain only letters, numbers, and hyphens |
In order to configure custom validation, first create a configuration file in the repository.
File path: .github/validator/config.yml
validators:
- field: repository_name
script: repo_doesnt_exist
- field: read_team
script: team_exists
- field: write_team
script: team_exists
- field: topics
script: topics_valid
The following scripts can be used to validate the new repository request.
.github/validator/repo_doesnt_exist.js
module.exports = async (field) => {
if (typeof field !== 'string') return 'Field type is invalid'
const { getOctokit } = require('@actions/github')
const core = require('@actions/core')
const octokit = getOctokit(core.getInput('github-token', { required: true }))
try {
// This should throw a 404 error
await octokit.rest.repos.get({
org: '<org-name>',
repo: field
})
core.error(`Repository '${field}' already exists`)
return `Repository '${field}' already exists`
} catch (error) {
if (error.status === 404) {
core.info(`Repository '${field}' does not exist`)
return 'success'
}
}
}
.github/validator/team_exists.js
module.exports = async (field) => {
if (typeof field !== 'string') return 'Field type is invalid'
const { getOctokit } = require('@actions/github')
const core = require('@actions/core')
const octokit = getOctokit(core.getInput('github-token', { required: true }))
try {
await octokit.rest.teams.getByName({
org: process.env.ORGANIZATION ?? '',
team_slug: field
})
core.info(`Team '${field}' exists`)
return 'success'
} catch (error) {
if (error.status === 404) {
core.error(`Team '${field}' does not exist`)
return `Team '${field}' does not exist`
}
}
}
.github/validator/topics_valid.js
For details about the requirements for repository topics, see About topics.
module.exports = async (field) => {
if (typeof field !== 'string') return 'Field type is invalid'
const topics = field.split(/[\r\n]+/)
if (topics.length > 20)
return `There are ${request.topics.length} topics (max: 20)`
const invalidTopics = []
for (const topic of topics) {
if (
topic !== topic.toLowerCase() ||
topic.length > 50 ||
!topic.match(/^[a-z0-9-]+$/)
)
invalidTopics.push(topic)
}
if (invalidTopics.length > 0)
return `The following topics are invalid: ${JSON.stringify(invalidTopics)}`
}
Now that issue validation has been configured, you can add it as a step to your workflow. Additional updates are noted with comments.
name: Issue Opened/Edited/Reopened
on:
issues:
types:
- opened
- edited
- reopened
jobs:
new-repository-request:
name: New Repository Request
runs-on: ubuntu-latest
if: contains(github.event.issue.labels.*.name, 'issueops:new-repository')
# Since the validation step involves adding comments to issues, you will
# need to give it write permissions. If you are using a GitHub App to call
# other GitHub APIs, you will also need to add the appropriate permissions.
permissions:
contents: read
id-token: write
issues: write
steps:
# Always remove the validated/submitted labels first! This ensures that
# the validation logic runs any time the issue body is edited. It also
# ensures the issue must be re-submitted after editing.
- name: Remove Labels
id: remove-label
uses: issue-ops/labeler@vX.X.X
with:
action: remove
issue_number: ${{ github.event.issue.number }}
labels: |
issueops:validated
issueops:submitted
# If your validation script checks things beyond the scope of the
# repository it is running in, you will need to create a GitHub App and
# generate an installation access token in your workflow.
- name: Get App Token
id: token
uses: actions/create-github-app-token@vX.X.X
with:
app_id: ${{ secrets.MY_GITHUB_APP_ID }}
private_key: ${{ secrets.MY_GITHUB_APP_PEM }}
owner: ${{ github.repository_owner }}
- name: Checkout
id: checkout
uses: actions/checkout@vX.X.X
# Install Node.js so the workflow can add npm packages that are used by
# the custom validator scripts (e.g. '@octokit/rest').
- name: Setup Node.js
id: setup-node
uses: actions/setup-node@vX.X.X
with:
node-version-file: .node-version
cache: npm
# Install NPM packages needed by the validator scripts.
- name: Install Packages
id: npm
run: npm ci
- name: Parse Issue
id: parse
uses: issue-ops/parser@vX.X.X
with:
body: ${{ github.event.issue.body }}
issue-form-template: new-repository-request.yml
# Add a step to validate the issue.
- name: Validate Issue
id: validate
uses: issue-ops/validator@vX.X.X
with:
issue-form-template: new-repository-request.yml
github-token: ${{ steps.token.outputs.token }}
parsed-issue-body: ${{ steps.parse.outputs.json }}
# Add a label to mark the request as validated.
- if: ${{ steps.validate.outputs.result == 'success' }}
name: Add Validated Label
id: add-label
uses: issue-ops/labeler@vX.X.X
with:
action: add
issue_number: ${{ github.event.issue.number }}
labels: |
issueops:validated
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 to learn how to submit the request for approval by an authorized user.