Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/jira-issue-required.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Automations Repository
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Check Whether PR/commit is associated to a Jira issue
uses: ./jira-issue-required
Expand All @@ -21,3 +21,6 @@ jobs:
jira_status_allowed_to_merge: ${{ vars.JIRA_STATUS_ALLOWED_TO_MERGE }}
default_hotfix_prefix: ${{ vars.default_hotfix_prefix }}
default_revert_prefix: ${{ vars.default_revert_prefix }}
check_jira_valid_project_prefixes: ${{ vars.CHECK_JIRA_VALID_PROJECT_PREFIXES }}
jira_valid_project_prefixes: ${{ vars.JIRA_VALID_PROJECT_PREFIXES }}
approved_field_for_development_by_field_is_empty: ${{ vars.APPROVED_FIELD_FOR_DEVELOPMENT_BY_FIELD_IS_EMPTY }}
54 changes: 44 additions & 10 deletions jira-issue-required/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Jira Issue Required

Essa action se integra ao Jira para garantir que a branch associada ao Pull Request possua um card. A verificação acontece através do nome da branch que deve conter o código do projeto e sua identificação. Exemplos:
Essa action se integra ao Jira para garantir que a branch associada ao Pull Request possua um card. A verificação acontece através do nome da branch (ou título do PR) que deve conter o código do projeto e sua identificação. Exemplos:
- find-1
- feature/find-1
- bugfix/find-1
Expand All @@ -14,24 +14,20 @@ Essa action se integra ao Jira para garantir que a branch associada ao Pull Requ
- JIRA_USER_EMAIL: Um email de um user que acesso ao Jira para validar se o card exista;
- JIRA_API_TOKEN: Token criado para este fim deve ser criado em [API Tokens](https://id.atlassian.com/manage-profile/security/api-tokens);
- DEFAULT_HOTFIX_PREFIX: Prefixo padrão para branches de Hotfix isentas de seguir o fluxo completo em prol da resolução rápida em produção. Valor padrão: `hotfix/`
- JIRA_STATUS_ALLOWED_TO_MERGE: Status do Jira que permitirá o merge. Valor padrão: `To Deployment`

Opcional:
- `possible_issue_reference`: Este parâmetro pode ser usado para sobrescrever a string sob a qual a action procurará a *issue reference*. Por padrão, é o nome da branch.
- JIRA_STATUS_ALLOWED_TO_MERGE: Status do Jira que permitirá o merge. **Obrigatório** (sem default), ex.: `To Deployment`. Recomenda-se configurá-lo como *variable* de organização/repositório.

2. Em alguns casos, um repositório tem relação com Jira em organizações diferentes, e para resolver esse problema foi criada uma configuração alternativa de Jira:
- JIRA_ALTERNATIVE_BASE_URL: Com o valor `https://<sua org>.atlassian.net`;
- JIRA_ALTERNATIVE_USER_EMAIL: Um email de um user que acesso ao Jira para validar se o card exista;
- JIRA_ALTERNATIVE_API_TOKEN: Token criado para este fim deve ser criado em [API Tokens](https://id.atlassian.com/manage-profile/security/api-tokens);

Essas variáveis são de preenchimento opcional.
As três variáveis `JIRA_ALTERNATIVE_*` são de preenchimento opcional.

3. Dentro do seu repositório que usará o workflow, crie o arquivo `.github/workflows/jira-issue-required.yml` com o seguinte conteúdo
```yml
on:
pull_request:
types:
- opened
types: [opened, synchronize, reopened, edited]

push:
branches:
Expand All @@ -48,11 +44,49 @@ jobs:
jira_base_url: ${{ secrets.JIRA_BASE_URL }}
jira_user_email: ${{ secrets.JIRA_USER_EMAIL }}
jira_api_token: ${{ secrets.JIRA_API_TOKEN }}
# Jira alternativo (opcional):
jira_alternative_base_url: ${{ secrets.JIRA_ALTERNATIVE_BASE_URL }}
jira_alternative_user_email: ${{ secrets.JIRA_ALTERNATIVE_USER_EMAIL }}
jira_alternative_api_token: ${{ secrets.JIRA_ALTERNATIVE_API_TOKEN }}
# configuração opcional:
# Obrigatório (sem default):
jira_status_allowed_to_merge: ${{ vars.JIRA_STATUS_ALLOWED_TO_MERGE }}
possible_issue_reference: ${{ github.head_ref }}
# Configuração opcional:
default_hotfix_prefix: ${{ vars.default_hotfix_prefix }}
default_revert_prefix: ${{ vars.default_revert_prefix }}
check_jira_valid_project_prefixes: "True"
jira_valid_project_prefixes: "PROJ1,PROJ2"
approved_field_for_development_by_field_is_empty: "customfield_11758"
```

## Inputs

| Input | Obrigatório | Default | Descrição |
| --- | --- | --- | --- |
| `jira_base_url` | sim | — | URL base do Jira, ex.: `https://<sua org>.atlassian.net`. |
| `jira_user_email` | sim | — | Email de um usuário com acesso ao Jira. |
| `jira_api_token` | sim | — | Token de API criado em [API Tokens](https://id.atlassian.com/manage-profile/security/api-tokens). |
| `jira_status_allowed_to_merge` | sim | — | Status do Jira que libera o merge, ex.: `To Deployment`. Não possui default: se não for informado, o status recebido nunca casará e todos os merges serão bloqueados. |
| `jira_alternative_base_url` | não | — | URL base de um Jira alternativo (organização diferente). |
| `jira_alternative_user_email` | não | — | Email do usuário no Jira alternativo. |
| `jira_alternative_api_token` | não | — | Token de API do Jira alternativo. |
| `possible_issue_reference` | não | nome da branch | String onde a action procura a *issue reference*. Por padrão usa o nome da branch (e o título do PR em eventos de pull request). |
| `default_hotfix_prefix` | não | — | Prefixo de branches de hotfix isentas do check (ex.: `hotfix/`). |
| `default_revert_prefix` | não | — | Prefixo de branches de revert isentas do check (ex.: `revert/`). |
| `check_jira_valid_project_prefixes` | não | `True` | Habilita a validação dos prefixos de projeto (spaces). Valores `false`/`0`/`no`/`off` (case-insensitive) desabilitam; qualquer outro valor mantém habilitado (*fail-secure*). |
| `jira_valid_project_prefixes` | não | — | Prefixos de projetos válidos, separados por vírgula, ex.: `PROJ1,PROJ2`. Obrigatório quando `check_jira_valid_project_prefixes` está habilitado: o card encontrado deve pertencer a um desses spaces. |
| `approved_field_for_development_by_field_is_empty` | não | — | Nome do campo do Jira (ex.: `customfield_11758`) que indica quem aprovou o card para desenvolvimento. Quando informado, o merge só é liberado se o campo não estiver vazio. A validação só ocorre quando o card existe. |

## Outputs

| Output | Descrição |
| --- | --- |
| `issue_key` | A chave do issue detectada na referência (branch/título), ex.: `SHS-491`. |
| `issue_status` | O status do issue retornado pelo Jira, quando encontrado. |

### Exceções ao check (hotfix / revert)

Branches cujo nome começa com `default_hotfix_prefix` ou `default_revert_prefix` são isentas da verificação do card no Jira, permitindo respostas rápidas em produção.

### Validação de prefixos de projeto (SOX)

Quando `check_jira_valid_project_prefixes` está habilitado, o prefixo do card (ex.: `PROJ` em `PROJ-123`) precisa constar em `jira_valid_project_prefixes`. Ao usar o Jira alternativo, informe em `jira_valid_project_prefixes` a **união** dos prefixos válidos das duas instâncias, pois a validação roda em ambos os checks.
180 changes: 89 additions & 91 deletions jira-issue-required/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,156 +22,154 @@ inputs:
required: false
description: Create one at https://id.atlassian.com/manage-profile/security/api-tokens
possible_issue_reference:
required: false
description: Uma string que possivelmente contém o issue no Jira. Pode ser o nome da branch, o título do PR, etc. O padrão é o nome da branch.
default: ""
jira_status_allowed_to_merge:
required: true
description: O status do Jira que permite o merge -> To Deployment
description: O status do Jira que permite o merge, por exemplo, To Deployment
default_hotfix_prefix:
required: false
description: Prefixo da branch de hotfix. Default -> hotfix/
default_revert_prefix:
required: false
description: Prefixo da branch de revert. Default -> revert/
check_jira_valid_project_prefixes:
required: false
default: "True"
description: Habilita a verificação dos prefixos de projetos válidos no Jira. Padrão -> True. Se for false/0/no/off (case-insensitive), a verificação é ignorada; qualquer outro valor a mantém habilitada.
jira_valid_project_prefixes:
required: false
description: "Prefixos dos projetos válidos no Jira (spaces), separados por vírgula. Ex: PROJ1,PROJ2. Quando informado, o card encontrado na branch ou título deve pertencer a um desses spaces; caso contrário, o fluxo SOX não reconhecerá o space como válido."
default: ""
approved_field_for_development_by_field_is_empty:
required: false
default: ""
description: Nome do campo contendo quem aprovou para desenvolvimento para verificar se não está vazio.

outputs:
issue_key:
description: "A chave do issue detectada na referência (branch/título), ex.: SHS-491."
value: ${{ steps.export_outputs.outputs.issue_key }}
issue_status:
description: O status do issue retornado pelo Jira, quando encontrado.
value: ${{ steps.export_outputs.outputs.issue_status }}

runs:
using: composite
steps:
- name: Setup | Environment
env:
POSSIBLE_ISSUE_REFERENCE_INPUT: ${{ inputs.possible_issue_reference }}
ACTION_PATH: ${{ github.action_path }}
shell: bash
run: |
echo "POSSIBLE_ISSUE_REFERENCE=${{ inputs.possible_issue_reference }}" >> "$GITHUB_ENV"
echo "ISSUE_KEY_FOUND=0" >> "$GITHUB_ENV"
echo "PREFIX_TO_IGNORE_FOUND=0" >> "$GITHUB_ENV"
echo "BRANCH_NAME=0" >> "$GITHUB_ENV"

- name: Get Possible Issue Reference - Default / Push Event in PR
if: env.POSSIBLE_ISSUE_REFERENCE == '' && github.event_name == 'push' && github.event.pull_request != null
shell: bash
run: echo "POSSIBLE_ISSUE_REFERENCE=${{ github.ref_name }} ${{ github.event.pull_request.title }}" >> "$GITHUB_ENV"

- name: Get Possible Issue Reference - Default / Push Event without PR
# Strip CR/LF from untrusted input so it can't inject extra GITHUB_ENV lines.
{
printf 'POSSIBLE_ISSUE_REFERENCE=%s\n' "${POSSIBLE_ISSUE_REFERENCE_INPUT//[$'\r\n']/ }"
echo "ISSUE_KEY_FOUND=0"
echo "PREFIX_TO_IGNORE_FOUND=0"
echo "BRANCH_NAME="
echo "JIRA_CHECK_HELPER_PATH=$ACTION_PATH/jira_issue_check.sh"
} >> "$GITHUB_ENV"

chmod +x "$ACTION_PATH/jira_issue_check.sh"

- name: Get Possible Issue Reference - Default / Push Event
if: env.POSSIBLE_ISSUE_REFERENCE == '' && github.event_name == 'push'
env:
REF_NAME: ${{ github.ref_name }}
shell: bash
run: echo "POSSIBLE_ISSUE_REFERENCE=${{ github.ref_name }}" >> "$GITHUB_ENV"
run: printf 'POSSIBLE_ISSUE_REFERENCE=%s\n' "${REF_NAME//[$'\r\n']/ }" >> "$GITHUB_ENV"

- name: Get Possible Issue Reference - Default / Pull Request Event
if: env.POSSIBLE_ISSUE_REFERENCE == '' && startsWith(github.event_name, 'pull_request')
env:
HEAD_REF: ${{ github.head_ref }}
PR_TITLE: ${{ github.event.pull_request.title }}
shell: bash
run: echo "POSSIBLE_ISSUE_REFERENCE=${{ github.head_ref }} ${{ github.event.pull_request.title }}" >> "$GITHUB_ENV"
run: |
# PR title is attacker-controlled: pass via env and strip newlines,
# never interpolate GitHub expressions straight into the shell.
REFERENCE="$HEAD_REF $PR_TITLE"
printf 'POSSIBLE_ISSUE_REFERENCE=%s\n' "${REFERENCE//[$'\r\n']/ }" >> "$GITHUB_ENV"

- name: Get Branch name for Hotfix/Revert exceptions
env:
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
REF_NAME: ${{ github.ref_name }}
shell: bash
run: |
if [[ -n "${{ github.event.pull_request.head.ref }}" ]]; then
echo "BRANCH_NAME=${{ github.event.pull_request.head.ref }}" >> "$GITHUB_ENV"
if [[ -n "$PR_HEAD_REF" ]]; then
BRANCH="$PR_HEAD_REF"
else
echo "BRANCH_NAME=${{ github.ref_name }}" >> "$GITHUB_ENV"
BRANCH="$REF_NAME"
fi
printf 'BRANCH_NAME=%s\n' "${BRANCH//[$'\r\n']/ }" >> "$GITHUB_ENV"

- name: Check if issue key exists
env:
JIRA_BASE_URL: ${{ inputs.jira_base_url }}
JIRA_USER_EMAIL: ${{ inputs.jira_user_email }}
JIRA_API_TOKEN: ${{ inputs.jira_api_token }}
JIRA_STATUS_ALLOWED_TO_MERGE: ${{ inputs.JIRA_STATUS_ALLOWED_TO_MERGE }}
JIRA_STATUS_ALLOWED_TO_MERGE: ${{ inputs.jira_status_allowed_to_merge }}
DEFAULT_HOTFIX_PREFIX: ${{ inputs.default_hotfix_prefix }}
DEFAULT_REVERT_PREFIX: ${{ inputs.default_revert_prefix }}
BRANCH_NAME: ${{ env.BRANCH_NAME }}
CHECK_JIRA_VALID_PROJECT_PREFIXES: ${{ inputs.check_jira_valid_project_prefixes }}
JIRA_VALID_PROJECT_PREFIXES: ${{ inputs.jira_valid_project_prefixes }}
APPROVED_FIELD_FOR_DEVELOPMENT_BY_FIELD_IS_EMPTY: ${{ inputs.approved_field_for_development_by_field_is_empty }}
JIRA_CHECK_CONTEXT: Primary Jira check
shell: bash
run: |
ISSUE_KEY=$(echo "$POSSIBLE_ISSUE_REFERENCE" | grep -oE '[a-zA-Z0-9]{1,10}-[0-9]+' | head -n 1)
echo "ISSUE_KEY=$ISSUE_KEY" >> "$GITHUB_ENV"

echo "DEFAULT_HOTFIX_PREFIX [$DEFAULT_HOTFIX_PREFIX]"
echo "DEFAULT_REVERT_PREFIX [$DEFAULT_REVERT_PREFIX]"
echo "BRANCH_NAME [$BRANCH_NAME]"

if [[ "${{ env.BRANCH_NAME }}" == "${{ env.DEFAULT_HOTFIX_PREFIX }}"* ]]; then
echo "PREFIX_TO_IGNORE_FOUND=1" >> "$GITHUB_ENV"
echo "Hotfix branch detected, skipping Jira issue status check."
exit 0
fi

if [[ "${{ env.BRANCH_NAME }}" == "${{ env.DEFAULT_REVERT_PREFIX }}"* ]]; then
echo "PREFIX_TO_IGNORE_FOUND=1" >> "$GITHUB_ENV"
echo "Revert branch detected, skipping Jira issue status check."
exit 0
fi

echo "Searching for Jira issue $ISSUE_KEY at $JIRA_BASE_URL"
JIRA_API_ISSUE_ENDPOINT="$JIRA_BASE_URL/rest/api/2/issue/$ISSUE_KEY?fields=key,status"
echo "Requesting issue at $JIRA_API_ISSUE_ENDPOINT"
JIRA_RESPONSE_STATUS=$(curl --request GET --url "$JIRA_API_ISSUE_ENDPOINT" --user "${JIRA_USER_EMAIL}:${JIRA_API_TOKEN}" | jq .fields.status.name) >> "$GITHUB_ENV"
echo "Response Status [$JIRA_RESPONSE_STATUS]"
echo "JIRA_STATUS_ALLOWED_TO_MERGE -> [$JIRA_STATUS_ALLOWED_TO_MERGE]"
echo "JIRA_RESPONSE_STATUS=$JIRA_RESPONSE_STATUS" >> "$GITHUB_ENV"
echo "JIRA_STATUS_ALLOWED_TO_MERGE=$JIRA_STATUS_ALLOWED_TO_MERGE" >> "$GITHUB_ENV"

if [ "$JIRA_RESPONSE_STATUS" != "null" ]; then
echo "ISSUE_KEY_FOUND=1" >> "$GITHUB_ENV"
echo "Issue key found!"
fi
run: bash "$JIRA_CHECK_HELPER_PATH"

- name: Check if issue key exists in alternative Jira
if: env.ISSUE_KEY_FOUND == 0 && env.PREFIX_TO_IGNORE_FOUND==0 && inputs.jira_alternative_base_url != '' && inputs.jira_alternative_user_email != '' && inputs.jira_alternative_api_token != ''
if: env.ISSUE_KEY_FOUND == 0 && env.PREFIX_TO_IGNORE_FOUND == 0 && inputs.jira_alternative_base_url != '' && inputs.jira_alternative_user_email != '' && inputs.jira_alternative_api_token != ''
env:
JIRA_BASE_URL: ${{ inputs.jira_alternative_base_url }}
JIRA_USER_EMAIL: ${{ inputs.jira_alternative_user_email }}
JIRA_API_TOKEN: ${{ inputs.jira_alternative_api_token }}
JIRA_STATUS_ALLOWED_TO_MERGE: ${{ inputs.JIRA_STATUS_ALLOWED_TO_MERGE }}
JIRA_STATUS_ALLOWED_TO_MERGE: ${{ inputs.jira_status_allowed_to_merge }}
DEFAULT_HOTFIX_PREFIX: ${{ inputs.default_hotfix_prefix }}
DEFAULT_REVERT_PREFIX: ${{ inputs.default_revert_prefix }}
BRANCH_NAME: ${{ env.BRANCH_NAME }}
CHECK_JIRA_VALID_PROJECT_PREFIXES: ${{ inputs.check_jira_valid_project_prefixes }}
JIRA_VALID_PROJECT_PREFIXES: ${{ inputs.jira_valid_project_prefixes }}
APPROVED_FIELD_FOR_DEVELOPMENT_BY_FIELD_IS_EMPTY: ${{ inputs.approved_field_for_development_by_field_is_empty }}
JIRA_CHECK_CONTEXT: Alternative Jira check
shell: bash
run: |
ISSUE_KEY=$(echo "$POSSIBLE_ISSUE_REFERENCE" | grep -oE '[a-zA-Z0-9]{1,10}-[0-9]+' | head -n 1)
echo "ISSUE_KEY=$ISSUE_KEY" >> "$GITHUB_ENV"
echo "Alternative Jira check"

echo "DEFAULT_HOTFIX_PREFIX [$DEFAULT_HOTFIX_PREFIX]"
echo "DEFAULT_REVERT_PREFIX [$DEFAULT_REVERT_PREFIX]"
echo "BRANCH_NAME [$BRANCH_NAME]"

if [[ "${{ env.BRANCH_NAME }}" == "${{ env.DEFAULT_HOTFIX_PREFIX }}"* ]]; then
echo "PREFIX_TO_IGNORE_FOUND=1" >> "$GITHUB_ENV"
echo "Hotfix branch detected, skipping Jira issue status check."
exit 0
fi

if [[ "${{ env.BRANCH_NAME }}" == "${{ env.DEFAULT_REVERT_PREFIX }}"* ]]; then
echo "PREFIX_TO_IGNORE_FOUND=1" >> "$GITHUB_ENV"
echo "Revert branch detected, skipping Jira issue status check."
exit 0
fi

echo "Searching for Jira issue $ISSUE_KEY at $JIRA_BASE_URL"
JIRA_API_ISSUE_ENDPOINT="$JIRA_BASE_URL/rest/api/2/issue/$ISSUE_KEY?fields=key,status"
echo "Requesting issue at $JIRA_API_ISSUE_ENDPOINT"
JIRA_RESPONSE_STATUS=$(curl --request GET --url "$JIRA_API_ISSUE_ENDPOINT" --user "${JIRA_USER_EMAIL}:${JIRA_API_TOKEN}" | jq .fields.status.name) >> "$GITHUB_ENV"
echo "Response Status [$JIRA_RESPONSE_STATUS]"
echo "JIRA_STATUS_ALLOWED_TO_MERGE -> [$JIRA_STATUS_ALLOWED_TO_MERGE]"
echo "JIRA_RESPONSE_STATUS=$JIRA_RESPONSE_STATUS" >> "$GITHUB_ENV"
echo "JIRA_STATUS_ALLOWED_TO_MERGE=$JIRA_STATUS_ALLOWED_TO_MERGE" >> "$GITHUB_ENV"

if [ "$JIRA_RESPONSE_STATUS" != "null" ]; then
echo "ISSUE_KEY_FOUND=1" >> "$GITHUB_ENV"
echo "Issue key found!"
fi
run: bash "$JIRA_CHECK_HELPER_PATH"

- name: Fail if not found
if: env.ISSUE_KEY_FOUND == 0 && env.PREFIX_TO_IGNORE_FOUND == 0
env:
POSSIBLE_ISSUE_REFERENCE: ${{ env.POSSIBLE_ISSUE_REFERENCE }}
shell: bash
run: |
echo "Issue not found in '${{ env.POSSIBLE_ISSUE_REFERENCE }}'!"
echo "Issue not found in '$POSSIBLE_ISSUE_REFERENCE'!"
exit 1

- name: Fail if status is not allowed to deploy
if: env.ISSUE_KEY_FOUND == 1 && env.PREFIX_TO_IGNORE_FOUND == 0
env:
JIRA_STATUS_ALLOWED_TO_MERGE: ${{ inputs.jira_status_allowed_to_merge }}
JIRA_RESPONSE_STATUS: ${{ env.JIRA_RESPONSE_STATUS }}
shell: bash
run: |
JIRA_STATUS_ALLOWED_TO_MERGE_STR="${{ env.JIRA_STATUS_ALLOWED_TO_MERGE }}"
if [ ${{ env.JIRA_RESPONSE_STATUS }} != "$JIRA_STATUS_ALLOWED_TO_MERGE_STR" ]; then
echo "Issue status is not allowed to merge because ${{ env.JIRA_RESPONSE_STATUS }} is not $JIRA_STATUS_ALLOWED_TO_MERGE_STR!"
if [[ "$JIRA_RESPONSE_STATUS" != "$JIRA_STATUS_ALLOWED_TO_MERGE" ]]; then
echo "Issue status is not allowed to merge because '$JIRA_RESPONSE_STATUS' is not '$JIRA_STATUS_ALLOWED_TO_MERGE'!"
exit 1
fi

- name: Export outputs
id: export_outputs
if: always()
env:
ISSUE_KEY: ${{ env.ISSUE_KEY }}
JIRA_RESPONSE_STATUS: ${{ env.JIRA_RESPONSE_STATUS }}
shell: bash
run: |
{
printf 'issue_key=%s\n' "$ISSUE_KEY"
printf 'issue_status=%s\n' "$JIRA_RESPONSE_STATUS"
} >> "$GITHUB_OUTPUT"
Loading
Loading