Leeres Repository nach Loeschen des letzten Tags entfernen

Die Registry-API loescht nur Manifeste/Tags, das Repository-Verzeichnis
bleibt im Speicher und damit im Katalog ("keine Tags") stehen. Da der
Auth-Container den Registry-Speicher gemountet hat, wird das leere
Verzeichnis jetzt entfernt:
- beim Loeschen des letzten Tags direkt
- bei der Garbage Collection werden zusaetzlich alle Tag-losen
  Repositories aufgeraeumt (fuer bereits vorhandene Reste)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 11:10:27 +02:00
parent b273098b50
commit 4a11aabdf3
2 changed files with 63 additions and 4 deletions
+59 -1
View File
@@ -1,6 +1,7 @@
import os
import re
import secrets
import shutil
import sqlite3
import subprocess
from functools import wraps
@@ -26,6 +27,7 @@ ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "changeme")
DB_PATH = os.environ.get("DB_PATH", "/data/users.db")
HTPASSWD_PATH = os.environ.get("HTPASSWD_PATH", "/auth/htpasswd")
GC_CONFIG = os.environ.get("GC_CONFIG", "/app/registry-gc-config.yml")
REGISTRY_DATA = os.environ.get("REGISTRY_DATA", "/var/lib/registry")
REGISTRY_URL = os.environ.get("REGISTRY_URL", "http://registry:5000")
# Host:Port ohne Schema, fuer skopeo-Ziel (z. B. "registry:5000")
REGISTRY_HOST = REGISTRY_URL.split("://", 1)[-1]
@@ -213,6 +215,51 @@ def delete_manifest(name, digest):
return False
def repo_has_tags(name):
"""True, wenn das Repository noch mindestens einen Tag hat."""
data = query_registry(f"/v2/{name}/tags/list")
return bool(data and data.get("tags"))
def remove_repository_storage(name):
"""Entfernt das leere Repository-Verzeichnis aus dem Registry-Speicher.
Die Registry-API kennt kein "Repository loeschen"; nach dem Loeschen des
letzten Tags bleibt sonst ein leerer Eintrag im Katalog stehen.
"""
base = os.path.realpath(
os.path.join(REGISTRY_DATA, "docker", "registry", "v2", "repositories")
)
target = os.path.realpath(os.path.join(base, *name.split("/")))
# Sicherheit: Ziel muss innerhalb des repositories-Verzeichnisses liegen
if target != base and not target.startswith(base + os.sep):
return False
if not os.path.isdir(target):
return False
shutil.rmtree(target, ignore_errors=True)
# Leere Eltern-Verzeichnisse (z. B. "bitnami" nach "bitnami/redis") aufraeumen
parent = os.path.dirname(target)
while parent != base and os.path.isdir(parent) and not os.listdir(parent):
try:
os.rmdir(parent)
except OSError:
break
parent = os.path.dirname(parent)
return True
def prune_empty_repositories():
"""Entfernt alle Repositories ohne Tags aus dem Speicher. Gibt Anzahl zurueck."""
data = query_registry("/v2/_catalog")
if not data:
return 0
removed = 0
for name in data.get("repositories", []):
if not repo_has_tags(name) and remove_repository_storage(name):
removed += 1
return removed
def remote_digest(source, src_creds=None, src_token=None):
"""Liefert den Manifest-Digest eines Quell-Images, ohne es herunterzuladen."""
cmd = ["skopeo", "inspect", "--format", "{{.Digest}}", f"docker://{source}"]
@@ -439,6 +486,15 @@ def delete_image():
return redirect(url_for("images"))
if delete_manifest(name, digest):
# War das der letzte Tag? Dann das leere Repository entfernen, damit
# es nicht weiter (ohne Tags) im Katalog stehen bleibt.
if not repo_has_tags(name) and remove_repository_storage(name):
flash(
f'Image "{name}:{tag}" geloescht - Repository "{name}" entfernt '
"(keine Tags mehr).",
"success",
)
else:
flash(f'Image "{name}:{tag}" wurde geloescht.', "success")
else:
flash(f'Image "{name}:{tag}" konnte nicht geloescht werden.', "error")
@@ -465,9 +521,11 @@ def garbage_collect():
for line in result.stdout.splitlines()
if line.startswith("blob eligible for deletion")
)
pruned = prune_empty_repositories()
extra = f" {pruned} leere(s) Repository entfernt." if pruned else ""
flash(
"Garbage Collection abgeschlossen. "
f"{freed} nicht mehr benoetigte Blob(s) wurden entfernt.",
f"{freed} nicht mehr benoetigte Blob(s) wurden entfernt.{extra}",
"success",
)
else:
+3 -2
View File
@@ -188,8 +188,9 @@
<p style="margin-top: 0.6rem; font-size: 0.85rem; color: #6b7280; line-height: 1.6;">
Beim Loeschen eines Images verschwindet der Tag sofort, der belegte Speicherplatz wird
aber erst hierdurch freigegeben. Entfernt alle Daten, die von keinem Image mehr
referenziert werden. <strong>Wichtig:</strong> waehrend der Aufraeumung keine Images
hochladen, sonst koennen frisch hochgeladene Daten verloren gehen.
referenziert werden, sowie leere Repositories ohne Tags.
<strong>Wichtig:</strong> waehrend der Aufraeumung keine Images hochladen, sonst
koennen frisch hochgeladene Daten verloren gehen.
</p>
</div>
</div>