mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-15 00:22:28 +00:00
266 lines
11 KiB
YAML
266 lines
11 KiB
YAML
name: Release Helm Chart to GitHub Pages
|
|
run-name: Release Helm Chart ${{ inputs.chart_version }}
|
|
|
|
# Called only from release-convergence.yml while it holds the global customer-
|
|
# promotion lease. Chart lint, packaging, OCI publication, and the exact-
|
|
# candidate install/upgrade smoke complete before activation. This workflow
|
|
# verifies and promotes that immutable source-run artifact after activation.
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
chart_version:
|
|
description: "Exact chart version (e.g., 6.3.0-rc.5)."
|
|
required: true
|
|
type: string
|
|
source_release_run_id:
|
|
description: "Exact create-release.yml run containing the qualified chart."
|
|
required: true
|
|
type: string
|
|
target_commitish:
|
|
description: "Exact activated release source commit."
|
|
required: true
|
|
type: string
|
|
chart_digest:
|
|
description: "OCI chart digest committed by release activation."
|
|
required: true
|
|
type: string
|
|
|
|
permissions:
|
|
actions: read
|
|
contents: write
|
|
packages: read
|
|
|
|
jobs:
|
|
release:
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 20
|
|
steps:
|
|
- name: Checkout release verification control
|
|
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Require activated GitHub release and source run
|
|
id: identity
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
VERSION: ${{ inputs.chart_version }}
|
|
SOURCE_RELEASE_RUN_ID: ${{ inputs.source_release_run_id }}
|
|
TARGET_COMMITISH: ${{ inputs.target_commitish }}
|
|
CHART_DIGEST: ${{ inputs.chart_digest }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ -z "${VERSION}" ]] || \
|
|
[[ ! "${SOURCE_RELEASE_RUN_ID}" =~ ^[0-9]+$ ]] || \
|
|
[[ ! "${TARGET_COMMITISH}" =~ ^[0-9a-f]{40}$ ]] || \
|
|
[[ ! "${CHART_DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
|
|
echo "::error::Helm Pages requires an exact version, source run ID, commit SHA, and chart digest."
|
|
exit 1
|
|
fi
|
|
|
|
release_tag="v${VERSION}"
|
|
release_state="$(
|
|
gh api "repos/${{ github.repository }}/releases/tags/${release_tag}" \
|
|
--jq '[.tag_name, .target_commitish, (.draft | tostring), (.published_at // "")] | @tsv'
|
|
)"
|
|
actual_tag="$(awk -F '\t' '{print $1}' <<<"${release_state}")"
|
|
actual_commit="$(awk -F '\t' '{print $2}' <<<"${release_state}")"
|
|
is_draft="$(awk -F '\t' '{print $3}' <<<"${release_state}")"
|
|
published_at="$(awk -F '\t' '{print $4}' <<<"${release_state}")"
|
|
if [[ "${actual_tag}" != "${release_tag}" ]] || \
|
|
[[ "${actual_commit}" != "${TARGET_COMMITISH}" ]] || \
|
|
[[ "${is_draft}" != "false" ]] || \
|
|
[[ -z "${published_at}" ]]; then
|
|
echo "::error::Helm Pages refuses to advertise inactive or mismatched release ${release_tag}."
|
|
exit 1
|
|
fi
|
|
|
|
marker="$(mktemp)"
|
|
curl -fsSL --retry 4 --retry-delay 2 --retry-all-errors \
|
|
-o "${marker}" \
|
|
"https://github.com/${{ github.repository }}/releases/download/${release_tag}/release-activation.json"
|
|
jq -e \
|
|
--arg tag "${release_tag}" \
|
|
--arg target_commitish "${TARGET_COMMITISH}" \
|
|
--arg source_release_run_id "${SOURCE_RELEASE_RUN_ID}" \
|
|
--arg helm_chart_digest "${CHART_DIGEST}" \
|
|
'.schema_version == 1 and .tag == $tag and .target_commitish == $target_commitish and .source_release_run_id == $source_release_run_id and .helm_chart_digest == $helm_chart_digest' \
|
|
"${marker}" >/dev/null
|
|
rm -f "${marker}"
|
|
|
|
source_run="$(
|
|
gh api "repos/${{ github.repository }}/actions/runs/${SOURCE_RELEASE_RUN_ID}" \
|
|
--jq '[.path, .event, .head_sha] | @tsv'
|
|
)"
|
|
run_path="$(awk -F '\t' '{print $1}' <<<"${source_run}")"
|
|
run_event="$(awk -F '\t' '{print $2}' <<<"${source_run}")"
|
|
run_sha="$(awk -F '\t' '{print $3}' <<<"${source_run}")"
|
|
if [[ "${run_path}" != ".github/workflows/create-release.yml" ]] || \
|
|
[[ "${run_event}" != "workflow_dispatch" ]] || \
|
|
[[ "${run_sha}" != "${TARGET_COMMITISH}" ]]; then
|
|
echo "::error::Source release run ${SOURCE_RELEASE_RUN_ID} does not own the activated ${release_tag} chart."
|
|
exit 1
|
|
fi
|
|
|
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
echo "release_tag=${release_tag}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Download immutable qualified chart
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
VERSION: ${{ steps.identity.outputs.version }}
|
|
SOURCE_RELEASE_RUN_ID: ${{ inputs.source_release_run_id }}
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p dist
|
|
gh run download "${SOURCE_RELEASE_RUN_ID}" \
|
|
--repo "${{ github.repository }}" \
|
|
--name "pulse-chart-${VERSION}" \
|
|
--dir dist
|
|
|
|
- name: Set up Helm
|
|
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
|
|
with:
|
|
version: v3.15.2
|
|
|
|
- name: Verify immutable chart identity
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
VERSION: ${{ steps.identity.outputs.version }}
|
|
CHART_PATH: dist/pulse-${{ steps.identity.outputs.version }}.tgz
|
|
RELEASE_TAG: ${{ steps.identity.outputs.release_tag }}
|
|
TARGET_COMMITISH: ${{ inputs.target_commitish }}
|
|
CHART_DIGEST: ${{ inputs.chart_digest }}
|
|
run: |
|
|
set -euo pipefail
|
|
proof="$(./scripts/verify-release-helm-chart.sh \
|
|
"${RELEASE_TAG}" "${TARGET_COMMITISH}" \
|
|
"${GITHUB_REPOSITORY}" "${CHART_DIGEST}")"
|
|
if [ "${proof}" != "chart_digest=${CHART_DIGEST}" ]; then
|
|
echo "::error::Activated Helm OCI provenance did not produce the committed digest."
|
|
exit 1
|
|
fi
|
|
python3 - <<'PY'
|
|
import os
|
|
import pathlib
|
|
import re
|
|
import tarfile
|
|
|
|
chart_path = pathlib.Path(os.environ["CHART_PATH"])
|
|
version = os.environ["VERSION"]
|
|
if not chart_path.is_file():
|
|
raise SystemExit(f"missing immutable qualified chart: {chart_path}")
|
|
with tarfile.open(chart_path, "r:gz") as archive:
|
|
for member in archive.getmembers():
|
|
parts = pathlib.PurePosixPath(member.name).parts
|
|
if member.name.startswith("/") or any(part in {"", ".", ".."} for part in parts):
|
|
raise SystemExit(f"unsafe chart member: {member.name}")
|
|
stream = archive.extractfile(archive.getmember("pulse/Chart.yaml"))
|
|
if stream is None:
|
|
raise SystemExit("qualified chart has no readable pulse/Chart.yaml")
|
|
metadata = stream.read().decode("utf-8")
|
|
|
|
def value(key: str) -> str:
|
|
match = re.search(rf"(?m)^{re.escape(key)}:\s*[\"']?([^\"'\s]+)", metadata)
|
|
return match.group(1) if match else ""
|
|
|
|
if value("name") != "pulse" or value("version") != version or value("appVersion") != version:
|
|
raise SystemExit("qualified chart metadata does not match the activated release")
|
|
PY
|
|
sha256sum "${CHART_PATH}"
|
|
helm show chart "${CHART_PATH}"
|
|
|
|
- name: Check out current Pages index
|
|
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
|
with:
|
|
ref: gh-pages
|
|
path: gh-pages
|
|
fetch-depth: 1
|
|
persist-credentials: true # required: authenticated git writes
|
|
|
|
- name: Publish chart release and merge Pages index
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
VERSION: ${{ steps.identity.outputs.version }}
|
|
TARGET_COMMITISH: ${{ inputs.target_commitish }}
|
|
run: |
|
|
set -euo pipefail
|
|
chart="pulse-${VERSION}.tgz"
|
|
chart_path="dist/${chart}"
|
|
chart_release="helm-chart-${VERSION}"
|
|
chart_release_url="https://github.com/${{ github.repository }}/releases/download/${chart_release}"
|
|
|
|
if gh release view "${chart_release}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
|
|
gh release upload "${chart_release}" "${chart_path}" --clobber \
|
|
--repo "${GITHUB_REPOSITORY}"
|
|
else
|
|
gh release create "${chart_release}" "${chart_path}" \
|
|
--repo "${GITHUB_REPOSITORY}" \
|
|
--target "${TARGET_COMMITISH}" \
|
|
--title "Helm chart ${VERSION}" \
|
|
--notes "Helm chart for Pulse ${VERSION}." \
|
|
--prerelease \
|
|
--latest=false
|
|
fi
|
|
gh release edit "${chart_release}" --prerelease --latest=false \
|
|
--repo "${GITHUB_REPOSITORY}"
|
|
|
|
git -C gh-pages config user.name "$GITHUB_ACTOR"
|
|
git -C gh-pages config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
|
index_work="$(mktemp -d)"
|
|
cleanup() { rm -rf "${index_work}"; }
|
|
trap cleanup EXIT
|
|
cp "${chart_path}" "${index_work}/${chart}"
|
|
if [[ -f gh-pages/index.yaml ]]; then
|
|
cp gh-pages/index.yaml "${index_work}/index.yaml"
|
|
helm repo index "${index_work}" \
|
|
--url "${chart_release_url}" \
|
|
--merge "${index_work}/index.yaml"
|
|
else
|
|
helm repo index "${index_work}" --url "${chart_release_url}"
|
|
fi
|
|
cp "${index_work}/index.yaml" gh-pages/index.yaml
|
|
|
|
if ! grep -q "version: ${VERSION}" gh-pages/index.yaml; then
|
|
echo "::error::Helm Pages index is missing version ${VERSION}."
|
|
exit 1
|
|
fi
|
|
git -C gh-pages add index.yaml
|
|
if git -C gh-pages diff --cached --quiet; then
|
|
echo "Helm Pages index already contains ${VERSION}."
|
|
else
|
|
git -C gh-pages commit -m "Update Helm chart index for ${VERSION}"
|
|
git -C gh-pages push origin HEAD:gh-pages
|
|
fi
|
|
|
|
- name: Verify public Pages chart
|
|
env:
|
|
VERSION: ${{ steps.identity.outputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
public_repo="https://rcourtman.github.io/Pulse"
|
|
public_ready=false
|
|
for attempt in $(seq 1 30); do
|
|
public_index="$(mktemp)"
|
|
if curl -fsSL --retry 2 --retry-delay 1 --retry-all-errors \
|
|
-o "${public_index}" "${public_repo}/index.yaml" && \
|
|
grep -q "version: ${VERSION}" "${public_index}"; then
|
|
helm repo remove pulse-public >/dev/null 2>&1 || true
|
|
helm repo add pulse-public "${public_repo}" --force-update
|
|
helm repo update pulse-public
|
|
if helm show chart pulse-public/pulse --version "${VERSION}" >/dev/null; then
|
|
public_ready=true
|
|
rm -f "${public_index}"
|
|
break
|
|
fi
|
|
fi
|
|
rm -f "${public_index}"
|
|
echo "Public Helm repository has not exposed ${VERSION} yet (${attempt}/30)."
|
|
sleep 2
|
|
done
|
|
if [[ "${public_ready}" != "true" ]]; then
|
|
echo "::error::Public Helm repository did not expose chart ${VERSION}."
|
|
exit 1
|
|
fi
|
|
echo "[OK] Public Helm repository serves pulse ${VERSION}."
|