Skip to content

Commit 5288d13

Browse files
committed
chore: use regctl to delete container tags
quay.io would need an oauth application otherwise, which would have access to the whole organization. This defeats the point of blast radius reduction.
1 parent 5a0e1df commit 5288d13

5 files changed

Lines changed: 70 additions & 43 deletions

File tree

.github/workflows/cleanup-e2e-images.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ jobs:
1919
with:
2020
persist-credentials: false
2121

22+
- name: Install regctl
23+
run: |
24+
curl -sL https://github.com/regclient/regclient/releases/download/v0.11.2/regctl-linux-amd64 -o /usr/local/bin/regctl
25+
chmod +x /usr/local/bin/regctl
26+
2227
- name: Clean up e2e-* tags from capcs-staging
2328
env:
24-
QUAY_E2E_TOKEN: ${{ secrets.QUAY_E2E_TOKEN }}
29+
QUAY_E2E_USERNAME: ${{ secrets.QUAY_E2E_USERNAME }}
30+
QUAY_E2E_PASSWORD: ${{ secrets.QUAY_E2E_PASSWORD }}
2531
run: make clean-e2e-images

.github/workflows/e2e-weekly.yml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,17 @@ jobs:
4343
GINKGO_LABEL_FILTER="ha || upgrade || self-hosted || kcp-remediation || conformance" \
4444
KUBETEST_CONFIGURATION=./data/kubetest/conformance-fast.yaml
4545
46+
- name: Install regctl
47+
if: always()
48+
run: |
49+
curl -sL https://github.com/regclient/regclient/releases/download/v0.11.2/regctl-linux-amd64 -o /usr/local/bin/regctl
50+
chmod +x /usr/local/bin/regctl
51+
4652
- name: Clean up e2e image
4753
if: always()
48-
env:
49-
QUAY_E2E_TOKEN: ${{ secrets.QUAY_E2E_TOKEN }}
50-
TAG: e2e-weekly-${{ github.sha }}
5154
run: |
52-
curl -s -X DELETE \
53-
-H "Authorization: Bearer ${QUAY_E2E_TOKEN}" \
54-
"https://quay.io/api/v1/repository/cloudscalech/capcs-staging/tag/${TAG}"
55+
TAG="e2e-$(git rev-parse --short HEAD)"
56+
regctl tag delete "quay.io/cloudscalech/capcs-staging:${TAG}" || true
5557
5658
- name: Redact secrets from artifacts
5759
if: always()

.github/workflows/test-e2e.yml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,21 @@ jobs:
5757
TEST_TARGET: ${{ github.event.inputs.test_target }}
5858
run: make $TEST_TARGET
5959

60+
- name: Install regctl
61+
if: >-
62+
github.event.inputs.test_target == 'test-e2e-self-hosted' ||
63+
github.event.inputs.test_target == 'test-e2e'
64+
run: |
65+
curl -sL https://github.com/regclient/regclient/releases/download/v0.11.2/regctl-linux-amd64 -o /usr/local/bin/regctl
66+
chmod +x /usr/local/bin/regctl
67+
6068
- name: Clean up e2e image
6169
if: >-
6270
github.event.inputs.test_target == 'test-e2e-self-hosted' ||
6371
github.event.inputs.test_target == 'test-e2e'
64-
env:
65-
QUAY_E2E_TOKEN: ${{ secrets.QUAY_E2E_TOKEN }}
66-
TAG: e2e-manual-${{ github.sha }}
6772
run: |
68-
curl -s -X DELETE \
69-
-H "Authorization: Bearer ${QUAY_E2E_TOKEN}" \
70-
"https://quay.io/api/v1/repository/cloudscalech/capcs-staging/tag/${TAG}"
73+
TAG="e2e-$(git rev-parse --short HEAD)"
74+
regctl tag delete "quay.io/cloudscalech/capcs-staging:${TAG}" || true
7175
7276
- name: Redact secrets from artifacts
7377
if: always()

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ docker-push: ## Push docker image with the manager.
271271
$(CONTAINER_TOOL) push ${IMG}
272272

273273
.PHONY: clean-e2e-images
274-
clean-e2e-images: ## Delete e2e-* tags older than 7 days from capcs-staging (requires QUAY_E2E_TOKEN)
274+
clean-e2e-images: ## Delete e2e-* tags older than 7 days from capcs-staging (requires regctl + quay.io auth)
275275
@./hack/clean-e2e-images.sh
276276

277277
# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple

hack/clean-e2e-images.sh

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,73 @@
11
#!/usr/bin/env bash
22
# Delete e2e-* image tags older than 7 days from capcs-staging on quay.io.
3-
# Requires QUAY_E2E_TOKEN environment variable (OAuth token with repo:admin scope).
3+
# Requires regctl to be installed and authenticated (via docker login or regctl registry login).
4+
# If QUAY_E2E_USERNAME and QUAY_E2E_PASSWORD are set, the script logs in automatically.
45
# Set DRY_RUN=true to list tags without deleting them.
56

