55 schedule :
66 - cron : " 0 0 * * 3"
77 workflow_dispatch :
8+ inputs :
9+ dry-run :
10+ default : false
11+ type : boolean
812
913permissions : {}
1014
1923 permissions :
2024 packages : read # is needed to list package versions
2125 steps :
22- - uses : step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
26+ - uses : step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
2327 with :
2428 disable-sudo-and-containers : true
2529 egress-policy : audit
2832 run : |
2933 set -Eeuo pipefail
3034 ORG="${GH_REPO%%/*}"
35+
3136 gh api "/orgs/${ORG}/packages/container/${GH_PACKAGE}/versions" \
3237 --paginate \
3338 --jq '.[].name' 2>/dev/null > digests.txt || touch digests.txt
6065 with :
6166 delete-orphaned-images : true
6267 delete-untagged : true
63- packages : amp-devcontainer,amp-devcontainer-cpp,amp-devcontainer-rust
68+ dry-run : ${{ inputs.dry-run == true }}
69+ packages : amp-devcontainer-base,amp-devcontainer-cpp,amp-devcontainer-rust
6470
6571 cleanup-attestations :
6672 name : 🔏 Cleanup Orphaned Attestations (${{ matrix.package }})
7480 attestations : write # is needed to delete attestations
7581 packages : read # is needed to list remaining package versions after cleanup
7682 steps :
77- - uses : step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
83+ - uses : step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
7884 with :
7985 disable-sudo-and-containers : true
8086 egress-policy : audit
@@ -91,21 +97,35 @@ jobs:
9197 ORG="${GH_REPO%%/*}"
9298
9399 # Get remaining digests after image cleanup
94- current_digests=$( gh api "/orgs/${ORG}/packages/container/${GH_PACKAGE}/versions" \
100+ if ! gh api "/orgs/${ORG}/packages/container/${GH_PACKAGE}/versions" \
95101 --paginate \
96- --jq '.[].name' 2>/dev/null || echo "")
102+ --jq '.[].name' > current-digests.txt; then
103+ echo "Package not found or API error, skipping attestation cleanup"
104+ exit 0
105+ fi
106+
107+ # Find orphaned digests (present before cleanup but no longer in current)
108+ orphaned=$(comm -23 <(grep -v '^$' digests.txt | sort -u) <(sort -u current-digests.txt))
109+
110+ if [[ -z "$orphaned" ]]; then
111+ echo "No orphaned digests found"
112+ exit 0
113+ fi
114+
115+ count=$(echo "$orphaned" | wc -l)
116+ echo "Found ${count} orphaned digests"
117+ echo "$orphaned"
118+
119+ if [[ "${DRY_RUN}" == "true" ]]; then
120+ echo "Dry-run mode: skipping attestation deletion"
121+ exit 0
122+ fi
97123
98- # Delete attestations for digests that no longer have a package version
99- while read -r digest; do
100- [[ -z "$digest" ]] && continue
101- if ! echo "$current_digests" | grep -qx "$digest"; then
102- echo "Deleting attestations for removed digest: ${digest}"
103- encoded_digest="${digest//:/%3A}"
104- gh api --method DELETE "/orgs/${ORG}/attestations/digest/${encoded_digest}" \
105- 2>/dev/null && echo "Deleted" || echo "No attestations found (already cleaned up)"
106- fi
107- done < digests.txt
124+ echo "Deleting attestations for ${count} orphaned digests"
125+ echo "$orphaned" | jq -R . | jq -sc '{subject_digests: .}' | \
126+ gh api --method POST "/orgs/${ORG}/attestations/delete-request" --input -
108127 env :
128+ DRY_RUN : ${{ inputs.dry-run == true }}
109129 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
110130 GH_REPO : ${{ github.repository }}
111131 GH_PACKAGE : ${{ matrix.package }}
0 commit comments