Skip to content

add delete confirmations for jobs #348

add delete confirmations for jobs

add delete confirmations for jobs #348

name: Docker - riverproui
on:
push:
branches:
- "master"
tags:
- "riverproui/v*"
pull_request:
branches:
- "master"
workflow_dispatch:
inputs:
ref:
description: "Release ref to operate on (format: riverproui/vX.Y.Z)"
required: true
verify_only:
description: "Verification/prefetch only (skip rebuild and pushes)"
required: false
type: boolean
default: false
ecr_manifest_digest:
description: "Optional ECR index digest (sha256:...) to verify index without ECR access"
required: false
type: string
force_prefetch:
description: "Force run prefetch/verification even if ref is not a release tag"
required: false
type: boolean
default: false
env:
ECR_REGION: us-east-2
jobs:
build-riverproui:
name: "Build image: riverproui (${{ matrix.docker_platform }})"
runs-on: ${{ matrix.runner }}
# Skip this job when manually dispatched with verify_only=true
if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.verify_only) }}
env:
ECR_ACCOUNT_ID: ${{ secrets.ECR_CACHE_AWS_ACCOUNT_ID }}
ECR_ROLE_ARN: ${{ secrets.ECR_CACHE_ROLE_ARN }}
strategy:
matrix:
include:
- docker_platform: linux/amd64
runner: ubuntu-24.04
- docker_platform: linux/arm64
runner: ubuntu-24.04-arm
permissions:
contents: read
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref || github.ref }}
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.ECR_ROLE_ARN }}
aws-region: ${{ env.ECR_REGION }}
role-session-name: GitHubActions
- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Prepare
run: |
platform=${{ matrix.docker_platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta for Pro
id: meta-pro
uses: docker/metadata-action@v5
with:
images: ${{ format('{0}.dkr.ecr.{1}.amazonaws.com/riverqueue/riverproui', env.ECR_ACCOUNT_ID, env.ECR_REGION) }}
labels: |
org.opencontainers.image.source=https://github.com/riverqueue/riverui
org.opencontainers.image.description=River UI Pro is a web-based user interface for River, with pro features.
org.opencontainers.image.licenses=commercial
- name: Build & push (by digest)
id: build-pro
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.pro
pull: true
platforms: ${{ matrix.docker_platform }}
provenance: true
labels: ${{ steps.meta-pro.outputs.labels }}
cache-from: type=registry,ref=${{ env.ECR_ACCOUNT_ID }}.dkr.ecr.${{ env.ECR_REGION }}.amazonaws.com/riverqueue/riverproui:cache-${{ env.PLATFORM_PAIR }}
cache-to: type=registry,ref=${{ env.ECR_ACCOUNT_ID }}.dkr.ecr.${{ env.ECR_REGION }}.amazonaws.com/riverqueue/riverproui:cache-${{ env.PLATFORM_PAIR }},mode=max
outputs: type=image,name=${{ env.ECR_ACCOUNT_ID }}.dkr.ecr.${{ env.ECR_REGION }}.amazonaws.com/riverqueue/riverproui,push-by-digest=true,name-canonical=true,push=true
secrets: |
"riverpro_credential=${{ secrets.RIVERPRO_GO_MOD_CREDENTIAL }}"
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build-pro.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: pro-digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
# Keep platform digests longer to allow safe reruns without rebuilds
retention-days: 14
merge-riverproui:
name: "Merge manifests: riverproui"
runs-on: ubuntu-latest
needs:
- build-riverproui
# Skip this job when manually dispatched with verify_only=true
if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.verify_only) }}
permissions:
contents: read
id-token: write
attestations: write
env:
ECR_ACCOUNT_ID: ${{ secrets.ECR_CACHE_AWS_ACCOUNT_ID }}
ECR_ROLE_ARN: ${{ secrets.ECR_CACHE_ROLE_ARN }}
TAG: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}
SHA_TAG: ${{ format('sha-{0}', github.sha) }}
outputs:
manifest_digest: ${{ steps.manifest_digest.outputs.digest }}
tag: ${{ steps.compute_tag.outputs.tag }}
immutable_tags: ${{ steps.export_immutable_tags.outputs.tags }}
mutable_tags: ${{ steps.tag_mutable.outputs.tags }}
steps:
- name: Checkout full history (no tags yet)
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref || github.ref }}
fetch-depth: 0 # full history
# keep fetch-tags off to avoid the conflict on tag events
- name: Fetch tags
run: git fetch --tags --force
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up ORAS CLI
uses: oras-project/setup-oras@v1
- name: Install Cosign
uses: sigstore/cosign-installer@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.ECR_ROLE_ARN }}
aws-region: ${{ env.ECR_REGION }}
role-session-name: GitHubActions
- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: pro-digests-*
merge-multiple: true
- name: Compute TAG
id: compute_tag
run: |
if [ -n "${{ inputs.ref }}" ]; then
REF="${{ inputs.ref }}"
if [[ "$REF" =~ ^refs/tags/riverproui/v ]]; then
TAG="${REF#refs/tags/riverproui/v}"
elif [[ "$REF" =~ ^riverproui/v ]]; then
TAG="${REF#riverproui/v}"
elif [[ "$REF" =~ ^v[0-9]+\.[0-9]+\.[0-9]+ ]]; then
TAG="${REF#v}"
else
TAG="$REF"
fi
elif [[ "${{ github.ref }}" =~ ^refs/tags/riverproui/v[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9\.-]+)?$ ]]; then
TAG="${GITHUB_REF_NAME#riverproui/v}"
elif [ "${{ github.event_name }}" = "pull_request" ]; then
TAG="pr-${{ github.event.pull_request.number }}"
else
TAG="${{ github.ref_name }}"
fi
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "TAG=$TAG" >> $GITHUB_ENV
- name: Prepare ECR vars
run: |
ECR_IMAGE="${ECR_ACCOUNT_ID}.dkr.ecr.${ECR_REGION}.amazonaws.com/riverqueue/riverproui"
echo "ECR_IMAGE=${ECR_IMAGE}" >> $GITHUB_ENV
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.ECR_IMAGE }}
tags: |
type=raw,value=${{ env.TAG }}
type=ref,event=branch
type=ref,event=pr
type=sha,pattern=sha-{{sha}}
type=semver,pattern={{version}},match=riverproui/v(.*)
- name: Create manifest list and push immutable tags
working-directory: /tmp/digests
run: |
set -euo pipefail
# Deterministic ordering of platform digests (avoid non-deterministic glob order)
mapfile -t DIGEST_FILES < <(ls -1 | sort)
if crane manifest "$ECR_IMAGE:$TAG" > /tmp/current-index.json 2>/dev/null; then
mapfile -t HAVE < <(jq -r '.manifests[].digest | sub("^sha256:", "")' /tmp/current-index.json | sort)
mapfile -t WANT < <(printf '%s\n' "${DIGEST_FILES[@]}" | sort)
if [ "${HAVE[*]-}" = "${WANT[*]-}" ]; then
echo "Index $ECR_IMAGE:$TAG already has the same platform digests; skipping create."
exit 0
fi
fi
docker buildx imagetools create \
--annotation "index:org.opencontainers.image.source=https://github.com/riverqueue/riverui" \
--annotation "index:org.opencontainers.image.description=River UI Pro is a web-based user interface for River, with pro features." \
--annotation "index:org.opencontainers.image.licenses=commercial" \
`echo "${{ steps.meta.outputs.tags }}" | xargs -n1 echo -t` \
$(printf "$ECR_IMAGE@sha256:%s " "${DIGEST_FILES[@]}")
- name: Export immutable tags (short) for downstream jobs
id: export_immutable_tags
run: |
TAGS=$(jq -r '.tags[] | split(":") | .[1]' <<< "$DOCKER_METADATA_OUTPUT_JSON")
echo "tags<<EOF" >> $GITHUB_OUTPUT
echo "$TAGS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Tag mutable on release if latest in series
id: tag_mutable
if: startsWith(github.ref, 'refs/tags/riverproui/v') || (github.event_name == 'workflow_dispatch' && (startsWith(inputs.ref, 'refs/tags/riverproui/v') || startsWith(inputs.ref, 'riverproui/v') || startsWith(inputs.ref, 'v')))
working-directory: /tmp/digests
run: |
STRIPPED_VERSION="${GITHUB_REF_NAME#riverproui/v}"
MAJOR="${STRIPPED_VERSION%%.*}"
MINOR_PATCH="${STRIPPED_VERSION#${MAJOR}.}"
MINOR="${MINOR_PATCH%%.*}"
declare -a mutable_tags=()
GLOBAL_LATEST=$(git tag --list 'riverproui/v*' --sort=-v:refname | grep -E '^riverproui/v[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9\.-]+)?$' | head -n1)
if [ "$GLOBAL_LATEST" = "riverproui/v${STRIPPED_VERSION}" ]; then
mutable_tags+=("latest")
fi
LATEST_IN_MAJOR=$(git tag --list "riverproui/v${MAJOR}.*" --sort=-v:refname | head -n1)
if [ "$LATEST_IN_MAJOR" = "riverproui/v${STRIPPED_VERSION}" ]; then
mutable_tags+=("v${MAJOR}")
fi
LATEST_IN_MINOR=$(git tag --list "riverproui/v${MAJOR}.${MINOR}.*" --sort=-v:refname | head -n1)
if [ "$LATEST_IN_MINOR" = "riverproui/v${STRIPPED_VERSION}" ]; then
mutable_tags+=("v${MAJOR}.${MINOR}")
fi
for tag in "${mutable_tags[@]}"; do
set -euo pipefail
NEW_DIGEST=$(crane digest "$ECR_IMAGE:$TAG")
CURRENT_DIGEST=$(crane digest "$ECR_IMAGE:$tag" 2>/dev/null || true)
if [ -n "$CURRENT_DIGEST" ] && [ "$CURRENT_DIGEST" = "$NEW_DIGEST" ]; then
echo "Mutable tag $tag already points to $NEW_DIGEST; skipping."
continue
fi
mapfile -t DIGEST_FILES < <(ls -1 | sort)
docker buildx imagetools create \
--annotation "index:org.opencontainers.image.source=https://github.com/riverqueue/riverui" \
--annotation "index:org.opencontainers.image.description=River UI Pro is a web-based user interface for River, with pro features." \
--annotation "index:org.opencontainers.image.licenses=commercial" \
-t "$ECR_IMAGE:$tag" \
$(printf "$ECR_IMAGE@sha256:%s " "${DIGEST_FILES[@]}")
done
if [ ${#mutable_tags[@]} -gt 0 ]; then
echo "tags<<EOF" >> $GITHUB_OUTPUT
printf "%s\n" "${mutable_tags[@]}" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
fi
- name: Install crane
uses: imjasonh/setup-crane@v0.4
- name: Compute manifest digest for attestation
id: manifest_digest
run: |
DIGEST=$(crane digest "$ECR_IMAGE:$TAG")
echo "digest=$DIGEST" >> $GITHUB_OUTPUT
- name: Generate build provenance attestation (not pushed)
id: attest
uses: actions/attest-build-provenance@v2
with:
push-to-registry: false
subject-digest: ${{ steps.manifest_digest.outputs.digest }}
subject-name: riverqueue.com/riverproui
- name: Resolve platform manifest digests
id: platform_digests
run: |
crane manifest "$ECR_IMAGE:$TAG" > /tmp/index.json
AMD64_DIGEST=$(jq -r '.manifests[] | select(.platform.os=="linux" and .platform.architecture=="amd64") .digest' /tmp/index.json)
ARM64_DIGEST=$(jq -r '.manifests[] | select(.platform.os=="linux" and .platform.architecture=="arm64") .digest' /tmp/index.json)
echo "amd64=$AMD64_DIGEST" >> "$GITHUB_OUTPUT"
echo "arm64=$ARM64_DIGEST" >> "$GITHUB_OUTPUT"
- name: Extract DSSE envelope from bundle
run: |
jq '.dsseEnvelope' "${{ steps.attest.outputs.bundle-path }}" > /tmp/dsse.json
- name: Decode DSSE payload to predicate (SLSA v1 statement)
run: |
jq -r '.payload' /tmp/dsse.json | base64 -d > /tmp/predicate.json
test "$(jq -r '.predicateType' /tmp/predicate.json)" = "https://slsa.dev/provenance/v1"
- name: Push Cosign attestations (index + per-platform)
env:
COSIGN_EXPERIMENTAL: "true"
COSIGN_REPOSITORY: ${{ env.ECR_ACCOUNT_ID }}.dkr.ecr.${{ env.ECR_REGION }}.amazonaws.com/riverqueue/riverproui
ECR_IMAGE: ${{ env.ECR_ACCOUNT_ID }}.dkr.ecr.${{ env.ECR_REGION }}.amazonaws.com/riverqueue/riverproui
run: |
# Idempotency guard: skip if a bundle is already present for this subject.
for subject in "${{ steps.manifest_digest.outputs.digest }}" "${{ steps.platform_digests.outputs.amd64 }}" "${{ steps.platform_digests.outputs.arm64 }}"; do
existing=$(oras discover --format json "${ECR_IMAGE}@${subject}" | jq -r '.manifests[]? | select(.artifactType=="application/vnd.dev.sigstore.bundle.v0.3+json" or .artifactType=="application/vnd.dev.sigstore.bundle+json;version=0.3") | .digest' | head -n1)
if [ -n "$existing" ]; then
echo "Bundle already exists for ${subject} (manifest $existing). Skipping cosign attest."
continue
fi
cosign attest \
--predicate /tmp/predicate.json \
--type https://slsa.dev/provenance/v1 \
--yes \
--new-bundle-format \
"${ECR_IMAGE}@${subject}"
done
prefetch-riverproui-through-live-registry:
# Prefetch only for release semver tags
name: "Prefetch riverproui via live registry"
runs-on: ubuntu-latest
needs:
- merge-riverproui
env:
IMAGE_NAME: riverqueue.com/riverproui
MANIFEST_DIGEST: ${{ needs.merge-riverproui.outputs.manifest_digest }}
TAG: ${{ needs.merge-riverproui.outputs.tag }}
IMMUTABLE_TAGS: ${{ needs.merge-riverproui.outputs.immutable_tags }}
MUTABLE_TAGS: ${{ needs.merge-riverproui.outputs.mutable_tags }}
# Only run on release tag events (refs/tags/riverproui/vX.Y.Z),
# or when manually forced via workflow_dispatch: force_prefetch=true.
# For workflow_dispatch, inputs.ref should be in the form 'riverproui/vX.Y.Z'.
if: startsWith(github.ref, 'refs/tags/riverproui/v') || (github.event_name == 'workflow_dispatch' && (startsWith(inputs.ref, 'riverproui/v') || inputs.force_prefetch))
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref || github.ref }}
fetch-depth: 0 # full history
# keep fetch-tags off to avoid the conflict on tag events
- name: Fetch tags
run: git fetch --tags --force
# Compute TAG when running via workflow_dispatch. Expect inputs.ref to be 'riverproui/vX.Y.Z'.
- name: Compute TAG for workflow_dispatch
if: github.event_name == 'workflow_dispatch'
run: |
REF="${{ inputs.ref }}"
if [[ "$REF" =~ ^riverproui/v ]]; then
TAG="${REF#riverproui/v}"
elif [[ "$REF" =~ ^refs/tags/riverproui/v ]]; then
TAG="${REF#refs/tags/riverproui/}"
else
echo "inputs.ref must be of the form 'riverproui/vX.Y.Z' unless force_prefetch=true" >&2
if [ "${{ inputs.force_prefetch }}" != "true" ]; then
exit 1
fi
# Best-effort fallback: try to strip common prefixes
TAG="${REF#refs/tags/}"
TAG="${TAG#v}"
fi
echo "TAG=$TAG" >> $GITHUB_ENV
- name: Force refresh pushed tags
run: |
pushed_tags="$IMMUTABLE_TAGS"
if [ -n "$MUTABLE_TAGS" ]; then
if [ -n "$pushed_tags" ]; then
pushed_tags="$pushed_tags,$MUTABLE_TAGS"
else
pushed_tags="$MUTABLE_TAGS"
fi
fi
# In verify_only mode or when upstream jobs are skipped, fallback to current TAG
if [ -z "$pushed_tags" ] && [ -n "$TAG" ]; then
pushed_tags="$TAG"
fi
IFS=',' read -r -a tags <<< "$pushed_tags"
declare -a unique_tags=()
for t in "${tags[@]}"; do
if [[ ! " ${unique_tags[*]} " =~ " ${t} " ]]; then
unique_tags+=("$t")
fi
done
for tag in "${unique_tags[@]}"; do
if [ -n "$tag" ]; then
echo "Force refreshing tag: $tag"
curl -f -u river:"${{ secrets.RIVERPRO_GO_MOD_CREDENTIAL }}" \
-H "X-Force-Fetch-From-Upstream: ${{ secrets.FORCE_FETCH_SECRET }}" \
-H "Accept: application/vnd.docker.distribution.manifest.list.v2+json" \
"https://riverqueue.com/v2/riverproui/manifests/${tag}" -o /dev/null
curl -f -u river:"${{ secrets.RIVERPRO_GO_MOD_CREDENTIAL }}" \
-H "X-Force-Fetch-From-Upstream: ${{ secrets.FORCE_FETCH_SECRET }}" \
-H "Accept: application/vnd.oci.image.index.v1+json" \
"https://riverqueue.com/v2/riverproui/manifests/${tag}" -o /dev/null
fi
done
- name: Login to live registry
uses: docker/login-action@v3
with:
registry: riverqueue.com
username: river
password: ${{ secrets.RIVERPRO_GO_MOD_CREDENTIAL }}
- name: Install crane
uses: imjasonh/setup-crane@v0.4
- name: Set up ORAS CLI
uses: oras-project/setup-oras@v1
- name: Install Cosign
uses: sigstore/cosign-installer@v3
- name: Resolve manifest digest
id: resolve-digest
run: |
DIGEST=$(crane digest "$IMAGE_NAME:$TAG")
echo "digest=$DIGEST" >> $GITHUB_OUTPUT
- name: Fetch index manifest with crane (Docker preferred)
run: crane manifest "$IMAGE_NAME:$TAG" > /tmp/index-manifest-docker.json
- name: Fetch index manifest with oras (OCI preferred)
run: oras manifest fetch --output /tmp/index-manifest-oci.json "$IMAGE_NAME:$TAG"
- name: Extract platform manifest digests
id: platform-digests
run: |
AMD64_DIGEST=$(jq -r '.manifests[] | select(.platform.architecture == "amd64" and .platform.os == "linux") | .digest' /tmp/index-manifest-docker.json)
ARM64_DIGEST=$(jq -r '.manifests[] | select(.platform.architecture == "arm64" and .platform.os == "linux") | .digest' /tmp/index-manifest-docker.json)
echo "amd64_digest=$AMD64_DIGEST" >> $GITHUB_OUTPUT
echo "arm64_digest=$ARM64_DIGEST" >> $GITHUB_OUTPUT
- name: Prefetch buildx attestation manifests referenced by index (legacy non-referrers)
env:
AUTH_USER: river
AUTH_PASSWORD: ${{ secrets.RIVERPRO_GO_MOD_CREDENTIAL }}
FORCE_FETCH_SECRET: ${{ secrets.FORCE_FETCH_SECRET }}
REGISTRY_MANIFEST_URL: https://riverqueue.com/v2/riverproui/manifests
run: |
bash scripts/ci/prefetch-buildx-attestation-manifests.sh /tmp/index-manifest-docker.json
- name: Fetch amd64 manifest with crane (Docker media type)
run: crane manifest "$IMAGE_NAME@${{ steps.platform-digests.outputs.amd64_digest }}" > /tmp/amd64-manifest-docker.json
- name: Fetch amd64 manifest with oras (OCI media type)
run: oras manifest fetch --output /tmp/amd64-manifest-oci.json "$IMAGE_NAME@${{ steps.platform-digests.outputs.amd64_digest }}"
- name: Fetch arm64 manifest with crane (Docker media type)
run: crane manifest "$IMAGE_NAME@${{ steps.platform-digests.outputs.arm64_digest }}" > /tmp/arm64-manifest-docker.json
- name: Fetch arm64 manifest with oras (OCI media type)
run: oras manifest fetch --output /tmp/arm64-manifest-oci.json "$IMAGE_NAME@${{ steps.platform-digests.outputs.arm64_digest }}"
- name: Prefetch image blobs (configs + layers)
run: |
# Blobs are identical across media types; fetch once per platform.
for m in /tmp/amd64-manifest-docker.json /tmp/arm64-manifest-docker.json; do
cfg=$(jq -r '.config.digest // empty' "$m")
if [ -n "$cfg" ]; then
oras blob fetch --output /dev/null "$IMAGE_NAME@$cfg"
fi
jq -r '.layers[]?.digest // empty' "$m" | while read -r ld; do
[ -n "$ld" ] && oras blob fetch --output /dev/null "$IMAGE_NAME@$ld"
done
done
# Prefetch referrers for index (ECR digest if provided) and per-arch manifests.
# If neither MANIFEST_DIGEST (from merge job) nor ecr_manifest_digest (manual input)
# is available, we skip index referrer prefetch rather than fail.
- name: Prefetch all referrers content
env:
AUTH_USER: river
AUTH_PASSWORD: ${{ secrets.RIVERPRO_GO_MOD_CREDENTIAL }}
FORCE_FETCH_SECRET: ${{ secrets.FORCE_FETCH_SECRET }}
REGISTRY_MANIFEST_URL: https://riverqueue.com/v2/riverproui/manifests
REGISTRY_REFERRERS_URL: https://riverqueue.com/v2/riverproui/referrers
run: |
# Include both the ECR-derived index digest (attestations live here)
# and the live registry's index digest (may differ but could gain referrers).
FALLBACK="${{ inputs.ecr_manifest_digest }}"
INDEX_SUBJECT="${MANIFEST_DIGEST:-$FALLBACK}"
subjects=()
if [ -n "$INDEX_SUBJECT" ]; then
subjects+=("$INDEX_SUBJECT")
else
echo "Note: no index subject digest provided; skipping index referrers prefetch."
fi
subjects+=("${{ steps.resolve-digest.outputs.digest }}" "${{ steps.platform-digests.outputs.amd64_digest }}" "${{ steps.platform-digests.outputs.arm64_digest }}")
bash scripts/ci/prefetch-referrers-content.sh "${subjects[@]}"
# Verify index (if digest is available) and per-arch attestations against live registry.
# Index-level attestation is bound to the ECR index digest; provide it via MANIFEST_DIGEST
# or the ecr_manifest_digest input. If missing, we skip index verification.
- name: Verify image attestation via Workers (registry-based)
env:
COSIGN_EXPERIMENTAL: "true"
run: |
# Verify against the ECR-derived index digest (MANIFEST_DIGEST).
# The live tag's index digest may differ and typically has no bundles.
FALLBACK="${{ inputs.ecr_manifest_digest }}"
INDEX_SUBJECT="${MANIFEST_DIGEST:-$FALLBACK}"
if [ -n "$INDEX_SUBJECT" ]; then
cosign verify-attestation \
--type https://slsa.dev/provenance/v1 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp '^https://github.com/riverqueue/riverui/.*' \
--new-bundle-format \
"$IMAGE_NAME@$INDEX_SUBJECT" \
| tee /tmp/cosign-verify-registry-index.txt
else
echo "Skipping index-level verification: no MANIFEST_DIGEST or ecr_manifest_digest provided."
fi
cosign verify-attestation \
--type https://slsa.dev/provenance/v1 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp '^https://github.com/riverqueue/riverui/.*' \
--new-bundle-format \
"$IMAGE_NAME@${{ steps.platform-digests.outputs.amd64_digest }}" \
| tee /tmp/cosign-verify-registry-amd64.txt
cosign verify-attestation \
--type https://slsa.dev/provenance/v1 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp '^https://github.com/riverqueue/riverui/.*' \
--new-bundle-format \
"$IMAGE_NAME@${{ steps.platform-digests.outputs.arm64_digest }}" \
| tee /tmp/cosign-verify-registry-arm64.txt
# Offline verification of Sigstore bundle: only attempt for index if digest provided.
- name: Verify Sigstore bundle against subject bytes (offline crypto)
env:
AUTH_USER: river
AUTH_PASSWORD: ${{ secrets.RIVERPRO_GO_MOD_CREDENTIAL }}
FORCE_FETCH_SECRET: ${{ secrets.FORCE_FETCH_SECRET }}
REGISTRY_MANIFEST_URL: https://riverqueue.com/v2/riverproui/manifests
REGISTRY_REFERRERS_URL: https://riverqueue.com/v2/riverproui/referrers
run: |
FALLBACK="${{ inputs.ecr_manifest_digest }}"
INDEX_SUBJECT="${MANIFEST_DIGEST:-$FALLBACK}"
if [ -n "$INDEX_SUBJECT" ]; then
bash scripts/ci/verify-sigstore-bundles-offline.sh "$INDEX_SUBJECT"
else
echo "Skipping offline index bundle verification: no MANIFEST_DIGEST or ecr_manifest_digest provided."
fi
bash scripts/ci/verify-sigstore-bundles-offline.sh \
"${{ steps.platform-digests.outputs.amd64_digest }}" \
"${{ steps.platform-digests.outputs.arm64_digest }}"
- name: Upload debug artifacts
if: failure() || cancelled()
uses: actions/upload-artifact@v4
with:
name: prefetch-debug
path: |
/tmp/index-manifest-docker.json
/tmp/index-manifest-oci.json
/tmp/subject.json
/tmp/bundle.json
/tmp/cosign-verify-registry-index.txt
/tmp/cosign-verify-registry-amd64.txt
/tmp/cosign-verify-registry-arm64.txt
retention-days: 1