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.
278 lines
11 KiB
YAML
278 lines
11 KiB
YAML
name: Publish Docker Images
|
|
run-name: Publish Docker Images ${{ inputs.tag }}
|
|
|
|
# Called only after exact candidate qualification and public tag creation.
|
|
# Versioned registry images are public release surfaces, not private staging.
|
|
# activate_release joins the published digests before activation.
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
tag:
|
|
description: 'Release tag (e.g., v4.34.0)'
|
|
required: true
|
|
type: string
|
|
container_artifact:
|
|
description: 'Exact-candidate container payload artifact from this release run'
|
|
required: true
|
|
type: string
|
|
source_sha:
|
|
description: 'Exact source commit bound to the candidate payload'
|
|
required: true
|
|
type: string
|
|
outputs:
|
|
server_digest:
|
|
description: 'Verified multi-registry digest for the Pulse server image'
|
|
value: ${{ jobs.verify.outputs.server_digest }}
|
|
control_plane_digest:
|
|
description: 'Verified multi-registry digest for the Pulse control-plane image'
|
|
value: ${{ jobs.verify.outputs.control_plane_digest }}
|
|
|
|
concurrency:
|
|
group: docker-publish-${{ inputs.tag }}
|
|
cancel-in-progress: false
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
publish:
|
|
name: Publish ${{ matrix.image }} image
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 60 # Increased for multi-arch builds with QEMU
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
image:
|
|
- server
|
|
- control-plane
|
|
permissions:
|
|
contents: read
|
|
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: Extract version from release tag
|
|
id: version
|
|
env:
|
|
INPUT_TAG: ${{ inputs.tag }}
|
|
run: |
|
|
TAG="${INPUT_TAG}"
|
|
VERSION="${TAG#v}"
|
|
|
|
# Detect if this is a prerelease (RC, alpha, beta)
|
|
IS_PRERELEASE="false"
|
|
if [[ "$VERSION" =~ -rc\.[0-9]+$ ]] || [[ "$VERSION" =~ -alpha\.[0-9]+$ ]] || [[ "$VERSION" =~ -beta\.[0-9]+$ ]]; then
|
|
IS_PRERELEASE="true"
|
|
echo "Detected prerelease version"
|
|
fi
|
|
|
|
python3 scripts/write_github_output.py tag "${TAG}"
|
|
python3 scripts/write_github_output.py version "${VERSION}"
|
|
echo "is_prerelease=${IS_PRERELEASE}" >> $GITHUB_OUTPUT
|
|
echo "Publishing Docker images for ${TAG} (prerelease: ${IS_PRERELEASE})"
|
|
|
|
- name: Validate release line policy
|
|
env:
|
|
TAG: ${{ steps.version.outputs.tag }}
|
|
SOURCE_SHA: ${{ inputs.source_sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
python3 scripts/release_control/validate_artifact_release_line.py \
|
|
--tag "${TAG}" \
|
|
--anticipated-source-sha "${SOURCE_SHA}" \
|
|
--purpose "Docker publish"
|
|
|
|
- name: Verify exact source checkout
|
|
env:
|
|
EXPECTED_SOURCE_SHA: ${{ inputs.source_sha }}
|
|
EXPECTED_VERSION: ${{ steps.version.outputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
test "$(git rev-parse HEAD)" = "${EXPECTED_SOURCE_SHA}"
|
|
test "$(tr -d '\n\r[:space:]' < VERSION)" = "${EXPECTED_VERSION}"
|
|
|
|
- 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
|
|
env:
|
|
EXPECTED_SOURCE_SHA: ${{ inputs.source_sha }}
|
|
VERSION: ${{ steps.version.outputs.version }}
|
|
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 "${EXPECTED_SOURCE_SHA}"
|
|
|
|
- name: Set up QEMU
|
|
uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
|
|
|
- name: Log in to Docker Hub
|
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
|
with:
|
|
username: ${{ secrets.DOCKER_USERNAME }}
|
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
|
|
- name: Log in to GHCR
|
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Assemble and push Pulse server image (multi-arch)
|
|
if: matrix.image == 'server'
|
|
id: build_server_image
|
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
|
with:
|
|
context: .
|
|
target: runtime_prebuilt
|
|
build-contexts: |
|
|
release_payload=${{ runner.temp }}/release-container-payload/payload/release
|
|
platforms: linux/amd64,linux/arm64
|
|
push: true
|
|
provenance: mode=max
|
|
sbom: true
|
|
tags: |
|
|
rcourtman/pulse:${{ steps.version.outputs.tag }}
|
|
rcourtman/pulse:${{ steps.version.outputs.version }}
|
|
ghcr.io/${{ github.repository_owner }}/pulse:${{ steps.version.outputs.tag }}
|
|
ghcr.io/${{ github.repository_owner }}/pulse:${{ steps.version.outputs.version }}
|
|
|
|
- name: Attest Pulse server image on Docker Hub
|
|
if: matrix.image == 'server'
|
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
|
with:
|
|
subject-name: docker.io/rcourtman/pulse
|
|
subject-digest: ${{ steps.build_server_image.outputs.digest }}
|
|
push-to-registry: true
|
|
create-storage-record: false
|
|
|
|
- name: Attest Pulse server image on GHCR
|
|
if: matrix.image == 'server'
|
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
|
with:
|
|
subject-name: ghcr.io/${{ github.repository_owner }}/pulse
|
|
subject-digest: ${{ steps.build_server_image.outputs.digest }}
|
|
push-to-registry: true
|
|
create-storage-record: false
|
|
|
|
- name: Assemble and push Pulse control-plane image (multi-arch)
|
|
if: matrix.image == 'control-plane'
|
|
id: build_control_plane_image
|
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
|
with:
|
|
context: .
|
|
file: deploy/provider-msp/Dockerfile.control-plane
|
|
target: control_plane_prebuilt
|
|
build-contexts: |
|
|
compiled_payload=${{ runner.temp }}/release-container-payload/payload/compiled
|
|
platforms: linux/amd64,linux/arm64
|
|
push: true
|
|
provenance: mode=max
|
|
sbom: true
|
|
tags: |
|
|
rcourtman/pulse-control-plane:${{ steps.version.outputs.tag }}
|
|
rcourtman/pulse-control-plane:${{ steps.version.outputs.version }}
|
|
ghcr.io/${{ github.repository_owner }}/pulse-control-plane:${{ steps.version.outputs.tag }}
|
|
ghcr.io/${{ github.repository_owner }}/pulse-control-plane:${{ steps.version.outputs.version }}
|
|
|
|
- name: Attest Pulse control-plane image on Docker Hub
|
|
if: matrix.image == 'control-plane'
|
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
|
with:
|
|
subject-name: docker.io/rcourtman/pulse-control-plane
|
|
subject-digest: ${{ steps.build_control_plane_image.outputs.digest }}
|
|
push-to-registry: true
|
|
create-storage-record: false
|
|
|
|
- name: Attest Pulse control-plane image on GHCR
|
|
if: matrix.image == 'control-plane'
|
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
|
with:
|
|
subject-name: ghcr.io/${{ github.repository_owner }}/pulse-control-plane
|
|
subject-digest: ${{ steps.build_control_plane_image.outputs.digest }}
|
|
push-to-registry: true
|
|
create-storage-record: false
|
|
|
|
- name: Output image information
|
|
env:
|
|
IMAGE_KIND: ${{ matrix.image }}
|
|
WORKFLOW_OUTPUT_1: ${{ steps.version.outputs.tag }}
|
|
WORKFLOW_OUTPUT_2: ${{ steps.version.outputs.version }}
|
|
run: |
|
|
echo "✅ Exact-version ${IMAGE_KIND} Docker image staged successfully!"
|
|
if [[ "${IMAGE_KIND}" == "server" ]]; then
|
|
echo "Server images (linux/amd64, linux/arm64):"
|
|
echo " - rcourtman/pulse:${WORKFLOW_OUTPUT_1}"
|
|
echo " - rcourtman/pulse:${WORKFLOW_OUTPUT_2}"
|
|
echo "Pulse Agent binaries ship as release assets, not as a Docker image."
|
|
echo "See the GitHub release page for pulse-agent-{darwin,freebsd,linux,windows}-{amd64,arm64,...}."
|
|
else
|
|
echo "Control-plane images (linux/amd64, linux/arm64):"
|
|
echo " - rcourtman/pulse-control-plane:${WORKFLOW_OUTPUT_1}"
|
|
echo " - rcourtman/pulse-control-plane:${WORKFLOW_OUTPUT_2}"
|
|
fi
|
|
echo "Floating aliases are promoted separately at the activation barrier."
|
|
|
|
verify:
|
|
name: Verify exact image identities and provenance
|
|
needs: publish
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 15
|
|
permissions:
|
|
contents: read
|
|
packages: read
|
|
outputs:
|
|
server_digest: ${{ steps.proof.outputs.server_digest }}
|
|
control_plane_digest: ${{ steps.proof.outputs.control_plane_digest }}
|
|
steps:
|
|
- name: Checkout release verification control
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
|
|
|
- name: Log in to Docker Hub
|
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
|
with:
|
|
username: ${{ secrets.DOCKER_USERNAME }}
|
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
|
|
- name: Log in to GHCR
|
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Verify exact tags and signed provenance
|
|
id: proof
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
REPOSITORY: ${{ github.repository }}
|
|
SOURCE_SHA: ${{ inputs.source_sha }}
|
|
TAG: ${{ inputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
./scripts/verify-release-container-images.sh \
|
|
"${TAG}" "${SOURCE_SHA}" \
|
|
"${REPOSITORY}" >> "$GITHUB_OUTPUT"
|