Saltar al contenido
mypipelines
Shared (cross-cutting)·Reusable workflow·on: workflow_call

Shared Create Issue On Failure

Shared - Create Issue on Failure

.github/workflows/shared-create-issue-on-failure.yml

.github/workflows/shared-create-issue-on-failure.yml
name: Shared - Create Issue on Failure
on:
workflow_call:
inputs:
status:
description: 'Pipeline status: success, failure, cancelled'
required: true
type: string
environment:
description: 'Deployment environment (develop, prod)'
required: false
type: string
default: ''
version:
description: 'Release version (e.g., 1.2.3)'
required: false
type: string
default: ''
changelog:
description: 'Release changelog (commit list)'
required: false
type: string
default: ''
message:
description: 'Custom message to include in the issue body'
required: false
type: string
default: ''
labels:
description: 'Labels for the GitHub issue (comma-separated)'
required: false
type: string
default: 'bug,pipeline-failure'
# Least privilege for what this workflow actually does. Without it the
# job inherits whatever the calling repository's default happens to be,
# which on older repositories is write-all.
permissions:
contents: read
issues: write
jobs:
create-issue:
name: Create Issue
if: inputs.status == 'failure'
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: Create GitHub issue on failure
env:
GH_TOKEN: ${{ github.token }}
ENVIRONMENT: ${{ inputs.environment }}
VERSION: ${{ inputs.version }}
CHANGELOG: ${{ inputs.changelog }}
CUSTOM_MESSAGE: ${{ inputs.message }}
REPO: ${{ github.repository }}
BRANCH: ${{ github.ref_name }}
ACTOR: ${{ github.actor }}
SERVER_URL: ${{ github.server_url }}
RUN_ID: ${{ github.run_id }}
ISSUE_LABELS: ${{ inputs.labels }}
run: |
set -euo pipefail
ISSUE_TITLE="Pipeline failure: ${REPO##*/} (${BRANCH}) — ${ENVIRONMENT:-no-env}"
ISSUE_BODY="## Pipeline Failure\n\n"
ISSUE_BODY+="| Field | Value |\n"
ISSUE_BODY+="|-------|-------|\n"
ISSUE_BODY+="| **Repository** | [${REPO}](${SERVER_URL}/${REPO}) |\n"
ISSUE_BODY+="| **Branch** | \`${BRANCH}\` |\n"
ISSUE_BODY+="| **Environment** | ${ENVIRONMENT:-N/A} |\n"
ISSUE_BODY+="| **Actor** | @${ACTOR} |\n"
ISSUE_BODY+="| **Run** | [View workflow run](${SERVER_URL}/${REPO}/actions/runs/${RUN_ID}) |\n"
ISSUE_BODY+="| **Commit** | \`${GITHUB_SHA:0:7}\` |\n"
if [ -n "$CUSTOM_MESSAGE" ]; then
ISSUE_BODY+="\n### Message\n${CUSTOM_MESSAGE}\n"
fi
if [ -n "$VERSION" ] && [ -n "$CHANGELOG" ]; then
ISSUE_BODY+="\n### Changelog (v${VERSION})\n${CHANGELOG}\n"
fi
ISSUE_BODY+="\n---\n*Auto-generated by CI pipeline*"
# The body reaches gh through a file and the arguments through an array.
# Both title and body carry text this workflow does not control: BRANCH is
# github.ref_name and CHANGELOG is raw `git log %s` output, so a commit
# subject is attacker-authored input. Any construct that re-parses these
# values as shell -- `eval`, or a command substitution around them -- turns
# a commit subject into code running on the runner. printf '%b' expands the
# \n escapes above without ever re-entering the parser.
BODY_FILE="${RUNNER_TEMP:-/tmp}/issue-body.md"
printf '%b' "$ISSUE_BODY" > "$BODY_FILE"
GH_ARGS=(
--repo "$REPO"
--title "$ISSUE_TITLE"
--body-file "$BODY_FILE"
--assignee "$ACTOR"
)
if [ -n "$ISSUE_LABELS" ]; then
IFS=',' read -ra LABELS <<< "$ISSUE_LABELS"
for LABEL in "${LABELS[@]}"; do
LABEL=$(echo "$LABEL" | xargs)
if [ -n "$LABEL" ]; then
GH_ARGS+=(--label "$LABEL")
fi
done
fi
ISSUE_URL=$(gh issue create "${GH_ARGS[@]}" 2>&1) || true
if [ -n "$ISSUE_URL" ]; then
echo "GitHub issue created: $ISSUE_URL"
else
echo "::warning::Failed to create GitHub issue"
fi