Cleanup Images #39
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: Cleanup Images | |
| on: | |
| schedule: | |
| - cron: "0 0 * * 3" | |
| workflow_dispatch: | |
| inputs: | |
| dry-run: | |
| default: false | |
| type: boolean | |
| permissions: {} | |
| jobs: | |
| collect-digests: | |
| name: 📦 Collect Digests (${{ matrix.package }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| package: [amp-devcontainer-base, amp-devcontainer-cpp, amp-devcontainer-rust] | |
| permissions: | |
| packages: read # is needed to list package versions | |
| steps: | |
| - uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 | |
| with: | |
| disable-sudo-and-containers: true | |
| egress-policy: audit | |
| allowed-endpoints: api.github.com:443 | |
| - name: Collect package digests | |
| run: | | |
| set -Eeuo pipefail | |
| ORG="${GH_REPO%%/*}" | |
| gh api "/orgs/${ORG}/packages/container/${GH_PACKAGE}/versions" \ | |
| --paginate \ | |
| --jq '.[].name' 2>/dev/null > digests.txt || touch digests.txt | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| GH_PACKAGE: ${{ matrix.package }} | |
| - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 | |
| with: | |
| name: digests-before-cleanup-${{ matrix.package }} | |
| path: digests.txt | |
| if-no-files-found: warn | |
| retention-days: 1 | |
| cleanup-images: | |
| name: 🧹 Clean Images | |
| if: always() | |
| needs: collect-digests | |
| runs-on: ubuntu-latest | |
| permissions: | |
| packages: write # is needed by dataaxiom/ghcr-cleanup-action to delete untagged and orphaned images | |
| steps: | |
| - uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 | |
| with: | |
| disable-sudo: true | |
| allowed-endpoints: > | |
| api.github.com:443 | |
| ghcr.io:443 | |
| - uses: dataaxiom/ghcr-cleanup-action@cd0cdb900b5dbf3a6f2cc869f0dbb0b8211f50c4 # v1.0.16 | |
| with: | |
| delete-orphaned-images: true | |
| delete-untagged: true | |
| dry-run: ${{ inputs.dry-run == true }} | |
| packages: amp-devcontainer-base,amp-devcontainer-cpp,amp-devcontainer-rust | |
| cleanup-attestations: | |
| name: 🔏 Cleanup Orphaned Attestations (${{ matrix.package }}) | |
| needs: cleanup-images | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| package: [amp-devcontainer-base, amp-devcontainer-cpp, amp-devcontainer-rust] | |
| permissions: | |
| attestations: write # is needed to delete attestations | |
| packages: read # is needed to list remaining package versions after cleanup | |
| steps: | |
| - uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 | |
| with: | |
| disable-sudo-and-containers: true | |
| egress-policy: audit | |
| allowed-endpoints: api.github.com:443 | |
| - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 | |
| id: download-digests | |
| continue-on-error: true | |
| with: | |
| name: digests-before-cleanup-${{ matrix.package }} | |
| - name: Delete orphaned attestations | |
| if: steps.download-digests.outcome == 'success' | |
| run: | | |
| set -Eeuo pipefail | |
| ORG="${GH_REPO%%/*}" | |
| # Get remaining digests after image cleanup | |
| if ! gh api "/orgs/${ORG}/packages/container/${GH_PACKAGE}/versions" \ | |
| --paginate \ | |
| --jq '.[].name' > current-digests.txt; then | |
| echo "Package not found or API error, skipping attestation cleanup" | |
| exit 0 | |
| fi | |
| # Find orphaned digests (present before cleanup but no longer in current) | |
| orphaned=$(comm -23 <(grep -v '^$' digests.txt | sort -u) <(sort -u current-digests.txt)) | |
| if [[ -z "$orphaned" ]]; then | |
| echo "No orphaned digests found" | |
| exit 0 | |
| fi | |
| count=$(echo "$orphaned" | wc -l) | |
| echo "Found ${count} orphaned digests" | |
| echo "$orphaned" | |
| if [[ "${DRY_RUN}" == "true" ]]; then | |
| echo "Dry-run mode: skipping attestation deletion" | |
| exit 0 | |
| fi | |
| echo "Deleting attestations for ${count} orphaned digests" | |
| echo "$orphaned" | jq -R . | jq -sc '{subject_digests: .}' | \ | |
| gh api --method POST "/orgs/${ORG}/attestations/delete-request" --input - | |
| env: | |
| DRY_RUN: ${{ inputs.dry-run == true }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| GH_PACKAGE: ${{ matrix.package }} |