Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/prepare-release-as-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.OPENBAO_OPERATOR_RELEASE_PR_APP_ID }}
client-id: ${{ secrets.OPENBAO_OPERATOR_RELEASE_PR_CLIENT_ID }}
private-key: ${{ secrets.OPENBAO_OPERATOR_RELEASE_PR_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ jobs:
if: ${{ steps.release-artifact-guard.outputs.skip_release_please != 'true' }}
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.OPENBAO_OPERATOR_RELEASE_PR_APP_ID }}
client-id: ${{ secrets.OPENBAO_OPERATOR_RELEASE_PR_CLIENT_ID }}
private-key: ${{ secrets.OPENBAO_OPERATOR_RELEASE_PR_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.OPENBAO_OPERATOR_RELEASE_TAG_APP_ID }}
client-id: ${{ secrets.OPENBAO_OPERATOR_RELEASE_TAG_CLIENT_ID }}
private-key: ${{ secrets.OPENBAO_OPERATOR_RELEASE_TAG_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
Expand Down
19 changes: 15 additions & 4 deletions docs/contribute/release-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ Release-please remains the source of truth for release notes. After release-plea

Keep `CHANGELOG.md` generated by release-please. Put hand-written release summaries, migration notes, and operator-facing callouts in `release-notes/X.Y.Z.md`; the website release generator and draft GitHub Release creation prepend that file to the generated changelog entry for the matching version.

For patch releases, make the source that release-please sees match the release note you want. Prefer one cherry-picked conventional commit per user-facing fix. If a backport PR is squash-merged, make the squash title deliberately user-facing because it becomes the generated changelog entry. Use `release-notes/X.Y.Z.md` for extra context, but do not hand-edit generated `CHANGELOG.md` sections.

</Callout>

<DecisionTable
Expand All @@ -159,13 +161,22 @@ Keep `CHANGELOG.md` generated by release-please. Put hand-written release summar
{
cells: [
"Post-release",
"GitHub Release exists, assets verify, provenance evidence is recorded, Artifact Hub metadata is visible, and announcement links are captured.",
"GitHub Release exists, assets verify, provenance evidence is recorded, Artifact Hub metadata is visible, no unexpected release-please PR or branch remains, and announcement links are captured.",
],
emphasis: "caution",
},
]}
/>

<CommandBlock
language="bash"
label="verify"
title="Run post-release verification"
code={`VERSION=X.Y.Z REPO=dc-tec/openbao-operator hack/ci/verify-post-release.sh`}
>
Requires `gh`, `jq`, `git`, Docker Buildx, and `cosign`. Checks the remote tag, published GitHub Release assets, checksum signature, OCI Helm chart publication and signature, and leftover release-please PRs or branches.
</CommandBlock>

<CommandBlock
language="bash"
label="verify"
Expand Down Expand Up @@ -193,14 +204,14 @@ jq '.release, .identity_constraints, .images, .chart, .release_artifacts.checksu

## Verifying artifacts {#5-verifying-artifacts}

Use the verification skeleton above as the default post-release evidence pack. Keep the exact workflow identity, tag ref, and digest-pinned subjects aligned with the artifacts that were just published.
Run `hack/ci/verify-post-release.sh` first and use the verification skeleton above for deeper spot checks or release evidence capture. Keep the exact workflow identity, tag ref, and digest-pinned subjects aligned with the artifacts that were just published. Once the release is fully published, delete the matching `release-please--branches--<base>` branch unless release-please still has an active release PR for that base branch.

<Callout type="note" title="release-please token requirements">

Release automation must use non-default tokens so the resulting tag and GitHub Release can trigger downstream workflows. Use two repo-scoped GitHub Apps:

- `OPENBAO_OPERATOR_RELEASE_PR_APP_ID` and `OPENBAO_OPERATOR_RELEASE_PR_PRIVATE_KEY` for PR-only `release-please`
- `OPENBAO_OPERATOR_RELEASE_TAG_APP_ID` and `OPENBAO_OPERATOR_RELEASE_TAG_PRIVATE_KEY` for the custom `Release Tag` workflow
- `OPENBAO_OPERATOR_RELEASE_PR_CLIENT_ID` and `OPENBAO_OPERATOR_RELEASE_PR_PRIVATE_KEY` for PR-only `release-please`
- `OPENBAO_OPERATOR_RELEASE_TAG_CLIENT_ID` and `OPENBAO_OPERATOR_RELEASE_TAG_PRIVATE_KEY` for the custom `Release Tag` workflow

The tag app should be the only actor with semver tag ruleset bypass, and it only needs repository `contents: write`.

Expand Down
172 changes: 172 additions & 0 deletions hack/ci/verify-post-release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#!/usr/bin/env bash

set -euo pipefail

usage() {
cat >&2 <<'USAGE'
usage: VERSION=X.Y.Z [REPO=dc-tec/openbao-operator] hack/ci/verify-post-release.sh

Verifies the post-release invariants that should hold after the Release workflow
has published a stable release.

Environment:
VERSION Required release version, for example 0.3.0.
REPO GitHub repository. Default: dc-tec/openbao-operator.
GIT_REMOTE Git remote used for branch/tag checks. Default: https://github.com/${REPO}.git.
ALLOW_DRAFT Set to 1 to allow a draft GitHub Release. Default: 0.
USAGE
}

