from __future__ import annotations import ast import json import os import re import shutil import subprocess import sys from pathlib import Path from typing import Any import pytest from scripts import bootstrap_controller_artifact_promoter as bootstrap ROOT = Path(__file__).resolve().parents[2] LEGACY_REVISION = "5553c04ce497c08a35ed301052dc5e0b02e5d9b4" LEGACY_TREE = "1624e1748053f998d8427c8c8a4203277d22f13c" APPOPS_CONTAINER_ID = "72daa9f638defc4f8897d8aad23c4181054b2a1bc04c993078a5ac9597eeb0ec" APPOPS_IMAGE_ID = "sha256:9b5eddf32c8eb5361106fc35cc0e056c49d287a20d1fb26490662cc8447f09d4" CONTROLLER_CONTAINER_ID = "e4e7550405aa2a9a91901faf1177d46a35b0fc43c304976ec8971865d43e30eb" CONTROLLER_IMAGE_ID = "sha256:c474d435a4f55ac5205570da0caa47e103f89a6c2ef96ea45861a23eb7b005dc" CONTROLLER_IMAGE_REF = "docker.gitea.com/runner-images:ubuntu-latest" CONTROLLER_REVISION = "034c9f844d2455850ba37b31379b7edaa786583e" CONTROLLER_TREE = "6b8e92942e6e5eca8a9aa9da0a1509b2019d7b7b" REVISION = "a" * 40 TREE = "b" * 40 CALLER_ID = "c" * 64 MODERN_HELPER_ID = "f" * 64 LEGACY_HELPER_SOURCE_FIXTURE = ''' def helper_container_spec(*, revision: str, image_id: str) -> dict[str, Any]: if not _REVISION.fullmatch(revision): raise ValueError("controller_promoter_revision_invalid") if not _IMAGE_ID.fullmatch(image_id): raise ValueError("controller_promoter_image_invalid") command = f"""set -Eeuo pipefail revision='{revision}' version='{CONTROLLER_VERSION}' repo='/gitea-repositories/jens/unraid-appops-gateway.git' work='/mnt/cache/appdata/gitea-deploy-runners/.controller-promoter-{revision[:12]}' rm -rf -- "$work" install -d -m 0700 "$work" cleanup() {{ rm -rf -- "$work"; }} trap cleanup EXIT command -v git >/dev/null command -v docker >/dev/null docker compose version >/dev/null command -v python3 >/dev/null command -v tar >/dev/null command -v curl >/dev/null git --git-dir="$repo" cat-file -e "$revision^{{commit}}" tree="$(git --git-dir="$repo" rev-parse "$revision^{{tree}}")" git --git-dir="$repo" archive --format=tar "$revision" \\ ops/gitea-deploy-broker \\ scripts/prepare_controller_artifact.py \\ scripts/reconcile_controller_state.py \\ scripts/run_repopulse_adoption.py > "$work/controller.tar" archive_sha="$(sha256sum "$work/controller.tar" | cut -d ' ' -f 1)" git --git-dir="$repo" show "$revision:scripts/upgrade_deployment_controller.sh" > "$work/upgrade.sh" chmod 0700 "$work/upgrade.sh" bash "$work/upgrade.sh" \\ --archive "$work/controller.tar" \\ --archive-sha256 "$archive_sha" \\ --source-repository '{SOURCE_REPOSITORY}' \\ --source-revision "$revision" \\ --source-tree "$tree" \\ --controller-version "$version" \\ --confirm UPGRADE_DEPLOYMENT_CONTROLLER_V2 """ return { "Image": image_id, "User": "0:0", "Cmd": ["/bin/bash", "-lc", command], "Labels": { HELPER_LABEL: "true", "itworx.source.repository": SOURCE_REPOSITORY, "itworx.source.revision": revision, "itworx.controller.version": CONTROLLER_VERSION, }, "HostConfig": { "Binds": [ "/mnt/cache:/mnt/cache", "/mnt/user:/mnt/user", "/srv/example/appdata/gitea/git/repositories:/gitea-repositories:ro", "/var/run/docker.sock:/var/run/docker.sock", ], "NetworkMode": "host", "Privileged": False, "SecurityOpt": ["no-new-privileges:true"], "RestartPolicy": {"Name": "no"}, }, } ''' def _legacy_helper_spec() -> dict[str, Any]: tree = ast.parse(LEGACY_HELPER_SOURCE_FIXTURE) function = next( node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == "helper_container_spec" ) namespace: dict[str, Any] = { "re": re, "Any": Any, "_REVISION": re.compile(r"^[0-9a-f]{40}$"), "_IMAGE_ID": re.compile(r"^sha256:[0-9a-f]{64}$"), "SOURCE_REPOSITORY": "jens/unraid-appops-gateway", "HELPER_NAME": "itworx-controller-artifact-promoter", "CONTROLLER_VERSION": "2.1.5", "HELPER_LABEL": "itworx.controller.promoter", } exec( compile(ast.Module(body=[function], type_ignores=[]), "legacy-promoter", "exec"), namespace, ) return namespace["helper_container_spec"](revision=REVISION, image_id=CONTROLLER_IMAGE_ID) def _image(labels: dict[str, str]) -> dict[str, Any]: return {"Id": APPOPS_IMAGE_ID, "Config": {"Labels": dict(labels)}} def _appops_labels() -> dict[str, str]: return { "org.opencontainers.image.source": "https://gitea.example.com/Jens/Unraid-AppOps-Gateway", "org.opencontainers.image.revision": LEGACY_REVISION, "org.opencontainers.image.source-tree": LEGACY_TREE, "org.opencontainers.image.version": "1.8.0-platform-5553c04ce497", "org.opencontainers.image.created": "2026-10-01T16:32:01Z", "com.docker.compose.project": "unraid-appops-gateway", "com.docker.compose.service": "appops-gateway", "com.docker.compose.project.working_dir": "/srv/example/appdata/unraid-appops-gateway/source", "com.docker.compose.project.config_files": "/srv/example/appdata/unraid-appops-gateway/source/docker-compose.yml", } def _controller_labels() -> dict[str, str]: return { "io.buildah.version": "1.35.0", "org.opencontainers.image.authors": "Gitea", "org.opencontainers.image.created": "2026-10-01T00:01:57Z", "org.opencontainers.image.description": "Official docker images used by Gitea runner to run workflows.", "org.opencontainers.image.documentation": "https://gitea.com/gitea/runner-images/src/branch/main/README.md", "org.opencontainers.image.licenses": "MIT", "org.opencontainers.image.revision": "317f8e3c510e5ec773a103f5d9a9e5769105c59b", "org.opencontainers.image.source": "https://gitea.com/gitea/runner-images", "org.opencontainers.image.title": "Official Gitea Actions runner images", "org.opencontainers.image.url": "https://gitea.com/gitea/runner-images", "org.opencontainers.image.vendor": "Gitea", "org.opencontainers.image.version": "v26.10.01", } def _controller_container_labels() -> dict[str, str]: return { **_controller_labels(), "com.docker.compose.config-hash": "577d77247471213db8a5c465ee2450765ba52cb4048f983c42083ac45a16ddb7", "com.docker.compose.container-number": "1", "com.docker.compose.depends_on": "", "com.docker.compose.image": "sha256:adbb51b2873bf05546019c341caf6fd4368159546c36a5aea81b4d8bf088804e", "com.docker.compose.oneoff": "False", "com.docker.compose.project": "gitea-deploy-runners", "com.docker.compose.project.config_files": "/mnt/cache/appdata/gitea-deploy-runners/docker-compose.yml", "com.docker.compose.project.working_dir": "/mnt/cache/appdata/gitea-deploy-runners", "com.docker.compose.replace": "gitea-deploy-control", "com.docker.compose.service": "deploy-control", "com.docker.compose.version": "2.40.3", "tech.itworx.deployment.target": "unraid", "tech.itworx.role": "gitea-deploy-controller", "tech.itworx.gitea.scope": "Jens", } def _modern_controller_labels() -> dict[str, str]: return { **_controller_labels(), "com.docker.compose.config-hash": "a" * 64, "com.docker.compose.container-number": "1", "com.docker.compose.depends_on": "", "com.docker.compose.image": CONTROLLER_IMAGE_ID, "com.docker.compose.oneoff": "False", "com.docker.compose.project": "gitea-deploy-runners", "com.docker.compose.project.config_files": "-", "com.docker.compose.project.environment_file": "/dev/null", "com.docker.compose.project.working_dir": "/var/lib/docker/volumes/itworx-controller-hub-releases/_data", "com.docker.compose.replace": "gitea-deploy-control", "com.docker.compose.service": "deploy-control", "com.docker.compose.version": "2.40.3", "tech.itworx.deployment.target": "unraid", "tech.itworx.gitea.scope": "Jens", "tech.itworx.role": "gitea-deploy-controller", } def _modern_controller_mounts(source_revision: str) -> list[dict[str, Any]]: artifact_source = ( "/var/lib/docker/volumes/itworx-controller-hub-releases/_data/" f"controller-artifacts/{source_revision}" ) return [ {"Type": "bind", "Source": "/boot", "Destination": "/boot", "RW": True, "Propagation": "rprivate"}, {"Type": "bind", "Source": "/var/lib/docker", "Destination": "/var/lib/docker", "RW": False, "Propagation": "rslave"}, { "Type": "bind", "Source": "/srv/example/appdata/gitea/git/repositories", "Destination": "/gitea-repositories", "RW": False, "Propagation": "rprivate", }, {"Type": "bind", "Source": artifact_source, "Destination": "/opt/gitea-deploy", "RW": False, "Propagation": "rprivate"}, {"Type": "bind", "Source": "/var/run/docker.sock", "Destination": "/var/run/docker.sock", "RW": True, "Propagation": "rprivate"}, {"Type": "bind", "Source": "/mnt/user", "Destination": "/mnt/user", "RW": True, "Propagation": "rprivate"}, {"Type": "bind", "Source": "/mnt/cache", "Destination": "/mnt/cache", "RW": True, "Propagation": "rprivate"}, {"Type": "bind", "Source": "/mnt/disks", "Destination": "/mnt/disks", "RW": False, "Propagation": "rprivate"}, { "Type": "volume", "Name": "itworx-controller-hub-releases", "Source": "/var/lib/docker/volumes/itworx-controller-hub-releases/_data", "Destination": "/controller-hub-releases", "RW": True, "Propagation": "", }, { "Type": "bind", "Source": "/var/lib/docker/volumes/itworx-controller-hub-releases/_data/controller-config", "Destination": "/controller-config", "RW": False, "Propagation": "rprivate", }, ] def _modern_controller_inspect(source_revision: str) -> dict[str, Any]: controller = _controller_inspect() controller.update({"Id": "e" * 64, "Image": CONTROLLER_IMAGE_ID}) controller["Config"]["Image"] = CONTROLLER_IMAGE_ID controller["Config"]["Labels"] = _modern_controller_labels() controller["Mounts"] = _modern_controller_mounts(source_revision) return controller def _appops_inspect() -> dict[str, Any]: labels = _appops_labels() return { "Id": APPOPS_CONTAINER_ID, "Image": APPOPS_IMAGE_ID, "Name": "/Unraid-AppOps-Gateway", "State": {"Running": True, "Health": {"Status": "healthy"}}, "Config": { "Hostname": "unraid-appops-gateway", "Image": "unraid-appops-gateway:platform-5553c04ce497", "Labels": labels, }, "HostConfig": {"RestartPolicy": {"Name": "unless-stopped"}}, "Mounts": [ { "Type": "bind", "Source": "/srv/example/appdata/unraid-appops-gateway-1216", "Destination": "/data", "RW": True, }, { "Type": "bind", "Source": "/srv/example/appdata/comfyui-nvidia/basedir/models", "Destination": "/mnt/privatemuse-comfyui-models", "RW": True, }, ], } def _controller_inspect() -> dict[str, Any]: return { "Id": CONTROLLER_CONTAINER_ID, "Image": CONTROLLER_IMAGE_ID, "Name": "/gitea-deploy-control", "State": {"Running": True}, "Config": {"Image": CONTROLLER_IMAGE_REF, "Labels": _controller_container_labels()}, "HostConfig": { "Binds": [ "/mnt/user:/mnt/user:rw", "/mnt/cache:/mnt/cache:rw", "/mnt/disks:/mnt/disks:ro", "/boot:/boot:rw", "/var/lib/docker:/var/lib/docker:ro", "/srv/example/appdata/gitea/git/repositories:/gitea-repositories:ro", "/mnt/cache/appdata/gitea-deploy-runners/controller:/opt/gitea-deploy:ro", "/var/run/docker.sock:/var/run/docker.sock:rw", ], "Mounts": None, }, "Mounts": [ { "Type": "bind", "Source": "/boot", "Destination": "/boot", "RW": True, "Propagation": "rprivate", }, { "Type": "bind", "Source": "/var/lib/docker", "Destination": "/var/lib/docker", "RW": False, "Propagation": "rslave", }, { "Type": "bind", "Source": "/srv/example/appdata/gitea/git/repositories", "Destination": "/gitea-repositories", "RW": False, "Propagation": "rprivate", }, { "Type": "bind", "Source": "/mnt/cache/appdata/gitea-deploy-runners/controller", "Destination": "/opt/gitea-deploy", "RW": False, "Propagation": "rprivate", }, { "Type": "bind", "Source": "/var/run/docker.sock", "Destination": "/var/run/docker.sock", "RW": True, "Propagation": "rprivate", }, { "Type": "bind", "Source": "/mnt/user", "Destination": "/mnt/user", "RW": True, "Propagation": "rprivate", }, { "Type": "bind", "Source": "/mnt/cache", "Destination": "/mnt/cache", "RW": True, "Propagation": "rprivate", }, { "Type": "bind", "Source": "/mnt/disks", "Destination": "/mnt/disks", "RW": False, "Propagation": "rprivate", }, ], } def _inspect_from_spec( name: str, identifier: str, image_id: str, spec: dict[str, Any], *, state: dict[str, Any], ) -> dict[str, Any]: return { "Id": identifier, "Image": image_id, "Name": f"/{name}", "State": dict(state), "Config": { "Image": spec["Image"], "User": spec["User"], "Cmd": spec["Cmd"], "Labels": {**_controller_labels(), **spec["Labels"]}, "Hostname": identifier[:12], }, "HostConfig": {"ReadonlyRootfs": False, **spec["HostConfig"]}, } class FakeDocker: def __init__(self, caller_spec: dict[str, Any], *, existing_child: dict[str, Any] | None = None): self.containers = { APPOPS_CONTAINER_ID: _appops_inspect(), "gitea-deploy-control": _controller_inspect(), CALLER_ID[:12]: _inspect_from_spec( "itworx-controller-artifact-promoter", CALLER_ID, CONTROLLER_IMAGE_ID, caller_spec, state={"Running": True}, ), } self.images = { APPOPS_IMAGE_ID: _image(_appops_labels()), CONTROLLER_IMAGE_ID: { "Id": CONTROLLER_IMAGE_ID, "Config": {"Labels": _controller_labels()}, }, } self.existing_child = existing_child self.created: list[dict[str, Any]] = [] self.started: list[str] = [] self.log_bytes = b"controller_v2_ready revision=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" def inspect_container(self, reference: str) -> dict[str, Any] | None: if reference == "itworx-controller-artifact-promoter-modern": return self.existing_child return self.containers.get(reference) def inspect_image(self, image_id: str) -> dict[str, Any] | None: return self.images.get(image_id) def create_container(self, name: str, spec: dict[str, Any]) -> str: assert name == "itworx-controller-artifact-promoter-modern" self.created.append(spec) container = _inspect_from_spec( name, MODERN_HELPER_ID, spec["Image"], spec, state={"Running": False, "ExitCode": 0, "Status": "exited"}, ) self.containers[name] = container self.containers[MODERN_HELPER_ID] = container return MODERN_HELPER_ID def start_container(self, identifier: str) -> None: self.started.append(identifier) source_revision = self.created[-1]["Labels"]["itworx.source.revision"] self.containers["gitea-deploy-control"] = _modern_controller_inspect(source_revision) def container_logs(self, identifier: str, *, tail: int, max_bytes: int) -> bytes: assert identifier == MODERN_HELPER_ID assert tail <= 40 assert max_bytes <= 8192 return self.log_bytes[:max_bytes] class FakeProtectedGit: def __init__(self, helper_source: str): self.revision = REVISION self.tree = TREE self.helper_source = helper_source self.blobs_read: list[tuple[str, str]] = [] self.main_checks = 0 def verify_current_main(self, revision: str, tree: str) -> None: self.main_checks += 1 if (revision, tree) != (self.revision, self.tree): raise bootstrap.BootstrapError("controller_promoter_source_not_current_main") def verify_legacy_revision(self, revision: str, tree: str) -> None: if (revision, tree) != (LEGACY_REVISION, LEGACY_TREE): raise bootstrap.BootstrapError("controller_promoter_legacy_provenance_invalid") def read_blob(self, revision: str, path: str) -> bytes: self.blobs_read.append((revision, path)) if revision != self.revision or path != "backend/app/services/controller_artifact_helper_spec.py": raise bootstrap.BootstrapError("controller_promoter_source_blob_invalid") return self.helper_source.encode("utf-8") def read_legacy_blob(self, revision: str, path: str) -> bytes: self.blobs_read.append((revision, path)) if (revision, path) != ( LEGACY_REVISION, "backend/app/services/controller_artifact_promoter.py", ): raise bootstrap.BootstrapError("controller_promoter_legacy_blob_invalid") return LEGACY_HELPER_SOURCE_FIXTURE.encode("utf-8") def _health( *, revision: str = REVISION, tree: str = TREE, version: str = "2.1.14", artifact_sha256: str = "1" * 64, ) -> dict[str, Any]: return { "status": "ok", "protocolVersion": "itworx-controller-request/v2", "artifact": { "apiVersion": "itworx.controller-artifact/v1", "sourceRepository": "jens/unraid-appops-gateway", "sourceRevision": revision, "sourceTree": tree, "controllerVersion": version, "artifactSha256": artifact_sha256, }, "delivery": { "health": { "configuration": "healthy", "inbox": "healthy", "artifact": "healthy", } }, } def _run_bootstrap( docker: FakeDocker, git: FakeProtectedGit, *, health: dict[str, Any] | None = None, initial_health: dict[str, Any] | None = None, hostname: str = CALLER_ID[:12], ): source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_text( encoding="utf-8" ) git.helper_source = source initial = initial_health or _health( revision=CONTROLLER_REVISION, tree=CONTROLLER_TREE, version="2.1.12", artifact_sha256=bootstrap.LEGACY_CONTROLLER_ARTIFACT_SHA256, ) health_reads = [initial, health or _health()] def read_health() -> dict[str, Any]: return health_reads.pop(0) if health_reads else health or _health() return bootstrap.run_bootstrap( "jens/unraid-appops-gateway", REVISION, TREE, docker=docker, protected_git=git, hostname=hostname, health_reader=read_health, monotonic=lambda: 0.0, sleeper=lambda _seconds: None, child_timeout_seconds=0.0, ) def test_initial_controller_runtime_accepts_exact_current_compose_container(): docker = FakeDocker(_legacy_helper_spec()) base_image_labels = bootstrap._inspect_controller_base_image(docker) health = _health( revision=CONTROLLER_REVISION, tree=CONTROLLER_TREE, version="2.1.12", artifact_sha256=bootstrap.LEGACY_CONTROLLER_ARTIFACT_SHA256, ) assert ( bootstrap._verify_controller_runtime( docker, health_payload=health, base_image_labels=base_image_labels, ) == base_image_labels ) def test_immutable_legacy_helper_source_fixture_passes_bootstrap_identity_guard(): legacy_spec = _legacy_helper_spec() docker = FakeDocker(legacy_spec) git = FakeProtectedGit("") result = _run_bootstrap(docker, git) assert result["status"] == "promoted" assert docker.created assert git.blobs_read == [ (REVISION, "backend/app/services/controller_artifact_helper_spec.py"), (LEGACY_REVISION, "backend/app/services/controller_artifact_promoter.py"), ] @pytest.mark.parametrize( "mutation", [ "caller-image", "caller-label", "caller-revision", "caller-command", "caller-bind", "appops-image", "appops-provenance", "target-id", "target-source", "target-tree", "target-version", "old-target-id", "old-target-image", "old-target-version", "target-config-image", "target-compose-hash", "target-compose-missing", "target-compose-extra", "target-compose-image", "target-tech-label", "target-tech-extra", "target-path", "target-extra-mount", "target-bind-mode", "base-image-installed-provenance", ], ) def test_bootstrap_rejects_unknown_runtime_identity_without_creating_child(mutation: str): docker = FakeDocker(_legacy_helper_spec()) if mutation == "caller-image": docker.containers[CALLER_ID[:12]]["Image"] = "sha256:" + "9" * 64 elif mutation == "caller-label": docker.containers[CALLER_ID[:12]]["Config"]["Labels"]["itworx.controller.version"] = "2.1.4" elif mutation == "caller-revision": docker.containers[CALLER_ID[:12]]["Config"]["Labels"]["itworx.source.revision"] = "d" * 40 elif mutation == "caller-command": docker.containers[CALLER_ID[:12]]["Config"]["Cmd"].append("--image-id") elif mutation == "caller-bind": docker.containers[CALLER_ID[:12]]["HostConfig"]["Binds"].append("/tmp:/payload") elif mutation == "appops-image": docker.containers[APPOPS_CONTAINER_ID]["Image"] = "sha256:" + "8" * 64 elif mutation == "appops-provenance": docker.images[APPOPS_IMAGE_ID]["Config"]["Labels"]["org.opencontainers.image.revision"] = "f" * 40 elif mutation == "target-id": docker.containers["gitea-deploy-control"]["Id"] = "0" * 64 elif mutation == "old-target-id": docker.containers["gitea-deploy-control"]["Id"] = "5539b672e66890e657d0f3b0e6e94f9c9ca5b47c245e120d330f6f1d5be73fae" elif mutation == "old-target-image": docker.containers["gitea-deploy-control"]["Image"] = "sha256:adbb51b2873bf05546019c341caf6fd4368159546c36a5aea81b4d8bf088804e" elif mutation == "target-source": docker.containers["gitea-deploy-control"]["Config"]["Labels"]["org.opencontainers.image.revision"] = "f" * 40 elif mutation == "target-tree": docker.containers["gitea-deploy-control"]["Config"]["Labels"]["org.opencontainers.image.source-tree"] = "f" * 40 elif mutation == "target-version": docker.containers["gitea-deploy-control"]["Config"]["Labels"]["org.opencontainers.image.version"] = "2.1.11" elif mutation == "old-target-version": docker.containers["gitea-deploy-control"]["Config"]["Labels"]["org.opencontainers.image.version"] = "v26.09.29" elif mutation == "target-config-image": docker.containers["gitea-deploy-control"]["Config"]["Image"] = "docker.gitea.com/runner-images:ubuntu-22.04" elif mutation == "target-compose-hash": docker.containers["gitea-deploy-control"]["Config"]["Labels"]["com.docker.compose.config-hash"] = "0" * 64 elif mutation == "target-compose-missing": docker.containers["gitea-deploy-control"]["Config"]["Labels"].pop("com.docker.compose.service") elif mutation == "target-compose-extra": docker.containers["gitea-deploy-control"]["Config"]["Labels"]["com.docker.compose.container_name"] = "gitea-deploy-control" elif mutation == "target-compose-image": docker.containers["gitea-deploy-control"]["Config"]["Labels"]["com.docker.compose.image"] = CONTROLLER_IMAGE_ID elif mutation == "target-tech-label": docker.containers["gitea-deploy-control"]["Config"]["Labels"]["tech.itworx.role"] = "other" elif mutation == "target-tech-extra": docker.containers["gitea-deploy-control"]["Config"]["Labels"]["tech.itworx.extra"] = "unexpected" elif mutation == "target-path": docker.containers["gitea-deploy-control"]["Mounts"][3]["Destination"] = "/controller-config" elif mutation == "target-extra-mount": docker.containers["gitea-deploy-control"]["Mounts"].append( { "Type": "bind", "Source": "/tmp", "Destination": "/tmp", "RW": True, "Propagation": "rprivate", } ) docker.containers["gitea-deploy-control"]["HostConfig"]["Binds"].append( "/tmp:/tmp:rw" ) elif mutation == "target-bind-mode": docker.containers["gitea-deploy-control"]["HostConfig"]["Binds"][6] = ( "/mnt/cache/appdata/gitea-deploy-runners/controller:/opt/gitea-deploy:rw" ) docker.containers["gitea-deploy-control"]["Mounts"][3]["RW"] = True elif mutation == "base-image-installed-provenance": labels = docker.images[CONTROLLER_IMAGE_ID]["Config"]["Labels"] labels["org.opencontainers.image.revision"] = CONTROLLER_REVISION labels["org.opencontainers.image.source-tree"] = CONTROLLER_TREE git = FakeProtectedGit("") with pytest.raises(bootstrap.BootstrapError): _run_bootstrap(docker, git) assert docker.created == [] @pytest.mark.parametrize( "mutation", ["revision", "tree", "version", "artifact-sha", "delivery-health"], ) def test_bootstrap_requires_exact_mounted_controller_artifact_health(mutation: str): initial = _health( revision=CONTROLLER_REVISION, tree=CONTROLLER_TREE, version="2.1.12", artifact_sha256=bootstrap.LEGACY_CONTROLLER_ARTIFACT_SHA256, ) artifact = initial["artifact"] if mutation == "revision": artifact["sourceRevision"] = "9" * 40 elif mutation == "tree": artifact["sourceTree"] = "9" * 40 elif mutation == "version": artifact["controllerVersion"] = "2.1.11" elif mutation == "artifact-sha": artifact["artifactSha256"] = "9" * 64 elif mutation == "delivery-health": initial["delivery"]["health"]["artifact"] = "degraded" docker = FakeDocker(_legacy_helper_spec()) git = FakeProtectedGit("") with pytest.raises(bootstrap.BootstrapError): _run_bootstrap(docker, git, initial_health=initial) assert docker.created == [] def test_running_modern_child_returns_unknown_without_duplicate_or_stop(): helper_source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_text( encoding="utf-8" ) helper_spec = bootstrap.render_modern_helper_spec( helper_source, revision=REVISION, image_id=CONTROLLER_IMAGE_ID, current_controller_id=CONTROLLER_CONTAINER_ID, current_controller_revision=CONTROLLER_REVISION, current_controller_tree=CONTROLLER_TREE, current_controller_version="2.1.12", ) existing = _inspect_from_spec( "itworx-controller-artifact-promoter-modern", MODERN_HELPER_ID, CONTROLLER_IMAGE_ID, helper_spec, state={"Running": True, "Status": "running"}, ) docker = FakeDocker(_legacy_helper_spec(), existing_child=existing) git = FakeProtectedGit(helper_source) result = _run_bootstrap(docker, git, health=_health()) assert result["status"] == "outcome_unknown" assert result["reason"] == "controller_promoter_modern_helper_running" assert docker.created == [] assert docker.started == [] def test_completed_modern_child_with_wrong_artifact_is_not_relaunched(): helper_source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_text( encoding="utf-8" ) helper_spec = bootstrap.render_modern_helper_spec( helper_source, revision=REVISION, image_id=CONTROLLER_IMAGE_ID, current_controller_id=CONTROLLER_CONTAINER_ID, current_controller_revision=CONTROLLER_REVISION, current_controller_tree=CONTROLLER_TREE, current_controller_version="2.1.12", ) existing = _inspect_from_spec( "itworx-controller-artifact-promoter-modern", MODERN_HELPER_ID, CONTROLLER_IMAGE_ID, helper_spec, state={"Running": False, "ExitCode": 0, "Status": "exited"}, ) docker = FakeDocker(_legacy_helper_spec(), existing_child=existing) git = FakeProtectedGit(helper_source) result = _run_bootstrap( docker, git, initial_health=_health(tree="9" * 40), ) assert result["status"] == "outcome_unknown" assert result["reason"] == "controller_promoter_postcheck_failed" assert docker.created == [] def test_completed_child_uses_new_health_after_controller_recreation(): helper_source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_text( encoding="utf-8" ) helper_spec = bootstrap.render_modern_helper_spec( helper_source, revision=REVISION, image_id=CONTROLLER_IMAGE_ID, current_controller_id=CONTROLLER_CONTAINER_ID, current_controller_revision=CONTROLLER_REVISION, current_controller_tree=CONTROLLER_TREE, current_controller_version="2.1.12", ) existing = _inspect_from_spec( "itworx-controller-artifact-promoter-modern", MODERN_HELPER_ID, CONTROLLER_IMAGE_ID, helper_spec, state={"Running": False, "ExitCode": 0, "Status": "exited"}, ) docker = FakeDocker(_legacy_helper_spec(), existing_child=existing) docker.containers["gitea-deploy-control"] = _modern_controller_inspect(REVISION) git = FakeProtectedGit(helper_source) result = _run_bootstrap( docker, git, initial_health=_health(), ) assert result["status"] == "already_applied" assert result["revision"] == REVISION assert docker.created == [] assert docker.started == [] @pytest.mark.parametrize( "mutation", [ "post-image-config", "post-compose-hash", "post-compose-image", "post-compose-extra", "post-compose-environment-file", "post-tech-label", "post-tech-extra", "post-artifact-revision", "post-volume-name", "post-volume-source", "post-volume-mode", "post-config-bind-source", "post-config-bind-mode", "post-extra-mount", ], ) def test_postactivation_controller_requires_exact_compose_runtime(mutation: str): docker = FakeDocker(_legacy_helper_spec()) controller = _modern_controller_inspect(REVISION) docker.containers["gitea-deploy-control"] = controller if mutation == "post-image-config": controller["Config"]["Image"] = CONTROLLER_IMAGE_REF elif mutation == "post-compose-hash": controller["Config"]["Labels"]["com.docker.compose.config-hash"] = "bad" elif mutation == "post-compose-image": controller["Config"]["Labels"]["com.docker.compose.image"] = "sha256:" + "d" * 64 elif mutation == "post-compose-extra": controller["Config"]["Labels"]["com.docker.compose.extra"] = "unexpected" elif mutation == "post-compose-environment-file": controller["Config"]["Labels"]["com.docker.compose.project.environment_file"] = "/tmp/.env" elif mutation == "post-tech-label": controller["Config"]["Labels"]["tech.itworx.gitea.scope"] = "Other" elif mutation == "post-tech-extra": controller["Config"]["Labels"]["tech.itworx.extra"] = "unexpected" elif mutation == "post-artifact-revision": controller["Mounts"][3]["Source"] = ( "/var/lib/docker/volumes/itworx-controller-hub-releases/_data/" f"controller-artifacts/{'d' * 40}" ) elif mutation == "post-volume-name": controller["Mounts"][8]["Name"] = "foreign-volume" elif mutation == "post-volume-source": controller["Mounts"][8]["Source"] = "/tmp/foreign-volume" elif mutation == "post-volume-mode": controller["Mounts"][8]["RW"] = False elif mutation == "post-config-bind-source": controller["Mounts"][9]["Source"] = "/tmp/config" elif mutation == "post-config-bind-mode": controller["Mounts"][9]["RW"] = True elif mutation == "post-extra-mount": controller["Mounts"].append(dict(controller["Mounts"][0])) assert not bootstrap._verified_live_controller(docker, source_revision=REVISION) def test_postactivation_accepts_well_formed_compose_hash(): docker = FakeDocker(_legacy_helper_spec()) controller = _modern_controller_inspect(REVISION) controller["Config"]["Labels"]["com.docker.compose.config-hash"] = "b" * 64 docker.containers["gitea-deploy-control"] = controller assert bootstrap._verified_live_controller(docker, source_revision=REVISION) def test_successful_child_requires_exact_source_tree_version_and_health(): docker = FakeDocker(_legacy_helper_spec()) git = FakeProtectedGit("") result = _run_bootstrap(docker, git) assert result["status"] == "promoted" assert result["revision"] == REVISION assert result["controllerVersion"] == "2.1.14" assert result["artifactSha256"] == "1" * 64 assert docker.started == [MODERN_HELPER_ID] assert docker.created[0]["Image"] == CONTROLLER_IMAGE_ID assert docker.created[0]["HostConfig"]["ReadonlyRootfs"] is True def test_child_logs_are_bounded_and_redacted_before_result_output(): docker = FakeDocker(_legacy_helper_spec()) docker.log_bytes = ( b"controller_v2_ready revision=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" b"GITEA_ADMIN_TOKEN=very-secret-value\n" ) git = FakeProtectedGit("") result = _run_bootstrap(docker, git) assert result["status"] == "promoted" assert "very-secret-value" not in result["logTail"] assert "[redacted]" in result["logTail"] assert len(result["logTail"].encode("utf-8")) <= 4096 @pytest.mark.parametrize( "temporary_root_info,accepted", [ ("1777:0:0", True), ("700:0:0", True), ("777:0:0", False), ("755:0:0", False), ("770:0:0", False), ("1700:0:0", False), ("1777:1000:0", False), ("700:0:1000", False), ], ) def test_legacy_upgrade_signature_fetches_bootstrap_only_from_protected_git( tmp_path: Path, temporary_root_info: str, accepted: bool ): bash = shutil.which("bash") if bash is None: pytest.skip("bash is required to exercise the upgrade script") bin_dir = tmp_path / "bin" bin_dir.mkdir() git_trace = tmp_path / "git-args.jsonl" python_trace = tmp_path / "python-args.json" protected_source = "# trusted protected Git bootstrap source\n" git_shim = bin_dir / "git" git_shim.write_text( f"#!{sys.executable}\n" "import json, os, sys\n" f"trace = {str(git_trace)!r}\n" f"with open(trace, 'a', encoding='utf-8') as stream: stream.write(json.dumps(sys.argv[1:]) + '\\n')\n" "args = [arg for arg in sys.argv[1:] if not arg.startswith('--git-dir=')]\n" f"revision = {REVISION!r}\n" f"tree = {TREE!r}\n" "if args[-3:] == ['rev-parse', 'refs/heads/main', '--'] or args[-2:] == ['rev-parse', 'refs/heads/main']: print(revision)\n" "elif any(arg.endswith('^{tree}') for arg in args): print(tree)\n" f"elif len(args) >= 3 and args[:2] == ['cat-file', '-s']: print({len(protected_source)})\n" "elif len(args) >= 3 and args[0] == 'cat-file' and args[1] == 'blob' and args[-1].endswith(':scripts/bootstrap_controller_artifact_promoter.py'):\n" f" sys.stdout.write({protected_source!r})\n" "else: raise SystemExit(91)\n", encoding="utf-8", ) stat_shim = bin_dir / "stat" stat_shim.write_text( f"#!{sys.executable}\n" "import pathlib, sys\n" "args = sys.argv[1:]\n" "target = args[-1]\n" f"if target == '/tmp': print({temporary_root_info!r})\n" "elif args[1] == '%u:%a': print('0:700')\n" "elif args[1] == '%u:%a:%s': print('0:600:' + str(pathlib.Path(target).stat().st_size))\n" "else: raise SystemExit(1)\n", encoding="utf-8", ) python_shim = bin_dir / "python3" python_shim.write_text( f"#!{sys.executable}\n" "import json, pathlib, sys\n" f"path = pathlib.Path({str(python_trace)!r})\n" "path.write_text(json.dumps({'script': pathlib.Path(sys.argv[1]).read_text(), 'args': sys.argv[2:]}), encoding='utf-8')\n", encoding="utf-8", ) git_shim.chmod(0o755) stat_shim.chmod(0o755) python_shim.chmod(0o755) archive = f"/mnt/cache/appdata/gitea-deploy-runners/.controller-promoter-{REVISION[:12]}/controller.tar" result = subprocess.run( [ bash, str(ROOT / "scripts/upgrade_deployment_controller.sh"), "--archive", archive, "--archive-sha256", "a" * 64, "--source-repository", "jens/unraid-appops-gateway", "--source-revision", REVISION, "--source-tree", TREE, "--controller-version", "2.1.5", "--confirm", "UPGRADE_DEPLOYMENT_CONTROLLER_V2", ], cwd=ROOT, env={**os.environ, "PATH": f"{bin_dir}{os.pathsep}{os.environ['PATH']}"}, capture_output=True, text=True, check=False, ) if not accepted: assert result.returncode == 66, result.stderr assert "temporary root is unsafe" in result.stderr assert not python_trace.exists() calls = [json.loads(line) for line in git_trace.read_text(encoding="utf-8").splitlines()] assert not any("cat-file" in args for args in calls) return assert result.returncode == 0, result.stderr bootstrap_call = json.loads(python_trace.read_text(encoding="utf-8")) assert bootstrap_call["script"] == protected_source assert bootstrap_call["args"] == [ "--source-repository", "jens/unraid-appops-gateway", "--source-revision", REVISION, "--source-tree", TREE, ] calls = [json.loads(line) for line in git_trace.read_text(encoding="utf-8").splitlines()] assert any(args[-1:] == [f"{REVISION}:scripts/bootstrap_controller_artifact_promoter.py"] for args in calls) assert all(archive not in args for args in calls) @pytest.mark.parametrize( "mutation", ["source", "revision", "tree", "version", "extra", "image-id"], ) def test_upgrade_script_rejects_mutated_legacy_call_without_bootstrap( tmp_path: Path, mutation: str ): bash = shutil.which("bash") if bash is None: pytest.skip("bash is required to exercise the upgrade script") bin_dir = tmp_path / "bin" bin_dir.mkdir() marker = tmp_path / "bootstrap-ran" python_shim = bin_dir / "python3" python_shim.write_text( f"#!/usr/bin/env python3\nfrom pathlib import Path\nPath({str(marker)!r}).write_text('ran')\n", encoding="utf-8", ) python_shim.chmod(0o755) args = [ "--archive", f"/mnt/cache/appdata/gitea-deploy-runners/.controller-promoter-{REVISION[:12]}/controller.tar", "--archive-sha256", "a" * 64, "--source-repository", "jens/unraid-appops-gateway", "--source-revision", REVISION, "--source-tree", TREE, "--controller-version", "2.1.5", "--confirm", "UPGRADE_DEPLOYMENT_CONTROLLER_V2", ] if mutation == "source": args[args.index("jens/unraid-appops-gateway")] = "attacker/repo" elif mutation == "revision": args[args.index(REVISION)] = "A" * 40 elif mutation == "tree": args[args.index(TREE)] = "f" * 40 elif mutation == "version": args[args.index("2.1.5")] = "2.1.4" elif mutation == "extra": args.extend(["--unexpected", "value"]) elif mutation == "image-id": args.extend(["--image-id", CONTROLLER_IMAGE_ID]) result = subprocess.run( [bash, str(ROOT / "scripts/upgrade_deployment_controller.sh"), *args], cwd=ROOT, env={**os.environ, "PATH": f"{bin_dir}{os.pathsep}{os.environ['PATH']}"}, capture_output=True, text=True, check=False, ) assert result.returncode != 0 assert not marker.exists() @pytest.mark.parametrize("modern_call", ["missing-image", "shared-archive"]) @pytest.mark.parametrize("temporary_root_info", ["1777:0:0", "700:0:0", "777:0:0"]) def test_modern_upgrade_path_still_requires_private_archive_and_image_id( tmp_path: Path, modern_call: str, temporary_root_info: str ): bash = shutil.which("bash") if bash is None: pytest.skip("bash is required to exercise the upgrade script") bin_dir = tmp_path / "bin" bin_dir.mkdir() stat_shim = bin_dir / "stat" stat_shim.write_text( f"#!{sys.executable}\nimport sys\n" f"if sys.argv[-1] == '/tmp': print({temporary_root_info!r})\n" "else: raise SystemExit(91)\n", encoding="utf-8", ) stat_shim.chmod(0o755) archive = ( f"/tmp/itworx-controller-artifact-promoter-{REVISION[:12]}.AAAAAA/controller.tar" if modern_call == "missing-image" else f"/mnt/cache/appdata/gitea-deploy-runners/.controller-promoter-{REVISION[:12]}/controller.tar" ) args = [ "--archive", archive, "--archive-sha256", "a" * 64, "--source-repository", "jens/unraid-appops-gateway", "--source-revision", REVISION, "--source-tree", TREE, "--controller-version", "2.1.14", "--confirm", "UPGRADE_DEPLOYMENT_CONTROLLER_V2", ] if modern_call == "shared-archive": args.extend(["--image-id", CONTROLLER_IMAGE_ID]) result = subprocess.run( [bash, str(ROOT / "scripts/upgrade_deployment_controller.sh"), *args], cwd=ROOT, env={**os.environ, "PATH": f"{bin_dir}{os.pathsep}{os.environ['PATH']}"}, capture_output=True, text=True, check=False, ) assert result.returncode != 0 if modern_call == "shared-archive": if temporary_root_info == "777:0:0": assert "helper temporary root is unsafe" in result.stderr else: assert "archive is outside the helper private temporary directory" in result.stderr if modern_call == "missing-image": assert "image ID" in result.stderr