[CI][Backport] Automatic backport checklist on PRs targeting main#19997
[CI][Backport] Automatic backport checklist on PRs targeting main#19997mrodm wants to merge 22 commits into
Conversation
Adds ListActiveBackportBranches(path, packageName string, now time.Time) to dev/backports/inventory.go. Returns all active backport branches for a given package in inventory order, filtering out archived entries and entries whose maintained_until date has passed. Returns an empty slice (no error) when the package is not found. Part of elastic#19213. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces dev/backports/packages/detect.go with DetectPackages(files []string, packagesDir string) ([]string, error). Uses citools.ListPackages to discover all known package roots upfront, then maps each changed file path to its package by prefix match. Handles both flat (packages/<name>/) and nested (packages/<technology>/<name>/) structures without custom tree-walking. Returns a deduplicated list of package names in encounter order. Includes unit tests covering flat, nested, deduplication, name-from-manifest, non-package files, empty input, and error cases. Part of elastic#19213. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mage targets Adds two mage targets to magefile.go: - DetectBackportPackages(before, after string, asJSON *bool): runs git diff --name-only before..after and maps changed files to package names via bppackages.DetectPackages. Outputs one name per line or a JSON array with -json. - ListActiveBackportBranches(packageName string, asJSON *bool): returns all active branches for a package from .backports.yml. Outputs one branch name per line or a JSON array of full entries with -json. Both targets are used by the backport checklist GHA workflows introduced in elastic#19213. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds .github/workflows/backport-packages-detect.yml, the fork-safe
read-only half of the backport checklist two-workflow pair.
Triggers on pull_request (opened/synchronize) targeting main with no
write permissions. Checks out with full history (fetch-depth: 0) so
that git diff can resolve both base and head SHAs, installs mage, and
calls mage detectBackportPackages to map changed files to package names.
Uploads the result as a short-lived artifact (1 day retention):
{"pr_number": 1234, "packages": ["aws", "kubernetes"]}
Event context values are passed through env: to avoid script injection.
The write-permission half (post-backport-checklist.yml) consumes this
artifact via workflow_run and posts the checklist comment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds .github/workflows/post-backport-checklist.yml: triggers via workflow_run when backport-packages-detect succeeds, downloads the pr-packages artifact from the detecting run, finds/creates/updates a PR comment containing the backport checklist (<!-- backport-checklist --> marker, branch lines per package, preserved checkbox states). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract rendering and checkbox-state-preservation logic from the post-backport-checklist workflow into a new, unit-tested Go package (dev/backports/checklist), following the existing pattern set by dev/backports/changelog/comment.go. - BuildComment renders the full markdown checklist body (marker, per-package branch lines, maintained_until dates, tip footer); returns "" when no package has active branches. - ParseCheckedBranches extracts previously-ticked branch names from an existing comment body so their state is preserved on update. - RenderBackportChecklist mage target wires the two together: reads ARTIFACT env (path to pr-packages.json), existing body from stdin, and prints the rendered body. The workflow step now calls mage renderBackportChecklist instead of duplicating the rendering logic in bash. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tic#19213) The mage target was superseded by renderBackportChecklist, which calls backports.ListActiveBackportBranches internally. No workflow or script calls the standalone target, so the wrapper is removed. The underlying Go function and its tests are kept — they are still used by renderBackportChecklist. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
marker and hasActiveBranches are only used within the checklist package itself — no external caller references them — so they are unexported. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…l-slice return (elastic#19213) RenderBackportChecklist called ListActiveBackportBranches once per package, reading and parsing .backports.yml N times per invocation. Replace with ListAllActiveBackportBranches, which parses the inventory exactly once and returns results for all requested packages in a single call. ListActiveBackportBranches is removed as it had no remaining callers. Initialize DetectPackages result as make([]string, 0) instead of a var declaration so callers receive a non-nil empty slice, allowing the nil-to-empty guard in DetectBackportPackages to be dropped. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…astic#19213) Add citools.PackageInfo and ListPackagesWithNames, which return path and manifest name together from a single directory walk. Refactor ListPackages to delegate to it so the walk logic is not duplicated. Update DetectPackages, BuildPackageIndex, and buildKnownPackages to use ListPackagesWithNames. Each manifest is now read exactly once per invocation — previously ListPackages read every manifest for IsValid() and each caller re-read them all again to obtain the package name. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lastic#19213) Add trap 'rm -f "$BODY_FILE"' EXIT in post-backport-checklist.yml so the temp file is always removed on any exit path, not just the happy path. Remove the now-redundant explicit rm -f at the end of the script. Replace the single-use hasActiveBranches helper in checklist.go with an inline slices.ContainsFunc call, removing an unnecessary indirection. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- extract loadInventory helper to consolidate ReadFile+Unmarshal duplicated across ListAllActiveBackportBranches, CheckActive, and ValidateInventory - document fail-open behaviour for malformed maintained_until dates - pre-append filepath.Separator to package paths before the inner loop to avoid O(N×M) string allocations in DetectPackages - fix stale doc comment in DetectPackages (ListPackages → ListPackagesWithNames) - handle json.Marshal error in DetectBackportPackages instead of discarding with _ - detect duplicate checklist comments in post-backport-checklist workflow and post a warning to the PR instead of silently picking one Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- post-backport-checklist: add continue-on-error to download-artifact so a skipped detect job (backport:sync-changelog label) does not fail the post workflow before the missing-artifact guard is reached - checklist: match [X] uppercase checkbox in ParseCheckedBranches regex; add test case for uppercase state preservation - magefile: correct doc comment flag name -json → -asJSON - inventory: add missing doc comment to loadInventory - detect: document why make([]string, 0) is used over var declaration; assert non-nil in empty-result test to catch future regressions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| 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, "Check the branches to backport this change to.") |
There was a problem hiding this comment.
This message could be updated as part of #19214 . Right now, this message is just informative and therefore no pull requests are going to be created.
As part of #19214, as pull requests are going to be created, tihs message could be updated too to something like:
PRs will be created automatically on merge, or when you update this checklist after merge.
| if: | | ||
| build.branch == "nonexisting" |
There was a problem hiding this comment.
To be reverted before merging
| types: [opened, synchronize, reopened] | ||
| branches: | ||
| - main | ||
| - backport-checklist-automation |
There was a problem hiding this comment.
To be reverted before merging
There was a problem hiding this comment.
@v1v could you help us to review the permissions of this workflow (and port-backport-checklist too) and the events used if they make sense to avoid any security concerns?
TIA!! 😊
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| if checked[r.Branch] { | ||
| state = "x" | ||
| } | ||
| line := fmt.Sprintf("- [%s] `%s`", state, r.Branch) |
There was a problem hiding this comment.
Do you prefer that the comment just post a list of branches without the checkbox ? Thinking that, since it does not create Pull Requests yet the automation (to be done in #19214). WDYT ?
💚 Build Succeeded
History
cc @mrodm |
|
Tick the box to add this pull request to the merge queue (same as
|
Proposed commit message
Implement automatic backport checklist for PRs targeting main (#19213).
When a PR is opened or updated, a workflow detects which packages it touches and posts
a checklist comment listing the active backport branches for those packages. Authors tick
the boxes for the branches they want to backport to. The checklist is updated automatically
on each new push, preserving previously checked boxes.
Architecture — two-workflow fork-safety pattern:
backport-packages-detect.yml— triggered bypull_request(no secrets, fork-safe).Runs
mage detectBackportPackages, uploads{ pr_number, packages }as a workflow artifact.post-backport-checklist.yml— triggered byworkflow_runon the above. Haspull-requests: write. Downloads the artifact, renders the checklist viamage renderBackportChecklist, and posts or updates the comment viagh api.New Go packages:
dev/backports/packages— pureDetectPackagesfunction: maps git-diff file paths topackage names by prefix-matching against
citools.ListPackagesWithNamesoutput. Packagename is read from
manifest.yml, not inferred from the directory name.dev/backports/checklist— pureBuildComment/ParseCheckedBranchesfunctions:renders the full comment body and parses previously-checked branch names, preserving
checkbox state across re-renders.
New mage targets:
detectBackportPackages— runsgit diff --name-onlyand maps files to packages; emitsJSON when
-asJSONis passed.renderBackportChecklist— reads the artifact via$ARTIFACTenv var, parses existingchecked branches from stdin, and prints the new comment body (empty = no active branches).
backport:sync-changeloglabel guard:PRs created by the sync-changelog workflow (#19215) carry this label; the detect job is
skipped via a job-level
if:so they do not receive a checklist of their own. The labelrename from
automationtobackport:sync-changelogis handled in a separate PR.Notable behaviour:
clean up manually.
MaintainedUntilparse error: fails open (branch stays active) —ValidateInventoryenforces the date format so this path is only reachable via an unvalidated direct edit.
[X](uppercase) is treated identicallyto
[x].Author's Checklist
backport-checklist-automationfrom thebranchesfilter inbackport-packages-detect.yml(line 9) — kept during development for local testing.8e6c280916,7810771711) beforeopening the PR.
How to test this PR locally
mainthat touches at least one package underpackages/with anactive backport branch in
.backports.yml.Backport — detect packagesworkflow runs automatically. Check its artifact contains{ "pr_number": N, "packages": [...] }.Backport — post checklistworkflow runs next. Verify the checklist comment appearson the PR.
preserved.
backport:sync-changeloglabel — verify no checklist is posted.To run unit tests locally:
go test ./dev/backports/... ./dev/backports/packages/... ./dev/backports/checklist/...Related issues
automationlabel tobackport:sync-changelog)Related Pull Requests testing this feature:
Screenshots
Example of comment:
