Extract all queries to drivers, nuke sqlc, pro-specific executable #738
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Docker | |
| on: | |
| push: | |
| branches: | |
| - "master" | |
| - "*" # TEMPORARY FOR TESTING | |
| tags: | |
| - "v*" | |
| pull_request: | |
| branches: | |
| - "master" | |
| - "*" | |
| workflow_dispatch: | |
| inputs: | |
| ref: | |
| description: "Tag to build (e.g. v0.1.0)" | |
| required: true | |
| env: | |
| ECR_REGION: us-east-2 | |
| jobs: | |
| build-riverui: | |
| name: "Build image: riverui" | |
| runs-on: ubuntu-latest | |
| env: | |
| IMAGE_NAME: ${{ github.repository }} | |
| REGISTRY: ghcr.io | |
| strategy: | |
| matrix: | |
| docker_platform: | |
| - linux/amd64 | |
| - linux/arm64 | |
| outputs: | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| permissions: | |
| attestations: write | |
| contents: read | |
| id-token: write | |
| packages: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.ref || github.ref }} | |
| - name: Prepare | |
| run: | | |
| platform=${{ matrix.docker_platform }} | |
| echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v3 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Docker meta | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| labels: | | |
| org.opencontainers.image.source=https://github.com/riverqueue/riverui | |
| org.opencontainers.image.description="River UI is a web-based user interface for River, a fast and reliable background job system." | |
| org.opencontainers.image.licenses=MPL-2.0 | |
| tags: | | |
| type=ref,event=branch | |
| type=ref,event=pr | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} | |
| - name: Build and push to GitHub Container Registry | |
| id: build | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| pull: true | |
| platforms: ${{ matrix.docker_platform }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:cache-${{ env.PLATFORM_PAIR }} | |
| cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:cache-${{ env.PLATFORM_PAIR }},mode=max | |
| outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true,annotation-index.org.opencontainers.image.description=River UI | |
| - name: Generate artifact attestation | |
| uses: actions/attest-build-provenance@v2 | |
| with: | |
| push-to-registry: true | |
| subject-digest: ${{ steps.build.outputs.digest }} | |
| subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| - name: Export digest | |
| run: | | |
| mkdir -p /tmp/digests | |
| digest="${{ steps.build.outputs.digest }}" | |
| touch "/tmp/digests/${digest#sha256:}" | |
| - name: Upload digest | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: digests-oss-${{ env.PLATFORM_PAIR }} | |
| path: /tmp/digests/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| build-riverproui: | |
| name: "Build image: riverproui" | |
| runs-on: ubuntu-latest | |
| env: | |
| ECR_ACCOUNT_ID: ${{ secrets.ECR_CACHE_AWS_ACCOUNT_ID }} | |
| ECR_ROLE_ARN: ${{ secrets.ECR_CACHE_ROLE_ARN }} | |
| strategy: | |
| matrix: | |
| docker_platform: | |
| - linux/amd64 | |
| - linux/arm64 | |
| 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 QEMU | |
| uses: docker/setup-qemu-action@v3 | |
| - 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 | |
| retention-days: 1 | |
| merge-riverui: | |
| name: "Merge manifests: riverui" | |
| runs-on: ubuntu-latest | |
| env: | |
| IMAGE_NAME: ${{ github.repository }} | |
| REGISTRY: ghcr.io | |
| needs: | |
| - build-riverui | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Download digests | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: /tmp/digests | |
| pattern: digests-oss-* | |
| merge-multiple: true | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Docker meta | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| labels: | | |
| org.opencontainers.image.source=https://github.com/riverqueue/riverui | |
| org.opencontainers.image.description="River UI is a web-based user interface for River, a fast and reliable background job system." | |
| org.opencontainers.image.licenses=MPL-2.0 | |
| tags: | | |
| type=ref,event=branch | |
| type=ref,event=pr | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} | |
| - name: Create manifest list and push | |
| working-directory: /tmp/digests | |
| run: | | |
| docker buildx imagetools create \ | |
| --annotation "index:org.opencontainers.image.source=https://github.com/riverqueue/riverui" \ | |
| --annotation "index:org.opencontainers.image.description=River UI is a web-based user interface for River, a fast and reliable background job system." \ | |
| --annotation "index:org.opencontainers.image.licenses=MPL-2.0" \ | |
| $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ | |
| $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) | |
| merge-riverproui: | |
| name: "Merge manifests: riverproui" | |
| runs-on: ubuntu-latest | |
| needs: | |
| - build-riverproui | |
| 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 }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-tags: true | |
| - 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 [[ "${{ github.ref }}" =~ ^refs/tags/riverproui/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| TAG="${GITHUB_REF_NAME#riverproui/}" | |
| 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=ref,event=branch | |
| type=ref,event=pr | |
| type=sha,pattern=sha-{{sha}} | |
| type=semver,pattern=v{{version}},match=riverproui/(v.*) | |
| - name: Create manifest list and push immutable tags | |
| working-directory: /tmp/digests | |
| run: | | |
| 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 " *) | |
| - name: Tag mutable on release if latest in series | |
| if: startsWith(github.ref, 'refs/tags/riverproui/v') | |
| working-directory: /tmp/digests | |
| run: | | |
| STRIPPED_VERSION="${{ github.ref_name }}" | |
| STRIPPED_VERSION="${STRIPPED_VERSION#riverproui/}" | |
| MAJOR="${STRIPPED_VERSION#v}" | |
| MAJOR="${MAJOR%%.*}" | |
| MINOR_PATCH="${STRIPPED_VERSION#v${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]+$' | head -n1) | |
| if [ "$GLOBAL_LATEST" = "${{ github.ref_name }}" ]; then | |
| mutable_tags+=("latest") | |
| fi | |
| LATEST_IN_MAJOR=$(git tag --list "riverproui/v${MAJOR}.*" --sort=-v:refname | head -n1) | |
| if [ "$LATEST_IN_MAJOR" = "${{ github.ref_name }}" ]; 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" = "${{ github.ref_name }}" ]; then | |
| mutable_tags+=("v${MAJOR}.${MINOR}") | |
| fi | |
| for tag in "${mutable_tags[@]}"; do | |
| 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 " *) | |
| done | |
| - 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: | | |
| for subject in "${{ steps.manifest_digest.outputs.digest }}" "${{ steps.platform_digests.outputs.amd64 }}" "${{ steps.platform_digests.outputs.arm64 }}"; do | |
| 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 }} | |
| if: startsWith(github.ref, 'refs/tags/riverproui/v') | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-tags: true | |
| - name: Force refresh mutable tags | |
| if: startsWith(github.ref, 'refs/tags/riverproui/v') | |
| run: | | |
| STRIPPED_VERSION="${{ github.ref_name }}" | |
| STRIPPED_VERSION="${STRIPPED_VERSION#riverproui/}" | |
| MAJOR="${STRIPPED_VERSION#v}" | |
| MAJOR="${MAJOR%%.*}" | |
| MINOR_PATCH="${STRIPPED_VERSION#v${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]+$' | head -n1) | |
| if [ "$GLOBAL_LATEST" = "riverproui/${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/${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/${STRIPPED_VERSION}" ]; then | |
| mutable_tags+=("v${MAJOR}.${MINOR}") | |
| fi | |
| for tag in "${mutable_tags[@]}"; do | |
| echo "Force refreshing mutable 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 | |
| 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: 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 | |
| - name: Prefetch all referrers content | |
| run: | | |
| for subject in "${{ steps.resolve-digest.outputs.digest }}" "${{ steps.platform-digests.outputs.amd64_digest }}" "${{ steps.platform-digests.outputs.arm64_digest }}"; do | |
| oras discover --format json "$IMAGE_NAME@$subject" | tee /tmp/referrers.json | |
| digests=$(jq -r '.manifests[]?.digest // empty' /tmp/referrers.json) | |
| for d in $digests; do | |
| tmpfile=$(mktemp) | |
| # Fetch the full referrer manifest to a file so we can parse layers + config | |
| oras manifest fetch --output "$tmpfile" "$IMAGE_NAME@$d" | |
| # Prefetch config blob (often empty but warms cache) | |
| cfg=$(jq -r '.config.digest // empty' "$tmpfile") | |
| if [ -n "$cfg" ]; then | |
| oras blob fetch --output /dev/null "$IMAGE_NAME@$cfg" | |
| fi | |
| # Prefetch any layer blobs (DSSE envelope, bundle, etc.) | |
| jq -r '.layers[]?.digest // empty' "$tmpfile" | while read -r ld; do | |
| [ -n "$ld" ] && oras blob fetch --output /dev/null "$IMAGE_NAME@$ld" | |
| done | |
| done | |
| done | |
| - name: Verify image attestation via Workers (registry-based) | |
| env: | |
| COSIGN_EXPERIMENTAL: "true" | |
| run: | | |
| 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.resolve-digest.outputs.digest }}" \ | |
| | tee /tmp/cosign-verify-registry-index.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.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 | |
| - name: Verify Sigstore bundle against subject bytes (offline crypto) | |
| run: | | |
| verify_bundle () { | |
| subject="$1" | |
| # Exact subject bytes | |
| oras manifest fetch --output /tmp/subject.json "$IMAGE_NAME@$subject" | |
| # Find *all* bundle candidates for this subject (both media-type spellings) | |
| refjson=$(mktemp) | |
| oras discover --format json "$IMAGE_NAME@$subject" > "$refjson" | |
| mapfile -t bundle_mfs < <(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' "$refjson") | |
| ok=0 | |
| for bmf in "${bundle_mfs[@]}"; do | |
| [ -z "$bmf" ] && continue | |
| mf=$(mktemp) | |
| oras manifest fetch --output "$mf" "$IMAGE_NAME@$bmf" | |
| bundle_blob=$(jq -r '.layers[0].digest' "$mf") | |
| oras blob fetch --output /tmp/bundle.json "$IMAGE_NAME@$bundle_blob" | |
| if cosign verify-blob-attestation \ | |
| --bundle /tmp/bundle.json \ | |
| --new-bundle-format \ | |
| --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ | |
| --certificate-identity-regexp '^https://github.com/riverqueue/riverui/.*' \ | |
| /tmp/subject.json > /tmp/verify.out 2>&1; then | |
| echo "Verified OK with bundle $bmf" | |
| ok=1 | |
| break | |
| fi | |
| done | |
| if [ "$ok" -ne 1 ]; then | |
| echo "No matching bundle verified for subject $subject" | |
| cat /tmp/verify.out || true | |
| exit 1 | |
| fi | |
| } | |
| verify_bundle "${{ steps.resolve-digest.outputs.digest }}" | |
| verify_bundle "${{ steps.platform-digests.outputs.amd64_digest }}" | |
| verify_bundle "${{ 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 |