Release process¶
This page describes how a release is built, published and indexed.
Versioning¶
RELEASE_NOTES.md records every released version. Versions follow semantic versioning for user-visible behavior.
Check the latest published GitHub Release on the default branch before choosing the next version.
Publishing a release¶
A release is started by pushing a version tag that is reachable from the default branch, and by nothing else:
git push origin v0.1.0
release.yml builds every artifact, then creates the GitHub Release as a draft. Review that draft
and publish it by hand. Publishing it is what indexes the chart, because publish-chart.yml triggers on
a published release.
Never draft a new release from the GitHub UI. Doing so creates the tag, which starts release.yml
against a release that is already published. The workflow refuses before it checks out the code or logs
in to the registry, so nothing is pushed and recovery is cheap, but the release and its tag have to be
removed before the tag can be pushed properly:
gh release delete v0.1.0 --cleanup-tag --yes
Publishing the draft that release.yml leaves for you is a different action and is the correct final
step. The hazard is only in drafting a new release yourself.
To rebuild a release whose draft has not been published yet, re-run the workflow or dispatch it with the same tag. It refreshes the description and replaces the assets. A release that is already published is never rebuilt, since its image and provenance attestation are in the registry and cannot be withdrawn.
Release artifacts¶
Each release publishes:
| Artifact | Description |
|---|---|
| Container image | Multi-arch linux/amd64 and linux/arm64 on GitHub Container Registry |
| Provenance attestation | Signed Sigstore bundle for the image digest, pushed to GHCR and attached as cmp-issuer-<version>-provenance.sigstore.json |
| Installer manifest | dist/cmp-issuer-<version>-install.yaml from Kustomize |
| Helm chart | dist/cmp-issuer-<chart version>.tgz, packaged from charts/cmp-issuer and indexed into the chart repository |
| SBOM | CycloneDX at dist/cmp-issuer-<version>-sbom.cdx.json |
| Air-gapped bundle | cmp-issuer-<version>-airgap.tar.gz, unpacking to a directory of the same name, with the image as an OCI archive, the chart, the installer, the bill of materials, the notices and an INSTALL.txt |
The image is pushed and exported to the OCI archive by a single make docker-release build with two
exporters, so the bundled archive is the published image rather than a second build of the same source.
make release-bundle VERSION=<version> then assembles the tarball from what is already in dist, so
the image export, build-installer, sbom and the chart packaging all have to have run for the same
version first. The target names whichever one is missing.
Every target that writes a release artifact requires VERSION and names its output after it, so no two
releases can share a file name and a file copied out of dist still says which release produced it. Set
VERSION to the tag carried by IMG, for example:
make docker-archive IMG=ghcr.io/misiektoja/cmp-issuer:v0.1.0 VERSION=v0.1.0
The names come from one block at the top of the supply chain section of the Makefile, so a new
artifact is named alongside the existing ones rather than inline in the target that writes it. Two of
them do not carry the release tag verbatim:
- The packaged chart is
cmp-issuer-<chart version>.tgz, without the leadingv, because a Helm chart version has to be bare SemVer.helm packagealso names the directory inside the archive after the chart rather than the version, which Helm requires and which no build step here can change. make sbomrun withoutVERSION, asmake supply-chainand the supply chain workflow do, names the bill of materials after the commit it describes instead, since there is no release to name.
The GitHub release description is the RELEASE_NOTES.md section for the version being tagged. It is
extracted before the image is pushed, so a heading that is missing or no longer matches the tag fails
the run while retagging still costs nothing. The release is created as a draft, so the description can
still be edited before anyone sees it.
Build identity¶
The same VERSION is stamped into the binary, so a running manager names the release it came from
rather than reporting development. make build and every image target pass VERSION, the commit,
the commit date and IMG to the linker, and the Dockerfile forwards them as build arguments. Any
build that does not set VERSION falls back to git describe, so a development image still names the
tree it was built from.
The manager reports all of it on its first log line and from /manager --version, together with the
image, chart and release the install supplied. See
Troubleshooting.
buildDate is the commit date rather than the time of the build, so rebuilding a tag does not change
the binary. The variables the linker stamps live in internal/version; renaming one silently disables
the stamp, because the linker ignores an -X flag it cannot resolve.
CI workflows¶
| Workflow | Purpose |
|---|---|
test.yml |
Unit tests and OpenSSL interoperability |
lint.yml |
golangci-lint and actionlint |
codeql.yml |
CodeQL static analysis of the Go code |
scorecard.yml |
OpenSSF repository and supply chain checks, run after successful CodeQL analysis on main |
docs.yml |
Strict MkDocs build, and publishing the site from the default branch |
test-chart.yml |
Helm lint and a complete chart install for chart and build changes |
test-e2e.yml |
Kind e2e suite on dev and main, once per supported Kubernetes and cert-manager version, plus EJBCA enrollment |
ejbca-test-image.yml |
Publishes the preconfigured CMP server image, rebuilding it only when the upstream release moves |
interop-ncm.yml |
Hosted Nokia NCM enrollment on dev and main, weekly and by hand |
supply-chain.yml |
govulncheck and gitleaks on pull requests, adding SBOM and image scan on trusted branches |
go-patch.yml |
Weekly check for a newer Go patch in the targeted release series, opening the bump against dev |
release.yml |
Build and publish the release artifacts on a version tag |
publish-chart.yml |
Add the released chart to the Helm repository index when the release is published |
Pull requests target dev and run the fast unit, OpenSSL, lint and supply chain checks. The chart job
runs only when its inputs change. The three-version Kind matrix and EJBCA are deliberately deferred
until the change lands on dev, where failures can be fixed before promotion. Pushes to main repeat
the trusted-branch checks for the stable code. NCM remains automatic on both trusted branches but never
receives pull request code or fork credentials. Superseded runs are cancelled, except for NCM because
an external enrollment should not be abandoned halfway through.
Chart repository¶
release.yml packages the chart and attaches it to a draft GitHub Release. Publishing that release
triggers publish-chart.yml, which adds the new version to charts/index.yaml on the gh-pages
branch, so helm repo add picks it up.
The chart archives stay attached to their GitHub Release and the index points at those asset URLs, so
GitHub Pages serves only index.yaml. Existing entries are merged rather than replaced, which keeps
older versions installable, and the index lives under charts/ so the documentation site occupies the
root of the same Pages site.
The first run creates the gh-pages branch. GitHub Pages has to be enabled for the repository and
pointed at that branch before the repository URL resolves.
Documentation site¶
docs.yml runs the strict MkDocs build for documentation-related changes on pull requests and pushes
to dev and main. On the default branch it also publishes the rendered site to the root of the
gh-pages branch, so the documentation and the chart repository share one Pages site.
The publishing step replaces the previous documentation, so a page removed from docs/ disappears from
the site, but it leaves charts/index.yaml and .nojekyll alone. Both publishers take the gh-pages
concurrency group, so a release that lands while a documentation change is publishing waits instead of
racing.
Pre-release checklist¶
make test,make lint,make docs-build,make helm-lintmake test-e2ewhen controller or e2e specs changed, andmake test-e2e-ejbcawhen enrollment changed- Update
RELEASE_NOTES.mdwith user-facing entries under a## [<chart version>]heading carrying the release date, since the release workflow publishes that section as the GitHub release description - Refresh
artifacthub.io/changesincharts/cmp-issuer/Chart.yaml, along with any otherartifacthub.ioannotation the release changes. Chart metadata is frozen once the version is published, so a correction needs a new chart version - Point
config/manager/kustomization.yamlat the image the release will publish, which is what a clone applies when it has not runmake build-installer - Tag and push only when authorized to publish, and never create the release from the GitHub UI. See Publishing a release
Supply chain verification¶
Consumers can verify:
- Image digest against the provenance attestation in GHCR or the signed Sigstore bundle attached to the release
- Module vulnerabilities with published SBOM and govulncheck results
- Absence of leaked credentials via gitleaks history scan
Details in Provenance and supply chain.
License¶
Release artifacts contain Apache-2.0 original code plus dependency notices in THIRD_PARTY_NOTICES.md.