Desenvolvimento de software está repleto de tarefas repetitivas: gerenciar questões, lidar com aprovações e acionar fluxos de trabalho de CI/CD. Mas e se você pudesse automatizar esses tipos de tarefas diretamente dentro do GitHub Issues? Essa é a promessa de IssueOps com GitHub Actions, uma metodologia que transforma o GitHub Issues em um centro de comando para automação.
Seja você um desenvolvedor solo ou parte de uma equipe de engenharia, o IssueOps com GitHub Actions ajuda você a otimizar as operações sem nunca sair do seu repositório. Este artigo explora o conceito de IssueOps, usando terminologia de máquina de estado e estratégias para ajudá-lo a trabalhar de forma mais eficiente no GitHub.
O que é IssueOps?
IssueOps é a prática de usar o GitHub Issues, GitHub Actions e pull requests (PR) como uma interface para automatizar fluxos de trabalho. Em vez de alternar entre ferramentas ou acionar ações manualmente, você pode usar comentários de issues, rótulos e mudanças de estado para iniciar pipelines de CI/CD, atribuir tarefas e até mesmo implantar aplicativos.
Assim como os vários outros paradigmas *Ops (ChatOps, ClickOps e assim por diante), IssueOps com GitHub Actions é uma coleção de ferramentas, fluxos de trabalho e conceitos que, quando aplicados ao GitHub Issues, podem automatizar tarefas mundanas e repetitivas. A flexibilidade e o poder dos issues, juntamente com seu relacionamento com os pull requests, criam um número quase ilimitado de possibilidades, como gerenciar aprovações e implantações. Tudo isso pode realmente ajudar a simplificar seus fluxos de trabalho no GitHub.
É importante notar que IssueOps não é apenas uma coisa de DevOps! Onde DevOps oferece uma metodologia para aproximar desenvolvedores e operações, IssueOps é uma prática de automação de fluxo de trabalho centrada no GitHub Issues. IssueOps com GitHub Actions permite que você execute qualquer coisa, desde pipelines de CI/CD complexos até um sistema de reservas de pousadas. Se você pode interagir com ele através de uma API, há uma boa chance de você poder construí-lo com IssueOps!
Por que usar IssueOps?
Existem muitos benefícios em utilizar IssueOps. Veja como é útil na prática:
* É orientado por eventos, então você pode automatizar as coisas chatas: IssueOps com GitHub Actions permite que você automatize fluxos de trabalho diretamente do GitHub Issues e pull requests, transformando interações cotidianas — desde iniciar um pipeline de CI/CD e gerenciar aprovações até atualizar quadros de projetos — em gatilhos poderosos para GitHub Actions.
* É customizável, então você pode adaptar os fluxos de trabalho às suas necessidades: Não existem duas equipes que trabalham da mesma forma, e IssueOps é flexível o suficiente para se adaptar. Seja você automatizando a triagem de bugs ou acionando implantações, você pode personalizar os fluxos de trabalho com base no tipo de evento e nos dados fornecidos.
* É transparente, então você pode manter um registro: Todas as ações tomadas em um issue são registradas em sua linha do tempo, criando um registro fácil de seguir do que aconteceu e quando.
* É imutável, então você pode fazer uma auditoria sempre que precisar: Como o IssueOps com GitHub Actions usa o GitHub Issues e os pull requests como fonte da verdade, cada ação deixa um registro. Chega de correr atrás de aprovações no Slack ou acionar manualmente fluxos de trabalho: IssueOps mantém tudo estruturado, automatizado e auditável diretamente dentro do GitHub.
Para começar a usar IssueOps, siga este guia rápido:
1. Defina seus gatilhos: Identifique as ações que devem iniciar seus fluxos de trabalho, como abrir um issue, adicionar um rótulo ou mesclar um pull request. Esses eventos podem servir como gatilhos para GitHub Actions.
2. Configure o GitHub Actions: Use o GitHub Actions para definir o que acontece quando um evento ocorre. Por exemplo, se um issue é rotulado como “deploy”, você pode acionar um script de implantação.
3. Teste e itere: Como qualquer boa automação, os fluxos de trabalho de IssueOps devem ser testados e refinados. Comece pequeno, veja o que funciona e expanda a partir daí.
Definindo fluxos de trabalho IssueOps e como eles são como máquinas de estado finito
A maioria dos fluxos de trabalho IssueOps seguem o mesmo padrão básico:
1. Um usuário abre um issue e fornece informações sobre uma solicitação.
2. O issue é validado para garantir que contenha as informações necessárias.
3. O issue é enviado para processamento.
4. A aprovação é solicitada de um usuário ou equipe autorizada.
5. A solicitação é processada e o issue é fechado.
Suponha que você seja um administrador de uma organização e queira reduzir a sobrecarga de gerenciamento de membros da equipe. Nesse caso, você pode usar o IssueOps com GitHub Actions para construir um processo automatizado de solicitação e aprovação de associação. Dentro de um fluxo de trabalho como este, você teria várias etapas principais:
1. Um usuário cria uma solicitação para ser adicionado a uma equipe.
2. A solicitação é validada.
3. A solicitação é enviada para aprovação.
4. Um administrador aprova ou nega esta solicitação.
5. A solicitação é processada:
* Se aprovado, o usuário é adicionado à equipe.
* Se negado, o usuário não é adicionado à equipe.
6. O usuário é notificado do resultado.
Ao projetar seus próprios fluxos de trabalho IssueOps, pode ser muito útil pensar neles como uma máquina de estado finito: um modelo para como os objetos se movem através de uma série de estados em resposta a eventos externos. Dependendo de certas regras definidas dentro da máquina de estado, várias ações diferentes podem ocorrer em resposta a mudanças de estado. Se isso for um pouco complexo demais, você também pode pensar nisso como um fluxograma.
Para aplicar esta comparação ao IssueOps, um issue é o objeto que é processado por uma máquina de estado. Ele muda de estado em resposta a eventos. Conforme o objeto muda de estado, certas ações podem ser executadas como parte de uma transição, desde que quaisquer condições necessárias (guards) sejam atendidas. Uma vez que um estado final é alcançado, o issue pode ser fechado.
Isso se divide em alguns conceitos-chave:
* Estado: Um ponto no ciclo de vida de um objeto que satisfaz certas condições.
* Evento: Uma ocorrência externa que aciona uma mudança de estado.
* Transição: Um elo entre dois estados que, quando atravessado por um objeto, fará com que certas ações sejam executadas.
* Ação: Uma tarefa atômica que é executada quando uma transição é feita.
* Guard: Uma condição que é avaliada quando um evento de gatilho ocorre. Uma transição é feita apenas se todas as condições de guard associadas forem atendidas.
Conceitos-chave por trás de máquinas de estado
O benefício de dividir seu fluxo de trabalho nesses componentes é que você pode procurar por casos extremos, impor condições e criar um resultado robusto e confiável.
Estados
Dentro de uma máquina de estado, um estado define o status atual de um objeto. Conforme o objeto transita pela máquina de estado, ele mudará de estado em resposta a eventos externos. Ao construir fluxos de trabalho IssueOps, os estados comuns para issues incluem aberto, enviado, aprovado, negado e fechado.
Estes devem ser suficientes como os estados principais a serem considerados ao construir nossos fluxos de trabalho em nosso exemplo de associação de equipe acima.
Eventos
Em uma máquina de estado, um evento pode ser qualquer forma de interação com o objeto e seu estado atual. Ao construir seu próprio IssueOps, você deve considerar eventos tanto do ponto de vista do usuário quanto do GitHub.
Em nosso exemplo de solicitação de associação de equipe, existem vários eventos que podem acionar uma mudança de estado. A solicitação pode ser criada, enviada, aprovada, negada ou processada.
Neste exemplo, um usuário interagindo com um issue — como adicionar rótulos, comentar ou atualizar marcos — também pode mudar seu estado. No GitHub Actions, existem muitos eventos que podem acionar seus fluxos de trabalho.
Aqui estão algumas interações, ou eventos, que afetariam nosso exemplo de fluxo de trabalho IssueOps quando se trata de gerenciar membros da equipe:
| Solicitação | Evento | Estado |
| :—————- | :———— | :——- |
| Solicitação criada | `issues` | `opened` |
| Solicitação aprovada | `issue_comment` | `created`|
| Solicitação negada | `issue_comment` | `created`|
Como você pode ver, o mesmo gatilho de fluxo de trabalho do GitHub pode se aplicar a vários eventos em nossa máquina de estado. Por causa disso, a validação é fundamental. Dentro de seus fluxos de trabalho, você deve verificar tanto o tipo de evento quanto as informações fornecidas pelo usuário. Neste caso, podemos acionar condicionalmente diferentes etapas do fluxo de trabalho com base no conteúdo do evento `issue_comment`.
“`
jobs:
approve:
name: Process Approval
runs-on: ubuntu-latest
if: ${{ startsWith(github.event.comment.body, ‘.approve’) }}
# …
deny:
name: Process Denial
runs-on: ubuntu-latest
if: ${{ startsWith(github.event.comment.body, ‘.deny’) }}
# …
“`
Transições
Uma transição é simplesmente a mudança de um estado para outro. Em nosso exemplo, por exemplo, uma transição ocorre quando alguém abre um issue. Quando uma solicitação atende a certas condições, ou guards, a mudança de estado pode ocorrer. Quando a transição ocorre, algumas ações ou processamento também podem ocorrer.
Com nosso exemplo de fluxo de trabalho, você pode pensar nas transições em si como as linhas conectando diferentes nós no diagrama de estado. Ou as linhas conectando caixas em um fluxograma.
Guards
Guards são condições que devem ser verificadas antes que um evento possa acionar uma transição para um estado diferente. Em nosso caso, sabemos que os seguintes guards devem estar em vigor:
* Uma solicitação não deve transitar para um estado Aprovado a menos que um administrador comente `.approve` no issue.
* Uma solicitação não deve transitar para um estado Negado a menos que um administrador comente `.deny` no issue.
E depois que a solicitação é aprovada e o usuário é adicionado à equipe? Isso é referido como uma transição não protegida. Não há condições que devem ser atendidas, então a transição acontece imediatamente!
Ações
Por fim, ações são tarefas específicas que são executadas durante uma transição. Elas podem afetar o objeto em si, mas este não é um requisito em nossa máquina de estado. Em nosso exemplo, as seguintes ações podem ocorrer em momentos diferentes:
* Os administradores são notificados de que uma solicitação foi enviada.
* O usuário é adicionado à equipe solicitada.
* O usuário é notificado do resultado.
Construindo um fluxo de trabalho de associação de equipe com IssueOps
Agora que toda a explicação está fora do caminho, vamos mergulhar na construção do nosso exemplo! Para referência, vamos nos concentrar nos fluxos de trabalho do GitHub Actions envolvidos na construção desta automação. Existem algumas configurações adicionais de repositório e permissões envolvidas que são discutidas em mais detalhes nestes documentos do IssueOps.
Passo 1: Modelo de formulário de issue
Os formulários de issue do GitHub permitem que você crie issues padronizados e formatados com base em um conjunto de campos de formulário. Combinado com a ação issue-ops/parser, você pode obter JSON confiável e legível por máquina a partir do Markdown do corpo do issue. Para nosso exemplo, vamos criar um formulário simples que aceita uma única entrada: a equipe onde queremos adicionar o usuário.
“`
name: Team Membership Request
description: Submit a new membership request
title: New Team Membership Request
labels:
– team-membership
body:
– type: input
id: team
attributes:
label: Team Name
description: The team name you would like to join
placeholder: my-team
validations:
required: true
“`
Quando os issues são criados usando este formulário, eles serão analisados em JSON, que pode então ser passado para o resto do fluxo de trabalho IssueOps.
“`
{
“team”: “my-team”
}
“`
Passo 2: Validação de issue
Com um corpo de issue legível por máquina, podemos executar verificações de validação adicionais para garantir que as informações fornecidas sigam quaisquer regras que possamos ter em vigor. Por exemplo, não podemos adicionar automaticamente um usuário a uma equipe se a equipe ainda não existir! É aí que a ação issue-ops/validator entra em jogo. Usando um modelo de formulário de issue e um script de validação personalizado, podemos confirmar a existência da equipe com antecedência.
“`javascript
module.exports = async (field) => {
const { Octokit } = require(‘@octokit/rest’)
const core = require(‘@actions/core’)
const github = new Octokit({
auth: core.getInput(‘github-token’, { required: true })
})
try {
// Check if the team exists
core.info(`Checking if team ‘${field}’ exists`)
await github.rest.teams.getByName({
org: process.env.GITHUB_REPOSITORY_OWNER ?? ”,
team_slug: field
})
core.info(`Team ‘${field}’ exists`)
return ‘success’
} catch (error) {
if (error.status === 404) {
// If the team does not exist, return an error message
core.error(`Team ‘${field}’ does not exist`)
return `Team ‘${field}’ does not exist`
} else {
// Otherwise, something else went wrong…
throw error
}
}
}
“`
Quando incluído em nosso fluxo de trabalho IssueOps, isso adiciona quaisquer erros de validação ao comentário no issue.
Passo 3: Fluxos de trabalho de issue
O principal “ponto de entrada” deste fluxo de trabalho ocorre quando um usuário cria ou edita seu issue de solicitação de associação de equipe. Este fluxo de trabalho deve se concentrar fortemente na validação de quaisquer entradas do usuário! Por exemplo, o que deve acontecer se o usuário inserir uma equipe que não existe?
Em nossa máquina de estado, este fluxo de trabalho é responsável por lidar com tudo até o estado opened. Sempre que um issue é criado, editado ou atualizado, ele irá re-executar a validação para garantir que a solicitação esteja pronta para ser processada. Neste caso, uma condição de guard adicional é introduzida. Antes que a solicitação possa ser enviada, o usuário deve comentar com `.submit` após a validação ter sido aprovada.
“`
name: Process Issue Open/Edit
on:
issues:
types:
– opened
– edited
– reopened
permissions:
contents: read
id-token: write
issues: write
jobs:
validate:
name: Validate Request
runs-on: ubuntu-latest
# This job should only be run on issues with the `team-membership` label.
if: ${{ contains(github.event.issue.labels.*.name, ‘team-membership’) }}
steps:
# This is required to ensure the issue form template and any validation
# scripts are included in the workspace.
– name: Checkout
id: checkout
uses: actions/checkout@v4
# Since this workflow includes custom validation scripts, we need to
# install Node.js and any dependencies.
– name: Setup Node.js
id: setup-node
uses: actions/setup-node@v4
# Install dependencies from `package.json`.
– name: Install Dependencies
id: install
run: npm install
# GitHub App authentication is required if you want to interact with any
# resources outside the scope of the repository this workflow runs in.
– name: Get GitHub App Token
id: token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.ISSUEOPS_APP_ID }}
private-key: ${{ secrets.ISSUEOPS_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
# Remove any labels and start fresh. This is important because the
# issue may have been closed and reopened.
– name: Remove Labels
id: remove-label
uses: issue-ops/labeler@v2
with:
action: remove
github_token: ${{ steps.token.outputs.token }}
labels: |
validated
approved
denied
issue_number: ${{ github.event.issue.number }}
repository: ${{ github.repository }}
# Parse the issue body into machine-readable JSON, so that it can be
# processed by the rest of the workflow.
– name: Parse Issue Body
id: parse
uses: issue-ops/parser@v4
with:
body: ${{ github.event.issue.body }}
issue-form-template: team-membership.yml
workspace: ${{ github.workspace }}
# Validate early and often! Validation should be run any time an issue is
# interacted with, to ensure that any changes to the issue body are valid.
– name: Validate Request
id: validate
uses: issue-ops/validator@v3
with:
add-comment: true
github-token: ${{ steps.token.outputs.token }}
issue-form-template: team-membership.yml
issue-number: ${{ github.event.issue.number }}
parsed-issue-body: ${{ steps.parse.outputs.json }}
workspace: ${{ github.workspace }}
# If validation passes, add the validated label to the issue.
– if: ${{ steps.validate.outputs.result == ‘success’ }}
name: Add Validated Label
id: add-label
uses: issue-ops/labeler@v2
with:
action: add
github_token: ${{ steps.token.outputs.token }}
labels: |
validated
issue_number: ${{ github.event.issue.number }}
repository: ${{ github.repository }}
# The `issue-ops/validator` action will automatically notify the user that
# the request was validated. However, you can optionally add instruction
# on what to do next.
– if: ${{ steps.validate.outputs.result == ‘success’ }}
name: Notify User (Success)
id: notify-success
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.issue.number }}
body: |
Hello! Your request has been validated successfully!
Please comment with `.submit` to submit this request.
“`
Passo 4: Fluxos de trabalho de comentário de issue
Uma vez que o issue é criado, qualquer processamento adicional é acionado usando comentários de issue — e isso pode ser feito com um fluxo de trabalho. No entanto, para tornar as coisas um pouco mais fáceis de seguir, vamos dividir isso em alguns fluxos de trabalho separados.
Fluxo de trabalho de envio
O primeiro fluxo de trabalho lida com o usuário enviando a solicitação. A principal tarefa que ele executa é validar o corpo do issue contra o modelo de formulário para garantir que ele não tenha sido modificado.
“`
name: Process Submit Comment
on:
issue_comment:
types:
– created
permissions:
contents: read
id-token: write
issues: write
jobs:
submit:
name: Submit Request
runs-on: ubuntu-latest
# This job should only be run when the following conditions are true:
#
# – A user comments `.submit` on the issue.
# – The issue has the `team-membership` label.
# – The issue has the `validated` label.
# – The issue does not have the `approved` or `denied` labels.
# – The issue is open.
if: |
startsWith(github.event.comment.body, ‘.submit’) &&
contains(github.event.issue.labels.*.name, ‘team-membership’) == true &&
contains(github.event.issue.labels.*.name, ‘approved’) == false &&
contains(github.event.issue.labels.*.name, ‘denied’) == false &&
github.event.issue.state == ‘open’
steps:
# First, we are going to re-run validation. This is important because
# the issue body may have changed since the last time it was validated.
# This is required to ensure the issue form template and any validation
# scripts are included in the workspace.
– name: Checkout
id: checkout
uses: actions/checkout@v4
# Since this workflow includes custom validation scripts, we need to
# install Node.js and any dependencies.
– name: Setup Node.js
id: setup-node
uses: actions/setup-node@v4
# Install dependencies from `package.json`.
– name: Install Dependencies
id: install
run: npm install
# GitHub App authentication is required if you want to interact with any
# resources outside the scope of the repository this workflow runs in.
– name: Get GitHub App Token
id: token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.ISSUEOPS_APP_ID }}
private-key: ${{ secrets.ISSUEOPS_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
# Remove the validated label. This will be re-added if validation passes.
– name: Remove Validated Label
id: remove-label
uses: issue-ops/labeler@v2
with:
action: remove
github_token: ${{ steps.token.outputs.token }}
labels: |
validated
issue_number: ${{ github.event.issue.number }}
repository: ${{ github.repository }}
# Parse the issue body into machine-readable JSON, so that it can be
# processed by the rest of the workflow.
– name: Parse Issue Body
id: parse
uses: issue-ops/parser@v4
with:
body: ${{ github.event.issue.body }}
issue-form-template: team-membership.yml
workspace: ${{ github.workspace }}
# Validate early and often! Validation should be run any time an issue is
# interacted with, to ensure that any changes to the issue body are valid.
– name: Validate Request
id: validate
uses: issue-ops/validator@v3
with:
add-comment: false # Don’t add another validation comment.
github-token: ${{ steps.token.outputs.token }}
issue-form-template: team-membership.yml
issue-number: ${{ github.event.issue.number }}
parsed-issue-body: ${{ steps.parse.outputs.json }}
workspace: ${{ github.workspace }}
# If validation passed, add the validated and submitted labels to the issue.
– if: ${{ steps.validate.outputs.result == ‘success’ }}
name: Add Validated Label
id: add-label
uses: issue-ops/labeler@v2
with:
action: add
github_token: ${{ steps.token.outputs.token }}
labels: |
validated
submitted
issue_number: ${{ github.event.issue.number }}
repository: ${{ github.repository }}
# If validation succeeded, alert the administrator team so they can
# approve or deny the request.
– if: ${{ steps.validate.outputs.result == ‘success’ }}
name: Notify Admin (Success)
id: notify-success
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.issue.number }}
body: |
ready for your review. Please comment with `.approve` or `.deny`
to approve or deny this request.
“`
Fluxo de trabalho de negação
Se a solicitação for negada, o usuário deve ser notificado e o issue deve ser fechado.
“`
name: Process Denial Comment
on:
issue_comment:
types:
– created
permissions:
contents: read
id-token: write
issues: write
jobs:
submit:
name: Deny Request
runs-on: ubuntu-latest
# This job should only be run when the following conditions are true:
#
# – A user comments `.deny` on the issue.
# – The issue has the `team-membership` label.
# – The issue has the `validated` label.
# – The issue has the `submitted` label.
# – The issue does not have the `approved` or `denied` labels.
# – The issue is open.
if: |
startsWith(github.event.comment.body, ‘.deny’) &&
contains(github.event.issue.labels.*.name, ‘team-membership’) == true &&
contains(github.event.issue.labels.*.name, ‘submitted’) == true &&
contains(github.event.issue.labels.*.name, ‘validated’) == true &&
contains(github.event.issue.labels.*.name, ‘approved’) == false &&
contains(github.event.issue.labels.*.name, ‘denied’) == false &&
github.event.issue.state == ‘open’
steps:
# This time, we do not need to re-run validation because the request is
# being denied. It can just be closed.
# However, we do need to confirm that the user who commented `.deny` is
# a member of the administrator team.
# GitHub App authentication is required if you want to interact with any
# resources outside the scope of the repository this workflow runs in.
– name: Get GitHub App Token
id: token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.ISSUEOPS_APP_ID }}
private-key: ${{ secrets.ISSUEOPS_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
# Check if the user who commented `.deny` is a member of the
# administrator team.
– name: Check Admin Membership
id: check-admin
uses: actions/github-script@v7
with:
github-token: ${{ steps.token.outputs.token }}
script: |
try {
await github.rest.teams.getMembershipForUserInOrg({
org: context.repo.owner,
team_slug: ‘admins’,
username: context.actor,
})
core.setOutput(‘member’, ‘true’)
} catch (error) {
if (error.status === 404) {
core.setOutput(‘member’, ‘false’)
}
throw error
}
# If the user is not a member of the administrator team, exit the
# workflow.
– if: ${{ steps.check-admin.outputs.member == ‘false’ }}
name: Exit
run: exit 0
# If the user is a member of the administrator team, add the denied label.
– name: Add Denied Label
id: add-label
uses: issue-ops/labeler@v2
with:
action: add
github_token: ${{ steps.token.outputs.token }}
labels: |
denied
issue_number: ${{ github.event.issue.number }}
repository: ${{ github.repository }}
# Notify the user that the request was denied.
– name: Notify User
id: notify
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.issue.number }}
body: |
This request has been denied and will be closed.
# Close the issue as not planned.
– name: Close Issue
id: close
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.update({
issue_number: ${{ github.event.issue.number }},
owner: context.repo.owner,
repo: context.repo.repo,
state: ‘closed’,
state_reason: ‘not_planned’
})
“`
Fluxo de trabalho de aprovação
Finalmente, precisamos lidar com a aprovação da solicitação. Neste caso, precisamos adicionar o usuário à equipe, notificá-lo e fechar o issue.
“`
name: Process Approval Comment
on:
issue_comment:
types:
– created
permissions:
contents: read
id-token: write
issues: write
jobs:
submit:
name: Approve Request
runs-on: ubuntu-latest
# This job should only be run when the following conditions are true:
#
# – A user comments `.approve` on the issue.
# – The issue has the `team-membership` label.
# – The issue has the `validated` label.
# – The issue has the `submitted` label.
# – The issue does not have the `approved` or `denied` labels.
# – The issue is open.
if: |
startsWith(github.event.comment.body, ‘.approve’) &&
contains(github.event.issue.labels.*.name, ‘team-membership’) == true &&
contains(github.event.issue.labels.*.name, ‘submitted’) == true &&
contains(github.event.issue.labels.*.name, ‘validated’) == true &&
contains(github.event.issue.labels.*.name, ‘approved’) == false &&
contains(github.event.issue.labels.*.name, ‘denied’) == false &&
github.event.issue.state == ‘open’
steps:
# This time, we do not need to re-run validation because the request is
# being approved. It can just be processed.
# This is required to ensure the issue form template is included in the
# workspace.
– name: Checkout
id: checkout
uses: actions/checkout@v4
# We do need to confirm that the user who commented `.approve` is a member
# of the administrator team. GitHub App authentication is required if you
# want to interact with any resources outside the scope of the repository
# this workflow runs in.
– name: Get GitHub App Token
id: token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.ISSUEOPS_APP_ID }}
private-key: ${{ secrets.ISSUEOPS_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
# Check if the user who commented `.approve` is a member of the
# administrator team.
– name: Check Admin Membership
id: check-admin
uses: actions/github-script@v7
with:
github-token: ${{ steps.token.outputs.token }}
script: |
try {
await github.rest.teams.getMembershipForUserInOrg({
org: context.repo.owner,
team_slug: ‘admins’,
username: context.actor,
})
core.setOutput(‘member’, ‘true’)
} catch (error) {
if (error.status === 404) {
core.setOutput(‘member’, ‘false’)
}
throw error
}
# If the user is not a member of the administrator team, exit the
# workflow.
– if: ${{ steps.check-admin.outputs.member == ‘false’ }}
name: Exit
run: exit 0
# Parse the issue body into machine-readable JSON, so that it can be
# processed by the rest of the workflow.
– name: Parse Issue body
id: parse
uses: issue-ops/parser@v4
with:
body: ${{ github.event.issue.body }}
issue-form-template: team-membership.yml
workspace: ${{ github.workspace }}
– name: Add to Team
id: add
uses: actions/github-script@v7
with:
github-token: ${{ steps.token.outputs.token }}
script: |
const parsedIssue = JSON.parse(‘${{ steps.parse.outputs.json }}’)
await github.rest.teams.addOrUpdateMembershipForUserInOrg({
org: context.repo.owner,
team_slug: parsedIssue.team,
username: ‘${{ github.event.issue.user.login }}’,
role: ‘member’
})
– name: Notify User
id: notify
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.issue.number }}
body: |
This request has been processed successfully!
– name: Close Issue
id: close
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.update({
issue_number: ${{ github.event.issue.number }},
owner: context.repo.owner,
repo: context.repo.repo,