175 lines
6.7 KiB
Python
175 lines
6.7 KiB
Python
"""Regenerate test-only V1 wire goldens with Go and source-pinned Moby structs.
|
|
|
|
No Hub normalizer code is imported. This installs nothing, builds no image and
|
|
changes only the fixed test fixture. Go 1.27.2 is a local regeneration tool.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import tempfile
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
FIXTURE = ROOT / "tests/fixtures/moby_v1_golden.json"
|
|
REFERENCE = ROOT / "tests/fixtures/moby_v1_reference/main.go"
|
|
PINS = (
|
|
(
|
|
"Old",
|
|
"onbuild_null",
|
|
"89c5e8fd66634b6128fc4c0e6f1236e2540e46e0",
|
|
"image/image.go",
|
|
"5ce9ea9e6daeb5cecf03dbfafe7c895516da782010e2797b1fc0f979161cf5be",
|
|
"08df9dce6147eb2a4edb9859d81fedbdb13f2af2e249db38bf79361f67db4bcf",
|
|
),
|
|
(
|
|
"New",
|
|
"onbuild_omitempty",
|
|
"5834c866f83c0bae2d0be28f38b92f32cd07ca38",
|
|
"daemon/internal/image/image.go",
|
|
"c186304737e0343a04e0f9a4e88f8f81b867e86159cd7bf68dd0d35a8392d3a8",
|
|
"06f5e71501399a33906ef7b006d49d71a700e6c84d7442a90e9d240cb52b81ee",
|
|
),
|
|
)
|
|
|
|
|
|
def source(commit: str, path: str, expected: str) -> str:
|
|
url = f"https://raw.githubusercontent.com/moby/moby/{commit}/{path}"
|
|
with urllib.request.build_opener(urllib.request.ProxyHandler({})).open(
|
|
url, timeout=20
|
|
) as response:
|
|
data = response.read(200_001)
|
|
if (
|
|
not isinstance(data, bytes)
|
|
or len(data) > 200_000
|
|
or hashlib.sha256(data).hexdigest() != expected
|
|
):
|
|
raise ValueError("pinned reference source digest mismatch")
|
|
return data.decode("utf-8")
|
|
|
|
|
|
def declaration(text: str, original: str, replacement: str) -> str:
|
|
match = re.search(rf"(?ms)^type {original} struct \{{\n.*?^\}}", text)
|
|
if not match:
|
|
raise ValueError("pinned reference struct is missing")
|
|
matched_source = match.group()
|
|
if not isinstance(matched_source, str):
|
|
raise ValueError("pinned reference declaration is not text")
|
|
return matched_source.replace(f"type {original} struct", f"type {replacement} struct", 1)
|
|
|
|
|
|
def generate(go: Path) -> None:
|
|
if not go.is_absolute() or not go.is_file():
|
|
raise ValueError("an existing absolute Go executable is required")
|
|
env = dict(os.environ)
|
|
env.update(
|
|
GOTOOLCHAIN="local",
|
|
GOPROXY="off",
|
|
GOSUMDB="off",
|
|
GO111MODULE="off",
|
|
GOMAXPROCS="2",
|
|
GOFLAGS="-p=2",
|
|
CGO_ENABLED="0",
|
|
)
|
|
version = subprocess.run( # noqa: S603 - explicit existing absolute regeneration compiler
|
|
[str(go), "version"], check=True, capture_output=True, text=True, env=env
|
|
)
|
|
if version.stdout.split()[2] != "go1.27.2":
|
|
raise ValueError("reference regeneration requires Go 1.27.2")
|
|
# These are public synthetic inputs: no runtime config, credentials or image
|
|
# bytes are sent to the reference compiler or persisted in this fixture.
|
|
inputs = [
|
|
{
|
|
"architecture": "amd64",
|
|
"os": "linux",
|
|
"created": "2026-10-14T00:01:02.123456789Z",
|
|
"container_config": {
|
|
"User": "constructor",
|
|
"Cmd": ["µ<&>\u2028\u2029"],
|
|
"Labels": {"z": "last", "a": "first"},
|
|
"StopTimeout": 0,
|
|
},
|
|
"config": {
|
|
"User": "10001:10001",
|
|
"Cmd": ["reference"],
|
|
"Labels": {"z": "µ<&>\u2028\u2029", "a": "first"},
|
|
"StopTimeout": 0,
|
|
"Healthcheck": {"Test": ["CMD", "true"], "Interval": 30000000000, "Retries": 2},
|
|
},
|
|
"rootfs": {"type": "layers", "diff_ids": ["sha256:" + "a" * 64, "sha256:" + "b" * 64]},
|
|
},
|
|
{
|
|
"architecture": "amd64",
|
|
"os": "linux",
|
|
"config": {"User": "10001:10001"},
|
|
"rootfs": {"type": "layers", "diff_ids": ["sha256:" + "c" * 64]},
|
|
},
|
|
]
|
|
declarations = [
|
|
'package main\nimport "time"\n',
|
|
"""type HealthConfig struct {
|
|
Test []string `json:",omitempty"`
|
|
Interval time.Duration `json:",omitempty"`
|
|
Timeout time.Duration `json:",omitempty"`
|
|
StartPeriod time.Duration `json:",omitempty"`
|
|
StartInterval time.Duration `json:",omitempty"`
|
|
Retries int `json:",omitempty"`
|
|
}\n""",
|
|
]
|
|
for label, _dialect, commit, image_path, image_hash, config_hash in PINS:
|
|
config = declaration(
|
|
source(commit, "api/types/container/config.go", config_hash), "Config", label + "Config"
|
|
)
|
|
config = (
|
|
config.replace("strslice.StrSlice", "[]string")
|
|
.replace("nat.PortSet", "map[string]struct{}")
|
|
.replace("network.PortSet", "map[string]struct{}")
|
|
)
|
|
image = declaration(source(commit, image_path, image_hash), "V1Image", label + "V1")
|
|
declarations.extend((config, image.replace("container.Config", label + "Config")))
|
|
local = ROOT / ".luna"
|
|
local.mkdir(exist_ok=True)
|
|
if local.is_symlink() or not local.resolve().is_relative_to(ROOT.resolve()):
|
|
raise ValueError("reference scratch directory must stay in this checkout")
|
|
with tempfile.TemporaryDirectory(prefix="moby-v1-reference-", dir=local) as name:
|
|
temp = Path(name)
|
|
if not temp.resolve().is_relative_to(local.resolve()):
|
|
raise ValueError("reference temporary directory escaped its owner")
|
|
env["GOCACHE"] = str(temp / "cache")
|
|
(temp / "main.go").write_bytes(REFERENCE.read_bytes())
|
|
(temp / "types.go").write_text("\n".join(declarations), encoding="utf-8")
|
|
(temp / "inputs.json").write_text(json.dumps(inputs, ensure_ascii=False), encoding="utf-8")
|
|
completed = subprocess.run( # noqa: S603 - fixed standard-library-only reference program
|
|
[str(go), "run", ".", str(temp / "inputs.json")],
|
|
cwd=temp,
|
|
env=env,
|
|
stdin=subprocess.DEVNULL,
|
|
check=True,
|
|
capture_output=True,
|
|
timeout=180,
|
|
)
|
|
if len(completed.stdout) > 100_000:
|
|
raise ValueError("reference output exceeded its fixed bound")
|
|
cases = json.loads(completed.stdout)
|
|
result = {
|
|
"schema": "itworx.moby-v1-wire-golden/v1",
|
|
"goVersion": "go1.27.2",
|
|
"referenceMainSha256": hashlib.sha256(REFERENCE.read_bytes()).hexdigest(),
|
|
"sourceCommits": [pin[2] for pin in PINS],
|
|
"cases": cases,
|
|
}
|
|
FIXTURE.write_text(json.dumps(result, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
print(f"Generated {len(cases)} independent Go reference cases")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--go", type=Path, required=True)
|
|
generate(parser.parse_args().go)
|