mirror of
https://github.com/pgsty/minio.git
synced 2026-09-05 18:16:16 +03:00
ci: make server release retries tag-idempotent
Serialize release and finalize work by tag, replace only one unfinalized Draft, refuse published or finalized release state, and pin GoReleaser to the checked-out tag. Add fail-closed release-state fixtures to the release pipeline. Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
@@ -77,8 +77,10 @@ release:
|
|||||||
name: silo
|
name: silo
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: false
|
prerelease: false
|
||||||
mode: append
|
replace_existing_draft: true
|
||||||
replace_existing_artifacts: false
|
replace_existing_artifacts: false
|
||||||
|
mode: replace
|
||||||
|
# Draft replacement matches the release name; keep it identical to the tag.
|
||||||
name_template: "{{ .Tag }}"
|
name_template: "{{ .Tag }}"
|
||||||
|
|
||||||
changelog:
|
changelog:
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ permissions:
|
|||||||
artifact-metadata: write
|
artifact-metadata: write
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: finalize-release
|
group: release-${{ inputs.tag }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
name: Release
|
name: Release
|
||||||
|
|
||||||
|
# Retry contract: an absent or single unfinalized Draft may be rebuilt from
|
||||||
|
# scratch; a published release or a Draft carrying finalize's GPG-derived
|
||||||
|
# provenance marker is terminal for this lane. The per-tag lock serializes
|
||||||
|
# workflows, but a maintainer must not publish the Draft while this job runs.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -16,6 +21,10 @@ permissions:
|
|||||||
attestations: write
|
attestations: write
|
||||||
artifact-metadata: write
|
artifact-metadata: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -78,6 +87,15 @@ jobs:
|
|||||||
echo "Invalid release tag format: ${TAG}" >&2
|
echo "Invalid release tag format: ${TAG}" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
if ! TAG_COMMIT="$(git rev-parse "${TAG}^{commit}" 2>/dev/null)"; then
|
||||||
|
echo "Release tag ${TAG} does not resolve to a commit" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
HEAD_COMMIT="$(git rev-parse HEAD)"
|
||||||
|
if [ "${TAG_COMMIT}" != "${HEAD_COMMIT}" ]; then
|
||||||
|
echo "Release tag ${TAG} resolves to ${TAG_COMMIT}, checkout is ${HEAD_COMMIT}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
VERSION_HYPHEN="${TAG#RELEASE.}"
|
VERSION_HYPHEN="${TAG#RELEASE.}"
|
||||||
PKG_VERSION="$(echo "${VERSION_HYPHEN}" | sed -E 's/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/\1\2\3\4\5\6.0.0/')"
|
PKG_VERSION="$(echo "${VERSION_HYPHEN}" | sed -E 's/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/\1\2\3\4\5\6.0.0/')"
|
||||||
VERSION_COLON="$(echo "${VERSION_HYPHEN}" | sed -E 's/T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/T\1:\2:\3Z/')"
|
VERSION_COLON="$(echo "${VERSION_HYPHEN}" | sed -E 's/T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/T\1:\2:\3Z/')"
|
||||||
@@ -93,6 +111,13 @@ jobs:
|
|||||||
echo "Package version: ${PKG_VERSION}"
|
echo "Package version: ${PKG_VERSION}"
|
||||||
echo "LDFLAGS: ${LDFLAGS}"
|
echo "LDFLAGS: ${LDFLAGS}"
|
||||||
|
|
||||||
|
- name: Check existing release state
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
buildscripts/check-release-state.sh "${RELEASE_TAG}"
|
||||||
|
|
||||||
# Both installer actions are pinned to immutable commits. The explicit
|
# Both installer actions are pinned to immutable commits. The explicit
|
||||||
# tool versions keep the release format reproducible across workflow
|
# tool versions keep the release format reproducible across workflow
|
||||||
# reruns while the installers verify the downloaded executables.
|
# reruns while the installers verify the downloaded executables.
|
||||||
@@ -113,6 +138,7 @@ jobs:
|
|||||||
args: release --clean --skip=validate --config .github/goreleaser.yml
|
args: release --clean --skip=validate --config .github/goreleaser.yml
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GORELEASER_CURRENT_TAG: ${{ env.RELEASE_TAG }}
|
||||||
LDFLAGS: ${{ env.LDFLAGS }}
|
LDFLAGS: ${{ env.LDFLAGS }}
|
||||||
PKG_VERSION: ${{ env.PKG_VERSION }}
|
PKG_VERSION: ${{ env.PKG_VERSION }}
|
||||||
|
|
||||||
@@ -189,6 +215,14 @@ jobs:
|
|||||||
test -s "${BUNDLE_PATH}"
|
test -s "${BUNDLE_PATH}"
|
||||||
cp "${BUNDLE_PATH}" "dist/silo_${PKG_VERSION}_provenance.sigstore.json"
|
cp "${BUNDLE_PATH}" "dist/silo_${PKG_VERSION}_provenance.sigstore.json"
|
||||||
|
|
||||||
|
- name: Confirm unfinalized Draft release state
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
REQUIRE_DRAFT: "true"
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
buildscripts/check-release-state.sh "${RELEASE_TAG}"
|
||||||
|
|
||||||
- name: Upload nFPM packages to Draft release
|
- name: Upload nFPM packages to Draft release
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ on:
|
|||||||
- "buildscripts/minio-upgrade.sh"
|
- "buildscripts/minio-upgrade.sh"
|
||||||
- "buildscripts/sign-release-rpms.sh"
|
- "buildscripts/sign-release-rpms.sh"
|
||||||
- "buildscripts/verify-build-provenance.sh"
|
- "buildscripts/verify-build-provenance.sh"
|
||||||
|
- "buildscripts/check-release-state.sh"
|
||||||
|
- "buildscripts/check-release-state_test.sh"
|
||||||
- "buildscripts/verify-rebrand.sh"
|
- "buildscripts/verify-rebrand.sh"
|
||||||
- "buildscripts/verify-helm-migration.sh"
|
- "buildscripts/verify-helm-migration.sh"
|
||||||
- "buildscripts/helm-migration-guard/**"
|
- "buildscripts/helm-migration-guard/**"
|
||||||
@@ -479,6 +481,8 @@ jobs:
|
|||||||
bash -n buildscripts/minio-upgrade.sh
|
bash -n buildscripts/minio-upgrade.sh
|
||||||
bash -n buildscripts/sign-release-rpms.sh
|
bash -n buildscripts/sign-release-rpms.sh
|
||||||
bash -n buildscripts/verify-build-provenance.sh
|
bash -n buildscripts/verify-build-provenance.sh
|
||||||
|
bash -n buildscripts/check-release-state.sh
|
||||||
|
bash -n buildscripts/check-release-state_test.sh
|
||||||
bash -n buildscripts/verify-rebrand.sh
|
bash -n buildscripts/verify-rebrand.sh
|
||||||
bash -n buildscripts/verify-helm-migration.sh
|
bash -n buildscripts/verify-helm-migration.sh
|
||||||
sh -n buildscripts/package/postinstall.sh
|
sh -n buildscripts/package/postinstall.sh
|
||||||
@@ -493,9 +497,12 @@ jobs:
|
|||||||
test -x buildscripts/package-release.sh
|
test -x buildscripts/package-release.sh
|
||||||
test -x buildscripts/sign-release-rpms.sh
|
test -x buildscripts/sign-release-rpms.sh
|
||||||
test -x buildscripts/verify-build-provenance.sh
|
test -x buildscripts/verify-build-provenance.sh
|
||||||
|
test -x buildscripts/check-release-state.sh
|
||||||
|
test -x buildscripts/check-release-state_test.sh
|
||||||
test -x buildscripts/verify-rebrand.sh
|
test -x buildscripts/verify-rebrand.sh
|
||||||
test -x buildscripts/verify-helm-migration.sh
|
test -x buildscripts/verify-helm-migration.sh
|
||||||
test -x buildscripts/package/postinstall.sh
|
test -x buildscripts/package/postinstall.sh
|
||||||
test -x buildscripts/package/preremove.sh
|
test -x buildscripts/package/preremove.sh
|
||||||
test -x buildscripts/package/lifecycle_test.sh
|
test -x buildscripts/package/lifecycle_test.sh
|
||||||
test -x dockerscripts/docker-entrypoint_test.sh
|
test -x dockerscripts/docker-entrypoint_test.sh
|
||||||
|
buildscripts/check-release-state_test.sh
|
||||||
|
|||||||
Executable
+76
@@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Fail closed before a release job can replace published or finalized assets.
|
||||||
|
# An ordinary Draft is retry state; a finalized Draft contains GPG-derived
|
||||||
|
# materials and must never be replaced by the build lane.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
release_tag="${1:-}"
|
||||||
|
fixture="${2:-}"
|
||||||
|
repository="${GITHUB_REPOSITORY:-pgsty/silo}"
|
||||||
|
require_draft="${REQUIRE_DRAFT:-false}"
|
||||||
|
|
||||||
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
|
echo "jq is required to inspect GitHub release state" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! "${release_tag}" =~ ^RELEASE\.[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}-[0-9]{2}-[0-9]{2}Z$ ]]; then
|
||||||
|
echo "Invalid release tag format: ${release_tag:-<empty>}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${fixture}" ]; then
|
||||||
|
release_json="$(<"${fixture}")"
|
||||||
|
else
|
||||||
|
error_file="$(mktemp)"
|
||||||
|
trap 'rm -f "${error_file}"' EXIT
|
||||||
|
if ! release_json="$(
|
||||||
|
gh api --paginate "repos/${repository}/releases?per_page=100" --jq '.[]' 2>"${error_file}" |
|
||||||
|
jq --arg tag "${release_tag}" -s '[.[] | select(.tag_name == $tag)]'
|
||||||
|
)"; then
|
||||||
|
cat "${error_file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! jq -e 'type == "array" and all(.[]; type == "object" and (.tag_name | type == "string") and (.draft | type == "boolean"))' \
|
||||||
|
<<<"${release_json}" >/dev/null 2>&1; then
|
||||||
|
echo "Invalid release state response for ${release_tag}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! jq -e --arg tag "${release_tag}" 'all(.[]; .tag_name == $tag)' \
|
||||||
|
<<<"${release_json}" >/dev/null 2>&1; then
|
||||||
|
echo "Release state returned a tag other than ${release_tag}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
release_count="$(jq 'length' <<<"${release_json}")"
|
||||||
|
if [ "${release_count}" -eq 0 ]; then
|
||||||
|
if [ "${require_draft}" = "true" ]; then
|
||||||
|
echo "Expected one Draft release for ${release_tag}, found none" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "No existing release for ${release_tag}."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${release_count}" -ne 1 ]; then
|
||||||
|
echo "Refusing to choose among ${release_count} releases for ${release_tag}; clean duplicate Drafts first" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$(jq -r '.[0].draft' <<<"${release_json}")" != "true" ]; then
|
||||||
|
echo "Refusing to overwrite published release ${release_tag}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
finalize_markers="$(jq '[.[0].assets[]? | select(.name | endswith("_packages_provenance.sigstore.json"))] | length' <<<"${release_json}")"
|
||||||
|
if [ "${finalize_markers}" -ne 0 ]; then
|
||||||
|
echo "Refusing to replace finalized Draft ${release_tag}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Existing unfinalized Draft ${release_tag} will be replaced from scratch."
|
||||||
Executable
+68
@@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
checker="${script_dir}/check-release-state.sh"
|
||||||
|
tag="RELEASE.2026-08-29T00-00-00Z"
|
||||||
|
fixture="$(mktemp)"
|
||||||
|
stdout_file="$(mktemp)"
|
||||||
|
stderr_file="$(mktemp)"
|
||||||
|
trap 'rm -f "${fixture}" "${stdout_file}" "${stderr_file}"' EXIT
|
||||||
|
|
||||||
|
expect_success() {
|
||||||
|
if ! "${checker}" "$@" >"${stdout_file}" 2>"${stderr_file}"; then
|
||||||
|
cat "${stderr_file}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_failure() {
|
||||||
|
if "${checker}" "$@" >"${stdout_file}" 2>"${stderr_file}"; then
|
||||||
|
echo "Expected release-state check to fail: $*" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
printf '[]\n' >"${fixture}"
|
||||||
|
expect_success "${tag}" "${fixture}"
|
||||||
|
grep -qF "No existing release for ${tag}." "${stdout_file}"
|
||||||
|
|
||||||
|
if REQUIRE_DRAFT=true "${checker}" "${tag}" "${fixture}" >"${stdout_file}" 2>"${stderr_file}"; then
|
||||||
|
echo "Expected required-Draft check to fail when no release exists" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
grep -qF "Expected one Draft release for ${tag}, found none" "${stderr_file}"
|
||||||
|
|
||||||
|
printf '[{"tag_name":"%s","draft":true,"assets":[]}]\n' "${tag}" >"${fixture}"
|
||||||
|
expect_success "${tag}" "${fixture}"
|
||||||
|
grep -qF "Existing unfinalized Draft ${tag} will be replaced from scratch." "${stdout_file}"
|
||||||
|
if ! REQUIRE_DRAFT=true "${checker}" "${tag}" "${fixture}" >"${stdout_file}" 2>"${stderr_file}"; then
|
||||||
|
cat "${stderr_file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '[{"tag_name":"%s","draft":true,"assets":[{"name":"silo_20260829000000.0.0_packages_provenance.sigstore.json"}]}]\n' "${tag}" >"${fixture}"
|
||||||
|
expect_failure "${tag}" "${fixture}"
|
||||||
|
grep -qF "Refusing to replace finalized Draft ${tag}" "${stderr_file}"
|
||||||
|
|
||||||
|
printf '[{"tag_name":"%s","draft":false}]\n' "${tag}" >"${fixture}"
|
||||||
|
expect_failure "${tag}" "${fixture}"
|
||||||
|
grep -qF "Refusing to overwrite published release ${tag}" "${stderr_file}"
|
||||||
|
|
||||||
|
printf '[{"tag_name":"%s","draft":true},{"tag_name":"%s","draft":true}]\n' "${tag}" "${tag}" >"${fixture}"
|
||||||
|
expect_failure "${tag}" "${fixture}"
|
||||||
|
grep -qF "Refusing to choose among 2 releases" "${stderr_file}"
|
||||||
|
|
||||||
|
printf '[{"tag_name":"RELEASE.2026-08-28T00-00-00Z","draft":true}]\n' >"${fixture}"
|
||||||
|
expect_failure "${tag}" "${fixture}"
|
||||||
|
grep -qF "other than ${tag}" "${stderr_file}"
|
||||||
|
|
||||||
|
printf '{not-json}\n' >"${fixture}"
|
||||||
|
expect_failure "${tag}" "${fixture}"
|
||||||
|
grep -qF "Invalid release state response for ${tag}" "${stderr_file}"
|
||||||
|
|
||||||
|
expect_failure "not-a-release-tag" "${fixture}"
|
||||||
|
grep -qF "Invalid release tag format" "${stderr_file}"
|
||||||
|
|
||||||
|
echo "release-state decision tests passed"
|
||||||
Reference in New Issue
Block a user