Repository navigation
[CI][Backport] Automatic backport checklist on PRs targeting main #19997
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
1daf66f
[backport] add ListActiveBackportBranches to inventory
mrodm 55ea883
[backport] add DetectPackages to dev/backports/packages
mrodm 72f85d8
[backport] add DetectBackportPackages and ListActiveBackportBranches …
mrodm 0b0afb3
[backport] add backport-packages-detect workflow (#19213)
mrodm 6932972
[backport] add post-backport-checklist workflow (#19213)
mrodm 74dad4e
[backport] make checklist comment logic testable (#19213)
mrodm 2fb2348
[backport] remove unused listActiveBackportBranches mage target (#19213)
mrodm f6e76d5
[backport] unexport internal-only checklist symbols (#19213)
mrodm 81774d4
[backport] parse .backports.yml once per checklist render and drop ni…
mrodm 1dfe680
[backport] eliminate double manifest reads in package enumeration (#1…
mrodm 1e223d4
[backport] guarantee temp file cleanup and inline single-use helper (…
mrodm bb65ba1
[backport] address high-effort code review findings (#19213)
mrodm 678fa3c
[backport] split post-checklist step into two (#19213)
mrodm 816d049
[backport] skip detect job for sync-changelog PRs (#19213)
mrodm 8e6c280
Add temporarily other target branch
mrodm 552e752
[backport] join split lines in BuildComment (#19213)
mrodm 75dadd7
fix second code-review findings in backport checklist automation
mrodm 6abd326
remove unimplemented automation promise from checklist comment
mrodm 7810771
Avoid triggering all packages - to be reverted
mrodm 94a1b7d
Add reopened event to the backport-packages-detect workflow
mrodm ac73375
Merge remote-tracking branch 'upstream/main' into backport-checklist-…
mrodm adcd05b
use mage parameter instead of env var in renderBackportChecklist
mrodm 79ec40c
add job-level concurrency to post-checklist workflow
mrodm 9bb7bec
render branches as plain list until auto-backport PR creation lands (…
mrodm 366f20b
add contents: read to job-level permissions in post-checklist workflow
mrodm ae8e854
Remove changes to debug workflows
mrodm 95edbd5
Merge remote-tracking branch 'upstream/main' into backport-checklist-…
mrodm 49bb7a6
note in checklist comment that manual edits will be overwritten on push
mrodm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| --- | ||
| name: Backport — detect packages | ||
|
|
||
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, reopened] | ||
| branches: | ||
| - main | ||
|
|
||
| jobs: | ||
| detect-packages: | ||
| runs-on: ubuntu-latest | ||
| # Skip PRs created by the sync-changelog workflow — those backport changelog | ||
| # entries back to main and do not need a backport checklist of their own. | ||
| if: ${{ !contains(github.event.pull_request.labels.*.name, 'backport:sync-changelog') }} | ||
| # No permissions — this job runs in the restricted pull_request context | ||
| # and must be fork-safe. Write permissions live in post-backport-checklist.yml | ||
| # which is triggered via workflow_run and runs in the base repository context. | ||
| permissions: | ||
| contents: read | ||
|
|
||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v7 | ||
| with: | ||
| # Full history is required so that git diff can resolve both the PR | ||
| # base SHA and the head SHA, which may not be present in a shallow clone. | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Set up Go | ||
| uses: actions/setup-go@v6 | ||
| with: | ||
| go-version-file: .go-version | ||
|
|
||
| - name: Install mage | ||
| run: go install github.com/magefile/mage | ||
|
fr4nc1sc0-r4m0n marked this conversation as resolved.
Resolved
|
||
|
|
||
| - name: Detect packages touched by this PR | ||
| env: | ||
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | ||
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| run: | | ||
| PACKAGES_JSON=$(mage -d "$GITHUB_WORKSPACE" detectBackportPackages "$BASE_SHA" "$HEAD_SHA" -asJSON) | ||
|
|
||
| jq -n \ | ||
| --argjson pkgs "$PACKAGES_JSON" \ | ||
| --argjson pr "$PR_NUMBER" \ | ||
| '{"pr_number": $pr, "packages": $pkgs}' > pr-packages.json | ||
|
|
||
| echo "Detected packages: $PACKAGES_JSON" | ||
|
|
||
| - name: Upload artifact | ||
| uses: actions/upload-artifact@v7 | ||
| with: | ||
| name: pr-packages-${{ github.run_id }} | ||
| path: pr-packages.json | ||
| if-no-files-found: ignore | ||
| retention-days: 1 | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| --- | ||
|
fr4nc1sc0-r4m0n marked this conversation as resolved.
Resolved
|
||
| name: Backport — post checklist | ||
|
|
||
| on: | ||
| workflow_run: | ||
| workflows: ["Backport — detect packages"] | ||
| types: [completed] | ||
|
|
||
|
fr4nc1sc0-r4m0n marked this conversation as resolved.
Resolved
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| post-checklist: | ||
| runs-on: ubuntu-latest | ||
| if: github.event.workflow_run.conclusion == 'success' | ||
| # Cancel any in-progress run for the same branch when rapid pushes trigger | ||
| # overlapping workflow_run jobs — the newest run wins. | ||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} | ||
| cancel-in-progress: true | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
|
mrodm marked this conversation as resolved.
Resolved
|
||
| # Required by actions/download-artifact when fetching from another workflow run. | ||
| actions: read | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v7 | ||
|
|
||
| - name: Set up Go | ||
| uses: actions/setup-go@v6 | ||
| with: | ||
| go-version-file: .go-version | ||
|
|
||
| - name: Install mage | ||
| run: go install github.com/magefile/mage | ||
|
|
||
| - name: Download artifact | ||
| # continue-on-error so a missing artifact (e.g. detect job was skipped due to | ||
| # backport:sync-changelog label) does not fail the job — the next step checks | ||
| # for the file and exits cleanly when it is absent. | ||
| continue-on-error: true | ||
| uses: actions/download-artifact@v7 | ||
| with: | ||
| run-id: ${{ github.event.workflow_run.id }} | ||
| github-token: ${{ secrets.GITHUB_TOKEN }} | ||
| name: pr-packages-${{ github.event.workflow_run.id }} | ||
| path: artifacts | ||
|
|
||
| - name: Validate artifact and find checklist comment | ||
| id: find-comment | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| REPOSITORY: ${{ github.repository }} | ||
| ARTIFACT: artifacts/pr-packages.json | ||
| run: | | ||
|
fr4nc1sc0-r4m0n marked this conversation as resolved.
Resolved
|
||
| if [[ ! -f "$ARTIFACT" ]]; then | ||
| echo "Artifact not found — exiting" | ||
| exit 0 | ||
| fi | ||
|
|
||
| PR_NUMBER=$(jq -r '.pr_number' "$ARTIFACT") | ||
| echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" | ||
|
|
||
| # Collect all comments that contain the checklist marker across all pages. | ||
| MATCHING=$(gh api "repos/$REPOSITORY/issues/$PR_NUMBER/comments" \ | ||
| --paginate \ | ||
| --jq '.[] | select(.body | contains("<!-- backport-checklist -->"))' \ | ||
| | jq -s '.') | ||
|
|
||
| COUNT=$(jq 'length' <<< "$MATCHING") | ||
| if [[ "$COUNT" -gt 1 ]]; then | ||
| IDS=$(jq -r '.[].id' <<< "$MATCHING" | paste -sd ', ') | ||
| gh api --method POST \ | ||
| "repos/$REPOSITORY/issues/$PR_NUMBER/comments" \ | ||
| --field body=":warning: **Backport checklist error**: found ${COUNT} duplicate checklist comments (IDs: ${IDS}). Please delete the extras manually so the checklist can be updated automatically." | ||
| echo "Duplicate checklist comments found — posted notice to PR #$PR_NUMBER" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "comment_id=$(jq -r 'first.id // empty' <<< "$MATCHING")" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Render and post checklist comment | ||
| if: steps.find-comment.outputs.pr_number != '' | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| REPOSITORY: ${{ github.repository }} | ||
| ARTIFACT: artifacts/pr-packages.json | ||
| PR_NUMBER: ${{ steps.find-comment.outputs.pr_number }} | ||
| COMMENT_ID: ${{ steps.find-comment.outputs.comment_id }} | ||
| run: | | ||
| EXISTING_BODY="" | ||
|
fr4nc1sc0-r4m0n marked this conversation as resolved.
Resolved
|
||
| if [[ -n "$COMMENT_ID" ]]; then | ||
| EXISTING_BODY=$(gh api "repos/$REPOSITORY/issues/comments/$COMMENT_ID" \ | ||
| --jq '.body // empty') | ||
| fi | ||
|
|
||
| # Render the new comment body. The existing body is piped into stdin so | ||
| # the mage target can parse previously-checked branches and preserve their | ||
| # state. Prints nothing when no package has any active branch. | ||
| NEW_BODY=$(printf '%s' "$EXISTING_BODY" | \ | ||
| mage -d "$GITHUB_WORKSPACE" renderBackportChecklist "$ARTIFACT") | ||
|
|
||
| if [[ -z "$NEW_BODY" ]]; then | ||
| echo "No active backport branches — nothing to post" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Write to a file to avoid quoting issues when passing to gh api. | ||
| BODY_FILE=$(mktemp) | ||
| trap 'rm -f "$BODY_FILE"' EXIT | ||
| printf '%s' "$NEW_BODY" > "$BODY_FILE" | ||
|
|
||
| if [[ -n "$COMMENT_ID" ]]; then | ||
| gh api --method PATCH \ | ||
| "repos/$REPOSITORY/issues/comments/$COMMENT_ID" \ | ||
| --field body=@"$BODY_FILE" | ||
| echo "Updated checklist comment #$COMMENT_ID on PR #$PR_NUMBER" | ||
| else | ||
| gh api --method POST \ | ||
| "repos/$REPOSITORY/issues/$PR_NUMBER/comments" \ | ||
| --field body=@"$BODY_FILE" | ||
| echo "Created checklist comment on PR #$PR_NUMBER" | ||
| fi | ||
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| // Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| // or more contributor license agreements. Licensed under the Elastic License; | ||
| // you may not use this file except in compliance with the Elastic License. | ||
|
|
||
| // Package checklist provides pure functions for building and parsing the | ||
| // backport-checklist comment posted on pull requests targeting main. | ||
| // No I/O is performed here — callers supply any dynamic data (active branches, | ||
| // existing comment body) and receive back a rendered string. | ||
| package checklist | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "regexp" | ||
| "slices" | ||
| "strings" | ||
|
|
||
| "github.com/elastic/integrations/dev/backports" | ||
| ) | ||
|
|
||
| // marker is the HTML comment embedded at the start of every checklist comment. | ||
| // It is used to find and update an existing checklist rather than posting a new one. | ||
| const marker = "<!-- backport-checklist -->" | ||
|
|
||
| // PackageBranches pairs a package name with its active backport branches. | ||
| // Branches are in inventory order. | ||
| type PackageBranches struct { | ||
| Package string | ||
| Branches []backports.ActiveResult | ||
| } | ||
|
|
||
| // checkedLineRe matches a checked checkbox line: "- [x] `branch-name`..." | ||
| var checkedLineRe = regexp.MustCompile("^- \\[[xX]\\] `([^`]+)`") | ||
|
|
||
| // ParseCheckedBranches scans body for "- [x] `<branch>`" lines and returns the | ||
| // set of branch names that are currently ticked. An empty or marker-free body | ||
| // returns an empty (non-nil) map so callers never need a nil check. | ||
| func ParseCheckedBranches(body string) map[string]bool { | ||
| checked := make(map[string]bool) | ||
| if !strings.Contains(body, marker) { | ||
| return checked | ||
| } | ||
| for line := range strings.SplitSeq(body, "\n") { | ||
| if m := checkedLineRe.FindStringSubmatch(strings.TrimRight(line, "\r")); m != nil { | ||
| checked[m[1]] = true | ||
| } | ||
| } | ||
| return checked | ||
| } | ||
|
|
||
| // BuildComment renders the full comment body starting with marker. | ||
| // Packages that have no active branches are omitted, so stale sections disappear | ||
| // automatically on recompute without any special removal logic. | ||
| // The checked parameter is accepted for forward-compatibility but currently has no | ||
| // effect on the output — branches are rendered as plain list items until #19214 | ||
| // (auto-backport PR creation) is implemented, at which point checkboxes will be restored. | ||
| // | ||
| // Returns "" when no package has any active branch; callers should skip posting. | ||
| func BuildComment(pkgs []PackageBranches, checked map[string]bool) string { | ||
| if !slices.ContainsFunc(pkgs, func(p PackageBranches) bool { return len(p.Branches) > 0 }) { | ||
| return "" | ||
| } | ||
|
|
||
| var b strings.Builder | ||
|
|
||
| fmt.Fprintln(&b, marker) | ||
| fmt.Fprintln(&b, "## Backport branches") | ||
| fmt.Fprintln(&b) | ||
| fmt.Fprintln(&b, "Only branches for packages touched by this PR's current diff are shown.") | ||
| fmt.Fprintln(&b) | ||
| fmt.Fprintln(&b, "Consider backporting this change to the branches listed below.") | ||
| fmt.Fprintln(&b) | ||
| fmt.Fprintln(&b, "This comment is updated automatically on each push — manual edits will be overwritten.") | ||
|
|
||
| for _, p := range pkgs { | ||
| if len(p.Branches) == 0 { | ||
| continue | ||
| } | ||
|
|
||
| // One blank line before every package header (between intro and first | ||
| // package, and between consecutive packages). | ||
| fmt.Fprintln(&b) | ||
| fmt.Fprintf(&b, "**%s**\n", p.Package) | ||
| for _, r := range p.Branches { | ||
| line := fmt.Sprintf("- `%s`", r.Branch) | ||
| if r.MaintainedUntil != nil { | ||
| line += fmt.Sprintf(" (maintained until %s)", *r.MaintainedUntil) | ||
| } | ||
| fmt.Fprintln(&b, line) | ||
| } | ||
| } | ||
|
|
||
| fmt.Fprintln(&b) | ||
| fmt.Fprintln(&b, "---") | ||
| fmt.Fprintln(&b) | ||
| fmt.Fprintln(&b, "> [!TIP]") | ||
| fmt.Fprintln(&b, "> If a branch above is no longer required, set `archived: true` in its entry in `.backports.yml` to stop it appearing here.") | ||
| fmt.Fprintln(&b, `> If the branch has a known end-of-life date, prefer `+"`"+`maintained_until: "YYYY-MM-DD"`+"`"+` — it will be excluded automatically once that date passes.`) | ||
|
|
||
| return b.String() | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@v1v could you help us to review the permissions of this workflow (and
post-backport-checklisttoo) and the events used if they make sense to avoid any security concerns?TIA!! 😊