Skip to content

Backport PR

Backport PR #1499

name: Backport PR
on:
# SECURITY: This workflow uses workflow_run to create pull requests and write commits.
#
# - The artifact only contains the PR number (integer)
# - The default branch is checked out, not PR code
# - The backport tool uses PR number to fetch commits via API, not execute code
# - PR state and labels are verified via API before processing
#
# zizmor: ignore[dangerous-triggers] see above
workflow_run:
workflows: ["Backport PR - Trigger"]
types: [completed]
permissions:
contents: write
pull-requests: write
id-token: write
jobs:
backport:
# Only run if the trigger workflow succeeded.
if: >
github.repository == 'grafana/alloy' &&
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- name: Get GitHub app secrets 🔐
id: get-secrets
uses: grafana/shared-workflows/actions/get-vault-secrets@a37de51f3d713a30a9e4b21bcdfbd38170020593 # get-vault-secrets/v1.3.0
with:
export_env: false
repo_secrets: |
ALLOYBOT_APP_ID=alloybot:app_id
ALLOYBOT_PRIVATE_KEY=alloybot:private_key
- name: Generate token 🔐
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
id: app-token
with:
app-id: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_APP_ID }}
private-key: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_PRIVATE_KEY }}
owner: grafana
repositories: alloy
- name: Download PR info artifact 🛬
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: backport-info
path: backport-info
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ steps.app-token.outputs.token }}
- name: Read PR number 📖
id: pr-number
run: |
PR_NUMBER=$(cat backport-info/pr_number)
echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT"
echo "PR number: ${PR_NUMBER}"
- name: Verify PR is merged and get backport labels 🏷️
id: pr-info
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
PR_NUMBER: ${{ steps.pr-number.outputs.pr_number }}
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const prNumber = parseInt(process.env.PR_NUMBER, 10);
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
if (!pr.merged) {
core.setFailed(`PR #${prNumber} is not merged`);
return;
}
console.log(`PR #${prNumber} is merged`);
const backportLabels = pr.labels
.map(l => l.name)
.filter(name => name.startsWith('backport/'));
if (backportLabels.length === 0) {
core.setFailed(`PR #${prNumber} has no backport labels`);
return;
}
console.log(`Backport labels: ${backportLabels.join(', ')}`);
core.setOutput('labels', JSON.stringify(backportLabels));
- name: Checkout repository 🛎️
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ steps.app-token.outputs.token }}
ref: ${{ github.event.repository.default_branch }}
fetch-depth: 0
persist-credentials: true # Needed for subsequent git operations
- name: Set up Go 🏗️
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: tools/go.mod
cache-dependency-path: tools/go.sum
- name: Run backport tool 🍒
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
LABELS: ${{ steps.pr-info.outputs.labels }}
PR_NUMBER: ${{ steps.pr-number.outputs.pr_number }}
run: |
cd tools
# Use while-read loop to safely handle labels with special characters
echo "${LABELS}" | jq -r '.[]' | while IFS= read -r label; do
echo "🍒 Processing backport for label: $label"
go run ./release/backport \
--pr "${PR_NUMBER}" \
--label "$label" || echo "⚠️ Backport for $label failed, moving to next..."
done