mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
ad1cfd33c3
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
279 lines
11 KiB
YAML
279 lines
11 KiB
YAML
name: Publish Docker Images
|
|
run-name: Publish Docker Images ${{ inputs.tag }}
|
|
|
|
# Called by create-release.yml as soon as the immutable candidate exists.
|
|
# Exact-version images are inert staging surfaces, so assembly and publication
|
|
# overlap container qualification and draft creation. The release-readiness
|
|
# barrier still joins both paths before any customer-facing alias can move.
|
|
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"
|