diff --git a/.github/workflows/jira-issue-required.yml b/.github/workflows/jira-issue-required.yml index 1624d1e..363c50c 100644 --- a/.github/workflows/jira-issue-required.yml +++ b/.github/workflows/jira-issue-required.yml @@ -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 @@ -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 }} diff --git a/jira-issue-required/README.md b/jira-issue-required/README.md index bb0376e..15127ad 100644 --- a/jira-issue-required/README.md +++ b/jira-issue-required/README.md @@ -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 @@ -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://.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: @@ -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://.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. diff --git a/jira-issue-required/action.yml b/jira-issue-required/action.yml index 6dd5b3e..3204398 100644 --- a/jira-issue-required/action.yml +++ b/jira-issue-required/action.yml @@ -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" diff --git a/jira-issue-required/jira_issue_check.sh b/jira-issue-required/jira_issue_check.sh new file mode 100644 index 0000000..7384a80 --- /dev/null +++ b/jira-issue-required/jira_issue_check.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Temp files are cleaned up on any exit path (including `set -e` aborts). +STATUS_BODY_FILE="" +APPROVED_BODY_FILE="" +trap 'rm -f "$STATUS_BODY_FILE" "$APPROVED_BODY_FILE"' EXIT + +# Defensive defaults so `set -u` never trips on an optional/unset input. +POSSIBLE_ISSUE_REFERENCE="${POSSIBLE_ISSUE_REFERENCE:-}" +JIRA_CHECK_CONTEXT="${JIRA_CHECK_CONTEXT:-}" +DEFAULT_HOTFIX_PREFIX="${DEFAULT_HOTFIX_PREFIX:-}" +DEFAULT_REVERT_PREFIX="${DEFAULT_REVERT_PREFIX:-}" +BRANCH_NAME="${BRANCH_NAME:-}" +CHECK_JIRA_VALID_PROJECT_PREFIXES="${CHECK_JIRA_VALID_PROJECT_PREFIXES:-True}" +JIRA_VALID_PROJECT_PREFIXES="${JIRA_VALID_PROJECT_PREFIXES:-}" +APPROVED_FIELD_FOR_DEVELOPMENT_BY_FIELD_IS_EMPTY="${APPROVED_FIELD_FOR_DEVELOPMENT_BY_FIELD_IS_EMPTY:-}" + +# Issue keys start with a letter (e.g. SHS-491); avoids false positives like "2-3". +ISSUE_KEY=$(echo "$POSSIBLE_ISSUE_REFERENCE" | grep -oiE '[a-z][a-z0-9]{1,9}-[0-9]+' | head -n 1 || true) +echo "ISSUE_KEY=$ISSUE_KEY" >> "$GITHUB_ENV" + +if [[ -n "$JIRA_CHECK_CONTEXT" ]]; then + echo "$JIRA_CHECK_CONTEXT" +fi + +echo "DEFAULT_HOTFIX_PREFIX [$DEFAULT_HOTFIX_PREFIX]" +echo "DEFAULT_REVERT_PREFIX [$DEFAULT_REVERT_PREFIX]" +echo "BRANCH_NAME [$BRANCH_NAME]" +echo "JIRA_VALID_PROJECT_PREFIXES [$JIRA_VALID_PROJECT_PREFIXES]" +echo "ISSUE_KEY [$ISSUE_KEY]" + +if [[ -n "$DEFAULT_HOTFIX_PREFIX" && "$BRANCH_NAME" == "$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 [[ -n "$DEFAULT_REVERT_PREFIX" && "$BRANCH_NAME" == "$DEFAULT_REVERT_PREFIX"* ]]; then + echo "PREFIX_TO_IGNORE_FOUND=1" >> "$GITHUB_ENV" + echo "Revert branch detected, skipping Jira issue status check." + exit 0 +fi + +# Fail-secure: only explicit off-values disable the check; anything else keeps it on. +# `tr` (not ${x,,}) keeps this portable to bash 3.2 (e.g. macOS runners). +CHECK_PREFIXES_LOWER=$(printf '%s' "$CHECK_JIRA_VALID_PROJECT_PREFIXES" | tr '[:upper:]' '[:lower:]') +case "$CHECK_PREFIXES_LOWER" in + false|0|no|off) + echo "Project prefix validation disabled (CHECK_JIRA_VALID_PROJECT_PREFIXES=$CHECK_JIRA_VALID_PROJECT_PREFIXES)." + ;; + *) + if [[ -z "$JIRA_VALID_PROJECT_PREFIXES" ]]; then + echo "JIRA_VALID_PROJECT_PREFIXES is empty. Please configure the valid Jira project prefixes." + exit 1 + fi + + if [[ -n "$ISSUE_KEY" ]]; then + ISSUE_PREFIX=$(echo "$ISSUE_KEY" | cut -d'-' -f1 | tr '[:lower:]' '[:upper:]') + IFS=',' read -ra VALID_PREFIXES <<< "$JIRA_VALID_PROJECT_PREFIXES" + PREFIX_IS_VALID=0 + for PREFIX in "${VALID_PREFIXES[@]}"; do + # Trim surrounding whitespace, then upper-case (tr for bash 3.2 portability). + TRIMMED_PREFIX="${PREFIX#"${PREFIX%%[![:space:]]*}"}" + TRIMMED_PREFIX="${TRIMMED_PREFIX%"${TRIMMED_PREFIX##*[![:space:]]}"}" + TRIMMED_PREFIX=$(printf '%s' "$TRIMMED_PREFIX" | tr '[:lower:]' '[:upper:]') + if [[ "$ISSUE_PREFIX" == "$TRIMMED_PREFIX" ]]; then + PREFIX_IS_VALID=1 + break + fi + done + if [[ "$PREFIX_IS_VALID" == "0" ]]; then + echo "The space '$ISSUE_PREFIX' is not recognized as valid in the SOX workflow." + exit 1 + fi + echo "Space '$ISSUE_PREFIX' recognized as valid in the SOX workflow." + fi + ;; +esac + +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" + +STATUS_BODY_FILE="$(mktemp)" +STATUS_HTTP_CODE=$(curl -sS --max-time 30 --retry 2 --retry-connrefused \ + --request GET --url "$JIRA_API_ISSUE_ENDPOINT" \ + --user "${JIRA_USER_EMAIL}:${JIRA_API_TOKEN}" \ + --write-out '%{http_code}' --output "$STATUS_BODY_FILE") || { + echo "Network error while contacting Jira at $JIRA_BASE_URL." + exit 1 + } + +# Issue absent in this Jira: not an error, let the caller try the alternative Jira. +if [[ "$STATUS_HTTP_CODE" == "404" ]]; then + echo "Issue $ISSUE_KEY not found at $JIRA_BASE_URL (HTTP 404)." + exit 0 +fi + +if [[ "$STATUS_HTTP_CODE" -lt 200 || "$STATUS_HTTP_CODE" -ge 300 ]]; then + echo "Unexpected HTTP status $STATUS_HTTP_CODE from Jira at $JIRA_BASE_URL." + cat "$STATUS_BODY_FILE" + exit 1 +fi + +JIRA_RESPONSE_STATUS=$(jq -r '.fields.status.name' < "$STATUS_BODY_FILE") +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" + +ISSUE_KEY_FOUND=0 +if [[ "$JIRA_RESPONSE_STATUS" != "null" ]]; then + ISSUE_KEY_FOUND=1 + echo "ISSUE_KEY_FOUND=1" >> "$GITHUB_ENV" + echo "Issue key found!" +fi + +# Only validate the responsible field when the card actually exists in THIS Jira, +# otherwise the alternative-Jira flow would abort here on a non-existent issue. +if [[ "$ISSUE_KEY_FOUND" == 1 && -n "$APPROVED_FIELD_FOR_DEVELOPMENT_BY_FIELD_IS_EMPTY" ]]; then + echo "Checking approved for development field is empty ($APPROVED_FIELD_FOR_DEVELOPMENT_BY_FIELD_IS_EMPTY)" + APPROVED_FIELD_ENDPOINT="$JIRA_BASE_URL/rest/api/2/issue/$ISSUE_KEY?fields=$APPROVED_FIELD_FOR_DEVELOPMENT_BY_FIELD_IS_EMPTY" + echo "Requesting issue at $APPROVED_FIELD_ENDPOINT" + + APPROVED_BODY_FILE="$(mktemp)" + APPROVED_HTTP_CODE=$(curl -sS --max-time 30 --retry 2 --retry-connrefused \ + --request GET --url "$APPROVED_FIELD_ENDPOINT" \ + --user "${JIRA_USER_EMAIL}:${JIRA_API_TOKEN}" \ + --write-out '%{http_code}' --output "$APPROVED_BODY_FILE") || { + echo "Network error while contacting Jira at $JIRA_BASE_URL." + exit 1 + } + + if [[ "$APPROVED_HTTP_CODE" -lt 200 || "$APPROVED_HTTP_CODE" -ge 300 ]]; then + echo "Unexpected HTTP status $APPROVED_HTTP_CODE from Jira at $JIRA_BASE_URL." + cat "$APPROVED_BODY_FILE" + exit 1 + fi + + # Pass the field name as data (--arg) instead of interpolating it into the jq + # program: avoids jq injection and handles field names with special chars. + JIRA_RESPONSE_RESPONSIBLE_FIELD=$(jq -r --arg f "$APPROVED_FIELD_FOR_DEVELOPMENT_BY_FIELD_IS_EMPTY" '.fields[$f]' < "$APPROVED_BODY_FILE") + if [[ "$JIRA_RESPONSE_RESPONSIBLE_FIELD" != "null" ]]; then + echo "Approved for development by field isn't empty." + else + echo "Approved for development by field is empty." + exit 1 + fi +fi