Files
pulse/.github/workflows/qualify-release-containers.yml
pulse-triage[bot] ad1cfd33c3 fix(ci): qualify grouped release action pin consumers
The grouped action upgrade leaves signing, network and publication consumer assertions on superseded pins. Align those contracts and check every consumer against reviewed immutable upstream manifests, retaining exact dispatch and release trust boundaries without claiming hosted execution.

Change-source: pulse-maintainer
2026-09-10 00:35:47 +01:00

227 lines
9.5 KiB
YAML

name: Qualify Exact-Candidate Containers
on:
workflow_call:
inputs:
version:
description: 'Version number without the leading v'
required: true
type: string
container_artifact:
description: 'Exact-candidate container payload artifact from this run'
required: true
type: string
permissions:
contents: read
jobs:
qualify:
name: Exact-Candidate Container and Helm Smoke
# Stable qualification must be independent of persistent runner disk state.
# Prereleases keep the faster PVE lane, where a later clean rehearsal can
# expose and repair runner capacity without blocking a stable patch.
runs-on: ${{ !contains(inputs.version, '-') && 'ubuntu-24.04' || fromJSON('["self-hosted","Linux","X64","pulse-pve-build"]') }}
timeout-minutes: 15
env:
VERSION: ${{ inputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# A reusable workflow inherits github.sha from its caller. Do not
# accept a caller-controlled checkout ref: the caller event commit is
# the immutable source identity for both code and candidate payload.
ref: ${{ github.sha }}
persist-credentials: false
- name: Verify exact caller source
env:
EXPECTED_SOURCE_SHA: ${{ github.sha }}
run: test "$(git rev-parse HEAD)" = "${EXPECTED_SOURCE_SHA}"
- name: Download exact-candidate container payload
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ inputs.container_artifact }}
path: ${{ runner.temp }}/release-container-payload
- name: Verify exact-candidate container payload
run: |
python3 scripts/release_candidate_manifest.py verify-local \
--release-dir "$RUNNER_TEMP/release-container-payload/payload" \
--manifest "$RUNNER_TEMP/release-container-payload/release-container-payload.json" \
--version "${VERSION}" \
--source-sha "${{ github.sha }}"
- name: Assemble exact-candidate runtime and agent images
run: |
set -euo pipefail
docker buildx build \
--target runtime_prebuilt \
--platform linux/amd64 \
--build-context "release_payload=${RUNNER_TEMP}/release-container-payload/payload/release" \
--load \
--tag "pulse-helm-smoke:${VERSION}" \
.
docker buildx build \
--target agent_runtime_prebuilt \
--platform linux/amd64 \
--build-context "release_payload=${RUNNER_TEMP}/release-container-payload/payload/release" \
--load \
--tag "pulse-agent-candidate:${VERSION}" \
.
docker buildx build \
--file deploy/provider-msp/Dockerfile.control-plane \
--target control_plane_prebuilt \
--platform linux/amd64 \
--build-context "compiled_payload=${RUNNER_TEMP}/release-container-payload/payload/compiled" \
--load \
--tag "pulse-control-plane-candidate:${VERSION}" \
.
- name: Verify container binaries match immutable candidate
run: |
set -euo pipefail
expected_server="$(sha256sum "$RUNNER_TEMP/release-container-payload/payload/release/amd64/bin/pulse" | awk '{print $1}')"
actual_server="$(docker run --rm --entrypoint /bin/sh "pulse-helm-smoke:${VERSION}" -c 'sha256sum /app/pulse' | awk '{print $1}')"
expected_agent="$(sha256sum "$RUNNER_TEMP/release-container-payload/payload/release/amd64/bin/pulse-agent-linux-amd64" | awk '{print $1}')"
actual_embedded_agent="$(docker run --rm --entrypoint /bin/sh "pulse-helm-smoke:${VERSION}" -c 'sha256sum /usr/local/bin/pulse-agent' | awk '{print $1}')"
actual_agent="$(docker run --rm --entrypoint /bin/sh "pulse-agent-candidate:${VERSION}" -c 'sha256sum /usr/local/bin/pulse-agent' | awk '{print $1}')"
expected_control_plane="$(sha256sum "$RUNNER_TEMP/release-container-payload/payload/compiled/binaries/pulse-control-plane-linux-amd64" | awk '{print $1}')"
actual_control_plane="$(docker run --rm --entrypoint /bin/sh "pulse-control-plane-candidate:${VERSION}" -c 'sha256sum /usr/local/bin/pulse-control-plane' | awk '{print $1}')"
test "${actual_server}" = "${expected_server}"
test "${actual_embedded_agent}" = "${expected_agent}"
test "${actual_agent}" = "${expected_agent}"
test "${actual_control_plane}" = "${expected_control_plane}"
docker run --rm --entrypoint /bin/sh "pulse-helm-smoke:${VERSION}" -c '
set -euo pipefail
test "$(readlink /usr/local/bin/pulse-agent)" = "/opt/pulse/bin/pulse-agent-linux-amd64"
test -x /usr/local/bin/pulse-agent
for arch in amd64 arm64 386; do
for suffix in "" .sig .sshsig; do
alias="/opt/pulse/bin/pulse-agent-windows-${arch}${suffix}"
target="pulse-agent-windows-${arch}.exe${suffix}"
test -L "${alias}"
test "$(readlink "${alias}")" = "${target}"
test -s "${alias}"
done
done
for sidecar in /opt/pulse/bin/*.sig /opt/pulse/bin/*.sshsig; do
test ! -x "$sidecar"
done
'
- name: Set up Helm
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
with:
version: v3.15.2
- name: Helm smoke test with local release-line image
env:
SMOKE_IMAGE_REPOSITORY: pulse-helm-smoke
SMOKE_IMAGE_TAG: ${{ inputs.version }}
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
prepare_cluster_tool() {
local name="$1"
local url="$2"
local expected_sha="$3"
local destination="$RUNNER_TEMP/$name"
local download="$destination.download"
local installed_path=""
installed_path="$(command -v "$name" 2>/dev/null || true)"
if [ -n "$installed_path" ] && printf '%s %s\n' "$expected_sha" "$installed_path" | sha256sum --check --status; then
install -m 0755 "$installed_path" "$destination"
else
rm -f "$download"
curl -fsSL -o "$download" "$url"
printf '%s %s\n' "$expected_sha" "$download" | sha256sum --check
chmod 0755 "$download"
mv "$download" "$destination"
fi
printf '%s %s\n' "$expected_sha" "$destination" | sha256sum --check
test -x "$destination"
}
prepare_cluster_tool \
kind \
https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64 \
513a7213d6d3332dd9ef27c24dab35e5ef10a04fa27274fe1c14d8a246493ded
prepare_cluster_tool \
kubectl \
https://dl.k8s.io/release/v1.27.3/bin/linux/amd64/kubectl \
fba6c062e754a120bc8105cde1344de200452fe014a8759e06e4eec7ed258a09
export PATH="$RUNNER_TEMP:$PATH"
test "$(command -v kind)" = "$RUNNER_TEMP/kind"
test "$(command -v kubectl)" = "$RUNNER_TEMP/kubectl"
kind version
kubectl version --client=true
cleanup
kind create cluster --name pulse-test --wait 5m
kind load docker-image "${SMOKE_IMAGE_REPOSITORY}:${SMOKE_IMAGE_TAG}" --name pulse-test
helm install pulse deploy/helm/pulse \
--set persistence.enabled=false \
--set server.secretEnv.create=true \
--set server.secretEnv.data.API_TOKENS=test-token \
--set image.repository="${SMOKE_IMAGE_REPOSITORY}" \
--set image.tag="${SMOKE_IMAGE_TAG}" \
--set image.pullPolicy=Never \
--wait --timeout 5m --debug
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
helm upgrade pulse deploy/helm/pulse \
--set persistence.enabled=false \
--set server.secretEnv.create=true \
--set server.secretEnv.data.API_TOKENS=test-token \
--set image.repository="${SMOKE_IMAGE_REPOSITORY}" \
--set image.tag="${SMOKE_IMAGE_TAG}" \
--set image.pullPolicy=Never \
--wait --timeout 5m --debug
trap - ERR
cleanup
echo "✓ Helm smoke test passed"