Skip to content

Commit 948dbe8

Browse files
dpark01claude
andcommitted
Fix tag cleanup to prevent cascade deletion of shared-digest images
Replace skopeo delete with crane delete in cleanup-images.yml. skopeo deletes the underlying manifest by digest, which caused all tags sharing the same digest to expire simultaneously. This is what killed 3.0.10-baseimage when the agent-skills-playbooks branch was deleted (PR #1054 merge on 2026-03-21) -- the baseimage manifest digest was shared across branches because baseimage has no per-branch build args. crane delete removes only the tag reference, leaving other tags pointing to the same manifest intact (verified locally on quay.io). Also adds: - Safety guard refusing to delete tags for version-like branch names - Weekly audit workflow to verify version tags still exist on Quay Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a7d8b51 commit 948dbe8

File tree

2 files changed

+95
-17
lines changed

2 files changed

+95
-17
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Audit Quay.io Tags
2+
3+
on:
4+
schedule:
5+
- cron: '0 8 * * 1' # Monday 8:00 UTC
6+
workflow_dispatch:
7+
8+
permissions: {}
9+
10+
jobs:
11+
audit-tags:
12+
runs-on: ubuntu-latest
13+
permissions: {}
14+
15+
steps:
16+
- name: Install crane
17+
uses: imjasonh/setup-crane@v0.4
18+
19+
- name: Log in to Quay.io
20+
uses: docker/login-action@v3
21+
with:
22+
registry: quay.io
23+
username: ${{ secrets.QUAY_USERNAME }}
24+
password: ${{ secrets.QUAY_TOKEN }}
25+
26+
- name: Audit version tags
27+
run: |
28+
set -euo pipefail
29+
REPO="quay.io/broadinstitute/viral-ngs"
30+
FLAVORS="baseimage core assemble classify phylo"
31+
FAILED=0
32+
33+
# Check the 5 most recent version tags
34+
VERSIONS=$(crane ls "$REPO" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -5)
35+
36+
if [[ -z "$VERSIONS" ]]; then
37+
echo "::error::No version tags found on ${REPO}"
38+
exit 1
39+
fi
40+
41+
for VERSION in $VERSIONS; do
42+
# Check mega tag (no suffix)
43+
if ! crane manifest "${REPO}:${VERSION}" > /dev/null 2>&1; then
44+
echo "::error::MISSING: ${REPO}:${VERSION}"
45+
FAILED=1
46+
else
47+
echo "OK: ${REPO}:${VERSION}"
48+
fi
49+
50+
# Check each flavor
51+
for FLAVOR in $FLAVORS; do
52+
TAG="${VERSION}-${FLAVOR}"
53+
if ! crane manifest "${REPO}:${TAG}" > /dev/null 2>&1; then
54+
echo "::error::MISSING: ${REPO}:${TAG}"
55+
FAILED=1
56+
else
57+
echo "OK: ${REPO}:${TAG}"
58+
fi
59+
done
60+
done
61+
62+
if [[ $FAILED -ne 0 ]]; then
63+
echo "::error::Some version tags are missing from Quay.io!"
64+
exit 1
65+
fi
66+
echo "All version tags verified."

.github/workflows/cleanup-images.yml

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,40 @@ jobs:
1111
permissions: {}
1212

1313
steps:
14-
- name: Delete feature branch images from Quay
14+
- name: Safety check - refuse version-like branch names
1515
run: |
16-
# Install skopeo
17-
sudo apt-get update && sudo apt-get install -y skopeo
16+
BRANCH_TAG="${{ github.event.ref }}"
17+
if [[ "$BRANCH_TAG" =~ ^v?[0-9]+\.[0-9]+ ]]; then
18+
echo "::error::Refusing to delete tags for version-like branch: $BRANCH_TAG"
19+
exit 1
20+
fi
21+
22+
- name: Install crane
23+
uses: imjasonh/setup-crane@v0.4
24+
25+
- name: Log in to Quay.io
26+
uses: docker/login-action@v3
27+
with:
28+
registry: quay.io
29+
username: ${{ secrets.QUAY_USERNAME }}
30+
password: ${{ secrets.QUAY_TOKEN }}
1831

32+
- name: Delete feature branch tags from Quay
33+
run: |
1934
BRANCH_TAG="${{ github.event.ref }}"
2035
QUAY_REPO="quay.io/broadinstitute/viral-ngs"
2136
22-
# Image tags to delete - must be kept in sync with deploy-to-quay in docker.yml
37+
# Image tag suffixes - must be kept in sync with deploy-to-quay in docker.yml
2338
# See: .github/workflows/docker.yml deploy-to-quay job matrix
24-
IMAGES=(
25-
"${BRANCH_TAG}-baseimage"
26-
"${BRANCH_TAG}-core"
27-
"${BRANCH_TAG}-assemble"
28-
"${BRANCH_TAG}-classify"
29-
"${BRANCH_TAG}-phylo"
30-
"${BRANCH_TAG}" # mega image (no suffix)
31-
)
32-
33-
for TAG in "${IMAGES[@]}"; do
39+
SUFFIXES=("-baseimage" "-core" "-assemble" "-classify" "-phylo" "")
40+
41+
for SUFFIX in "${SUFFIXES[@]}"; do
42+
TAG="${BRANCH_TAG}${SUFFIX}"
3443
echo "Deleting ${QUAY_REPO}:${TAG}..."
35-
skopeo delete \
36-
--creds "${{ secrets.QUAY_USERNAME }}:${{ secrets.QUAY_TOKEN }}" \
37-
"docker://${QUAY_REPO}:${TAG}" || echo "Tag ${TAG} not found or already deleted"
44+
# Use crane delete instead of skopeo delete. crane removes only the tag
45+
# reference, not the underlying manifest. This prevents cascade deletion
46+
# of other tags sharing the same digest (which caused the 3.0.10-baseimage
47+
# incident: skopeo deleted the manifest by digest, expiring all tags
48+
# pointing to it).
49+
crane delete "${QUAY_REPO}:${TAG}" 2>&1 || echo " Tag ${TAG} not found or already deleted"
3850
done

0 commit comments

Comments
 (0)