Verwaiste Tags loeschbar machen
Ein Tag konnte im Katalog stehen, dessen Manifest aber nicht mehr abrufbar war (Rest aus frueherem Loeschen/GC). Der Loeschen-Button schlug dann mit "Tag nicht gefunden" fehl, der Eintrag liess sich nicht entfernen. delete_image entfernt jetzt zusaetzlich den Tag-Verweis direkt aus dem Speicher: faellt der Manifest-Lookup ueber die API aus, wird der verwaiste Tag trotzdem entfernt (und bei letztem Tag das Repository). Pfad-Sicherheit in _safe_repo_path() gebuendelt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+47
-21
@@ -221,23 +221,33 @@ def repo_has_tags(name):
|
||||
return bool(data and data.get("tags"))
|
||||
|
||||
|
||||
def _repositories_base():
|
||||
return os.path.realpath(
|
||||
os.path.join(REGISTRY_DATA, "docker", "registry", "v2", "repositories")
|
||||
)
|
||||
|
||||
|
||||
def _safe_repo_path(*parts):
|
||||
"""Absoluter Pfad innerhalb des repositories-Verzeichnisses oder None (Schutz vor ../)."""
|
||||
base = _repositories_base()
|
||||
target = os.path.realpath(os.path.join(base, *parts))
|
||||
if target != base and not target.startswith(base + os.sep):
|
||||
return None
|
||||
return target
|
||||
|
||||
|
||||
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):
|
||||
target = _safe_repo_path(*name.split("/"))
|
||||
if not target or not os.path.isdir(target):
|
||||
return False
|
||||
shutil.rmtree(target, ignore_errors=True)
|
||||
# Leere Eltern-Verzeichnisse (z. B. "bitnami" nach "bitnami/redis") aufraeumen
|
||||
base = _repositories_base()
|
||||
parent = os.path.dirname(target)
|
||||
while parent != base and os.path.isdir(parent) and not os.listdir(parent):
|
||||
try:
|
||||
@@ -248,6 +258,19 @@ def remove_repository_storage(name):
|
||||
return True
|
||||
|
||||
|
||||
def remove_tag_storage(name, tag):
|
||||
"""Entfernt den Tag-Verweis direkt aus dem Speicher.
|
||||
|
||||
Noetig fuer verwaiste Tags, die noch im Katalog stehen, deren Manifest
|
||||
aber nicht mehr abrufbar ist (Loeschen ueber die API schlaegt dann fehl).
|
||||
"""
|
||||
target = _safe_repo_path(*name.split("/"), "_manifests", "tags", tag)
|
||||
if not target or not os.path.isdir(target):
|
||||
return False
|
||||
shutil.rmtree(target, ignore_errors=True)
|
||||
return True
|
||||
|
||||
|
||||
def prune_empty_repositories():
|
||||
"""Entfernt alle Repositories ohne Tags aus dem Speicher. Gibt Anzahl zurueck."""
|
||||
data = query_registry("/v2/_catalog")
|
||||
@@ -481,23 +504,26 @@ def delete_image():
|
||||
return redirect(url_for("images"))
|
||||
|
||||
digest = get_manifest_digest(name, tag)
|
||||
if not digest:
|
||||
# Normalfall: Manifest per Digest loeschen. Faellt der API-Lookup aus
|
||||
# (verwaister Tag: im Katalog gelistet, Manifest aber nicht abrufbar),
|
||||
# den Tag-Verweis direkt aus dem Speicher entfernen.
|
||||
manifest_deleted = delete_manifest(name, digest) if digest else False
|
||||
tag_removed = remove_tag_storage(name, tag)
|
||||
|
||||
if not manifest_deleted and not tag_removed:
|
||||
flash(f'Tag "{tag}" von "{name}" wurde nicht gefunden.', "error")
|
||||
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")
|
||||
# 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}" konnte nicht geloescht werden.', "error")
|
||||
flash(f'Image "{name}:{tag}" wurde geloescht.', "success")
|
||||
|
||||
return redirect(url_for("images"))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user