name: Release Helm Chart to GitHub Pages run-name: Release Helm Chart ${{ inputs.chart_version }} # Triggered automatically when publish-docker.yml completes, or manually # We wait for Docker publish because the smoke test pulls the Docker image on: workflow_run: workflows: ["Publish Docker Images"] types: [completed] workflow_dispatch: inputs: chart_version: description: "Chart version (e.g., 4.28.0)" required: true permissions: contents: write jobs: release: runs-on: ubuntu-24.04 # Only run if workflow_dispatch OR if workflow_run completed successfully if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} steps: - name: Determine chart version id: version env: GH_TOKEN: ${{ github.token }} run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then VERSION="${{ inputs.chart_version }}" else RUN_ID="${{ github.event.workflow_run.id }}" echo "Extracting version from workflow run ${RUN_ID}..." WORKFLOW_DATA=$(gh api repos/${{ github.repository }}/actions/runs/${RUN_ID}) TAG=$(echo "$WORKFLOW_DATA" | jq -r '.display_title' | grep -oP 'v?\d+\.\d+\.\d+(-[a-zA-Z]+\.\d+)?' || echo "") if [ -z "$TAG" ]; then echo "::error::Could not extract version from workflow_run" exit 1 fi VERSION="${TAG#v}" fi if [ -z "$VERSION" ]; then echo "::error::chart_version input is required" exit 1 fi echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "release_tag=v${VERSION}" >> "$GITHUB_OUTPUT" echo "Chart version: $VERSION" - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 fetch-tags: true - name: Validate release line policy id: release_line env: RELEASE_TAG: ${{ steps.version.outputs.release_tag }} run: | set -euo pipefail python3 scripts/release_control/validate_artifact_release_line.py \ --tag "${RELEASE_TAG}" \ --purpose "Helm pages release" \ --github-output "$GITHUB_OUTPUT" - name: Check out governed release branch env: REQUIRED_BRANCH: ${{ steps.release_line.outputs.required_branch }} run: | set -euo pipefail git checkout -B "$REQUIRED_BRANCH" "origin/$REQUIRED_BRANCH" - name: Configure Git run: | git config user.name "$GITHUB_ACTOR" git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - name: Install Helm uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 with: version: v3.15.2 - name: Install helm-docs run: | cd /tmp HELM_DOCS_VERSION="1.14.2" HELM_DOCS_ARCHIVE="helm-docs_${HELM_DOCS_VERSION}_Linux_x86_64.tar.gz" HELM_DOCS_SHA256="a8cf72ada34fad93285ba2a452b38bdc5bd52cc9a571236244ec31022928d6cc" wget "https://github.com/norwoodj/helm-docs/releases/download/v${HELM_DOCS_VERSION}/${HELM_DOCS_ARCHIVE}" printf '%s %s\n' "$HELM_DOCS_SHA256" "$HELM_DOCS_ARCHIVE" | sha256sum --check -- tar -xzf "$HELM_DOCS_ARCHIVE" sudo mv helm-docs /usr/local/bin/ helm-docs --version - name: Generate chart documentation env: REQUIRED_BRANCH: ${{ steps.release_line.outputs.required_branch }} run: | set -euo pipefail cd deploy/helm/pulse helm-docs # Commit if README changed if ! git diff --quiet README.md; then git config user.name "$GITHUB_ACTOR" git config user.email "$GITHUB_ACTOR@users.noreply.github.com" git add README.md git commit -m "Auto-update Helm chart documentation" git pull --rebase origin "$REQUIRED_BRANCH" git push origin HEAD:"$REQUIRED_BRANCH" fi cd ../../.. - name: Update Chart.yaml version env: REQUIRED_BRANCH: ${{ steps.release_line.outputs.required_branch }} run: | set -euo pipefail VERSION="${{ steps.version.outputs.version }}" python3 scripts/sync_chart_release_metadata.py \ --chart deploy/helm/pulse/Chart.yaml \ --version "$VERSION" \ --repo "${{ github.repository }}" # Commit if Chart.yaml changed if ! git diff --quiet deploy/helm/pulse/Chart.yaml; then git config user.name "$GITHUB_ACTOR" git config user.email "$GITHUB_ACTOR@users.noreply.github.com" git add deploy/helm/pulse/Chart.yaml git commit -m "Auto-update Helm chart version to $VERSION" git pull --rebase origin "$REQUIRED_BRANCH" git push origin HEAD:"$REQUIRED_BRANCH" fi - name: Validate Helm chart run: | # Strict linting helm lint deploy/helm/pulse --strict # Template validation with minimal values helm template pulse deploy/helm/pulse --set persistence.enabled=false > /dev/null # Template validation with common overrides helm template pulse deploy/helm/pulse \ --set ingress.enabled=true \ --set ingress.hosts[0].host=pulse.example.com \ --set agent.enabled=true > /dev/null echo "✓ Chart validation passed" - name: Smoke test with kind run: | set -euo pipefail cleanup() { kind delete cluster --name pulse-test >/dev/null 2>&1 || true } diagnose() { echo "::group::helm status" helm status pulse || true echo "::endgroup::" echo "::group::kubectl get all" kubectl get all -A || true echo "::endgroup::" echo "::group::kubectl describe pods" kubectl describe pods -A || true echo "::endgroup::" echo "::group::pod logs" pods=$(kubectl get pods -A -o name 2>/dev/null || true) for pod in $pods; do echo "### ${pod}" kubectl logs --all-containers=true --tail=200 "$pod" || true done echo "::endgroup::" echo "::group::events" kubectl get events -A --sort-by=.lastTimestamp || kubectl get events -A || true echo "::endgroup::" cleanup } trap 'diagnose' ERR # Install kind curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64 chmod +x ./kind sudo mv ./kind /usr/local/bin/kind # Create cluster kind create cluster --name pulse-test --wait 5m # Install chart helm install pulse deploy/helm/pulse \ --set persistence.enabled=false \ --set server.secretEnv.create=true \ --set server.secretEnv.data.API_TOKENS=test-token \ --wait --timeout 5m --debug # Verify deployment kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=pulse --timeout=180s || (kubectl describe pods -l app.kubernetes.io/name=pulse && exit 1) kubectl get pods -l app.kubernetes.io/name=pulse # Test upgrade helm upgrade pulse deploy/helm/pulse \ --set persistence.enabled=false \ --set server.secretEnv.create=true \ --set server.secretEnv.data.API_TOKENS=test-token \ --wait --timeout 5m --debug # Cleanup trap - ERR cleanup echo "✓ Smoke test passed" - name: Run chart-releaser uses: helm/chart-releaser-action@a917fd15b20e8b64b94d9158ad54cd6345335584 # v1.6.0 with: charts_dir: deploy/helm config: cr.yaml skip_existing: true env: CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" CR_RELEASE_NAME_TEMPLATE: "helm-chart-{{ .Version }}" CR_MAKE_RELEASE_LATEST: false - name: Ensure chart release and pages index env: GITHUB_TOKEN: ${{ github.token }} VERSION: ${{ steps.version.outputs.version }} REPOSITORY: ${{ github.repository }} RELEASE_TAG: ${{ steps.version.outputs.release_tag }} run: | set -euo pipefail CHART="pulse-${VERSION}.tgz" CHART_PATH="dist/${CHART}" CHART_RELEASE="helm-chart-${VERSION}" CHART_RELEASE_URL="https://github.com/${REPOSITORY}/releases/download/${CHART_RELEASE}" RELEASE_SHA="$(git rev-list -n 1 "${RELEASE_TAG}")" mkdir -p dist helm package deploy/helm/pulse \ --version "${VERSION}" \ --app-version "${VERSION}" \ --destination dist if gh release view "${CHART_RELEASE}" >/dev/null 2>&1; then gh release upload "${CHART_RELEASE}" "${CHART_PATH}" --clobber else gh release create "${CHART_RELEASE}" "${CHART_PATH}" \ --target "${RELEASE_SHA}" \ --title "Helm chart ${VERSION}" \ --notes "Helm chart for Pulse ${VERSION}." \ --prerelease \ --latest=false fi gh release edit "${CHART_RELEASE}" --prerelease --latest=false workdir="$(mktemp -d)" cleanup() { git worktree remove -f "${workdir}/gh-pages" >/dev/null 2>&1 || true rm -rf "${workdir}" } trap cleanup EXIT git fetch origin gh-pages git worktree add "${workdir}/gh-pages" origin/gh-pages git -C "${workdir}/gh-pages" checkout -B gh-pages origin/gh-pages index_work="${workdir}/index" mkdir -p "${index_work}" cp "${CHART_PATH}" "${index_work}/${CHART}" if [ -f "${workdir}/gh-pages/index.yaml" ]; then cp "${workdir}/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" "${workdir}/gh-pages/index.yaml" if ! grep -q "version: ${VERSION}" "${workdir}/gh-pages/index.yaml"; then echo "::error::Helm pages index is missing version ${VERSION}" exit 1 fi git -C "${workdir}/gh-pages" add index.yaml if git -C "${workdir}/gh-pages" diff --cached --quiet; then echo "Helm pages index already contains ${VERSION}" else git -C "${workdir}/gh-pages" commit -m "Update Helm chart index for ${VERSION}" git -C "${workdir}/gh-pages" push origin HEAD:gh-pages fi - name: Mark Helm chart release as pre-release (avoid latest override) env: GITHUB_TOKEN: ${{ github.token }} run: | TAG="helm-chart-${{ steps.version.outputs.version }}" gh release edit "$TAG" --prerelease --latest=false || echo "No helm chart release to edit for $TAG"