Loading ansible/tests/unit/test_candidate_resolution.py +21 −4 Changes for ansible/tests/unit/test_candidate_resolution.py: 21 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -27,7 +27,8 @@ def wheel(version, contents=b"runtime = 1\n"): class Registry: def __init__(self, fault=None): def __init__(self, fault=None, candidate_ref="develop"): self.candidate_ref = candidate_ref self.fault = fault self.names = {1: "oeg", 2: "fm", 3: "srm", 4: "sdk", 5: "helm"} self.heads = {name: str(i) * 40 for i, name in self.names.items()} Loading Loading @@ -56,11 +57,13 @@ class Registry: identity = int(path.split("/")[1]);name = self.names[identity] if path.endswith("/repository/branches/test/current-consumer"): result = {"commit": {"id": "6" * 40}} elif path.endswith("/repository/branches/develop"): elif path.endswith("/repository/branches/" + self.candidate_ref): head = "a" * 40 if self.fault == "moving-head" and self.reads[path] > 1 else self.heads[name] result = {"commit": {"id": head}} elif path.endswith("/pipelines"): assert query["sha"] == [self.heads[name]] if name != "sdk": assert query["ref"] == [self.candidate_ref] result = [] if self.fault == "missing-pipeline" else [ {"id": 100 + identity, "sha": self.heads[name], "status": "success", "web_url": url}] elif path.endswith("/jobs"): Loading @@ -73,6 +76,7 @@ class Registry: result = [{"id": identity, "path": self.paths[name], "location": "labs.etsi.org:5050/" + self.paths[name]}] elif "/registry/repositories/" in path: assert path.endswith("/tags/ci-" + self.candidate_ref + "-" + self.heads[name][:8]) result = {"digest": "sha256:" + str(identity) * 64} elif "/repository/files/" in path: assert query["ref"] == [self.heads["srm"]] Loading @@ -87,12 +91,17 @@ class Registry: return json.dumps(result).encode() def test_latest_image_refs_and_actual_locked_sdk_are_resolved_together(tmp_path): @pytest.mark.parametrize("candidate_ref", ["develop", "main"]) def test_latest_image_refs_and_actual_locked_sdk_are_resolved_together(tmp_path, candidate_ref): original = {"images": {}, "checkpoint": "terminated", "acceptance": {"sdk": {}}} if candidate_ref == "main": original["candidate_ref"] = candidate_ref unchanged = deepcopy(original) registry = Registry() registry = Registry(candidate_ref=candidate_ref) profile, evidence, sdk_wheel = resolve_candidate(original, tmp_path, fetch=registry) assert original == unchanged assert evidence["ref"] == candidate_ref assert all(":ci-" + candidate_ref + "-" in image for image in profile["images"].values()) assert profile["chart_revision"] == registry.heads["helm"] assert profile["images"]["srm"].endswith("@sha256:" + "3" * 64) assert profile["acceptance"]["sdk"]["version"] == "2.0.0rc4" Loading @@ -105,3 +114,11 @@ def test_latest_image_refs_and_actual_locked_sdk_are_resolved_together(tmp_path) def test_stale_unverified_or_changed_candidates_are_rejected(tmp_path, fault): with pytest.raises((ValueError, RuntimeError)): resolve_candidate({"acceptance": {"sdk": {}}}, tmp_path, fetch=Registry(fault)) def test_unknown_candidate_ref_is_rejected_before_any_lookup(tmp_path): def unexpected_lookup(url): pytest.fail("An invalid ref must not query a project or fall back") with pytest.raises(ValueError, match="candidate_ref"): resolve_candidate({"candidate_ref": "feature/unreviewed"}, tmp_path, fetch=unexpected_lookup) ci/lab/README.md +5 −2 Changes for ci/lab/README.md: 5 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -98,8 +98,11 @@ OOP_LAB_REFRESH_CANDIDATE=1 sh ci/run_platform_lab.sh \ ``` This mode selects source commits, successful image-build jobs, registry digests and the current chart commit. It checks the SDK wheel checksum and downstream SRM verification. The normal SRM image retains its locked SDK dependency: the and the current chart commit. Set `candidate_ref` in the profile to `main` for a coordinated release, or `develop` for iterative integration (the default). All component and chart heads come from that ref; missing builds fail instead of falling back to another branch or an older image. It checks the SDK wheel checksum and downstream SRM verification. The normal SRM image retains its locked SDK dependency: the runner compares its runtime contents with the wheel built from current SDK source, and fails if they differ. A package-version difference remains visible in `candidate.json`; matching runtime contents never imply a final version has Loading ci/lab/profile.release-2.0.json +1 −0 Changes for ci/lab/profile.release-2.0.json: 1 added line, 0 removed lines. Original line number Diff line number Diff line { "candidate_ref": "main", "chart_revision": "52a7b0d942af88d004a0781e365b0a6db7c66e2c", "images": { "oeg": "labs.etsi.org:5050/oop/code/open-exposure-gateway:ci-develop-7434f8ae@sha256:7c06caf7ad6d6bf67bd40a17f33db79bcb9fddb2ab1f79e949a0d7750d4d5907", Loading ci/resolve_candidate.py +8 −4 Changes for ci/resolve_candidate.py: 8 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -38,6 +38,9 @@ def resolve_candidate(original, directory, *, fetch=download): The SDK head wheel is a CI artifact, not a rewritten registry version. A changed SDK runtime or stale downstream check requires reintegration. """ candidate_ref = original.get("candidate_ref", "develop") if candidate_ref not in ("develop", "main"): raise ValueError("candidate_ref must be develop or main") directory = Path(directory) directory.mkdir(parents=True, exist_ok=True) profile = deepcopy(original) Loading @@ -48,17 +51,18 @@ def resolve_candidate(original, directory, *, fetch=download): projects = {key: api("projects/" + quote(path, safe=""))["id"] for key, path in PROJECTS.items()} def branch(key, ref="develop"): def branch(key, ref=None): ref = candidate_ref if ref is None else ref return api(f"projects/{projects[key]}/repository/branches/{quote(ref, safe='')}")["commit"]["id"] heads = {key: branch(key) for key in PROJECTS} evidence = {"ref": "develop", "source_heads": heads, "components": {}} evidence = {"ref": candidate_ref, "source_heads": heads, "components": {}} profile["images"], profile["local_images"] = {}, [] for key in ("oeg", "fm", "srm", "sdk"): project, sha = projects[key], heads[key] query = {"sha": sha, "status": "success", "per_page": 1} if key != "sdk": query["ref"] = "develop" query["ref"] = candidate_ref pipelines = api(f"projects/{project}/pipelines?" + urlencode(query)) if not pipelines: raise RuntimeError(f"Current {key} head has no successful candidate pipeline; no older fallback") Loading Loading @@ -99,7 +103,7 @@ def resolve_candidate(original, directory, *, fetch=download): else: repositories = api(f"projects/{project}/registry/repositories?per_page=100") repository = next(row for row in repositories if row["path"] == PROJECTS[key]) tag = "ci-develop-" + sha[:8] tag = "ci-" + candidate_ref + "-" + sha[:8] image = api(f"projects/{project}/registry/repositories/{repository['id']}/tags/{tag}") if not image["digest"].startswith("sha256:"): raise ValueError("Registry did not identify an immutable image") Loading Loading
ansible/tests/unit/test_candidate_resolution.py +21 −4 Changes for ansible/tests/unit/test_candidate_resolution.py: 21 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -27,7 +27,8 @@ def wheel(version, contents=b"runtime = 1\n"): class Registry: def __init__(self, fault=None): def __init__(self, fault=None, candidate_ref="develop"): self.candidate_ref = candidate_ref self.fault = fault self.names = {1: "oeg", 2: "fm", 3: "srm", 4: "sdk", 5: "helm"} self.heads = {name: str(i) * 40 for i, name in self.names.items()} Loading Loading @@ -56,11 +57,13 @@ class Registry: identity = int(path.split("/")[1]);name = self.names[identity] if path.endswith("/repository/branches/test/current-consumer"): result = {"commit": {"id": "6" * 40}} elif path.endswith("/repository/branches/develop"): elif path.endswith("/repository/branches/" + self.candidate_ref): head = "a" * 40 if self.fault == "moving-head" and self.reads[path] > 1 else self.heads[name] result = {"commit": {"id": head}} elif path.endswith("/pipelines"): assert query["sha"] == [self.heads[name]] if name != "sdk": assert query["ref"] == [self.candidate_ref] result = [] if self.fault == "missing-pipeline" else [ {"id": 100 + identity, "sha": self.heads[name], "status": "success", "web_url": url}] elif path.endswith("/jobs"): Loading @@ -73,6 +76,7 @@ class Registry: result = [{"id": identity, "path": self.paths[name], "location": "labs.etsi.org:5050/" + self.paths[name]}] elif "/registry/repositories/" in path: assert path.endswith("/tags/ci-" + self.candidate_ref + "-" + self.heads[name][:8]) result = {"digest": "sha256:" + str(identity) * 64} elif "/repository/files/" in path: assert query["ref"] == [self.heads["srm"]] Loading @@ -87,12 +91,17 @@ class Registry: return json.dumps(result).encode() def test_latest_image_refs_and_actual_locked_sdk_are_resolved_together(tmp_path): @pytest.mark.parametrize("candidate_ref", ["develop", "main"]) def test_latest_image_refs_and_actual_locked_sdk_are_resolved_together(tmp_path, candidate_ref): original = {"images": {}, "checkpoint": "terminated", "acceptance": {"sdk": {}}} if candidate_ref == "main": original["candidate_ref"] = candidate_ref unchanged = deepcopy(original) registry = Registry() registry = Registry(candidate_ref=candidate_ref) profile, evidence, sdk_wheel = resolve_candidate(original, tmp_path, fetch=registry) assert original == unchanged assert evidence["ref"] == candidate_ref assert all(":ci-" + candidate_ref + "-" in image for image in profile["images"].values()) assert profile["chart_revision"] == registry.heads["helm"] assert profile["images"]["srm"].endswith("@sha256:" + "3" * 64) assert profile["acceptance"]["sdk"]["version"] == "2.0.0rc4" Loading @@ -105,3 +114,11 @@ def test_latest_image_refs_and_actual_locked_sdk_are_resolved_together(tmp_path) def test_stale_unverified_or_changed_candidates_are_rejected(tmp_path, fault): with pytest.raises((ValueError, RuntimeError)): resolve_candidate({"acceptance": {"sdk": {}}}, tmp_path, fetch=Registry(fault)) def test_unknown_candidate_ref_is_rejected_before_any_lookup(tmp_path): def unexpected_lookup(url): pytest.fail("An invalid ref must not query a project or fall back") with pytest.raises(ValueError, match="candidate_ref"): resolve_candidate({"candidate_ref": "feature/unreviewed"}, tmp_path, fetch=unexpected_lookup)
ci/lab/README.md +5 −2 Changes for ci/lab/README.md: 5 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -98,8 +98,11 @@ OOP_LAB_REFRESH_CANDIDATE=1 sh ci/run_platform_lab.sh \ ``` This mode selects source commits, successful image-build jobs, registry digests and the current chart commit. It checks the SDK wheel checksum and downstream SRM verification. The normal SRM image retains its locked SDK dependency: the and the current chart commit. Set `candidate_ref` in the profile to `main` for a coordinated release, or `develop` for iterative integration (the default). All component and chart heads come from that ref; missing builds fail instead of falling back to another branch or an older image. It checks the SDK wheel checksum and downstream SRM verification. The normal SRM image retains its locked SDK dependency: the runner compares its runtime contents with the wheel built from current SDK source, and fails if they differ. A package-version difference remains visible in `candidate.json`; matching runtime contents never imply a final version has Loading
ci/lab/profile.release-2.0.json +1 −0 Changes for ci/lab/profile.release-2.0.json: 1 added line, 0 removed lines. Original line number Diff line number Diff line { "candidate_ref": "main", "chart_revision": "52a7b0d942af88d004a0781e365b0a6db7c66e2c", "images": { "oeg": "labs.etsi.org:5050/oop/code/open-exposure-gateway:ci-develop-7434f8ae@sha256:7c06caf7ad6d6bf67bd40a17f33db79bcb9fddb2ab1f79e949a0d7750d4d5907", Loading
ci/resolve_candidate.py +8 −4 Changes for ci/resolve_candidate.py: 8 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -38,6 +38,9 @@ def resolve_candidate(original, directory, *, fetch=download): The SDK head wheel is a CI artifact, not a rewritten registry version. A changed SDK runtime or stale downstream check requires reintegration. """ candidate_ref = original.get("candidate_ref", "develop") if candidate_ref not in ("develop", "main"): raise ValueError("candidate_ref must be develop or main") directory = Path(directory) directory.mkdir(parents=True, exist_ok=True) profile = deepcopy(original) Loading @@ -48,17 +51,18 @@ def resolve_candidate(original, directory, *, fetch=download): projects = {key: api("projects/" + quote(path, safe=""))["id"] for key, path in PROJECTS.items()} def branch(key, ref="develop"): def branch(key, ref=None): ref = candidate_ref if ref is None else ref return api(f"projects/{projects[key]}/repository/branches/{quote(ref, safe='')}")["commit"]["id"] heads = {key: branch(key) for key in PROJECTS} evidence = {"ref": "develop", "source_heads": heads, "components": {}} evidence = {"ref": candidate_ref, "source_heads": heads, "components": {}} profile["images"], profile["local_images"] = {}, [] for key in ("oeg", "fm", "srm", "sdk"): project, sha = projects[key], heads[key] query = {"sha": sha, "status": "success", "per_page": 1} if key != "sdk": query["ref"] = "develop" query["ref"] = candidate_ref pipelines = api(f"projects/{project}/pipelines?" + urlencode(query)) if not pipelines: raise RuntimeError(f"Current {key} head has no successful candidate pipeline; no older fallback") Loading Loading @@ -99,7 +103,7 @@ def resolve_candidate(original, directory, *, fetch=download): else: repositories = api(f"projects/{project}/registry/repositories?per_page=100") repository = next(row for row in repositories if row["path"] == PROJECTS[key]) tag = "ci-develop-" + sha[:8] tag = "ci-" + candidate_ref + "-" + sha[:8] image = api(f"projects/{project}/registry/repositories/{repository['id']}/tags/{tag}") if not image["digest"].startswith("sha256:"): raise ValueError("Registry did not identify an immutable image") Loading