99permissions : {}
1010
1111jobs :
12- cleanup-attestations :
13- name : 🔏 Cleanup Attestations (${{ matrix.package }})
12+ collect-digests :
13+ name : 📦 Collect Digests (${{ matrix.package }})
1414 runs-on : ubuntu-latest
15- permissions :
16- attestations : write # is needed to delete attestations
17- packages : read # is needed to list package versions to find digests
18- pull-requests : read # is needed to determine which pull requests are open
1915 strategy :
2016 fail-fast : false
2117 matrix :
2218 package : [amp-devcontainer-base, amp-devcontainer-cpp, amp-devcontainer-rust]
19+ permissions :
20+ packages : read # is needed to list package versions
2321 steps :
2422 - uses : step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
2523 with :
2624 disable-sudo-and-containers : true
2725 egress-policy : audit
2826 allowed-endpoints : api.github.com:443
29- - name : Delete outdated attestations
27+ - name : Collect package digests
3028 run : |
3129 set -Eeuo pipefail
32-
3330 ORG="${GH_REPO%%/*}"
34-
35- # Get all open PR numbers to determine which pr-N tags to keep
36- open_pr_numbers=$(gh api "/repos/${GH_REPO}/pulls?state=open&per_page=100" \
31+ gh api "/orgs/${ORG}/packages/container/${GH_PACKAGE}/versions" \
3732 --paginate \
38- --jq '.[].number' 2>/dev/null || true)
39-
40- # Get all package versions with their digests and tags
41- while IFS=$'\t' read -r digest tags_csv; do
42- [[ -z "$digest" ]] && continue
43-
44- keep=false
45-
46- # Check each tag to determine if this digest should be kept
47- IFS=',' read -r -a tags <<< "$tags_csv"
48- for tag in "${tags[@]}"; do
49- [[ -z "$tag" ]] && continue
50-
51- # Keep release semver tags (e.g., v6.5.2, 6.5.2, 6.5, 6)
52- if [[ "$tag" =~ ^v?[0-9]+(\.[0-9]+)*$ ]]; then
53- keep=true
54- break
55- fi
56-
57- # Keep edge tag
58- if [[ "$tag" == "edge" ]]; then
59- keep=true
60- break
61- fi
62-
63- # Keep pr-N tags for open pull requests
64- if [[ "$tag" =~ ^pr-([0-9]+)$ ]]; then
65- pr_number="${BASH_REMATCH[1]}"
66- if echo "$open_pr_numbers" | grep -qx "$pr_number"; then
67- keep=true
68- break
69- fi
70- fi
71- done
72-
73- if [[ "$keep" == "false" ]]; then
74- echo "Deleting attestations for ${GH_PACKAGE}@${digest} (tags: ${tags_csv})"
75- encoded_digest="${digest//:/%3A}"
76- gh api --method DELETE "/orgs/${ORG}/attestations/digest/${encoded_digest}" \
77- 2>/dev/null && echo "Deleted" || echo "No attestations found (already cleaned up)"
78- fi
79- done < <(
80- gh api "/orgs/${ORG}/packages/container/${GH_PACKAGE}/versions" \
81- --paginate \
82- --jq '.[] | "\(.name)\t\(.metadata.container.tags // [] | join(","))"' \
83- 2>/dev/null || true
84- )
33+ --jq '.[].name' 2>/dev/null > digests.txt || touch digests.txt
8534 env :
8635 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
8736 GH_REPO : ${{ github.repository }}
8837 GH_PACKAGE : ${{ matrix.package }}
38+ - uses : actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
39+ with :
40+ name : digests-before-cleanup-${{ matrix.package }}
41+ path : digests.txt
42+ if-no-files-found : warn
43+ retention-days : 1
8944
9045 cleanup-images :
9146 name : 🧹 Clean Images
9247 if : always()
93- needs : cleanup-attestations
48+ needs : collect-digests
9449 runs-on : ubuntu-latest
9550 permissions :
9651 packages : write # is needed by dataaxiom/ghcr-cleanup-action to delete untagged and orphaned images
@@ -106,3 +61,51 @@ jobs:
10661 delete-orphaned-images : true
10762 delete-untagged : true
10863 packages : amp-devcontainer,amp-devcontainer-cpp,amp-devcontainer-rust
64+
65+ cleanup-attestations :
66+ name : 🔏 Cleanup Orphaned Attestations (${{ matrix.package }})
67+ needs : cleanup-images
68+ runs-on : ubuntu-latest
69+ strategy :
70+ fail-fast : false
71+ matrix :
72+ package : [amp-devcontainer-base, amp-devcontainer-cpp, amp-devcontainer-rust]
73+ permissions :
74+ attestations : write # is needed to delete attestations
75+ packages : read # is needed to list remaining package versions after cleanup
76+ steps :
77+ - uses : step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
78+ with :
79+ disable-sudo-and-containers : true
80+ egress-policy : audit
81+ allowed-endpoints : api.github.com:443
82+ - uses : actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
83+ id : download-digests
84+ continue-on-error : true
85+ with :
86+ name : digests-before-cleanup-${{ matrix.package }}
87+ - name : Delete orphaned attestations
88+ if : steps.download-digests.outcome == 'success'
89+ run : |
90+ set -Eeuo pipefail
91+ ORG="${GH_REPO%%/*}"
92+
93+ # Get remaining digests after image cleanup
94+ current_digests=$(gh api "/orgs/${ORG}/packages/container/${GH_PACKAGE}/versions" \
95+ --paginate \
96+ --jq '.[].name' 2>/dev/null || echo "")
97+
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
108+ env :
109+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
110+ GH_REPO : ${{ github.repository }}
111+ GH_PACKAGE : ${{ matrix.package }}
0 commit comments