[TT-15942]: Add Sentinel One CNS scan workflow #77
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Cherry-pick to Release Branch | |
| on: | |
| issue_comment: | |
| types: [created] | |
| workflow_call: | |
| 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 (needed to locate merge commit reliably) | |
| - name: Checkout repository | |
| if: steps.check_command.outputs.release_valid == 'true' | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: true | |
| - name: Configure git | |
| if: steps.check_command.outputs.release_valid == 'true' | |
| run: | | |
| git config --global user.email "bot@tyk.io" | |
| git config --global user.name "Tyk Bot" | |
| # 3) 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: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| PR_NUMBER='${{ steps.check_command.outputs.pr_number }}' | |
| OWNER_REPO='${{ github.repository }}' | |
| # Get PR JSON (title, base ref, merged status, merge commit sha if available) | |
| 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 | |
| # Ensure we have the base branch locally | |
| git fetch origin "${BASE_REF}" --quiet | |
| # Fallbacks when merge_commit_sha is empty (e.g., different merge message styles) | |
| if [ -z "${MERGE_SHA}" ] || [ "${MERGE_SHA}" = "null" ]; then | |
| # Classic merge commit with default message | |
| 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 | |
| # Squash merge (default message contains "(#<PR_NUMBER>)") | |
| 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 a 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" | |
| # 4) 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: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_TOKEN: ${{ secrets.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 | |
| # Make sure we have both the base (where the merge commit lives) and the target release branch | |
| git fetch origin "${BASE_REF}" --quiet | |
| git fetch origin "${GITHUB_BRANCH}" --quiet | |
| # Ensure local release branch exists and is up-to-date | |
| git checkout -B "${GITHUB_BRANCH}" "origin/${GITHUB_BRANCH}" | |
| # Build branch name; include JIRA id if available | |
| 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 | |
| # Clean any stale branches locally/remotely | |
| git branch -D "${BRANCH_NAME}" 2>/dev/null || true | |
| git push origin --delete "${BRANCH_NAME}" 2>/dev/null || true | |
| # Create working branch based on the release branch | |
| git switch -c "${BRANCH_NAME}" | |
| # Decide whether the selected commit is 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 | |
| # Merge commit -> use -m 1 | |
| git cherry-pick -x -m 1 "${GITHUB_CHERRY_PICK_COMMIT}" || MERGE_FAILED=$? | |
| else | |
| # Normal / squash commit | |
| git cherry-pick -x "${GITHUB_CHERRY_PICK_COMMIT}" || MERGE_FAILED=$? | |
| fi | |
| # If conflicts happened, try to continue so we can still open a (draft) PR | |
| 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 | |
| # Prepare PR metadata from the cherry-picked commit | |
| MESSAGE="$(git show -s --format=%B "${GITHUB_CHERRY_PICK_COMMIT}")" | |
| TITLE="$(echo "${MESSAGE}" | head -n 1)" | |
| # If a PR already exists for this branch, reuse its URL | |
| 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" | |
| # 5) Comment back on the original PR with the result | |
| - name: Comment back on original PR | |
| if: always() && steps.check_command.outputs.release_valid == 'true' | |
| uses: actions/github-script@v6 | |
| with: | |
| github-token: ${{ secrets.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 | |
| }); |