Files
homelab-upgrade/fixtures/image-version/generate.py
T
homelab-updaterandClaude Opus 5 e9cb72a59c
ci / check (push) Successful in 37s
ci / publish (push) Successful in 19s
image_version: name the version of a digest-only pin from the container's labels
Port of image_version.sh with a contract test (16 cases computed by the old script's Python). For
apps with real updates, status sends a manifest that pins a digest only or a floating tag (dockge,
grafana) to image_version, which takes the version from the OCI or LinuxServer label and hands it to
image_tags; no usable label is STOP version_unknown. inspect exposes only the version labels.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 15:15:18 +02:00

55 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""Regenerates cases.json: the expected results of the OLD image_version.sh on fixed inputs.
usage: generate.py <path to old image_version.sh>
The old script runs its Python on VM103 inside a bash heredoc and asks Docker twice (`docker compose config` for the image,
`docker inspect` for the labels). This extracts that Python, swaps ONLY those two calls for values injected from the
environment, and runs it. Nothing in the old repo is modified and nothing talks to Docker.
"""
import hashlib, json, os, subprocess, sys
script_path = sys.argv[1]
source = open(script_path).read()
body = source.split("<<'PY'\n", 1)[1].rsplit("\nPY", 1)[0]
cfg_line = next(line for line in body.splitlines() if line.startswith("cfg = "))
image_line = next(line for line in body.splitlines() if line.startswith("image = "))
labels_line = next(line for line in body.splitlines() if line.startswith("raw_labels = "))
program = (body.replace(cfg_line, "cfg = {}")
.replace(image_line, "image = os.environ['IMAGE']")
.replace(labels_line, "raw_labels = os.environ['LABELS']"))
def old_result(image, labels):
env = {"IMAGE": image, "LABELS": labels if isinstance(labels, str) else json.dumps(labels), "CONTAINER": "demo-demo-1",
"SERVICE": "demo", "COMPOSE": "/dev/null", "PATH": os.environ["PATH"]}
out = subprocess.run([sys.executable, "-c", program], env=env, capture_output=True, text=True, check=True)
return json.loads(out.stdout)
D = "sha256:" + "d" * 64
LSIO = "Linuxserver.io version:- 4.0.15-ls289 Build-date:- 2026-09-01T00:00:00+00:00"
CASES = {
"digest only, OCI version label (dockge)": dict(image=f"louislam/dockge@{D}", labels={"org.opencontainers.image.version": "1.5.0", "maintainer": "x"}),
"latest with digest, OCI label (grafana)": dict(image=f"grafana/grafana:latest@{D}", labels={"org.opencontainers.image.version": "13.2.2"}),
"linuxserver build_version only": dict(image=f"linuxserver/bazarr@{D}", labels={"build_version": LSIO}),
"OCI label wins over build_version": dict(image=f"linuxserver/bazarr@{D}", labels={"org.opencontainers.image.version": "v1.5.3-ls301", "build_version": LSIO}),
"label-schema version is reported but not used": dict(image=f"org/app@{D}", labels={"org.label-schema.version": "2.1.0"}),
"empty OCI label falls back to build_version": dict(image=f"linuxserver/x@{D}", labels={"org.opencontainers.image.version": "", "build_version": LSIO}),
"build_version without the marker": dict(image=f"linuxserver/x@{D}", labels={"build_version": "custom build 7"}),
"no labels at all": dict(image=f"org/app@{D}", labels={}),
"labels are null (docker prints null)": dict(image=f"org/app@{D}", labels="null"),
"labels unreadable": dict(image=f"org/app@{D}", labels="not json"),
"no digest": dict(image="org/app:1.0", labels={"org.opencontainers.image.version": "1.0"}),
"no digest and no labels": dict(image="org/app", labels={}),
"registry with a port": dict(image=f"registry.local:5000/team/app@{D}", labels={"org.opencontainers.image.version": "3.2.1"}),
"registry with a port and a tag": dict(image=f"registry.local:5000/team/app:latest@{D}", labels={"org.opencontainers.image.version": "3.2.1"}),
"already versioned": dict(image=f"ghcr.io/linuxserver/jackett:v0.24.2601-ls30@{D}", labels={"org.opencontainers.image.version": "v0.24.2601-ls30"}),
"version label with spaces around": dict(image=f"org/app@{D}", labels={"org.opencontainers.image.version": " 1.0 "}),
}
doc = {"provenance": {"generatedBy": "fixtures/image-version/generate.py running the Python of the OLD image_version.sh",
"scriptSha256": hashlib.sha256(source.encode()).hexdigest()},
"cases": [{"name": n, "image": c["image"], "labels": c["labels"], "expected": old_result(**c)} for n, c in CASES.items()]}
out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "cases.json")
json.dump(doc, open(out, "w"), indent=1, ensure_ascii=False)
print("wrote", out, "cases:", len(doc["cases"]))