Files
Munitor/src/scripts/github_release.sh
T
james.maesandClaude Opus 5.5 57cb7a2aca fix: atomic release versions and immutable image tags
Two main merges built minutes apart both computed the same GitVersion
version. The later pipeline overwrote the image tag in GHCR, github-release
tagged whatever main pointed at (--target main), and the GitOps bump was a
no-op, so production ran an image without the later merge (Website-Backend
0.1.24 and 0.1.32).

- allocate_version: on main, reserve vX.Y.Z by creating the git tag through
  the GitHub refs API (create-if-absent). On conflict with another commit,
  take the next patch; reruns of the same commit reuse their tag. Existing
  tags are never moved. Runs in mvn/gradle/node build-and-test before the
  version is baked into artifacts.
- guard_image_tags: refuse to push a main or supplemental version tag that
  already exists for a different commit (org.opencontainers.image.revision
  label, now set on every build); fail closed on registry errors.
- update_cd_repo: verify vX.Y.Z belongs to this commit before bumping the
  GitOps repo; record the source commit in the GitOps commit message.
- github_release: target CIRCLE_SHA1 instead of main and refuse when the tag
  points elsewhere.
- Generator: render cd_path only when cd.path is set, so manifests pinned to
  orb versions predating the parameter (website develop branches, Concilium,
  makers4) stop failing with "Unexpected argument(s): cd_path".

Tests: tests/test_allocate_version.sh (incl. 8 concurrent allocators against
an atomic fake refs API) and tests/test_release_guards.sh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:56:58 -05:00

88 lines
3.1 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Source shared helpers
MUNITOR_HELPERS="${MUNITOR_HELPERS:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/munitor_helpers.sh}"
# shellcheck source=munitor_helpers.sh
if [[ -f "${MUNITOR_HELPERS}" ]]; then source "${MUNITOR_HELPERS}"
elif ! type munitor_header &>/dev/null; then
munitor_header() { echo "=== Munitor: ${1:-unknown} ==="; }
munitor_check_tool() { command -v "$1" &>/dev/null || { echo "ERROR: $1 not found"; exit 1; }; }
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
fi
munitor_header "github_release"
# Validate required environment
if [[ -z "${GITHUB_TOKEN:-}" ]]; then
echo "ERROR: GITHUB_TOKEN environment variable is not set."
echo "Add a 'github' context with GITHUB_TOKEN to your CircleCI project."
exit 1
fi
# PROJECT_VERSION is set by GitVersion in the build job and restored via .munitor-version
VERSION="${PROJECT_VERSION:?PROJECT_VERSION is not set. Ensure the build job ran and version metadata was persisted.}"
TAG="v${VERSION}"
echo "Version: ${VERSION}"
echo "Tag: ${TAG}"
munitor_check_tool gh --version
# Detect pre-release versions (RC, alpha, beta, SNAPSHOT)
if [[ "${VERSION}" =~ -(RC|alpha|beta|SNAPSHOT) ]]; then
PRERELEASE_FLAG="--prerelease"
TARGET="${CIRCLE_BRANCH:-main}"
else
PRERELEASE_FLAG=""
# Target the commit this pipeline built, never the moving branch head: a
# later merge must not receive this version's tag.
TARGET="${CIRCLE_SHA1:-main}"
fi
echo "Target: ${TARGET}"
echo "Pre-release: ${PRERELEASE_FLAG:-no}"
# Create tag + GitHub Release via API (skip if already exists)
# Uses gh release create which handles both tag and release in one call,
# bypassing CircleCI's read-only SSH deploy key entirely.
# The release tag is normally reserved by allocate_version. If it exists it
# must point at this commit; an existing tag is never moved.
if [[ -n "${CIRCLE_SHA1:-}" ]] && TAG_SHA=$(gh api "repos/{owner}/{repo}/commits/${TAG}" --jq '.sha' 2>/dev/null); then
if [[ "${TAG_SHA}" != "${CIRCLE_SHA1}" ]]; then
echo "ERROR: Tag ${TAG} already points at ${TAG_SHA}, not ${CIRCLE_SHA1}. Refusing to release."
exit 1
fi
echo "Tag ${TAG} points at ${CIRCLE_SHA1}."
fi
if gh release view "${TAG}" >/dev/null 2>&1; then
echo "Release ${TAG} already exists, skipping."
else
echo "Creating GitHub Release ${TAG}..."
gh release create "${TAG}" \
--generate-notes \
--target "${TARGET}" \
--title "Release ${VERSION}" \
${PRERELEASE_FLAG}
echo "GitHub Release ${TAG} created."
fi
# Upload release artifacts if specified
if [[ -n "${MUNITOR_RELEASE_ARTIFACTS:-}" ]]; then
echo "Uploading release artifacts: ${MUNITOR_RELEASE_ARTIFACTS}"
for artifact in ${MUNITOR_RELEASE_ARTIFACTS}; do
if [[ ! -f "${artifact}" ]]; then
echo "ERROR: Release artifact '${artifact}' not found."
echo " Working directory: $(pwd)"
echo " Available files: $(ls -la)"
exit 1
fi
done
# shellcheck disable=SC2086
gh release upload "${TAG}" ${MUNITOR_RELEASE_ARTIFACTS} --clobber
echo "Artifacts uploaded to ${TAG}."
fi
echo "github_release completed"