Loading ci/sdk-consumer.md +5 −0 Changes for ci/sdk-consumer.md: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -12,6 +12,11 @@ requirements and its content-addressed URL under `[tool.uv.sources]`. Run The lock must select that version, URL and wheel checksum. A dependency change is therefore visible in the SRM MR before any qualification starts. After stable SDK publication, the SRM MR may replace the candidate URL with the normal explicit SDK PyPI index and commit its regenerated lock. Qualification also accepts that form when the declared version and locked wheel hash match the exact SDK under test. A different wheel or index remains an error. The `sdk-consumer` job downloads the checked artifact and copies the committed `pyproject.toml` and `uv.lock` unchanged into `.cache/sdk-consumer/project`. It rejects an undeclared candidate before installation, then uses Loading ci/sdk_consumer.py +24 −10 Changes for ci/sdk_consumer.py: 24 added lines, 10 removed lines. Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ REGISTRY = "labs.etsi.org:5050/oop/code/service-resource-manager" CANDIDATE_REGISTRY = ( "https://labs.etsi.org/rep/api/v4/projects/396/packages/generic/openop-tfsdk-candidates" ) SDK_INDEX = "https://labs.etsi.org/rep/api/v4/projects/396/packages/pypi/simple" def digest(path): Loading Loading @@ -126,12 +127,24 @@ def prepare(root=ROOT, out=OUT, environment=None, call=subprocess.run, fetch=url require(len(packages) == 1, "The committed lock must select exactly one SDK") package = packages[0] expected_url = f"{CANDIDATE_REGISTRY}/{sdk['sha256']}/{sdk['filename']}" declared_url = ( declaration.get("tool", {}) .get("uv", {}) .get("sources", {}) .get("openop-tfsdk", {}) .get("url") uv = declaration.get("tool", {}).get("uv", {}) declared_source = uv.get("sources", {}).get("openop-tfsdk", {}) declared_url = declared_source.get("url") locked_wheels = [ p for p in package.get("wheels", []) if p.get("hash") == "sha256:" + sdk["sha256"] ] candidate_pin = ( declared_url == expected_url and package.get("source", {}).get("url") == expected_url ) stable_pin = ( package.get("source", {}).get("registry") == SDK_INDEX and any( i.get("name") == declared_source.get("index") and i.get("url") == SDK_INDEX and i.get("explicit") is True for i in uv.get("index", []) ) and any(isinstance(p.get("url"), str) for p in locked_wheels) ) require( package.get("version") == sdk["version"] Loading @@ -141,9 +154,8 @@ def prepare(root=ROOT, out=OUT, environment=None, call=subprocess.run, fetch=url ) for item in declaration.get("project", {}).get("dependencies", []) ) and declared_url == expected_url and package.get("source", {}).get("url") == expected_url and any(p.get("hash") == "sha256:" + sdk["sha256"] for p in package.get("wheels", [])), and (candidate_pin or stable_pin) and locked_wheels, "SDK candidate is not pinned by the committed SRM dependency and lock; " "update them in an SRM MR before qualification", ) Loading Loading @@ -179,7 +191,8 @@ def prepare(root=ROOT, out=OUT, environment=None, call=subprocess.run, fetch=url ) proof["dependency_changes"] = [] proof["source_inputs_unchanged"] = True proof["sdk_dependency_url"] = expected_url proof["sdk_dependency_kind"] = "candidate-url" if candidate_pin else "pypi" proof["sdk_dependency_url"] = expected_url if candidate_pin else locked_wheels[0]["url"] write(out / "candidate.json", proof) return proof Loading Loading @@ -381,6 +394,7 @@ def build_image(root=ROOT, out=OUT, call=subprocess.run): "dependency_changes": proof["dependency_changes"], "source_inputs_unchanged": proof["source_inputs_unchanged"], "sdk_dependency_url": proof["sdk_dependency_url"], "sdk_dependency_kind": proof["sdk_dependency_kind"], "project_sha256": proof["project_sha256"], "runtime_dependencies": inventory["dependencies"], "consumer_checks": {"job_id": proof["qualification_job_id"], "status": "passed"}, Loading ci/tests/test_sdk_consumer.py +38 −0 Changes for ci/tests/test_sdk_consumer.py: 38 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -187,3 +187,41 @@ def test_install_cannot_rewrite_the_copied_lock(wheel, tmp_path, monkeypatch): with pytest.raises(ValueError, match="changed committed"): consumer.prepare(root, root / "out", env, call=changing_install) @pytest.mark.parametrize("fault", [None, "wheel", "index"]) def test_stable_index_adoption_accepts_only_the_same_locked_wheel( wheel, tmp_path, monkeypatch, fault ): root = tmp_path / "srm" root.mkdir() env = committed_project(root, wheel) index = "https://labs.etsi.org/rep/api/v4/projects/396/packages/pypi/simple" (root / "pyproject.toml").write_text( '[project]\ndependencies = ["openop-tfsdk==2.0.0"]\n' '[tool.uv.sources]\nopenop-tfsdk = {index = "etsi-tf-sdk"}\n' '[[tool.uv.index]]\nname = "etsi-tf-sdk"\nurl = "' + index + '"\nexplicit = true\n' ) (root / "uv.lock").write_text( '[[package]]\nname = "openop-tfsdk"\nversion = "2.0.0"\n' 'source = {registry = "' + index + '"}\n' 'wheels = [{url = "https://labs.etsi.org/rep/api/v4/projects/396/' 'packages/pypi/files/example/candidate.whl", ' 'hash = "sha256:' + env["SDK_WHEEL_SHA256"] + '"}]\n' ) monkeypatch.setattr(consumer, "revision", lambda *_: "a" * 40) if fault == "wheel": path = root / "uv.lock" path.write_text(path.read_text().replace(env["SDK_WHEEL_SHA256"], "f" * 64)) if fault == "index": path = root / "pyproject.toml" path.write_text(path.read_text().replace(index, "https://example.invalid/simple")) if fault: with pytest.raises(ValueError, match="not pinned"): consumer.prepare( root, root / "out", env, call=lambda *_a, **_kw: pytest.fail("No installation") ) return proof = consumer.prepare(root, root / "out", env, call=lambda *_a, **_kw: None) assert proof["source_inputs_unchanged"] is True assert proof["sdk_dependency_kind"] == "pypi" Loading
ci/sdk-consumer.md +5 −0 Changes for ci/sdk-consumer.md: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -12,6 +12,11 @@ requirements and its content-addressed URL under `[tool.uv.sources]`. Run The lock must select that version, URL and wheel checksum. A dependency change is therefore visible in the SRM MR before any qualification starts. After stable SDK publication, the SRM MR may replace the candidate URL with the normal explicit SDK PyPI index and commit its regenerated lock. Qualification also accepts that form when the declared version and locked wheel hash match the exact SDK under test. A different wheel or index remains an error. The `sdk-consumer` job downloads the checked artifact and copies the committed `pyproject.toml` and `uv.lock` unchanged into `.cache/sdk-consumer/project`. It rejects an undeclared candidate before installation, then uses Loading
ci/sdk_consumer.py +24 −10 Changes for ci/sdk_consumer.py: 24 added lines, 10 removed lines. Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ REGISTRY = "labs.etsi.org:5050/oop/code/service-resource-manager" CANDIDATE_REGISTRY = ( "https://labs.etsi.org/rep/api/v4/projects/396/packages/generic/openop-tfsdk-candidates" ) SDK_INDEX = "https://labs.etsi.org/rep/api/v4/projects/396/packages/pypi/simple" def digest(path): Loading Loading @@ -126,12 +127,24 @@ def prepare(root=ROOT, out=OUT, environment=None, call=subprocess.run, fetch=url require(len(packages) == 1, "The committed lock must select exactly one SDK") package = packages[0] expected_url = f"{CANDIDATE_REGISTRY}/{sdk['sha256']}/{sdk['filename']}" declared_url = ( declaration.get("tool", {}) .get("uv", {}) .get("sources", {}) .get("openop-tfsdk", {}) .get("url") uv = declaration.get("tool", {}).get("uv", {}) declared_source = uv.get("sources", {}).get("openop-tfsdk", {}) declared_url = declared_source.get("url") locked_wheels = [ p for p in package.get("wheels", []) if p.get("hash") == "sha256:" + sdk["sha256"] ] candidate_pin = ( declared_url == expected_url and package.get("source", {}).get("url") == expected_url ) stable_pin = ( package.get("source", {}).get("registry") == SDK_INDEX and any( i.get("name") == declared_source.get("index") and i.get("url") == SDK_INDEX and i.get("explicit") is True for i in uv.get("index", []) ) and any(isinstance(p.get("url"), str) for p in locked_wheels) ) require( package.get("version") == sdk["version"] Loading @@ -141,9 +154,8 @@ def prepare(root=ROOT, out=OUT, environment=None, call=subprocess.run, fetch=url ) for item in declaration.get("project", {}).get("dependencies", []) ) and declared_url == expected_url and package.get("source", {}).get("url") == expected_url and any(p.get("hash") == "sha256:" + sdk["sha256"] for p in package.get("wheels", [])), and (candidate_pin or stable_pin) and locked_wheels, "SDK candidate is not pinned by the committed SRM dependency and lock; " "update them in an SRM MR before qualification", ) Loading Loading @@ -179,7 +191,8 @@ def prepare(root=ROOT, out=OUT, environment=None, call=subprocess.run, fetch=url ) proof["dependency_changes"] = [] proof["source_inputs_unchanged"] = True proof["sdk_dependency_url"] = expected_url proof["sdk_dependency_kind"] = "candidate-url" if candidate_pin else "pypi" proof["sdk_dependency_url"] = expected_url if candidate_pin else locked_wheels[0]["url"] write(out / "candidate.json", proof) return proof Loading Loading @@ -381,6 +394,7 @@ def build_image(root=ROOT, out=OUT, call=subprocess.run): "dependency_changes": proof["dependency_changes"], "source_inputs_unchanged": proof["source_inputs_unchanged"], "sdk_dependency_url": proof["sdk_dependency_url"], "sdk_dependency_kind": proof["sdk_dependency_kind"], "project_sha256": proof["project_sha256"], "runtime_dependencies": inventory["dependencies"], "consumer_checks": {"job_id": proof["qualification_job_id"], "status": "passed"}, Loading
ci/tests/test_sdk_consumer.py +38 −0 Changes for ci/tests/test_sdk_consumer.py: 38 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -187,3 +187,41 @@ def test_install_cannot_rewrite_the_copied_lock(wheel, tmp_path, monkeypatch): with pytest.raises(ValueError, match="changed committed"): consumer.prepare(root, root / "out", env, call=changing_install) @pytest.mark.parametrize("fault", [None, "wheel", "index"]) def test_stable_index_adoption_accepts_only_the_same_locked_wheel( wheel, tmp_path, monkeypatch, fault ): root = tmp_path / "srm" root.mkdir() env = committed_project(root, wheel) index = "https://labs.etsi.org/rep/api/v4/projects/396/packages/pypi/simple" (root / "pyproject.toml").write_text( '[project]\ndependencies = ["openop-tfsdk==2.0.0"]\n' '[tool.uv.sources]\nopenop-tfsdk = {index = "etsi-tf-sdk"}\n' '[[tool.uv.index]]\nname = "etsi-tf-sdk"\nurl = "' + index + '"\nexplicit = true\n' ) (root / "uv.lock").write_text( '[[package]]\nname = "openop-tfsdk"\nversion = "2.0.0"\n' 'source = {registry = "' + index + '"}\n' 'wheels = [{url = "https://labs.etsi.org/rep/api/v4/projects/396/' 'packages/pypi/files/example/candidate.whl", ' 'hash = "sha256:' + env["SDK_WHEEL_SHA256"] + '"}]\n' ) monkeypatch.setattr(consumer, "revision", lambda *_: "a" * 40) if fault == "wheel": path = root / "uv.lock" path.write_text(path.read_text().replace(env["SDK_WHEEL_SHA256"], "f" * 64)) if fault == "index": path = root / "pyproject.toml" path.write_text(path.read_text().replace(index, "https://example.invalid/simple")) if fault: with pytest.raises(ValueError, match="not pinned"): consumer.prepare( root, root / "out", env, call=lambda *_a, **_kw: pytest.fail("No installation") ) return proof = consumer.prepare(root, root / "out", env, call=lambda *_a, **_kw: None) assert proof["source_inputs_unchanged"] is True assert proof["sdk_dependency_kind"] == "pypi"