#!/usr/bin/env python3 """OCI-Archiv (docker save ab Docker 25 mit containerd-Store) -> klassisches docker-archive v1, wie RouterOS /container es erwartet.""" import gzip, hashlib, json, os, shutil, sys, tarfile, tempfile src, dst = sys.argv[1], sys.argv[2] work = tempfile.mkdtemp(prefix="oci2docker.") ext = os.path.join(work, "in") out = os.path.join(work, "out") os.makedirs(ext); os.makedirs(out) with tarfile.open(src) as t: t.extractall(ext) man = json.load(open(os.path.join(ext, "manifest.json"))) if len(man) != 1: sys.exit(f"erwarte genau 1 Image im Archiv, gefunden: {len(man)}") entry = man[0] # --- Config --- cfg_src = os.path.join(ext, entry["Config"]) cfg_raw = open(cfg_src, "rb").read() cfg_sha = hashlib.sha256(cfg_raw).hexdigest() cfg_name = f"{cfg_sha}.json" open(os.path.join(out, cfg_name), "wb").write(cfg_raw) cfg = json.loads(cfg_raw) # --- Layer: entpacken, falls gzip --- layers_out = [] for i, lp in enumerate(entry["Layers"]): blob = os.path.join(ext, lp) with open(blob, "rb") as f: magic = f.read(2) d = os.path.join(out, f"layer{i:02d}") os.makedirs(d) target = os.path.join(d, "layer.tar") if magic == b"\x1f\x8b": with gzip.open(blob, "rb") as fi, open(target, "wb") as fo: shutil.copyfileobj(fi, fo, 1024 * 1024) else: shutil.copyfile(blob, target) open(os.path.join(d, "VERSION"), "w").write("1.0") open(os.path.join(d, "json"), "w").write(json.dumps({"id": f"layer{i:02d}"})) layers_out.append(f"layer{i:02d}/layer.tar") # --- diff_ids gegenpruefen: sie muessen die UNKOMPRIMIERTEN Digests sein --- diff_ids = cfg.get("rootfs", {}).get("diff_ids", []) if len(diff_ids) != len(layers_out): sys.exit(f"diff_ids ({len(diff_ids)}) != Layer ({len(layers_out)})") for want, rel in zip(diff_ids, layers_out): h = hashlib.sha256() with open(os.path.join(out, rel), "rb") as f: for chunk in iter(lambda: f.read(1024 * 1024), b""): h.update(chunk) got = "sha256:" + h.hexdigest() if got != want: sys.exit(f"Layer-Digest weicht ab:\n erwartet {want}\n erhalten {got}") json.dump([{"Config": cfg_name, "RepoTags": entry.get("RepoTags") or [], "Layers": layers_out}], open(os.path.join(out, "manifest.json"), "w")) with tarfile.open(dst, "w") as t: for n in sorted(os.listdir(out)): t.add(os.path.join(out, n), arcname=n) shutil.rmtree(work) print(f"ok: {dst} ({os.path.getsize(dst)} Bytes, {len(layers_out)} Layer, diff_ids gepruegt)")