#!/usr/bin/env python3 """Prepare a fresh immutable candidate bundle. This command never launches the experiment.""" import argparse import hashlib import json from pathlib import Path import shutil import subprocess from build_saturation_observer import build def sha(path): return hashlib.sha256(path.read_bytes()).hexdigest() def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--output", type=Path, required=True) parser.add_argument("--libraries", type=Path, required=True) parser.add_argument("--image", required=True) options = parser.parse_args() source = Path(__file__).resolve().parent repo = source.parents[2] revision = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=repo, text=True).strip() if subprocess.check_output(["git", "status", "--porcelain"], cwd=repo, text=True).strip(): raise RuntimeError("Commit and review the exact source before preparing immutable artifacts") if not options.image.startswith("sha256:"): raise ValueError("Use an immutable Linux .NET SDK image digest") root = options.output.resolve() root.mkdir(parents=True, exist_ok=False) project = repo / "tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests.csproj" command = ["dotnet", "publish", str(project), "-c", "Release", "-r", "linux-arm64", "--self-contained", "true", "-p:RuntimeFrameworkVersion=10.0.14", "-p:PublishSingleFile=false", "-p:PublishTrimmed=false", "-p:UseAppHost=true", "--artifacts-path", str(root / "build"), "-o", str(root / "app"), "-m:2", "--nologo"] with (root / "publish.log").open("w") as log: subprocess.run(command, cwd=repo, stdout=log, stderr=subprocess.STDOUT, check=True) observer = build(root / "observer-build", options.libraries, True) shutil.copytree(observer, root / "observer") tools = root / "tools" tools.mkdir() for name in ["launch_saturation.py", "container_saturation.sh", "SATURATION.md"]: shutil.copy2(source / name, tools / name) manifest = {str(path.relative_to(root)): sha(path) for directory in ["app", "observer", "tools"] for path in sorted((root / directory).rglob("*")) if path.is_file()} runtime = json.loads((root / "app/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests.runtimeconfig.json").read_text()) included = runtime["runtimeOptions"]["includedFrameworks"] if not included or any(value["version"] != "10.0.14" for value in included): raise RuntimeError("Published app is not the prospectively fixed self-contained runtime") receipt = {"protocol": "sdk2-saturation-v1", "sourceRevision": revision, "image": options.image, "rid": "linux-arm64", "runtimeVersion": "10.0.14", "selfContained": True, "canonicalCycles": 384, "completedRestarts": 96, "settlingCycles": 1, "maximumPhaseSeconds": 1800, "windows": [[321, 352], [353, 384]], "observerBufferMiB": 16, "runtimeKeywords": "0x18", "rundown": False, "acceptance": False, "publishCommand": command, "testSourceSha256": sha(project.parent / "Sdk2LifecycleSoakTests.cs"), "observerSourceSha256": sha(source / "SaturationObserver.cs"), "files": manifest} receipt_path = root / "receipt.json" receipt_path.write_text(json.dumps(receipt, indent=2) + "\n") print(json.dumps({"bundle": str(root), "receiptSha256": sha(receipt_path), "sourceRevision": revision, "launched": False}, indent=2)) if __name__ == "__main__": main()