Repository navigation
chore: share the release workflow and cut releases here (ENG-9233) - #3
Conversation
|
@cubic-dev-ai please review |
@johnsca I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 3 files
Architecture diagram
sequenceDiagram
participant Dev as Developer
participant GH as GitHub Actions
participant CR as cut-release.yml
participant VR as version job
participant PB as publish job
participant GHAPI as GitHub API
Note over Dev,GHAPI: Release workflow via manual dispatch
Dev->>GH: Dispatch release (bump, dry_run)
GH->>GH: Enforce concurrency group
GH->>CR: Call workflow (bump, dry_run, STEP_SECURITY_API_KEY)
CR->>VR: Run version job (read-only token)
VR->>VR: Harden Runner (if API key provided)
VR->>VR: Validate bump is patch|minor|major
alt Invalid bump
VR-->>CR: Fail - request rejected
end
VR->>VR: Validate target is default branch
alt Non-default branch
VR-->>CR: Fail - release refused
end
VR->>GHAPI: Checkout full history (fetch-depth: 0)
VR->>GHAPI: Run github-tag-action (force_bump, push: false)
GHAPI-->>VR: next_tag, changelog, previous_tag
VR->>VR: Report version summary
VR->>VR: Require first release to be major
alt No tags and bump != major
VR-->>CR: Fail - first release must be major
end
VR-->>CR: Outputs new_tag, changelog
CR->>PB: Run publish job (write token, if success && !dry_run)
PB->>PB: Refuse run_attempt > 1
alt Re-run detected
PB-->>CR: Fail - re-run refused
end
PB->>GHAPI: Check tag collision (matching-refs)
alt Tag already exists
PB-->>CR: Fail - release over existing tag refused
else Tag free
PB->>GHAPI: gh release create (tag, target commit, notes)
GHAPI-->>PB: Release published
end
PB-->>CR: Complete
CR-->>GH: Workflow finished
GH-->>Dev: Status and summary
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
@cubic-dev-ai please review again |
@johnsca I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 3 files
Architecture diagram
sequenceDiagram
participant DEV as Developer
participant GH as GitHub Actions
participant CR as cut-release.yml
participant REL as release.yml
participant RD as Release Dispatch
participant TAG as git-tag-action
participant API as GitHub API
participant RELS as GitHub Releases
Note over DEV,RELS: Release Workflow Dispatch
DEV->>GH: Dispatch Release workflow
GH->>REL: Run release.yml
REL->>CR: Call cut-release.yml (workflow_call)
Note over CR: Version Job (read-only)
CR->>CR: Validate bump input (patch/minor/major)
alt Dry run or invalid bump
CR-->>DEV: Stop (validate or summarize)
end
CR->>CR: Check default branch
alt Not default branch
CR-->>DEV: Refuse release
end
CR->>GH: Checkout repo (fetch-depth: 0)
CR->>TAG: Work out next version
TAG->>TAG: Read tag history
TAG-->>CR: New tag & changelog
alt No previous tag
alt Bump not major
CR-->>DEV: Require first release = major
end
end
CR-->>REL: Pass version info
opt Dry run false
Note over CR: Publish Job (write access)
CR->>CR: Check run attempt (block re-runs)
CR->>API: Check if tag exists
alt Tag already exists
CR-->>DEV: Refuse (tag taken)
end
Note over CR: Claim tag ref first
CR->>API: POST git/refs (create tag)
CR->>API: Create release (target commit)
alt Release fails
CR->>API: Delete tag (cleanup)
CR-->>DEV: Report failure
else Release succeeds
AP I-->>RELS: Publish release
CR-->>DEV: Report success
end
end
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
@cubic-dev-ai please review again |
@johnsca I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 3 files
Architecture diagram
sequenceDiagram
participant Dev as Developer
participant GH as GitHub Actions
participant Caller as Caller Workflow (release.yml)
participant CutRelease as Called Workflow (cut-release.yml)
participant VersionJob as version job
participant PublishJob as publish job
participant Harden as Harden Runner (step-security)
participant Checkout as actions/checkout
participant TagAction as github-tag-action
participant GHAPI as GitHub API (git refs, releases)
Note over Dev,GHAPI: Release cut flow
Dev->>GH: Dispatch workflow_dispatch (bump, dry_run)
GH-->>Caller: Trigger with inputs
Caller->>GH: concurrency: release (serialize)
Caller->>CutRelease: workflow_call with permissions contents:write
CutRelease->>VersionJob: Start job (contents: read)
Note over CutRelease,VersionJob: Harden Runner enabled if STEP_SECURITY_API_KEY set
VersionJob->>Harden: Harden Runner (egress policy)
VersionJob->>VersionJob: Validate bump in {patch, minor, major} (case-sensitive)
VersionJob->>VersionJob: Refuse non-default branch (reads calling repo default_branch)
VersionJob->>Checkout: Fetch full tag history (fetch-depth: 0)
Checkout-->>VersionJob: Repository code with all tags
VersionJob->>TagAction: Work out next version (force_bump, push: false)
TagAction-->>VersionJob: previous_tag, new_tag, changelog
VersionJob->>VersionJob: Report version summary to step summary
VersionJob-->>CutRelease: Outputs (new_tag, changelog)
alt Not dry run (success() && !inputs.dry_run)
CutRelease->>PublishJob: Start job (contents: write)
Note over CutRelease,PublishJob: No checkout, no third-party actions, only gh CLI
PublishJob->>PublishJob: Refuse re-run (run_attempt != 1)
PublishJob->>GHAPI: Check matching refs for tags/TAG
alt Tag already exists
GHAPI-->>PublishJob: Tag exists
PublishJob->>PublishJob: Refuse (exit 1)
else Tag does not exist
PublishJob->>PublishJob: Arm EXIT trap (claimed=0)
PublishJob->>GHAPI: POST git/refs to claim tag ref (tag at current commit)
alt Claim succeeds
PublishJob->>PublishJob: Set claimed=1
PublishJob->>GHAPI: Create release for tag (with changelog)
alt Release creates successfully
GHAPI-->>PublishJob: Release published
Note over PublishJob: Tag permanently locked in
else Release fails
GHAPI-->>PublishJob: Error
PublishJob->>PublishJob: Cleanup: verify no release, then DELETE tag ref
Note over PublishJob: Safe retry possible (same version number)
end
else Claim fails (tag taken by another run/race)
GHAPI-->>PublishJob: 422 error
PublishJob->>PublishJob: Exit (tag belongs to someone else)
end
end
else dry_run is true
Note over CutRelease: Publish job skipped entirely
end
Note over GHAPI,Caller: Caller workflow ends, concurrency released
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
No issues found across 3 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Architecture diagram
sequenceDiagram
participant Dispatch as Dispatch Caller<br/>(release.yml)
participant CutWorkflow as Cut-Release Workflow<br/>(cut-release.yml)
participant VersionJob as Version Job<br/>(read-only)
participant PublishJob as Publish Job<br/>(write)
participant DefaultBranch as Caller's Default Branch
participant GitRefs as GitHub Ref API
participant TagAction as Version Tool<br/>(mathieudutour/github-tag-action)
participant HardenRunner as Harden Runner<br/>(step-security)
participant Releases as GitHub Release API
Note over Dispatch,CutWorkflow: Manual dispatch with bump + dry_run inputs
Dispatch->>CutWorkflow: Call workflow with bump & dry_run<br/>permissions: contents write
CutWorkflow->>VersionJob: Start version calculation<br/>permissions: contents read
alt STEP_SECURITY_API_KEY is set
VersionJob->>HardenRunner: Enable egress policy
end
VersionJob->>VersionJob: Validate bump value<br/>must be patch, minor, or major
VersionJob->>VersionJob: Check branch matches<br/>caller's default branch
alt Wrong branch
VersionJob-->>Dispatch: FAIL - refuse release<br/>from non-default branch
end
VersionJob->>DefaultBranch: Fetch full tag history<br/>with all branches
VersionJob->>TagAction: Calculate next version<br/>with forced bump value
TagAction-->>VersionJob: previous_tag, new_tag, changelog
alt First release (previous_version = 0.0.0) and bump != major
VersionJob-->>Dispatch: FAIL - first release<br/>must be major
else Dry run requested
VersionJob-->>Dispatch: Report version in<br/>step summary and stop
else Publishing allowed
VersionJob-->>PublishJob: Pass new_tag + changelog<br/>if previous job succeeded
end
PublishJob->>GitRefs: Check if tag already exists
alt Tag already exists
PublishJob-->>Dispatch: FAIL - refuse to publish<br/>over existing tag
end
PublishJob->>GitRefs: Claim tag ref<br/>with POST git/refs
PublishJob->>PublishJob: Set ownership flag<br/>for cleanup trap
opt Initial tag claim fails
PublishJob-->>Dispatch: FAIL - tag already taken
end
PublishJob->>PublishJob: Handle re-run attempt<br/>refuse if run_attempt != 1
PublishJob->>Releases: Create GitHub release<br/>with claimed tag
alt Release creation succeeds
Releases-->>PublishJob: Release published
else Release creation fails
PublishJob->>GitRefs: Delete owned tag ref<br/>via EXIT trap
end
PublishJob-->>Dispatch: Report success with<br/>version summary
[ENG-9233](https://stacklet.atlassian.net/browse/ENG-9233) What ---- - Adds `cut-release.yml`, a `workflow_call` workflow that works out the next version from the tag history and publishes a GitHub release for it. It is the release workflow from `terraform-azure-stacklet-cost-setup`, lifted so the other five module repositories call it instead of copying it. - Adds `release.yml`, the dispatch caller, so this repository cuts its own releases down the same path the module repositories will. - Documents `cut-release` in the README beside `run-script`, with the caller block to copy. Why ---- Five Terraform module repositories have no release workflow, and the sixth already had this one. Copying it five more times leaves six sets of the same guards to keep in step. The guards are the part worth sharing, because a published release is immutable and a slip in one cannot be taken back. The lift departs from the original in three places. 1. The `bump` input is a string rather than a choice, because `workflow_call` has no choice type. A new step rejects anything but patch, minor, or major. Without it the tag action reads an unrecognised `force_bump` as no bump, falls back to the commit messages, and publishes a version nobody asked for. 2. The branch guard compares against the calling repository's default branch rather than the literal `main`, so it holds for a caller whose default branch has another name. 3. The version job gains the Harden Runner step `run-script` already uses. That job fetches and runs a third-party action, while the publish job runs nothing but `gh`. The caller keeps the `workflow_dispatch` trigger and the `concurrency` group. Both matter only on the workflow a person dispatches, and a called workflow cannot serialise its caller. Testing ---- - [x] `just lint` passes. - [x] `uvx zizmor@1.23.1 -v --gh-token=... .github/workflows/` reports no findings, with the five online audits scheduled rather than skipped. - [x] Each action pin resolves to a commit via `gh api repos/<o>/<r>/commits/<sha>`. - [ ] After merge, dispatch Release on main with dry run checked, and confirm the summary reports v0.0.0 as previous and v1.0.0 as new. - [ ] Dispatch Release with a major bump and confirm v1.0.0 is published against the dispatched commit. Docs ---- The README gains a `cut-release` section under Reusable workflows. The pinning note at the top no longer says this repository publishes no release tags, because after this it does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…_branches, fix the re-run wording Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… deleting Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…k the bump case-sensitively Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
26e5738 to
dfced5f
Compare
ENG-9233
what
Adds
cut-release.yml, aworkflow_callworkflow that works out the next version from the tag history and publishes a GitHub release for it. It is the release workflow fromterraform-azure-stacklet-cost-setup, lifted so the other five module repositories call it instead of copying it.Adds
release.yml, the dispatch caller, so this repository cuts its own releases down the same path the module repositories will. Documentscut-releasein the README besiderun-script, with the caller block to copy.why
Five Terraform module repositories have no release workflow, and the sixth already had this one. Copying it five more times leaves six sets of the same guards to keep in step. The guards are the part worth sharing, because a published release is immutable and a slip in one cannot be taken back.
The lift departs from the original in four places.
bumpinput is a string rather than a choice, becauseworkflow_callhas no choice type. A new step rejects anything but patch, minor, or major. Without it the tag action reads an unrecognisedforce_bumpas no bump, falls back to the commit messages, and publishes a version nobody asked for.main, so it holds for a caller whose default branch has another name.run-scriptalready uses. That job fetches and runs a third-party action, while the publish job runs nothing butgh.POST git/refsbefore creating the release, rather than lettinggh release createmake it. The old order answers a name taken in the gap by releasing the tag that is there and ignoring--target, which publishes an immutable release against a commit nobody chose. AnEXITtrap removes the ref if the release call then fails, so a retry still works out the same number. It is armed before the write rather than after, which is safe because the cleanup confirms the tag is this run's before deleting it. Raised by cubic on this PR.The caller keeps the
workflow_dispatchtrigger and theconcurrencygroup. Both matter only on the workflow a person dispatches, and a called workflow cannot serialise its caller.testing
just lintpasses.uvx zizmor@1.23.1 -v --gh-token=... .github/workflows/reports no findings, with the five online audits scheduled rather than skipped.gh api repos/<owner>/<repo>/commits/<sha>.ghon ten paths: a clean publish leaves the tag, a failing release call deletes it, both refusal paths create nothing, a tag someone else holds survives the pre-check and the claim whether it sits on this commit or another, and neither a release that landed while the client died nor a failing release lookup loses its tag. A tag moved after the claim survives. A run killed before it records the claim leaves the tag, which is the deliberate side of that trade.patch,minor,major,Major,PATCH, empty,majorly, andpatch minor, accepting only the three lowercase values.mainwith dry run checked, and confirm the summary reportsv0.0.0as previous andv1.0.0as new.majorbump and confirmv1.0.0is published against the dispatched commit.docs
The README gains a
cut-releasesection under Reusable workflows. The pinning note at the top no longer says this repository publishes no release tags, because after this it does.