Repository navigation
ci: release versioned images and notes - #80
Conversation
Dependabot targets dev; the deployment guide describes the branch model (dev default, main frozen, releases from v* tags), the dev image tag and its signature identity, and that krini:staging is no longer updated. The README links point to dev and to ci.yml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run CI on dev only. Bring back the unit and end-to-end tests that were lost when ci.yml replaced checks.yml, as in cloud-registry-ui: a Test job (Vitest with the coverage threshold, report to Codecov) and End-to-end tests per browser in the Playwright image. The type check moves into Build. The gate and publish need the new jobs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sha-<commit> tags of images built before the rename verify with the staging identity; releases from v* tags are planned (#62). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # README.md
The release workflow runs the same smoke test on the release image. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add release.yml, adapted from cloud-registry-ui: prepare checks the signed tag, the commit on main, the versions in package.json and CITATION.cff, the curated notes and the CI run; the image is pushed by digest, scanned, smoke-tested as in CI, then attested, signed and tagged X.Y.Z (latest for the highest stable version), and the GitHub release is created with the shared publish-release action (pre-releases until 1.0). scan.yml scans the latest release weekly. docs/releasing.md describes the steps and the settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Once CITATION.cff has a version that isn't tagged, CI requires the same version in package.json and the curated release notes in the organization's format, so that release-prep pull requests fail instead of the release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Release readiness now always requires the same version in package.json and CITATION.cff, and treats a release as in preparation once CITATION.cff has a date-released. The smoke test runs a container of its own instead of one named app. Note where the pin of publish-release must be kept in sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Reviewer's GuideImplementationExtract the image smoke test into a reusable script so CI and release jobs can check the same container behavior.
ConfigurationAdd the tagged-release workflow that validates the release, builds and scans the image by digest, smoke-tests it, then attests, signs, tags, and publishes it.
Add release-readiness checks to CI and require them before publishing development images and before the CI gate passes.
Replace CI's inline image smoke test with the shared script used by the release workflow.
Add a scheduled workflow to scan the latest published release image for vulnerabilities.
DocumentationDocument release tags, image verification, and configuration for deploying versioned images.
Add the release procedure, pre-tag checks, repository settings, and scheduled-scan guidance.
Explain the versioned release images and link to the release instructions from the README.
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #80 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 12 12
Lines 273 273
Branches 77 77
=========================================
Hits 273 273 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Release readiness also starts on release notes or a version bump after an earlier release, and then requires date-released, which prepare in release.yml and the pre-tag commands now check too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Linked issuesChecked at commit 3d62461 #62 ci: release versioned images and notes · 2 of 3 met · 1 to check by hand
|
There was a problem hiding this comment.
Hey - I've found 6 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path=".github/workflows/ci.yml" line_range="184" />
<code_context>
+ - name: Submit Report to Codecov
+ uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ files: ./coverage/lcov.info
+ disable_search: true
</code_context>
<issue_to_address>
**Pull requests can expose the Codecov token**
When a same-repository pull request is opened by someone with write access and its workflow is modified to exfiltrate the secret, a contributor with write access can submit a same-repository pull request that changes the workflow to send `CODECOV_TOKEN` to an external host; because the Codecov step receives the secret on pull-request runs, the token can be leaked and abused for Codecov uploads.
Pass `CODECOV_TOKEN` only on trusted push runs, or otherwise ensure pull-request workflows cannot access it.
</issue_to_address>
### Comment 2
<location path=".github/workflows/release.yml" line_range="380" />
<code_context>
+ # of an older release nor a concurrent release moves `latest`
+ # back: the highest stable release among the tags on `main`, so
+ # that stray tags are ignored
+ - name: Check whether this is the highest release
+ id: latest
+ env:
</code_context>
<issue_to_address>
**Older release replaces latest**
When stable release image jobs overlap and a lower release checks the tags before a higher release is visible, then reaches the tag step after the higher release updates `latest`, the older job uses its stale `latest=true` decision in `imagetools create` and overwrites the newer digest, so users pulling `latest` get an older image.
Serialize stable release image tagging and recheck which release should own `latest` immediately before updating it.
Also at `.github/workflows/release.yml:51-53`, `.github/workflows/release.yml:403`, `.github/workflows/release.yml:412`.
</issue_to_address>
### Comment 3
<location path=".github/workflows/ci.yml" line_range="459" />
<code_context>
- needs: [lint, typecheck, build, audit, image, push, scan]
- # Docker Hub secrets; the environment only allows the branches `staging`
- # and `dev`
+ needs: [lint, test, build, audit, e2e, image, push, scan]
+ # Docker Hub secrets; the environment only allows the branch `dev`
environment: dev-images
</code_context>
<issue_to_address>
**Failed readiness still publishes dev**
When a push has passing `publish` dependencies but its `readiness` job fails, `publish` runs without depending on `readiness`, then signs and tags the commit's image as `dev` even though the commit failed CI.
Add `readiness` to the `publish` job's dependencies.
Also at `.github/workflows/ci.yml:436`.
</issue_to_address>
### Comment 4
<location path=".github/workflows/ci.yml" line_range="592" />
<code_context>
+ fi
+ # The commit of the (annotated) tag, if any: later commits are
+ # skipped, but not the tagged commit itself
+ tagged="$(git ls-remote origin "refs/tags/v${version}^{}" | cut -f1)"
+ if [[ -n "${tagged}" && "${tagged}" != "${SHA}" ]]; then
+ echo "v${version} is tagged: no release in preparation"
</code_context>
<issue_to_address>
**Invalid existing tag skips checks**
When a version tag already exists but is lightweight, points to another commit, or fails release validation, while the current prep change lacks required release metadata, `readiness` treats the existing tag as a completed release and exits successfully without checking the release notes or `date-released`; lightweight tags also evade the peeled-ref lookup. Incomplete or unreleasable version prep therefore passes CI.
Recognize lightweight and annotated tags, and skip prep checks only when the existing tag is confirmed to represent a completed, valid release.
Also at `.github/workflows/ci.yml:593`, `.github/workflows/ci.yml:600`.
</issue_to_address>
### Comment 5
<location path=".github/workflows/release.yml" line_range="393-394" />
<code_context>
+ cd repo
+ git fetch --quiet --filter=blob:none --tags "${REPOSITORY_URL}" \
+ "+refs/heads/main:refs/remotes/origin/main"
+ highest="$(git tag -l 'v*' --merged origin/main \
+ | grep -E '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' \
+ | sort -V | tail -n 1 || true)"
+ if [[ "${highest}" == "${TAG}" ]]; then
</code_context>
<issue_to_address>
**Latest points to an unreleased image**
When a newer stable version tag is on `main` but its release image has not been published, for example because its scan failed, the selection counts every matching stable tag merged into `main`, without checking whether its image passed the release scan and was tagged. If a newer release fails before publishing its image, this run skips updating `latest`, leaving it on an older release—or unset after the first release—so users pulling `latest` do not get the highest available release.
Select the highest stable version whose image has been successfully published, rather than the highest stable tag on `main` alone.
</issue_to_address>
### Comment 6
<location path=".github/workflows/release.yml" line_range="453" />
<code_context>
+ # release of it exists. Keep the pin in sync with the checker of the
+ # `readiness` job of `ci.yml` and with `docs/releasing.md`
+ - name: Check release notes
+ uses: elixir-cloud-aai/.github/actions/publish-release@31f22fccc1f3c597ed7c1d1f75b2f85522155348 # v1.1.0
+ with:
+ notes: docs/releases/${{ steps.version.outputs.version }}.md
</code_context>
<issue_to_address>
**Zero-major releases are marked stable**
When a stable 0.x tag, such as `v0.1.0`, is published, `prepare` computes `prerelease=true` for stable 0.x tags, but `publish` passes only `notes` and `commit` to the shared action, so the GitHub release is created as stable before 1.0.0.
Pass the computed prerelease output to the publish action so 0.x releases are marked as pre-releases.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 6 findings to address first, and if the release checks or image-tagging logic are wrong, the workflow could publish and sign a flawed image under a version tag and create a release that downstream deployments or users rely on. Reverting the workflow would not undo an already published release or uses of that image.
Blocking findings: .github/workflows/ci.yml:184, .github/workflows/release.yml:380, .github/workflows/ci.yml:459, .github/workflows/ci.yml:592, .github/workflows/release.yml:394, and 1 more
Publish image needs Release readiness, so that a commit that failed it gets no dev tags. Release image runs one at a time across tags, so that latest can't be moved back by an older decision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # .github/workflows/ci.yml # docs/deployment.md
Summary
Automates versioned releases, following the org's release pattern and cloud-registry-ui's release workflow, deviating only where Krini needs it.
Stacked on #75 (it builds on #75's
ci.yml): merge #75 first; this PR's diff shrinks to its own commits then..github/workflows/release.yml/Release, onv*tags (adapted from cloud-registry-ui):prepare/Prepare release: tag format (vX.Y.Z,-alphaN/-betaN/-rcN), annotated tag with a signature GitHub verified, commit onmain, versions inpackage.jsonandCITATION.cffequal to the tag, curated notesdocs/releases/<version>.mdin the org format (sharedpublish-releaseaction, check-only), no published release of the tag yet, and a successful CI run for the push of the commit todev.push/Push release image(environmentrelease): build once, push by digest with SBOM and provenance; stops if the version tag exists.scan/Scan release image(no credentials): Trivy via the sharedscan-imageaction, fails on fixable critical/high.smoke/Smoke test(no credentials, Krini-specific): CI's smoke test on exactly the pushed digest (non-root, read-only root file system, login page, start page redirect, image optimizer).release-image/Release image(environmentrelease): attest build provenance, sign with Sigstore (keyless), tagX.Y.Z(andlatestfor the highest stable version onmain).publish/Publish release: GitHub release with the curated notes plus GitHub's generated notes under## Commitsand the**Full Changelog**line (sharedpublish-releaseaction); pre-release until 1.0.0.smokejob, and theVERSIONbuild argument (Krini'sDockerfilesets the image's version label from it)..github/workflows/scan.yml/Scan: weekly Trivy scan of the latest release image (as in cloud-registry-ui).ci.yml: newreadiness/Release readinessjob (in the gate), so that release-prep PRs fail instead of the release: the versions inpackage.jsonandCITATION.cffmust always agree; unless the version is tagged on an earlier commit, adate-released, release notes or a version bump after an earlier release start the release preparation, and thendate-releasedmust be set and the curated notes must exist, without TODOs, in the org format.preparealso requiresdate-released. (cloud-registry-ui runs this check in itsdocsjob; Krini's docs job comes with docs: build the docs with MkDocs on Read the Docs #73, which can move it there if wanted.)scripts/check-image.sh, which CI'simagejob and the release'ssmokejob both run.docs/releasing.md(steps, pre-tag commands, repository settings, scheduled scan),docs/deployment.md(release tagsX.Y.Z/latest, verifying release images), README (Versioning).Closes #62
Settings the
releaseenvironment needsChecked by name only by the main session/uniqueg (agents can't read secrets):
release: required reviewers (leads), deployments limited tov*tags (done per uniqueg).release(not repository secrets):DOCKERHUB_LOGIN,DOCKERHUB_TOKEN(allowed to pushelixircloud/krini).DOCKERHUB_ORG(defaultelixircloud); forks must set it before a test tag.elixircloud/krinimust be public (the scan and smoke jobs pull without login); it is already used by CI.v*tags only by maintainers and immutable; immutable releases;mainfast-forward only by maintainers (imported from cloud-registry-ui).GITHUB_TOKENonly (contents: writeinpublish,actions: readinprepare).Verification
actionlint1.7.12 (with shellcheck 0.11.0) onci.yml,release.yml,scan.yml: no findings;shellcheck scripts/check-image.sh: no findings.readinessscript, extracted fromci.ymlwithyq, run locally in scratch repositories against the pinnedpublish.pychecker: 13 scenarios behave as intended (missingCITATION.cff, missing version, version mismatch, undated before any release, dated without notes, notes with TODO, notes not in format, good notes, tagged on another commit, tagged on this commit, version bumped after a release without date, notes without date, other pre-release tagged).docs/releasing.md:bash -npasses. Running them against a commit was not possible in my sandbox.scripts/check-image.sh: run by CI'sBuild imagejob on this PR (see checks).DOCKERHUB_ORGto a test namespace first (the workflows fall back toelixircloud), create thereleaseenvironment with test Docker Hub secrets, push a signed test tag (e.g.v0.0.1-rc1) on a commit that passed CI on the fork'sdevand is on itsmain, with matching versions and notes, and check the target namespace in the job logs.Release readinessandBuild image(which now runsscripts/check-image.sh). The externalVercel – krinicheck fails, as on other PRs and ondev(not required).Review
Full lane (
adversarial-reviewerandquality-reviewer, two rounds).Sourcery (6 threads, all answered and resolved): fixed
publishnot needingreadiness(a commit failing it could getdevtags) and serializedrelease-imageacross tags (stalelatestdecision); declined the Codecov token on same-repo PRs (as in cloud-registry-ui; write access needed), the existing-tag skip inreadiness(tags are immutable, lightweight tags make it stricter),latestafter a failed newer release (next patch moves it, as in cloud-registry-ui), and 0.x releases marked stable (the shared action'sprerelease: autoalready makes them pre-releases).Declined review findings
push,scan,smoke,release-image; the pre-tag commands indocs/releasing.mdduplicatingprepare; long header comment (medium/low, quality): kept as in cloud-registry-ui's release workflow so that the repos stay aligned and diffable; a shared script or action would be an org-wide change. The pin ofpublish-releasenow names the two other places to keep in sync.smoke(low): keeps "image can't be pulled" apart from smoke-test failures.date-releasedfrom the previous release isn't detected (low): the release-prep step indocs/releasing.mdsays to update it; a date comparison against tag dates would need more API calls than the gain is worth.scan.ymlpicks the newest pre-release by creation date, not by version, before 1.0 (low): same as cloud-registry-ui; matters only for backport releases, which Krini doesn't make.🤖 Generated with Claude Code
Summary by Sourcery
Automate validated, signed versioned releases for Krini images and GitHub releases, with vulnerability scanning, smoke tests, release-readiness checks, and supporting documentation.
New Features:
Enhancements:
CI:
Deployment:
latesttag for the highest stable release, with provenance attestations and Sigstore signatures.Documentation: