Skip to content

Update release-bot.yaml #85

Update release-bot.yaml

Update release-bot.yaml #85

Workflow file for this run

name: Cherry-pick to Release Branch (GitHub App ready)

Check failure on line 1 in .github/workflows/release-bot.yaml

View workflow run for this annotation

GitHub Actions / .github/workflows/release-bot.yaml

Invalid workflow file

(Line: 70, Col: 13): Unrecognized named-value: 'secrets'. Located at position 1 within expression: secrets.APP_ID != '' && secrets.APP_PRIVATE_KEY != '' && secrets.APP_INSTALLATION_ID != '', (Line: 79, Col: 13): Unrecognized named-value: 'secrets'. Located at position 1 within expression: secrets.APP_ID != '' && secrets.APP_PRIVATE_KEY != '' && (secrets.APP_INSTALLATION_ID == ''), (Line: 88, Col: 13): Unrecognized named-value: 'secrets'. Located at position 1 within expression: secrets.PROBE_APP_ID != '' && secrets.PROBE_APP_PRIVATE_KEY != '' && secrets.PROBE_APP_INSTALLATION_ID != '', (Line: 97, Col: 13): Unrecognized named-value: 'secrets'. Located at position 1 within expression: secrets.PROBE_APP_ID != '' && secrets.PROBE_APP_PRIVATE_KEY != '' && (secrets.PROBE_APP_INSTALLATION_ID == '')
on:
# Direct usage in this repo: comment "/release to <branch>" on a PR
issue_comment:
types: [created]
# Reusable usage from another workflow:
workflow_call:
secrets:
APP_ID:
description: "GitHub App ID (string)."
required: false
APP_PRIVATE_KEY:
description: "GitHub App private key (PEM, multiline)."
required: false
APP_INSTALLATION_ID:
description: "GitHub App installation ID (string)."
required: false
permissions:
contents: write
pull-requests: write
issues: write
jobs:
cherry_pick:
runs-on: ubuntu-latest
steps:
# 1) Only run when someone comments `/release to <branch>` on a PR
- name: Check for release command
id: check_command
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { issue, comment } = context.payload || {};
if (!issue || !issue.pull_request || !comment) {
core.setOutput('release_valid','false');
return;
}
const m = (comment.body || '').trim().match(/^\/release to\s+([^\s]+)\s*$/i);
if (!m) {
core.setOutput('release_valid','false');
return;
}
core.setOutput('release_valid','true');
core.setOutput('release_branch', m[1]);
core.setOutput('pr_number', issue.number);
- name: Skip if not a valid command
if: steps.check_command.outputs.release_valid == 'false'
run: |
echo "Skipping: no '/release to <branch>' command found."
# 2) Checkout repo with full history; do NOT persist the default token
- name: Checkout repository
if: steps.check_command.outputs.release_valid == 'true'
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
# 3) Optionally authenticate as a GitHub App
# First try secrets passed via workflow_call (APP_*), then fall back
# to repo-level secrets (PROBE_*). If none provided, use GITHUB_TOKEN.
- name: Create GitHub App token (workflow_call secrets with installation id)
id: app_token_call_id
if: ${{ secrets.APP_ID != '' && secrets.APP_PRIVATE_KEY != '' && secrets.APP_INSTALLATION_ID != '' }}
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
installation-id: ${{ secrets.APP_INSTALLATION_ID }}
- name: Create GitHub App token (workflow_call secrets by owner)
id: app_token_call_owner
if: ${{ secrets.APP_ID != '' && secrets.APP_PRIVATE_KEY != '' && (secrets.APP_INSTALLATION_ID == '') }}
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Create GitHub App token (repo PROBE_* secrets with installation id)
id: app_token_repo_id
if: ${{ secrets.PROBE_APP_ID != '' && secrets.PROBE_APP_PRIVATE_KEY != '' && secrets.PROBE_APP_INSTALLATION_ID != '' }}
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.PROBE_APP_ID }}
private-key: ${{ secrets.PROBE_APP_PRIVATE_KEY }}
installation-id: ${{ secrets.PROBE_APP_INSTALLATION_ID }}
- name: Create GitHub App token (repo PROBE_* secrets by owner)
id: app_token_repo_owner
if: ${{ secrets.PROBE_APP_ID != '' && secrets.PROBE_APP_PRIVATE_KEY != '' && (secrets.PROBE_APP_INSTALLATION_ID == '') }}
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.PROBE_APP_ID }}
private-key: ${{ secrets.PROBE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Select auth token & prepare git remote
if: steps.check_command.outputs.release_valid == 'true'
run: |
set -euo pipefail
TOKEN="${{ steps.app_token_call_id.outputs.token }}"
if [ -z "$TOKEN" ]; then TOKEN="${{ steps.app_token_call_owner.outputs.token }}"; fi
if [ -z "$TOKEN" ]; then TOKEN="${{ steps.app_token_repo_id.outputs.token }}"; fi
if [ -z "$TOKEN" ]; then TOKEN="${{ steps.app_token_repo_owner.outputs.token }}"; fi
if [ -z "$TOKEN" ]; then TOKEN="${{ secrets.GITHUB_TOKEN }}"; fi
echo "GITHUB_TOKEN=$TOKEN" >> "$GITHUB_ENV"
echo "GH_TOKEN=$TOKEN" >> "$GITHUB_ENV"
# Ensure pushes/pulls use our chosen token (and not checkout extraheader)
git config --local --unset-all http.https://github.com/.extraheader || true
git remote set-url origin "https://x-access-token:${TOKEN}@github.com/${{ github.repository }}.git"
git remote -v
shell: bash
- name: Configure git user
if: steps.check_command.outputs.release_valid == 'true'
run: |
git config --global user.email "bot@tyk.io"
git config --global user.name "Tyk Bot"
# 4) Find the merge commit created when the PR was merged into its base (e.g., master)
- name: Find merge commit on base branch
id: pr_details
if: steps.check_command.outputs.release_valid == 'true'
env:
GH_TOKEN: ${{ env.GH_TOKEN }}
run: |
set -euo pipefail
PR_NUMBER='${{ steps.check_command.outputs.pr_number }}'
OWNER_REPO='${{ github.repository }}'
PR_JSON="$(gh api "repos/${OWNER_REPO}/pulls/${PR_NUMBER}")"
PR_TITLE="$(jq -r '.title' <<<"$PR_JSON")"
BASE_REF="$(jq -r '.base.ref' <<<"$PR_JSON")"
MERGED="$(jq -r '.merged' <<<"$PR_JSON")"
MERGE_SHA="$(jq -r '.merge_commit_sha // empty' <<<"$PR_JSON")"
if [ "${MERGED}" != "true" ]; then
echo "PR #${PR_NUMBER} is not merged; cannot use merge commit." >&2
exit 1
fi
git fetch origin "${BASE_REF}" --quiet
if [ -z "${MERGE_SHA}" ] || [ "${MERGE_SHA}" = "null" ]; then
MERGE_SHA="$(git log "origin/${BASE_REF}" --merges --grep="Merge pull request #${PR_NUMBER}" -n 1 --pretty=format:%H || true)"
fi
if [ -z "${MERGE_SHA}" ]; then
MERGE_SHA="$(git log "origin/${BASE_REF}" --grep="(#${PR_NUMBER})" -n 1 --pretty=format:%H || true)"
fi
if [ -z "${MERGE_SHA}" ]; then
echo "Could not find merge commit for PR #${PR_NUMBER} on ${BASE_REF}. Was it 'rebase and merge'?" >&2
exit 1
fi
{
echo "COMMIT_SHA=${MERGE_SHA}"
echo "PR_TITLE=${PR_TITLE}"
echo "BASE_REF=${BASE_REF}"
} >> "$GITHUB_OUTPUT"
# 5) Cherry-pick that merge commit onto the requested release branch and open a PR
- name: Cherry-pick merge commit to target release
id: cherry_pick
if: steps.check_command.outputs.release_valid == 'true'
env:
GH_TOKEN: ${{ env.GH_TOKEN }}
GITHUB_TOKEN: ${{ env.GITHUB_TOKEN }}
GITHUB_REPO: ${{ github.repository }}
GITHUB_BRANCH: ${{ steps.check_command.outputs.release_branch }}
GITHUB_CHERRY_PICK_COMMIT: ${{ steps.pr_details.outputs.COMMIT_SHA }}
PR_TITLE: ${{ steps.pr_details.outputs.PR_TITLE }}
BASE_REF: ${{ steps.pr_details.outputs.BASE_REF }}
run: |
set -euo pipefail
git fetch origin "${BASE_REF}" --quiet
git fetch origin "${GITHUB_BRANCH}" --quiet
git checkout -B "${GITHUB_BRANCH}" "origin/${GITHUB_BRANCH}"
# Optional JIRA id in branch name
JIRA_ID="$(echo "${PR_TITLE}" | grep -oE '[A-Z]{1,10}-[0-9]{1,10}' | head -n 1 || true)"
BRANCH_NAME="merge/${GITHUB_BRANCH}/${GITHUB_CHERRY_PICK_COMMIT}"
if [ -n "${JIRA_ID}" ]; then
BRANCH_NAME="${BRANCH_NAME}/${JIRA_ID}"
fi
git branch -D "${BRANCH_NAME}" 2>/dev/null || true
git push origin --delete "${BRANCH_NAME}" 2>/dev/null || true
git switch -c "${BRANCH_NAME}"
# Is it a merge commit (has >1 parent)?
PARENTS_LINE="$(git rev-list --parents -n 1 "${GITHUB_CHERRY_PICK_COMMIT}")"
WORDS_COUNT="$(wc -w <<<"${PARENTS_LINE}")"
MERGE_FAILED=0
if [ "${WORDS_COUNT}" -gt 2 ]; then
git cherry-pick -x -m 1 "${GITHUB_CHERRY_PICK_COMMIT}" || MERGE_FAILED=$?
else
git cherry-pick -x "${GITHUB_CHERRY_PICK_COMMIT}" || MERGE_FAILED=$?
fi
if [ "${MERGE_FAILED}" -ne 0 ]; then
git add -A || true
git -c core.editor=true cherry-pick --continue --no-edit || true
fi
git push origin "${BRANCH_NAME}" --force
MESSAGE="$(git show -s --format=%B "${GITHUB_CHERRY_PICK_COMMIT}")"
TITLE="$(echo "${MESSAGE}" | head -n 1)"
# Reuse PR if it already exists
PR_URL="$(gh pr view --repo "${GITHUB_REPO}" --head "${BRANCH_NAME}" --json url -q .url 2>/dev/null || true)"
if [ -z "${PR_URL}" ]; then
if [ "${MERGE_FAILED}" -ne 0 ]; then
PR_URL="$(gh pr create --draft \
--repo "${GITHUB_REPO}" \
--base "${GITHUB_BRANCH}" \
--head "${BRANCH_NAME}" \
--title "Merging to ${GITHUB_BRANCH}: ${TITLE}" \
--body "${MESSAGE}")"
else
PR_URL="$(gh pr create \
--repo "${GITHUB_REPO}" \
--base "${GITHUB_BRANCH}" \
--head "${BRANCH_NAME}" \
--title "Merging to ${GITHUB_BRANCH}: ${TITLE}" \
--body "${MESSAGE}")"
fi
fi
{
echo "PR_URL=${PR_URL}"
echo "MERGE_FAILED=${MERGE_FAILED}"
echo "BRANCH_NAME=${BRANCH_NAME}"
} >> "$GITHUB_OUTPUT"
# 6) Comment back on the original PR with the result (uses selected token)
- name: Comment back on original PR
if: always() && steps.check_command.outputs.release_valid == 'true'
uses: actions/github-script@v6
with:
github-token: ${{ env.GITHUB_TOKEN }}
script: |
const prUrl = '${{ steps.cherry_pick.outputs.PR_URL }}';
const mergeFailed = '${{ steps.cherry_pick.outputs.MERGE_FAILED }}' === '1';
let body;
if ('${{ job.status }}' === 'success') {
body = mergeFailed
? `⚠️ Cherry-pick completed with conflicts. A draft PR was created: ${prUrl}`
: `✅ Cherry-pick successful. A PR was created: ${prUrl}`;
} else {
body = '❌ Cherry-pick failed. Please check the workflow logs.';
}
github.rest.issues.createComment({
issue_number: ${{ steps.check_command.outputs.pr_number }},
owner: context.repo.owner,
repo: context.repo.repo,
body
});