mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-26 21:47:20 +00:00
6ce41ae9d4
candidate_qualification and release_readiness only restated other jobs' results. publish_release_tag, publish_docker, publish_helm_chart and activate_release now each carry the exact candidate predicate in their own needs and if, guarded by !cancelled() with the same allowed integration skip, and activation joins the tag and both registry publications directly. The commit verdict restates every candidate result in place of the readiness join. recover-release-activation.yml accepts both shapes. A source run that has release_readiness still has to prove only that join, as before. A run without it has to prove publish_release_tag and at least one, and only successful, jobs under the publish_docker and publish_helm_chart caller IDs. Failure diagnostics and the inspection-only Helm chart artifact, which nothing downloads, are kept for three days instead of 14 and 90.
244 lines
9.5 KiB
YAML
244 lines
9.5 KiB
YAML
name: Publish Helm Chart
|
|
|
|
# Triggers:
|
|
# - release: published — fires when a release is initially created with
|
|
# draft=false (legacy publish path).
|
|
# - workflow_call — called from create-release.yml after candidate qualification
|
|
# and qualified public tag creation succeed. create-release.yml's publish step creates releases as drafts and
|
|
# PATCHes to draft=false later, which does NOT fire the `release: published`
|
|
# webhook (GitHub-documented quirk). Without this workflow_call trigger,
|
|
# v6 rc.1 → rc.5 published successfully but never produced a Helm chart on
|
|
# rcourtman.github.io/Pulse/index.yaml — customers running
|
|
# `helm install pulse pulse/pulse --version 6.0.0-rc.5` got chart-not-found.
|
|
# - workflow_dispatch — manual backfill / re-publish path.
|
|
|
|
on:
|
|
release:
|
|
types: [published]
|
|
workflow_call:
|
|
inputs:
|
|
chart_version:
|
|
description: "Chart version (e.g., 6.0.0-rc.5). Required for workflow_call."
|
|
required: true
|
|
type: string
|
|
app_version:
|
|
description: "Application version (must equal chart version; defaults to it)."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
outputs:
|
|
chart_digest:
|
|
description: "Verified OCI manifest digest for the exact-version chart."
|
|
value: ${{ jobs.publish.outputs.chart_digest }}
|
|
workflow_dispatch:
|
|
inputs:
|
|
chart_version:
|
|
description: "Chart version (required when running manually, use format 4.24.0)"
|
|
required: true
|
|
app_version:
|
|
description: "Application version (must equal chart version; defaults to it)"
|
|
required: false
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
publish:
|
|
name: Package and Push Helm Chart
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 30
|
|
outputs:
|
|
chart_digest: ${{ steps.proof.outputs.chart_digest }}
|
|
permissions:
|
|
contents: write # Required for gh release upload
|
|
packages: write
|
|
id-token: write
|
|
attestations: write
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
fetch-depth: 0
|
|
fetch-tags: true
|
|
|
|
- name: Determine chart version
|
|
id: versions
|
|
env:
|
|
INPUT_CHART_VERSION: ${{ inputs.chart_version }}
|
|
INPUT_APP_VERSION: ${{ inputs.app_version }}
|
|
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
|
|
run: |
|
|
# inputs.chart_version is set by both workflow_dispatch and
|
|
# workflow_call (from create-release.yml). Fall back to the
|
|
# release-event tag only when running from a `release: published`
|
|
# webhook with no inputs.
|
|
if [ -n "${INPUT_CHART_VERSION}" ]; then
|
|
CHART_VERSION="${INPUT_CHART_VERSION}"
|
|
APP_VERSION="${INPUT_APP_VERSION}"
|
|
if [ -z "$APP_VERSION" ]; then
|
|
APP_VERSION="$CHART_VERSION"
|
|
fi
|
|
RELEASE_TAG="v${CHART_VERSION}"
|
|
else
|
|
RELEASE_TAG="${RELEASE_TAG_NAME}"
|
|
if [ -z "$RELEASE_TAG" ]; then
|
|
echo "::error::No chart_version input supplied and release event has no tag_name"
|
|
exit 1
|
|
fi
|
|
CHART_VERSION="${RELEASE_TAG#v}"
|
|
APP_VERSION="$CHART_VERSION"
|
|
fi
|
|
|
|
# Pulse's server and agent image defaults use Chart.AppVersion.
|
|
# Source provenance alone cannot detect a mismatched image override.
|
|
if [ "$APP_VERSION" != "$CHART_VERSION" ]; then
|
|
echo "::error::Release chart app_version must equal chart_version."
|
|
exit 1
|
|
fi
|
|
|
|
IS_PRERELEASE="false"
|
|
if [[ "$APP_VERSION" =~ -rc\.[0-9]+$ ]] || [[ "$APP_VERSION" =~ -alpha\.[0-9]+$ ]] || [[ "$APP_VERSION" =~ -beta\.[0-9]+$ ]]; then
|
|
IS_PRERELEASE="true"
|
|
fi
|
|
|
|
python3 scripts/write_github_output.py chart_version "$CHART_VERSION"
|
|
python3 scripts/write_github_output.py app_version "$APP_VERSION"
|
|
python3 scripts/write_github_output.py release_tag "$RELEASE_TAG"
|
|
echo "is_prerelease=$IS_PRERELEASE" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Set up Helm
|
|
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
|
|
with:
|
|
version: v3.15.2
|
|
|
|
- name: Validate release line policy
|
|
id: release_line
|
|
env:
|
|
RELEASE_TAG: ${{ steps.versions.outputs.release_tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
python3 scripts/release_control/validate_artifact_release_line.py \
|
|
--tag "${RELEASE_TAG}" \
|
|
--purpose "Helm publish" \
|
|
--github-output "$GITHUB_OUTPUT"
|
|
|
|
- name: Check out validated release tag
|
|
env:
|
|
RELEASE_TAG: ${{ steps.versions.outputs.release_tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
release_sha="$(git rev-parse "refs/tags/${RELEASE_TAG}^{commit}")"
|
|
if [ "${release_sha}" != "${GITHUB_SHA}" ]; then
|
|
echo "::error::Helm provenance source ${GITHUB_SHA} does not match ${RELEASE_TAG} at ${release_sha}. Dispatch manual backfills from the exact tag ref."
|
|
exit 1
|
|
fi
|
|
git checkout --detach "refs/tags/${RELEASE_TAG}"
|
|
|
|
- name: Align chart metadata links
|
|
env:
|
|
WORKFLOW_OUTPUT_1: ${{ steps.versions.outputs.chart_version }}
|
|
run: |
|
|
python3 scripts/sync_chart_release_metadata.py \
|
|
--chart deploy/helm/pulse/Chart.yaml \
|
|
--version "${WORKFLOW_OUTPUT_1}" \
|
|
--repo "${{ github.repository }}"
|
|
|
|
- name: Helm lint (strict)
|
|
run: helm lint deploy/helm/pulse --strict
|
|
|
|
- name: Package chart
|
|
env:
|
|
WORKFLOW_OUTPUT_1: ${{ steps.versions.outputs.chart_version }}
|
|
WORKFLOW_OUTPUT_2: ${{ steps.versions.outputs.app_version }}
|
|
run: |
|
|
mkdir -p dist
|
|
helm package deploy/helm/pulse \
|
|
--version "${WORKFLOW_OUTPUT_1}" \
|
|
--app-version "${WORKFLOW_OUTPUT_2}" \
|
|
--destination dist
|
|
|
|
- name: Upload packaged chart artifact
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: pulse-chart-${{ steps.versions.outputs.chart_version }}
|
|
path: dist/pulse-${{ steps.versions.outputs.chart_version }}.tgz
|
|
# Inspection copy only. The chart is published to GHCR and Pages, and
|
|
# no job or workflow downloads this artifact.
|
|
retention-days: 3
|
|
|
|
- name: Authenticate with GHCR
|
|
env:
|
|
GHCR_TOKEN: ${{ github.token }}
|
|
GHCR_USERNAME: ${{ github.actor }}
|
|
run: |
|
|
echo "${GHCR_TOKEN}" | helm registry login ghcr.io --username "${GHCR_USERNAME}" --password-stdin
|
|
|
|
# Helm and actions/attest use separate credential stores. Keep the
|
|
# chart push authenticated through Helm, and authenticate the OCI
|
|
# attestation client explicitly before it publishes provenance.
|
|
- name: Authenticate OCI attestation client with GHCR
|
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ github.token }}
|
|
|
|
- name: Push exact-version chart to GHCR
|
|
id: push
|
|
env:
|
|
CHART_PATH: dist/pulse-${{ steps.versions.outputs.chart_version }}.tgz
|
|
run: |
|
|
set -euo pipefail
|
|
output="$(
|
|
helm push "${CHART_PATH}" \
|
|
oci://ghcr.io/${{ github.repository_owner }}/pulse-chart 2>&1
|
|
)"
|
|
printf '%s\n' "${output}"
|
|
mapfile -t digests < <(
|
|
sed -nE 's/^Digest:[[:space:]]*(sha256:[0-9a-f]{64})\r?$/\1/p' \
|
|
<<<"${output}" | sort -u
|
|
)
|
|
if [ "${#digests[@]}" -ne 1 ]; then
|
|
echo "::error::Helm push did not report one exact OCI chart digest."
|
|
exit 1
|
|
fi
|
|
echo "chart_digest=${digests[0]}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Attest exact-version OCI chart
|
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
|
with:
|
|
subject-name: ghcr.io/${{ github.repository_owner }}/pulse-chart/pulse
|
|
subject-digest: ${{ steps.push.outputs.chart_digest }}
|
|
push-to-registry: true
|
|
create-storage-record: false
|
|
|
|
- name: Verify public GHCR chart identity and provenance
|
|
id: proof
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_TAG: ${{ steps.versions.outputs.release_tag }}
|
|
EXPECTED_DIGEST: ${{ steps.push.outputs.chart_digest }}
|
|
run: |
|
|
set -euo pipefail
|
|
helm registry logout ghcr.io || true
|
|
proof_output="$(./scripts/verify-release-helm-chart.sh \
|
|
"${RELEASE_TAG}" "${GITHUB_SHA}" \
|
|
"${GITHUB_REPOSITORY}" "${EXPECTED_DIGEST}")"
|
|
if [[ ! "${proof_output}" =~ ^chart_digest=sha256:[0-9a-f]{64}$ ]]; then
|
|
echo "::error::Helm verification did not return one exact chart digest."
|
|
exit 1
|
|
fi
|
|
printf '%s\n' "${proof_output}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Attach chart to release
|
|
if: github.event_name == 'release'
|
|
env:
|
|
GITHUB_TOKEN: ${{ github.token }}
|
|
WORKFLOW_OUTPUT_1: ${{ steps.versions.outputs.release_tag }}
|
|
WORKFLOW_OUTPUT_2: ${{ steps.versions.outputs.chart_version }}
|
|
run: |
|
|
gh release upload "${WORKFLOW_OUTPUT_1}" \
|
|
"dist/pulse-${WORKFLOW_OUTPUT_2}.tgz" \
|
|
--clobber
|