From bff904caac3d8bc4dc793a5b967b8a2ffd2e3da2 Mon Sep 17 00:00:00 2001 From: Ketan Patel Date: Sat, 3 Oct 2026 00:11:36 -0400 Subject: [PATCH] Retire the legacy distroless tag after latest publication --- .gitea/workflows/build-push.yml | 9 ++++++- build/retire-distroless-tag.py | 43 +++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 build/retire-distroless-tag.py diff --git a/.gitea/workflows/build-push.yml b/.gitea/workflows/build-push.yml index f12efe6..f7feb82 100644 --- a/.gitea/workflows/build-push.yml +++ b/.gitea/workflows/build-push.yml @@ -39,7 +39,7 @@ jobs: - name: Install Docker CLI run: | apt-get update - apt-get install -y --no-install-recommends docker.io + apt-get install -y --no-install-recommends docker.io python3 rm -rf /var/lib/apt/lists/* - name: Log in to Docker Hub @@ -69,3 +69,10 @@ jobs: labels: | org.opencontainers.image.source=https://git.k2patel.in/k2patel/docker-flood-client org.opencontainers.image.revision=${{ github.sha }} + + - name: Retire the separate distroless tag + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} + DOCKER_REPOSITORY: k2patel/floodui + run: python3 build/retire-distroless-tag.py diff --git a/build/retire-distroless-tag.py b/build/retire-distroless-tag.py new file mode 100644 index 0000000..fbb1226 --- /dev/null +++ b/build/retire-distroless-tag.py @@ -0,0 +1,43 @@ +"""Remove only the retired distroless tag, after a successful latest publication.""" +import json +import os +from urllib.error import HTTPError +from urllib.request import Request, urlopen + +repo = os.environ['DOCKER_REPOSITORY'] +if repo not in {'k2patel/rtorrent', 'k2patel/floodui'}: + raise SystemExit('Unexpected repository') +base = 'https://hub.docker.com/v2/repositories/' + repo + '/tags/' + +def request(url, method='GET', data=None, token=None): + headers = {'Content-Type': 'application/json'} + if token: + headers['Authorization'] = 'Bearer ' + token + req = Request(url, method=method, headers=headers, + data=json.dumps(data).encode() if data is not None else None) + with urlopen(req, timeout=30) as response: + body = response.read() + return json.loads(body) if body else None + +try: + latest = request(base + 'latest/') + try: + old = request(base + 'distroless/') + except HTTPError as error: + if error.code == 404: + print(repo + ': only latest remains; no legacy tag to retire') + raise SystemExit(0) + raise + if not latest.get('digest'): + raise SystemExit('Refusing cleanup without a published latest digest') + auth = request('https://hub.docker.com/v2/users/login', method='POST', data={ + 'username': os.environ['DOCKER_USERNAME'], 'password': os.environ['DOCKER_TOKEN']}) + token = auth['token'] + request(base + 'distroless/', method='DELETE', token=token) + # Verify the latest image remained unchanged. + if request(base + 'latest/')['digest'] != latest['digest']: + raise SystemExit('latest changed during tag retirement; inspect the registry') + print(repo + ': retired distroless tag; latest retained at ' + latest['digest']) +except HTTPError as error: + # Never print an authentication response or token. + raise SystemExit(f'Docker Hub tag retirement failed: HTTP {error.code}. Token needs delete permission.')