fail() {
echo "error: $*" >&2
exit 1
}

info() {
echo "==> $*"
}

require_cmd() {
local cmd="$1"
command -v "${cmd}" >/dev/null 2>&1 || fail "required command not found: ${cmd}"
}

VERSION="${VERSION:-${1:-}}"
REPO="${REPO:-dc-tec/openbao-operator}"
OWNER="${REPO%%/*}"
GIT_REMOTE="${GIT_REMOTE:-https://github.com/${REPO}.git}"
ALLOW_DRAFT="${ALLOW_DRAFT:-0}"

if [[ "${VERSION}" == "-h" || "${VERSION}" == "--help" ]]; then
usage
exit 0
fi

if [[ -z "${VERSION}" ]]; then
usage
exit 2
fi

if ! [[ "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.+][0-9A-Za-z.-]+)?$ ]]; then
fail "VERSION must be SemVer, got '${VERSION}'"
fi

for cmd in gh jq git docker cosign; do
require_cmd "${cmd}"
done

required_assets=(
install.yaml
crds.yaml
checksums.txt
checksums.txt.bundle
checksums.txt.sigstore.json
checksums.intoto.jsonl
sbom-openbao-operator.spdx.json
sbom-openbao-init.spdx.json
sbom-openbao-backup.spdx.json
sbom-openbao-upgrade.spdx.json
provenance-index.json
)

info "checking remote tag ${VERSION}"
git ls-remote --exit-code --tags "${GIT_REMOTE}" "refs/tags/${VERSION}" >/dev/null ||
fail "release tag ${VERSION} was not found on ${GIT_REMOTE}"

info "checking GitHub Release ${VERSION}"
release_json="$(
gh release view "${VERSION}" \
--repo "${REPO}" \
--json assets,isDraft,isPrerelease,tagName,url
)"

release_tag="$(jq -r '.tagName' <<<"${release_json}")"
if [[ "${release_tag}" != "${VERSION}" ]]; then
fail "GitHub Release tagName is '${release_tag}', expected '${VERSION}'"
fi

is_draft="$(jq -r '.isDraft' <<<"${release_json}")"
if [[ "${is_draft}" == "true" && "${ALLOW_DRAFT}" != "1" ]]; then
fail "GitHub Release ${VERSION} is still a draft"
fi

mapfile -t asset_names < <(jq -r '.assets[].name' <<<"${release_json}" | LC_ALL=C sort)
missing_assets=()
for asset in "${required_assets[@]}"; do
if ! printf '%s\n' "${asset_names[@]}" | grep -Fxq "${asset}"; then
missing_assets+=("${asset}")
fi
done
if (( ${#missing_assets[@]} > 0 )); then
printf 'missing release assets:\n' >&2
printf ' - %s\n' "${missing_assets[@]}" >&2
exit 1
fi

release_url="$(jq -r '.url' <<<"${release_json}")"
info "release assets present: ${release_url}"

tmpdir="$(mktemp -d)"
trap 'rm -rf "${tmpdir}"' EXIT

info "downloading signature evidence"
gh release download "${VERSION}" \
--repo "${REPO}" \
--dir "${tmpdir}" \
--clobber \
--pattern checksums.txt \
--pattern checksums.txt.bundle \
--pattern provenance-index.json

identity="https://github.com/${REPO}/.github/workflows/release.yml@refs/tags/${VERSION}"

info "verifying checksums signature"
cosign verify-blob \
--new-bundle-format=true \
--bundle "${tmpdir}/checksums.txt.bundle" \
--certificate-identity "${identity}" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
"${tmpdir}/checksums.txt" >/dev/null

provenance_tag="$(jq -r '.release.tag' "${tmpdir}/provenance-index.json")"
if [[ "${provenance_tag}" != "${VERSION}" ]]; then
fail "provenance-index.json release tag is '${provenance_tag}', expected '${VERSION}'"
fi

chart_ref="ghcr.io/${OWNER}/charts/openbao-operator:${VERSION}"
info "checking Helm chart publication: ${chart_ref}"
chart_digest="$(
docker buildx imagetools inspect "${chart_ref}" --format '{{json .Manifest.Digest}}' | tr -d '"'
)"
if [[ -z "${chart_digest}" || "${chart_digest}" == "null" ]]; then
fail "could not resolve chart digest for ${chart_ref}"
fi

info "verifying Helm chart signature"
cosign verify \
--new-bundle-format=true \
--certificate-identity "${identity}" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
"ghcr.io/${OWNER}/charts/openbao-operator@${chart_digest}" >/dev/null

info "checking for open release-please PRs"
open_release_prs="$(
gh pr list \
--repo "${REPO}" \
--state open \
--json number,title,headRefName,url \
--jq '.[] | select(.headRefName | startswith("release-please--branches--")) | "#\(.number) \(.title) [\(.headRefName)] \(.url)"'
)"
if [[ -n "${open_release_prs}" ]]; then
echo "${open_release_prs}" >&2
fail "unexpected open release-please PRs remain"
fi

info "checking for stale release-please branches"
stale_branches="$(git ls-remote --heads "${GIT_REMOTE}" 'release-please--branches--*')"
if [[ -n "${stale_branches}" ]]; then
echo "${stale_branches}" >&2
fail "stale release-please branches remain"
fi

info "post-release verification passed for ${VERSION}"
Loading