atlas-iac/testing/tests/test_hermes_chat_router_release.py

417 lines
16 KiB
Python
Raw Normal View History

"""Exact-source, Harbor, and Flux contracts for the chat-router release lane."""
from __future__ import annotations
import importlib.util
import io
import json
from pathlib import Path
import sys
import urllib.parse
import pytest
import yaml
ROOT = Path(__file__).resolve().parents[2]
PIPELINE = ROOT / "ci/Jenkinsfile.hermes-chat-router-image"
RELEASE = ROOT / "ci/scripts/hermes_chat_router_release.py"
PROMOTE = ROOT / "ci/scripts/hermes_oci_promote.py"
MANIFEST = ROOT / "services/hermes/chat-router.yaml"
IMAGE_POLICY = ROOT / "services/hermes/image.yaml"
JENKINS = ROOT / "services/jenkins/configmap-jcasc.yaml"
TRIGGER = ROOT / "services/hermes/scripts/jenkins_image_build_trigger.py"
STATUS = ROOT / "services/hermes/scripts/hermes_image_release_status.py"
HARBOR_JOB = ROOT / "services/harbor/hermes-chat-router-immutability-job.yaml"
HARBOR_POLICY = (
ROOT / "services/harbor/scripts/harbor_hermes_chat_router_immutability_ensure.py"
)
HARBOR_GENERIC = ROOT / "services/harbor/scripts/harbor_immutable_rule_ensure.py"
def _load(path: Path, name: str):
spec = importlib.util.spec_from_file_location(name, path)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
class Response(io.BytesIO):
"""Minimal context-managed urllib response."""
def __init__(self, status: int, body: bytes = b"", headers=None):
super().__init__(body)
self.status = status
self.headers = headers or {}
def __enter__(self):
return self
def __exit__(self, *_args):
self.close()
def _fixture(tmp_path: Path):
module = _load(RELEASE, f"router_release_{tmp_path.name}")
digest = "sha256:" + "7" * 64
revision = "8" * 40
build = "23"
destination = f"{module.DEFAULT_IMAGE}:git-{revision}-build-{build}"
digest_file = tmp_path / "digest"
image_file = tmp_path / "image"
digest_file.write_text(digest + "\n", encoding="utf-8")
image_file.write_text(f"{destination}@{digest}\n", encoding="utf-8")
output = tmp_path / "release"
kwargs = {
"digest": digest,
"source_revision": revision,
"build_number": build,
"destination": destination,
"manifest": MANIFEST,
"output_dir": output,
}
module.write_release_artifacts(**kwargs)
return module, digest_file, image_file, kwargs
def test_pipeline_builds_one_exact_reviewed_router_revision() -> None:
"""The job detaches the reviewed main ancestor and never mutates Git or K8s."""
source = PIPELINE.read_text(encoding="utf-8")
assert 'test "${CONFIRM_PUBLISH}" = "PUBLISH HERMES CHAT ROUTER"' in source
assert 'test "${main_revision}" = "$(git rev-parse origin/main)"' in source
assert 'git merge-base --is-ancestor "${EXPECTED_SOURCE_REVISION}"' in source
assert 'git checkout --detach "${EXPECTED_SOURCE_REVISION}"' in source
assert 'test "${actual_revision}" = "${EXPECTED_SOURCE_REVISION}"' in source
assert "dockerfiles/Dockerfile.hermes-chat-router" in source
assert "GO111MODULE=off CGO_ENABLED=0 go test ./..." in source
assert "ci/scripts/hermes_chat_router_release.py assert-absent" in source
assert "ci/scripts/hermes_oci_promote.py" in source
assert "--digest-file=" in source
assert "--image-name-tag-with-digest-file=" in source
for label in (
"org.opencontainers.image.revision=${source_revision}",
"org.opencontainers.image.source=https://scm.bstein.dev/titan/atlas-iac",
"org.opencontainers.image.title=hermes-chat-router",
):
assert label in source
for forbidden in ("kubectl ", "flux reconcile", "git push", "git commit"):
assert forbidden not in source
pod_yaml = source.split('yaml """', 1)[1].split('"""', 1)[0]
pod = yaml.safe_load(pod_yaml)["spec"]
assert pod["serviceAccountName"] == "hermes-image-builder"
assert pod["automountServiceAccountToken"] is False
assert pod["enableServiceLinks"] is False
containers = {item["name"]: item for item in pod["containers"]}
assert set(containers) == {"jnlp", "python", "golang", "kaniko"}
assert pod["affinity"]["nodeAffinity"][
"requiredDuringSchedulingIgnoredDuringExecution"
]["nodeSelectorTerms"][0]["matchExpressions"][0]["values"] == ["titan-20"]
assert "docker.sock" not in source and "hostPath" not in source
for container in containers.values():
security = container["securityContext"]
assert security["allowPrivilegeEscalation"] is False
assert security["capabilities"]["drop"] == ["ALL"]
def test_jenkins_job_is_token_guarded_main_only_and_non_concurrent() -> None:
"""Only the fixed router job and fixed confirmation can enter the lane."""
config = yaml.safe_load(JENKINS.read_text(encoding="utf-8"))
jobs = config["data"]["jobs.yaml"]
assert jobs.count("pipelineJob('hermes-chat-router-image')") == 1
block = jobs.split("pipelineJob('hermes-chat-router-image')", 1)[1].split(
"pipelineJob('hermes-voice-image')", 1
)[0]
assert "branches('*/main')" in block
assert "scriptPath('ci/Jenkinsfile.hermes-chat-router-image')" in block
assert "authenticationToken(System.getenv('HERMES_AGENT_IMAGE_BUILD_TOKEN'))" in block
assert "PUBLISH HERMES CHAT ROUTER" in block
assert "pipelineTriggers" not in block
pipeline = PIPELINE.read_text(encoding="utf-8")
assert "disableConcurrentBuilds()" in pipeline
assert "artifactDaysToKeepStr: '30'" in pipeline
def test_release_renders_exactly_one_flux_consumer_and_revalidates(tmp_path: Path) -> None:
"""A release handoff contains only the one router Deployment mutation."""
module, digest_file, image_file, kwargs = _fixture(tmp_path)
output = kwargs["output_dir"]
patch = (output / "hermes-chat-router-image-update.patch").read_text()
assert patch.count(f"+ image: {module.DEFAULT_IMAGE}@{kwargs['digest']}") == 1
assert "services/hermes/chat-router.yaml" in patch
assert "chat-statefulset.yaml" not in patch
metadata = json.loads((output / "hermes-chat-router-image.json").read_text())
assert metadata["source_revision"] == kwargs["source_revision"]
assert metadata["flux_targets"] == [
"apps/Deployment/hermes/hermes-chat-router"
]
module.validate_release_artifacts(**kwargs)
assert module.validate_kaniko_evidence(
digest_text=digest_file.read_text(),
image_text=image_file.read_text(),
destination=kwargs["destination"],
) == kwargs["digest"]
(output / "extra").write_text("no\n")
with pytest.raises(ValueError, match="exactly three"):
module.validate_release_artifacts(**kwargs)
@pytest.mark.parametrize(
"source",
[
"apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: wrong\n",
"# services/hermes/chat-router.yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: hermes-chat-router\n",
],
)
def test_renderer_fails_closed_on_target_drift(source: str) -> None:
"""Identity or consumer-count drift cannot silently broaden promotion."""
module = _load(RELEASE, f"router_drift_{len(source)}")
with pytest.raises(ValueError):
module.render_workload(source, "sha256:" + "a" * 64)
def test_harbor_candidate_evidence_and_policy_are_exact() -> None:
"""Candidate acceptance requires an immutable tag, digest, and OCI revision."""
module = _load(RELEASE, "router_registry_contract")
revision = "a" * 40
digest = "sha256:" + "b" * 64
destination = f"{module.DEFAULT_IMAGE}:git-{revision}-build-9"
seen = []
def artifact_open(request, timeout):
seen.append((request, timeout))
return Response(
200,
json.dumps(
{
"digest": digest,
"tags": [
{"name": f"git-{revision}-build-9", "immutable": True}
],
"extra_attrs": {
"config": {
"Labels": {
"org.opencontainers.image.revision": revision
}
}
},
}
).encode(),
)
module.verify_registry_digest(
destination,
digest,
revision,
username="robot",
password="private",
opener=artifact_open,
)
assert "/repositories/hermes-chat-router/artifacts/" in seen[0][0].full_url
assert seen[0][0].get_header("Authorization").startswith("Basic ")
def rule_open(_request, _timeout):
expected = {
"disabled": False,
"action": "immutable",
"template": "immutable_template",
"tag_selectors": [
{
"kind": "doublestar",
"decoration": "matches",
"pattern": "git-*-build-*",
}
],
"scope_selectors": {
"repository": [
{
"kind": "doublestar",
"decoration": "repoMatches",
"pattern": "hermes-chat-router",
}
]
},
}
return Response(
200, json.dumps([expected]).encode(), {"X-Total-Count": "1"}
)
module.verify_immutable_policy(
username="robot", password="private", opener=rule_open
)
assert module.assert_tag_absent(
destination,
username="robot",
password="private",
opener=lambda *_args: Response(404),
) is None
with pytest.raises(RuntimeError, match="already exists"):
module.assert_tag_absent(
destination,
username="robot",
password="private",
opener=lambda *_args: Response(200),
)
def test_harbor_policy_is_flux_tracked_and_vault_injected() -> None:
"""The preflight rule is desired state and no credential is stored in Git."""
app = yaml.safe_load(
(ROOT / "clusters/atlas/flux-system/applications/harbor/kustomization.yaml")
.read_text(encoding="utf-8")
)
assert {
"apiVersion": "batch/v1",
"kind": "Job",
"name": "harbor-hermes-chat-router-immutability-ensure-1",
"namespace": "harbor",
} in app["spec"]["healthChecks"]
_load(HARBOR_GENERIC, "harbor_immutable_rule_ensure")
policy = _load(HARBOR_POLICY, "router_harbor_policy")
assert policy.REPOSITORY_PATTERN == "hermes-chat-router"
assert policy.TAG_PATTERN == "git-*-build-*"
job = yaml.safe_load(HARBOR_JOB.read_text(encoding="utf-8"))
template = job["spec"]["template"]
annotations = template["metadata"]["annotations"]
assert annotations["vault.hashicorp.com/role"] == "harbor-policy-bootstrap"
assert annotations[
"vault.hashicorp.com/agent-inject-secret-harbor-admin-password"
] == "kv/data/atlas/harbor/harbor-core"
container = template["spec"]["containers"][0]
assert "@sha256:" in container["image"]
assert container["securityContext"]["readOnlyRootFilesystem"] is True
assert container["securityContext"]["runAsNonRoot"] is True
source = HARBOR_POLICY.read_text().lower() + HARBOR_GENERIC.read_text().lower()
assert "private-token" not in source and "password123" not in source
def test_harbor_policy_helper_creates_once_and_rejects_weakened_rule() -> None:
"""Creation is verified by ID; a disabled matching rule fails closed."""
module = _load(HARBOR_GENERIC, "router_harbor_helper")
expected = module.expected_rule("hermes-chat-router", "git-*-build-*")
created = {**expected, "id": 19}
class Client:
origin = module.EXPECTED_ORIGIN
def __init__(self):
self.calls = 0
def request(self, method, path, payload=None):
self.calls += 1
if self.calls == 1:
return 200, b"[]", {"X-Total-Count": "0"}
if self.calls == 2:
assert method == "POST" and payload == expected
return 201, b"", {"Location": "/api/v2.0" + path + "/19"}
return 200, json.dumps([created]).encode(), {"X-Total-Count": "1"}
client = Client()
assert module.ensure_rule(
client,
project="bstein",
repository="hermes-chat-router",
tag_pattern="git-*-build-*",
) == 19
weakened = {**created, "disabled": True}
class Weakened:
origin = module.EXPECTED_ORIGIN
def request(self, *_args, **_kwargs):
return 200, json.dumps([weakened]).encode(), {"X-Total-Count": "1"}
with pytest.raises(RuntimeError, match="not enabled and exact"):
module.ensure_rule(
Weakened(),
project="bstein",
repository="hermes-chat-router",
tag_pattern="git-*-build-*",
)
def test_flux_policy_and_marker_select_only_router_releases() -> None:
"""Candidates remain invisible until the exact release suffix exists."""
documents = list(yaml.safe_load_all(IMAGE_POLICY.read_text(encoding="utf-8")))
repository = next(
item
for item in documents
if item["kind"] == "ImageRepository"
and item["metadata"]["name"] == "hermes-chat-router-release"
)
policy = next(
item
for item in documents
if item["kind"] == "ImagePolicy"
and item["metadata"]["name"] == "hermes-chat-router-release"
)
assert repository["spec"]["image"] == (
"registry.bstein.dev/bstein/hermes-chat-router"
)
assert policy["spec"]["filterTags"]["pattern"].endswith("-release$")
assert policy["spec"]["digestReflectionPolicy"] == "Always"
marker = '"$imagepolicy": "hermes:hermes-chat-router-release"'
assert MANIFEST.read_text(encoding="utf-8").count(marker) == 1
def test_runtime_trigger_and_status_support_only_fixed_router_contract(
tmp_path: Path,
) -> None:
"""Hermes can request and follow router delivery without choosing a job."""
trigger = _load(TRIGGER, "router_release_trigger")
token = tmp_path / "token"
token.write_text("private-token\n", encoding="utf-8")
captured = {}
def opener(request, timeout):
captured["request"] = request
assert timeout == 20
return Response(
201, headers={"Location": "https://ci.bstein.dev/queue/item/42/"}
)
revision = "c" * 40
result = trigger.trigger_build(
revision, component="router", token_file=token, opener=opener
)
fields = urllib.parse.parse_qs(captured["request"].data.decode())
assert fields["job"] == ["hermes-chat-router-image"]
assert fields["CONFIRM_PUBLISH"] == ["PUBLISH HERMES CHAT ROUTER"]
assert "private-token" not in json.dumps(result)
assert result["follow_command"].endswith(
f"--component router --revision {revision} --wait"
)
status = _load(STATUS, "router_release_status")
assert status.COMPONENTS["router"] == {
"policy": "hermes-chat-router-release",
"repository": "registry.bstein.dev/bstein/hermes-chat-router",
"workloads": (
("deployment", "hermes-chat-router", "hermes-chat-router"),
),
}
def test_generic_promoter_accepts_router_and_rejects_other_repositories() -> None:
"""The manifest copier allow-list adds only chat-router."""
module = _load(PROMOTE, "router_promote_contract")
revision = "d" * 40
digest = "sha256:" + "e" * 64
destination = (
"registry.bstein.dev/bstein/hermes-chat-router:"
f"git-{revision}-build-7"
)
component, tag, observed = module._validated_release(
destination, digest, revision, "7"
)
assert component == "hermes-chat-router"
assert tag == f"git-{revision}-build-7"
assert observed == digest
with pytest.raises(ValueError):
module._validated_release(
"registry.bstein.dev/bstein/not-router:git-" + revision + "-build-7",
digest,
revision,
"7",
)