fix: 修复 Host Docker GC 合并回归

This commit is contained in:
pikastech
2026-07-21 10:16:44 +02:00
parent 25598b46d0
commit 2d48833f1f
3 changed files with 48 additions and 45 deletions
+44
View File
@@ -1,6 +1,50 @@
from datetime import datetime, timezone
def parse_docker_human_size(raw):
raw = str(raw).split("(")[0].strip()
match = re.match(r"^([0-9.]+)\s*([KMGT]?B)$", raw, re.I)
if not match:
return None
multiplier = {"B": 1, "KB": 1000, "MB": 1000**2, "GB": 1000**3, "TB": 1000**4}.get(match.group(2).upper(), 1)
return int(float(match.group(1)) * multiplier)
def parse_docker_build_cache(text):
for line in text.splitlines():
if not line.startswith("Build Cache"):
continue
match = re.match(r"^Build Cache\s+\S+\s+\S+\s+(\S+)\s+(\S+)", line.strip())
if not match:
continue
size = parse_docker_human_size(match.group(1))
reclaim = parse_docker_human_size(match.group(2))
if size is None or reclaim is None:
return None
return {"sizeBytes": size, "reclaimableBytes": reclaim}
return None
def build_cache_only_payload(observed_at):
disk_before = df_snapshot()
section = host_docker_gc_section("buildCachePrune")
until = str(OPTIONS.get("buildCacheUntil") or section.get("until") or "24h")
if not host_docker_gc_enabled("buildCachePrune"):
return {"ok": False, "action": "gc remote %s" % ACTION, "providerId": PROVIDER_ID, "scope": "build-cache-only", "dryRun": ACTION == "plan", "mutation": False, "diagnosis": host_docker_gc_reason("buildCachePrune", "BuildKit cache GC is disabled by YAML")}
system_df = command(["docker", "system", "df"], 8)
if system_df["exitCode"] != 0:
return {"ok": False, "action": "gc remote %s" % ACTION, "providerId": PROVIDER_ID, "scope": "build-cache-only", "dryRun": ACTION == "plan", "mutation": False, "diagnosis": bounded(system_df)}
cache = parse_docker_build_cache(system_df["stdout"])
candidate = None
if cache is not None and cache["reclaimableBytes"] > 0:
candidate = {"id": "docker-builder:prune", "kind": "docker-build-cache-prune", "risk": "low", "sizeBytes": cache["sizeBytes"], "estimatedReclaimBytes": cache["reclaimableBytes"], "action": {"command": ["docker", "builder", "prune", "--all", "--force", "--filter", "until=%s" % until]}}
payload = {"ok": True, "action": "gc remote %s" % ACTION, "providerId": PROVIDER_ID, "scope": "build-cache-only", "configSource": "config/unidesk-cli.yaml#gc.remote.targets.%s.hostDockerGc.buildCachePrune" % PROVIDER_ID, "dryRun": ACTION == "plan", "mutation": ACTION == "run" and candidate is not None, "observedAt": observed_at, "diskBefore": disk_before, "until": until, "candidates": [candidate] if candidate else []}
if ACTION == "run" and candidate is not None:
payload["result"] = execute(candidate)
payload["diskAfter"] = df_snapshot()
return payload
def docker_containers():
ps = command(["docker", "ps", "-qa", "--no-trunc"], 5)
if ps["exitCode"] != 0 or not ps["stdout"].strip():
+4 -4
View File
@@ -120,6 +120,10 @@ def run_json(args, timeout=45):
}
if process.returncode != 0:
return None, result
try:
return json.loads(process.stdout or "{}"), result
except Exception:
return None, result
def command_lines(args, timeout=30, limit=10000):
@@ -150,10 +154,6 @@ def root_available_bytes():
return int(result["stdoutTail"].splitlines()[-1].strip())
except Exception:
return None
try:
return json.loads(process.stdout or "{}"), result
except Exception:
return None, result
def write_state(config, payload):
-41
View File
@@ -1053,47 +1053,6 @@ def parse_journal_usage(text):
mult = {"": 1, "K": 1024, "M": 1024**2, "G": 1024**3, "T": 1024**4}.get(m.group(2).upper(), 1)
return int(float(m.group(1)) * mult)
def parse_docker_human_size(raw):
raw = str(raw).split("(")[0].strip()
m = re.match(r"^([0-9.]+)\s*([KMGT]?B)$", raw, re.I)
if not m:
return None
mult = {"B": 1, "KB": 1000, "MB": 1000**2, "GB": 1000**3, "TB": 1000**4}.get(m.group(2).upper(), 1)
return int(float(m.group(1)) * mult)
def parse_docker_build_cache(text):
for line in text.splitlines():
if not line.startswith("Build Cache"):
continue
match = re.match(r"^Build Cache\s+\S+\s+\S+\s+(\S+)\s+(\S+)", line.strip())
if not match:
continue
size = parse_docker_human_size(match.group(1))
reclaim = parse_docker_human_size(match.group(2))
if size is None or reclaim is None:
return None
return {"sizeBytes": size, "reclaimableBytes": reclaim}
return None
def build_cache_only_payload(observed_at):
disk_before = df_snapshot()
section = host_docker_gc_section("buildCachePrune")
until = str(OPTIONS.get("buildCacheUntil") or section.get("until") or "24h")
if not host_docker_gc_enabled("buildCachePrune"):
return {"ok": False, "action": "gc remote %s" % ACTION, "providerId": PROVIDER_ID, "scope": "build-cache-only", "dryRun": ACTION == "plan", "mutation": False, "diagnosis": host_docker_gc_reason("buildCachePrune", "BuildKit cache GC is disabled by YAML")}
system_df = command(["docker", "system", "df"], 8)
if system_df["exitCode"] != 0:
return {"ok": False, "action": "gc remote %s" % ACTION, "providerId": PROVIDER_ID, "scope": "build-cache-only", "dryRun": ACTION == "plan", "mutation": False, "diagnosis": bounded(system_df)}
cache = parse_docker_build_cache(system_df["stdout"])
candidate = None
if cache is not None and cache["reclaimableBytes"] > 0:
candidate = {"id": "docker-builder:prune", "kind": "docker-build-cache-prune", "risk": "low", "sizeBytes": cache["sizeBytes"], "estimatedReclaimBytes": cache["reclaimableBytes"], "action": {"command": ["docker", "builder", "prune", "--all", "--force", "--filter", "until=%s" % until]}}
payload = {"ok": True, "action": "gc remote %s" % ACTION, "providerId": PROVIDER_ID, "scope": "build-cache-only", "configSource": "config/unidesk-cli.yaml#gc.remote.targets.%s.hostDockerGc.buildCachePrune" % PROVIDER_ID, "dryRun": ACTION == "plan", "mutation": ACTION == "run" and candidate is not None, "observedAt": observed_at, "diskBefore": disk_before, "until": until, "candidates": [candidate] if candidate else []}
if ACTION == "run" and candidate is not None:
payload["result"] = execute(candidate)
payload["diskAfter"] = df_snapshot()
return payload
# __UNIDESK_GC_REMOTE_HOST_DOCKER_HELPERS__
def cluster_preflight():