Korrupte/verwaiste Repos sichtbar machen + Live-GC sicher
Zwei Probleme nach dem alten --delete-untagged-Schaden: 1) Repos mit Tags aber ohne _layers (verwaist) tauchen nicht im Registry-Katalog auf -> waren in der Web-Oberflaeche unsichtbar und nicht loeschbar. images() listet jetzt zusaetzlich direkt aus dem Dateisystem (scan_fs_repositories/repo_tags), Vereinigung mit dem Katalog. So sind auch korrupte Repos sichtbar und ueber den Loeschen-Button entfernbar. 2) Die GC laeuft als separater Prozess auf dem gemounteten Speicher. Der In-Memory-Blob-Cache der laufenden Registry hielt geloeschte Blobs faelschlich fuer vorhanden -> erneuter Pull legte keine _layers-Verknuepfung an (Image fehlte im Katalog). Cache via REGISTRY_STORAGE_CACHE_BLOBDESCRIPTOR="" deaktiviert -> Live-GC sicher. Verifiziert: korruptes Repo -> in Liste sichtbar -> Loeschen -> GC -> erneut pushen -> Katalog korrekt und docker pull erfolgreich, ohne Registry-Neustart. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+35
-5
@@ -236,6 +236,36 @@ def _safe_repo_path(*parts):
|
||||
return target
|
||||
|
||||
|
||||
def scan_fs_repositories():
|
||||
"""Listet alle Repositories direkt aus dem Speicher (Dateisystem).
|
||||
|
||||
Erfasst auch Repositories, die der Registry-Katalog (/v2/_catalog) nicht
|
||||
zeigt - etwa nach Korruption, wenn das _layers-Verzeichnis fehlt.
|
||||
"""
|
||||
base = _repositories_base()
|
||||
repos = []
|
||||
if not os.path.isdir(base):
|
||||
return repos
|
||||
for root, dirs, _files in os.walk(base):
|
||||
if "_manifests" in dirs:
|
||||
name = os.path.relpath(root, base).replace(os.sep, "/")
|
||||
if name != ".":
|
||||
repos.append(name)
|
||||
dirs[:] = [] # innerhalb eines Repos nicht weiter absteigen
|
||||
return repos
|
||||
|
||||
|
||||
def repo_tags(name):
|
||||
"""Tags eines Repositories - aus dem Dateisystem, mit API als Fallback."""
|
||||
tags_dir = _safe_repo_path(*name.split("/"), "_manifests", "tags")
|
||||
if tags_dir and os.path.isdir(tags_dir):
|
||||
return sorted(
|
||||
d for d in os.listdir(tags_dir) if os.path.isdir(os.path.join(tags_dir, d))
|
||||
)
|
||||
data = query_registry(f"/v2/{name}/tags/list")
|
||||
return sorted(data.get("tags") or []) if data else []
|
||||
|
||||
|
||||
def remove_repository_storage(name):
|
||||
"""Entfernt das leere Repository-Verzeichnis aus dem Registry-Speicher.
|
||||
|
||||
@@ -389,13 +419,13 @@ def users():
|
||||
@app.route("/images")
|
||||
@login_required
|
||||
def images():
|
||||
repos = []
|
||||
# Repos aus dem Dateisystem UND dem Katalog (Vereinigung), damit auch
|
||||
# Repositories sichtbar/loeschbar sind, die der Katalog nicht zeigt.
|
||||
names = set(scan_fs_repositories())
|
||||
data = query_registry("/v2/_catalog")
|
||||
if data:
|
||||
for name in sorted(data.get("repositories", [])):
|
||||
tags_data = query_registry(f"/v2/{name}/tags/list")
|
||||
tags = sorted(tags_data.get("tags", [])) if tags_data and tags_data.get("tags") else []
|
||||
repos.append({"name": name, "tags": tags})
|
||||
names.update(data.get("repositories", []))
|
||||
repos = [{"name": name, "tags": repo_tags(name)} for name in sorted(names)]
|
||||
return render_template("images.html", repos=repos)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user