Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/actions/push-signed-test-image/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Push signed test image
description: Build a tiny multi-arch image, push it, sign it with cosign and attest its provenance, like a head workflow does
inputs:
image:
description: Image without a tag
required: true
tag:
description: Tag to push
required: true
variant:
description: Written into the image, so two pushes to the same tag differ
required: true
outputs:
digest:
description: Digest of the pushed image index
value: ${{ steps.build.outputs.digest }}
runs:
using: composite
steps:
- name: Write a unique marker
shell: bash
env:
MARKER: ${{ inputs.variant }}-${{ github.run_id }}-${{ github.run_attempt }}
run: echo "${MARKER}" > test-fixtures/delete-previous-head-image/marker

- name: Build and push
id: build
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: test-fixtures/delete-previous-head-image
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ inputs.image }}:${{ inputs.tag }}

- name: Sign
shell: bash
env:
IMAGE: ${{ inputs.image }}
DIGEST: ${{ steps.build.outputs.digest }}
run: cosign sign --yes "${IMAGE}@${DIGEST}"

- name: Attest build provenance
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-name: ${{ inputs.image }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
232 changes: 232 additions & 0 deletions .github/workflows/caller-delete-previous-head-image.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
name: Delete previous head image (real delete)

# Integration test for loft-sh/github-actions delete-previous-head-image with a
# real delete, against a throwaway package, before any production repo uses it.
#
# It pushes a signed release and two signed head images, then deletes the first
# head image with the job's GITHUB_TOKEN and checks that:
# - every version of the first head image is gone
# - head and the release still pull on both platforms and pass cosign verify
# - running delete again with the same digest is a no-op
#
# A second job covers an old head that shares a platform manifest with a release
# that signs each platform: the shared manifest and its signature must stay.

on:
push:
branches:
- devops-1807/**
paths:
- .github/workflows/caller-delete-previous-head-image.yaml
- .github/actions/push-signed-test-image/**
- test-fixtures/delete-previous-head-image/**
workflow_dispatch:

concurrency:
group: delete-previous-head-image
cancel-in-progress: false

permissions: {}

env:
IMAGE: ghcr.io/vclusterlabs-experiments/delete-previous-head-image-test
COSIGN_IDENTITY: ^https://github.com/vClusterLabs-Experiments/github-actions-test/\.github/workflows/caller-delete-previous-head-image\.yaml@

jobs:
real-delete:
name: Delete the previous head image for real
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # push, and delete versions of the package this repo publishes
id-token: write # cosign keyless signing and provenance
attestations: write # provenance attestation
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
with:
cosign-release: "v3.0.6"

- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4

- name: Push a signed release
uses: ./.github/actions/push-signed-test-image
with:
image: ${{ env.IMAGE }}
tag: v0.0.1
variant: release

- name: Push the first head image
id: first
uses: ./.github/actions/push-signed-test-image
with:
image: ${{ env.IMAGE }}
tag: head
variant: first-head

- name: Record the current head image
id: previous-head
uses: loft-sh/github-actions/.github/actions/delete-previous-head-image@delete-previous-head-image/v1
with:
mode: record
image: ${{ env.IMAGE }}

- name: Check record returned the first head image
env:
RECORDED: ${{ steps.previous-head.outputs.digest }}
PUSHED: ${{ steps.first.outputs.digest }}
run: test "${RECORDED}" = "${PUSHED}"

- name: Snapshot the first head image
env:
GITHUB_TOKEN: ${{ github.token }}
PREVIOUS: ${{ steps.previous-head.outputs.digest }}
run: bash test-fixtures/delete-previous-head-image/check.sh snapshot "${PREVIOUS}" | tee "${RUNNER_TEMP}/previous.txt"

- name: Push the second head image
id: second
uses: ./.github/actions/push-signed-test-image
with:
image: ${{ env.IMAGE }}
tag: head
variant: second-head

- name: Delete the previous head image
uses: loft-sh/github-actions/.github/actions/delete-previous-head-image@delete-previous-head-image/v1
with:
mode: delete
image: ${{ env.IMAGE }}
previous-digest: ${{ steps.previous-head.outputs.digest }}
current-digest: ${{ steps.second.outputs.digest }}

- name: Check every version of the first head image is gone
env:
GITHUB_TOKEN: ${{ github.token }}
run: bash test-fixtures/delete-previous-head-image/check.sh gone "${RUNNER_TEMP}/previous.txt"

- name: Check head still pulls and verifies
env:
GITHUB_TOKEN: ${{ github.token }}
run: bash test-fixtures/delete-previous-head-image/check.sh intact head

- name: Check the release still pulls and verifies
env:
GITHUB_TOKEN: ${{ github.token }}
run: bash test-fixtures/delete-previous-head-image/check.sh intact v0.0.1

- name: Delete again with the same digest
uses: loft-sh/github-actions/.github/actions/delete-previous-head-image@delete-previous-head-image/v1
with:
mode: delete
image: ${{ env.IMAGE }}
previous-digest: ${{ steps.previous-head.outputs.digest }}
current-digest: ${{ steps.second.outputs.digest }}

shared-platform:
name: Keep the signature of a platform shared with a release
needs: real-delete
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # push, and delete versions of the package this repo publishes
id-token: write # cosign keyless signing and provenance
attestations: write # provenance attestation
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
with:
cosign-release: "v3.0.6"

- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4

- name: Push a signed release
uses: ./.github/actions/push-signed-test-image
with:
image: ${{ env.IMAGE }}
tag: v0.0.2
variant: shared-release

- name: Sign the release's amd64 manifest on its own
id: platform
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
amd64="$(bash test-fixtures/delete-previous-head-image/check.sh platform v0.0.2 amd64)"
test -n "${amd64}"
cosign sign --yes "${IMAGE}@${amd64}"
echo "digest=${amd64}" >> "${GITHUB_OUTPUT}"

- name: Push a signed head image that reuses the release's amd64 manifest
id: old-head
env:
GITHUB_TOKEN: ${{ github.token }}
AMD64: ${{ steps.platform.outputs.digest }}
run: |
docker buildx imagetools create --tag "${IMAGE}:shared-head" \
--annotation "index:org.opencontainers.image.description=shared-head-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" \
"${IMAGE}@${AMD64}"
digest="$(bash test-fixtures/delete-previous-head-image/check.sh digest shared-head)"
test -n "${digest}"
cosign sign --yes "${IMAGE}@${digest}"
echo "digest=${digest}" >> "${GITHUB_OUTPUT}"

- name: Record the current head image
id: previous-head
uses: loft-sh/github-actions/.github/actions/delete-previous-head-image@delete-previous-head-image/v1
with:
mode: record
image: ${{ env.IMAGE }}
tag: shared-head

- name: Check record returned the old head image
env:
RECORDED: ${{ steps.previous-head.outputs.digest }}
PUSHED: ${{ steps.old-head.outputs.digest }}
run: test "${RECORDED}" = "${PUSHED}"

- name: Push the new head image
id: new-head
uses: ./.github/actions/push-signed-test-image
with:
image: ${{ env.IMAGE }}
tag: shared-head
variant: shared-new-head

- name: Delete the previous head image
uses: loft-sh/github-actions/.github/actions/delete-previous-head-image@delete-previous-head-image/v1
with:
mode: delete
image: ${{ env.IMAGE }}
tag: shared-head
previous-digest: ${{ steps.previous-head.outputs.digest }}
current-digest: ${{ steps.new-head.outputs.digest }}

- name: Check the shared manifest kept its signature
env:
GITHUB_TOKEN: ${{ github.token }}
OLD: ${{ steps.previous-head.outputs.digest }}
AMD64: ${{ steps.platform.outputs.digest }}
run: bash test-fixtures/delete-previous-head-image/check.sh shared "${OLD}" "${AMD64}"

- name: Check the release still pulls and verifies
env:
GITHUB_TOKEN: ${{ github.token }}
run: bash test-fixtures/delete-previous-head-image/check.sh intact v0.0.2
2 changes: 2 additions & 0 deletions test-fixtures/delete-previous-head-image/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM scratch
COPY marker /marker
Loading
Loading