67
set -euo pipefail
78

8-
REPO="cloudscalech/capcs-staging"
9-
API="https://quay.io/api/v1/repository/${REPO}/tag/"
9+
REPO="quay.io/cloudscalech/capcs-staging"
1010
MAX_AGE_DAYS="${MAX_AGE_DAYS:-7}"
1111
DRY_RUN="${DRY_RUN:-false}"
1212

13-
if [[ -z "${QUAY_E2E_TOKEN:-}" ]]; then
14-
echo "Error: QUAY_E2E_TOKEN environment variable is required" >&2
13+
if ! command -v regctl &>/dev/null; then
14+
echo "Error: regctl is not installed" >&2
1515
exit 1
1616
fi
1717

18+
# Login if credentials are provided
19+
if [[ -n "${QUAY_E2E_USERNAME:-}" && -n "${QUAY_E2E_PASSWORD:-}" ]]; then
20+
echo "${QUAY_E2E_PASSWORD}" | regctl registry login quay.io --user "${QUAY_E2E_USERNAME}" --pass-stdin
21+
fi
22+
23+
# Calculate cutoff timestamp
1824
cutoff=$(date -u -v-${MAX_AGE_DAYS}d +%s 2>/dev/null || date -u -d "${MAX_AGE_DAYS} days ago" +%s)
1925

2026
if [[ "${DRY_RUN}" == "true" ]]; then
2127
echo "DRY RUN: will list tags without deleting them"
2228
fi
2329
echo "Listing e2e-* tags older than ${MAX_AGE_DAYS} days..."
2430

25-
page=1
31+
# List all tags and filter for e2e-* prefix
32+
tags=$(regctl tag ls "${REPO}" | grep '^e2e-' || true)
33+
34+
if [[ -z "${tags}" ]]; then
35+
echo "No e2e-* tags found."
36+
exit 0
37+
fi
38+
2639
deleted=0
27-
while true; do
28-
response=$(curl -s -H "Authorization: Bearer ${QUAY_E2E_TOKEN}" \
29-
"${API}?filter_tag_name=like:e2e-%25&limit=100&page=${page}")
40+
while IFS= read -r name; do
41+
# Get the image creation timestamp (RFC3339 format)
42+
created=$(regctl image config "${REPO}:${name}" --format '{{.Created}}' 2>/dev/null || true)
43+
44+
if [[ -z "${created}" ]]; then
45+
echo "Warning: could not get creation time for tag ${name}, skipping"
46+
continue
47+
fi
3048

31-
tags=$(echo "${response}" | jq -r '.tags // [] | .[] | select(.end_ts == null) | "\(.name) \(.start_ts)"')
49+
# Convert RFC3339 to epoch (strip fractional seconds and Z suffix for macOS compatibility)
50+
stripped=$(echo "${created}" | sed 's/\.[0-9]*Z$//' | sed 's/Z$//')
51+
created_ts=$(date -u -jf "%Y-%m-%dT%H:%M:%S" "${stripped}" +%s 2>/dev/null \
52+
|| date -u -d "${created}" +%s 2>/dev/null \
53+
|| true)
3254

33-
if [[ -z "${tags}" ]]; then
34-
break
55+
if [[ -z "${created_ts}" ]]; then
56+
echo "Warning: could not parse creation time '${created}' for tag ${name}, skipping"
57+
continue
3558
fi
3659

37-
while IFS=' ' read -r name start_ts; do
38-
if [[ "${start_ts}" -lt "${cutoff}" ]]; then
39-
created=$(date -u -r "${start_ts}" 2>/dev/null || date -u -d "@${start_ts}")
40-
if [[ "${DRY_RUN}" == "true" ]]; then
41-
echo "Would delete tag: ${name} (created ${created})"
42-
else
43-
echo "Deleting tag: ${name} (created ${created})"
44-
curl -s -X DELETE -H "Authorization: Bearer ${QUAY_E2E_TOKEN}" "${API}${name}" > /dev/null
45-
fi
46-
deleted=$((deleted + 1))
60+
if [[ "${created_ts}" -lt "${cutoff}" ]]; then
61+
created_human=$(date -u -r "${created_ts}" 2>/dev/null || date -u -d "@${created_ts}")
62+
if [[ "${DRY_RUN}" == "true" ]]; then
63+
echo "Would delete tag: ${name} (created ${created_human})"
64+
else
65+
echo "Deleting tag: ${name} (created ${created_human})"
66+
regctl tag delete "${REPO}:${name}"
4767
fi
48-
done <<< "${tags}"
49-
50-
has_more=$(echo "${response}" | jq -r '.has_additional')
51-
if [[ "${has_more}" != "true" ]]; then
52-
break
68+
deleted=$((deleted + 1))
5369
fi
54-
page=$((page + 1))
55-
done
70+
done <<< "${tags}"
5671

5772
if [[ "${DRY_RUN}" == "true" ]]; then
5873
echo "Would delete ${deleted} e2e tag(s)."

0 commit comments

Comments
 (0)