Files
Unraid-AppOps-Gateway-Public/backend/tests/test_controller_hub_release_volume.py
T

2638 lines
113 KiB
Python

from __future__ import annotations
import ast
import hashlib
import importlib.util
import io
import json
import os
import stat
import subprocess
import sys
import tarfile
import types
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
spec = importlib.util.spec_from_file_location(
"controller_hub_volume", ROOT / "scripts/prepare_controller_hub_release_volume.py"
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
REVISION = "a" * 40
IMAGE_ID = "sha256:" + "b" * 64
ORIGINAL_COMPOSE = b"name: gitea-deploy-runners\nservices:\n deploy-control:\n image: immutable-controller\n"
# Frozen historical layouts; these fixtures must not follow the acceptance registry.
HISTORICAL_PRE_CANDIDATE_RELEASE_FILES = (
"controller_paths.py", "mcp_hub_release_store.py", "deploy.py",
"managed_appdata_paths.py", "prepare_managed_appdata_root.py", "runner_fleet.py",
"runner_capacity_controller.py", "runner_capacity_policy.py",
"runner_fleet_upgrade_v2.py", "status_server.py",
"repopulse_adoption_store.py", "repopulse_registry_restore.py",
"repopulse_adoption_executor.py", "repopulse_adoption_effects.py",
"repopulse_adoption_storage_effects.py", "repopulse_target_attestation.py",
"stage_repopulse_target_attestation.py", "repopulse_adoption_cli.py",
"stage_repopulse_maintenance_environment.py", "run_repopulse_adoption.py",
)
HISTORICAL_CANDIDATE_HELPER_RELEASE_FILES = (
"controller_paths.py", "mcp_hub_release_store.py", "deploy.py",
"managed_appdata_paths.py", "prepare_managed_appdata_root.py", "runner_fleet.py",
"runner_capacity_controller.py", "runner_capacity_policy.py",
"runner_fleet_upgrade_v2.py", "status_server.py", "prepare_mcp_hub_candidate.py",
"repopulse_adoption_store.py", "repopulse_registry_restore.py",
"repopulse_adoption_executor.py", "repopulse_adoption_effects.py",
"repopulse_adoption_storage_effects.py", "repopulse_target_attestation.py",
"stage_repopulse_target_attestation.py", "repopulse_adoption_cli.py",
"stage_repopulse_maintenance_environment.py", "run_repopulse_adoption.py",
)
HISTORICAL_CONFIG_ROOT_RELEASE_FILES = (
"controller_paths.py", "mcp_hub_release_store.py", "hub_permission_preparation.py",
"deploy.py", "managed_appdata_paths.py", "prepare_managed_appdata_root.py", "runner_fleet.py",
"runner_capacity_controller.py", "runner_capacity_policy.py", "runner_fleet_upgrade_v2.py",
"runner_profiles.py", "runner_admission_policy.py", "runner_admission_controller.py",
"runner_resource_envelope.py", "runner_fleet_specialize.py", "runner_statistics_binding.py",
"status_server.py", "repopulse_adoption_store.py", "repopulse_registry_restore.py",
"repopulse_adoption_executor.py", "repopulse_adoption_effects.py",
"repopulse_adoption_storage_effects.py", "repopulse_target_attestation.py",
"stage_repopulse_target_attestation.py", "repopulse_adoption_cli.py",
"stage_repopulse_maintenance_environment.py", "run_repopulse_adoption.py",
)
def volume():
return {
"Name": module.VOLUME,
"Driver": "local",
"Scope": "local",
"Options": None,
"Labels": dict(module.LABELS),
"Mountpoint": module.MOUNTPOINT.as_posix(),
}
@pytest.mark.parametrize(
("key", "value"),
[
("Name", "operator-volume"),
("Driver", "nfs"),
("Scope", "global"),
("Options", {"device": "/mnt/cache/appdata"}),
("Labels", {}),
("Mountpoint", "/mnt/cache/unsafe"),
],
)
def test_unknown_or_redirected_volume_is_never_adopted(key, value):
item = volume()
item[key] = value
with pytest.raises(module.VolumeTransitionError, match="identity_invalid"):
module.validate_volume(item)
def test_only_the_exact_local_volume_is_accepted():
module.validate_volume(volume())
def backup_volume():
return {
"Name": "itworx-forge-master-key-backup",
"Driver": "local",
"Scope": "local",
"Options": None,
"Labels": {
"itworx.managed-by": "deployment-controller",
"itworx.purpose": "forge-master-key-backup",
},
"Mountpoint": "/var/lib/docker/volumes/itworx-forge-master-key-backup/_data",
}
def test_backup_volume_identity_is_exact_and_never_repairs_existing_metadata():
module.validate_backup_volume(backup_volume())
bad = backup_volume()
bad["Labels"]["itworx.purpose"] = "foreign"
with pytest.raises(module.VolumeTransitionError):
module.validate_backup_volume(bad)
def test_promotion_preparer_creates_only_the_fixed_backup_volume_if_absent(monkeypatch, tmp_path):
commands = []
present = False
def run(arguments, **_kwargs):
nonlocal present
commands.append(arguments)
if arguments == ["docker", "info", "--format", "{{json .DockerRootDir}}"]:
return b'"/var/lib/docker"'
if arguments == ["docker", "volume", "ls", "--format", "{{.Name}}"]:
return b"" if not present else b"itworx-forge-master-key-backup\n"
if arguments[:3] == ["docker", "volume", "create"]:
present = True
return b"itworx-forge-master-key-backup\n"
if arguments == ["docker", "volume", "inspect", "itworx-forge-master-key-backup"]:
return json.dumps([backup_volume()]).encode()
raise AssertionError(arguments)
directory_calls = []
monkeypatch.setattr(module, "_run", run)
monkeypatch.setattr(module.os, "close", lambda _descriptor: None)
def directory(path, anchor):
directory_calls.append((path, anchor))
return 23
monkeypatch.setattr(module, "_directory", directory)
module.ensure_backup_volume(create=True)
create = [arguments for arguments in commands if arguments[:3] == ["docker", "volume", "create"]]
assert create == [[
"docker", "volume", "create", "--driver", "local",
"--label", "itworx.managed-by=deployment-controller",
"--label", "itworx.purpose=forge-master-key-backup",
"itworx-forge-master-key-backup",
]]
assert directory_calls == [
(module.DOCKER_ROOT / "volumes", module.DOCKER_ROOT),
(module.BACKUP_MOUNTPOINT, module.DOCKER_ROOT),
]
def test_existing_backup_volume_is_validated_without_relabel_or_recreation(monkeypatch, tmp_path):
commands = []
def run(arguments, **_kwargs):
commands.append(arguments)
if arguments == ["docker", "info", "--format", "{{json .DockerRootDir}}"]:
return b'"/var/lib/docker"'
if arguments == ["docker", "volume", "ls", "--format", "{{.Name}}"]:
return b"itworx-forge-master-key-backup\n"
if arguments == ["docker", "volume", "inspect", "itworx-forge-master-key-backup"]:
item = backup_volume()
item["CreatedAt"] = "2000-01-01T00:00:00Z"
return json.dumps([item]).encode()
raise AssertionError(arguments)
monkeypatch.setattr(module, "_run", run)
monkeypatch.setattr(module.os, "close", lambda _descriptor: None)
monkeypatch.setattr(
module, "_directory",
lambda _path, _anchor: 23,
)
module.ensure_backup_volume(create=False)
assert not any(arguments[:3] == ["docker", "volume", "create"] for arguments in commands)
def test_missing_backup_volume_is_not_created_outside_promotion(monkeypatch, tmp_path):
commands = []
def run(arguments, **_kwargs):
commands.append(arguments)
if arguments == ["docker", "info", "--format", "{{json .DockerRootDir}}"]:
return b'"/var/lib/docker"'
if arguments == ["docker", "volume", "ls", "--format", "{{.Name}}"]:
return b""
raise AssertionError(arguments)
monkeypatch.setattr(module, "_run", run)
monkeypatch.setattr(module.os, "close", lambda _descriptor: None)
monkeypatch.setattr(
module, "_directory",
lambda _path, _anchor: 23,
)
with pytest.raises(module.VolumeTransitionError, match="backup_volume_missing"):
module.ensure_backup_volume(create=False)
assert not any(arguments[:3] == ["docker", "volume", "create"] for arguments in commands)
def _prepare_promotion_doubles(monkeypatch, tmp_path, *, config_valid=True):
events = []
controller_id = "c" * 64
current = {
"Id": controller_id,
"Mounts": [{
"Destination": module.EXECUTABLE_TARGET,
"Source": module.CONTROLLER_DIR.as_posix(),
}],
}
previous_identity = {
"sourceRevision": "b" * 40,
"sourceTree": "c" * 40,
"controllerVersion": "2.1.0",
}
config_tree = ({}, {"": 0o755})
monkeypatch.setattr(module, "_validate_private_stage", lambda _stage: events.append("stage_validated"))
monkeypatch.setattr(module, "ensure_volume", lambda **_kwargs: None)
monkeypatch.setattr(
module, "_controller_image_inspect",
lambda _image_id: (events.append("controller_inspected") or current),
)
monkeypatch.setattr(
module, "_snapshot_attested_controller",
lambda *_args, **_kwargs: ({}, {"": 0o755}, previous_identity),
)
monkeypatch.setattr(module, "_validate_previous_private_tls", lambda _stage: None)
monkeypatch.setattr(module, "_release_tree", lambda *_args, **_kwargs: ({}, {"": 0o755}))
monkeypatch.setattr(module, "_config_tree", lambda *_args, **_kwargs: config_tree)
monkeypatch.setattr(module, "_read_config_leaf", lambda: config_tree)
monkeypatch.setattr(module, "_prepare_merged_tree", lambda *_args, **_kwargs: ({}, {"": 0o755}))
def validate_staged(*_args, **_kwargs):
events.append("config_validated")
if not config_valid:
raise module.VolumeTransitionError("staged_config_invalid")
monkeypatch.setattr(module, "_validate_staged_configuration", validate_staged)
monkeypatch.setattr(module, "_install_release_leaf", lambda *_args, **_kwargs: None)
monkeypatch.setattr(module, "_read_root_compose", lambda: (ORIGINAL_COMPOSE, 0o644))
monkeypatch.setattr(module, "_mirror_snapshot_tar", lambda: b"mirror")
monkeypatch.setattr(
module, "rendered_model",
lambda *_args, **_kwargs: (events.append("compose_rendered") or b"rendered-compose"),
)
monkeypatch.setattr(
module, "_verify_compose_image",
lambda *_args, **_kwargs: events.append("compose_verified"),
)
monkeypatch.setattr(
module, "ensure_backup_volume",
lambda **_kwargs: events.append("backup_volume_prepared"),
raising=False,
)
monkeypatch.setattr(module, "_release_tar_bytes", lambda *_args, **_kwargs: b"tar")
backup = tmp_path / "promotion-backup"
backup.mkdir(mode=0o700)
backup_fd = 23
monkeypatch.setattr(module, "_create_backup", lambda: (backup, backup_fd))
monkeypatch.setattr(module, "_write_backup", lambda *_args, **_kwargs: None)
monkeypatch.setattr(module.os, "close", lambda _descriptor: None)
return events
def test_promotion_prepares_backup_volume_after_source_stage_controller_and_config_checks(
monkeypatch, tmp_path
):
events = _prepare_promotion_doubles(monkeypatch, tmp_path)
module.prepare_promotion(
tmp_path / "stage",
source_revision="d" * 40,
source_tree="e" * 40,
controller_version="2.2.0",
image_id=IMAGE_ID,
)
assert events.index("stage_validated") < events.index("config_validated")
assert events.index("config_validated") < events.index("backup_volume_prepared")
assert events.index("compose_rendered") < events.index("backup_volume_prepared")
assert events.count("compose_verified") == 2
assert events.index("compose_verified") < events.index("backup_volume_prepared")
def test_promotion_does_not_prepare_backup_volume_when_staged_configuration_fails(
monkeypatch, tmp_path
):
events = _prepare_promotion_doubles(monkeypatch, tmp_path, config_valid=False)
with pytest.raises(module.VolumeTransitionError, match="staged_config_invalid"):
module.prepare_promotion(
tmp_path / "stage",
source_revision="d" * 40,
source_tree="e" * 40,
controller_version="2.2.0",
image_id=IMAGE_ID,
)
assert "backup_volume_prepared" not in events
@pytest.mark.parametrize(
("failed_step", "expected_phase", "error_code"),
[
("old_tls", "old_tls", "controller_volume_private_tls_unsafe"),
("config", "config", "controller_volume_config_missing"),
("release_build", "release_build", "controller_volume_source_archive_incomplete"),
("staged_config", "staged_config", "controller_volume_command_failed"),
("old_release", "old_release", "controller_volume_writer_failed"),
("new_release", "new_release", "controller_volume_writer_failed"),
],
)
def test_prepare_reports_only_fixed_failure_phase_and_code(
monkeypatch, tmp_path, capsys, failed_step, expected_phase, error_code
):
_prepare_promotion_doubles(monkeypatch, tmp_path)
def fail(*_args, **_kwargs):
raise module.VolumeTransitionError(error_code)
if failed_step == "old_tls":
monkeypatch.setattr(module, "_validate_previous_private_tls", fail)
elif failed_step == "config":
monkeypatch.setattr(module, "_read_config_leaf", fail)
elif failed_step == "release_build":
monkeypatch.setattr(module, "_prepare_merged_tree", fail)
elif failed_step == "staged_config":
monkeypatch.setattr(module, "_validate_staged_configuration", fail)
else:
installed = 0
def install(*_args, **_kwargs):
nonlocal installed
installed += 1
if installed == (1 if failed_step == "old_release" else 2):
fail()
monkeypatch.setattr(module, "_install_release_leaf", install)
monkeypatch.setattr(sys, "argv", [
"preparer", "prepare", "--stage-root", str(tmp_path / "stage"),
"--source-revision", "d" * 40, "--source-tree", "e" * 40,
"--controller-version", "2.2.0", "--image-id", IMAGE_ID,
])
with pytest.raises(SystemExit) as failure:
module.main()
assert str(failure.value) == (
"controller protected Hub volume transition failed "
f"phase={expected_phase} code={error_code}"
)
assert capsys.readouterr().out == ""
@pytest.mark.parametrize("error", [
module.VolumeTransitionError("controller_volume_writer_failed\nTOKEN=private"),
module.VolumeTransitionError("controller_volume_unreviewed_code"),
ValueError("TOKEN=private"),
])
def test_prepare_diagnostic_suppresses_untrusted_error_and_phase(monkeypatch, tmp_path, error):
def forged_prepare(*_args, **kwargs):
kwargs["on_phase"]("old_release\nTOKEN=private")
raise error
monkeypatch.setattr(module, "prepare_promotion", forged_prepare)
monkeypatch.setattr(sys, "argv", [
"preparer", "prepare", "--stage-root", str(tmp_path / "stage"),
"--source-revision", "d" * 40, "--source-tree", "e" * 40,
"--controller-version", "2.2.0", "--image-id", IMAGE_ID,
])
with pytest.raises(SystemExit) as failure:
module.main()
assert str(failure.value) == (
"controller protected Hub volume transition failed phase=prepare code=unclassified"
)
@pytest.mark.parametrize(
("stderr", "expected_condition"),
[
(b"container rootfs is marked read-only at /private/path TOKEN=fixture-secret",
"rootfs_read_only"),
(b"read-only file system at /private/path TOKEN=fixture-secret", "mount_read_only"),
(b"permission denied: /private/path TOKEN=fixture-secret", "permission_denied"),
(b"no such file or directory: /private/path TOKEN=fixture-secret",
"destination_missing"),
(b"No such container: private-container-id TOKEN=fixture-secret", "container_missing"),
(b"client version is too old TOKEN=fixture-secret", "api_version_unsupported"),
(b"unrecognized failure TOKEN=fixture-secret", None),
],
)
def test_writer_command_failure_is_categorized_without_retaining_stderr(
monkeypatch, stderr, expected_condition
):
monkeypatch.setattr(module.subprocess, "run", lambda *_args, **_kwargs: types.SimpleNamespace(
returncode=1, stdout=b"", stderr=stderr,
))
with pytest.raises(module.VolumeTransitionError) as failure:
module._run(
["docker", "cp", "-", "container:/private/path"],
data=b"TOKEN=fixture-secret",
diagnostic_action="writer_copy",
)
assert failure.value.args == ("controller_volume_command_failed",)
assert failure.value.command_failure == (
"writer_copy", "exit_1", expected_condition,
)
diagnostic = module._safe_command_diagnostic(failure.value.command_failure)
assert diagnostic is not None
assert "fixture-secret" not in repr(failure.value)
assert "fixture-secret" not in diagnostic
assert "private/path" not in diagnostic
@pytest.mark.parametrize(
("failure_kind", "expected_outcome"),
[("timeout", "timeout"), ("spawn", "spawn_failed"), ("overflow", "output_limit")],
)
def test_writer_command_failure_diagnostics_bound_non_exit_outcomes(
monkeypatch, failure_kind, expected_outcome
):
def run(*_args, **_kwargs):
if failure_kind == "timeout":
raise subprocess.TimeoutExpired(cmd=["docker", "cp"], timeout=45,
stderr=b"TOKEN=fixture-secret")
if failure_kind == "spawn":
raise OSError("TOKEN=fixture-secret")
return types.SimpleNamespace(returncode=0, stdout=b"oversized", stderr=b"")
monkeypatch.setattr(module.subprocess, "run", run)
kwargs = {"max_output": 1} if failure_kind == "overflow" else {}
with pytest.raises(module.VolumeTransitionError) as failure:
module._run(["docker", "cp", "-", "container:/target"],
diagnostic_action="writer_copy", **kwargs)
assert failure.value.command_failure == ("writer_copy", expected_outcome, None)
def test_prepare_reports_only_allowlisted_command_and_stderr_conditions(monkeypatch, tmp_path):
def failed_prepare(*_args, **kwargs):
kwargs["on_phase"]("old_release")
raise module.VolumeTransitionError(
"controller_volume_command_failed",
command_failure=("writer_copy", "exit_1", "rootfs_read_only"),
cleanup_failure=("writer_cleanup", "exit_2", "permission_denied"),
)
monkeypatch.setattr(module, "prepare_promotion", failed_prepare)
monkeypatch.setattr(sys, "argv", [
"preparer", "prepare", "--stage-root", str(tmp_path / "stage"),
"--source-revision", "d" * 40, "--source-tree", "e" * 40,
"--controller-version", "2.2.0", "--image-id", IMAGE_ID,
])
with pytest.raises(SystemExit) as failure:
module.main()
assert str(failure.value) == (
"controller protected Hub volume transition failed phase=old_release "
"code=controller_volume_command_failed "
"diagnostic=writer_copy_exit_1_rootfs_read_only "
"cleanupDiagnostic=writer_cleanup_exit_2_permission_denied"
)
@pytest.mark.parametrize("value", [
("docker cp /private/file", "exit_1", None),
("writer_copy", "exit_1", "TOKEN=fixture-secret"),
("writer_copy", "exit_999", None),
("writer_copy", "exit_1", "unknown_condition"),
["writer_copy", "exit_1", None],
])
def test_command_diagnostic_rejects_unreviewed_fields(value):
assert module._safe_command_diagnostic(value) is None
def test_release_writer_cleanup_failure_preserves_primary_copy_failure(monkeypatch):
partial, expected = _release_with_config()
monkeypatch.setattr(module, "_read_release_leaf", lambda _revision: partial)
monkeypatch.setattr(module, "ensure_volume", lambda **_kwargs: None)
monkeypatch.setattr(module, "_remove_stale_writer", lambda _image_id: None)
monkeypatch.setattr(module, "_inspect_writer", lambda *_args, **_kwargs: {})
calls = []
writer_id = "f" * 64
def run(arguments, *, diagnostic_action=None, **_kwargs):
calls.append(diagnostic_action)
if diagnostic_action == "writer_create":
return writer_id.encode()
if diagnostic_action == "writer_wait":
return b"0"
if diagnostic_action == "writer_copy":
raise module.VolumeTransitionError(
"controller_volume_command_failed",
command_failure=("writer_copy", "exit_1", "rootfs_read_only"),
)
if diagnostic_action == "writer_cleanup":
raise module.VolumeTransitionError(
"controller_volume_command_failed",
command_failure=("writer_cleanup", "exit_2", "permission_denied"),
)
return b""
monkeypatch.setattr(module, "_run", run)
with pytest.raises(module.VolumeTransitionError) as failure:
module._install_release_leaf(REVISION, expected, IMAGE_ID)
assert failure.value.command_failure == (
"writer_copy", "exit_1", "rootfs_read_only",
)
assert failure.value.cleanup_failure == (
"writer_cleanup", "exit_2", "permission_denied",
)
assert calls[-2:] == ["writer_copy", "writer_cleanup"]
def writer_inventory(inherited_labels):
identifier = "c" * 64
spec = module._writer_spec(IMAGE_ID, REVISION, kind="config")
return {
"Id": identifier, "Name": f"/{module.WRITER_NAME}", "Image": IMAGE_ID,
"Config": {
"Image": IMAGE_ID, "User": "0:0", "Entrypoint": ["python3"], "Cmd": spec[-2:],
"Labels": {**inherited_labels, module.WRITER_LABEL: "true",
module.WRITER_TARGET_LABEL: "config", module.WRITER_REVISION_LABEL: REVISION},
},
"HostConfig": {
"NetworkMode": "none", "Binds": [], "Privileged": False, "ReadonlyRootfs": True,
"CpuQuota": module.WRITER_CPU_QUOTA, "CpuPeriod": module.WRITER_CPU_PERIOD,
"Memory": module.WRITER_MEMORY_BYTES, "PidsLimit": module.WRITER_PIDS_LIMIT,
"CapDrop": ["ALL"], "Tmpfs": {"/tmp": "rw,nosuid,nodev,size=16777216,mode=1777"},
"SecurityOpt": ["no-new-privileges:true"],
},
"Mounts": [{"Type": "volume", "Name": module.VOLUME,
"Source": module.MOUNTPOINT.as_posix(), "Destination": module.TARGET, "RW": True}],
}
@pytest.mark.parametrize("damage", [None, "changed_image_label", "extra_label", "reserved_image_label", "falsey_labels"])
def test_writer_labels_are_bound_to_exact_image_inheritance(monkeypatch, damage):
inherited = {"org.opencontainers.image.revision": "d" * 40, "io.buildah.version": "1.40.0"}
item = writer_inventory(inherited)
if damage == "changed_image_label":
item["Config"]["Labels"]["org.opencontainers.image.revision"] = "e" * 40
elif damage == "extra_label":
item["Config"]["Labels"]["unexpected"] = "value"
elif damage == "reserved_image_label":
inherited[module.WRITER_LABEL] = "inherited-marker"
elif damage == "falsey_labels":
inherited = []
def run(arguments, **kwargs):
if arguments == ["docker", "inspect", item["Id"]]:
return json.dumps([item]).encode()
assert arguments == ["docker", "image", "inspect", IMAGE_ID]
return json.dumps([{"Id": IMAGE_ID, "Config": {"Labels": inherited}}]).encode()
monkeypatch.setattr(module, "_run", run)
if damage:
with pytest.raises(module.VolumeTransitionError, match="writer_.*invalid"):
module._inspect_writer(item["Id"], IMAGE_ID, REVISION, kind="config")
else:
assert module._inspect_writer(item["Id"], IMAGE_ID, REVISION, kind="config") == item
@pytest.mark.parametrize("damage", [None, "tmpfs_projection", "extra_bind", "wrong_volume", "read_only"])
def test_writer_accepts_declared_tmpfs_without_inspect_mount_projection(monkeypatch, damage):
item = writer_inventory({})
if damage == "tmpfs_projection":
item["Mounts"].append({"Type": "tmpfs", "Destination": "/tmp"})
elif damage == "extra_bind":
item["Mounts"].append({"Type": "bind", "Destination": "/foreign"})
elif damage == "wrong_volume":
item["Mounts"][0]["Name"] = "foreign-volume"
elif damage == "read_only":
item["Mounts"][0]["RW"] = False
def run(arguments, **kwargs):
if arguments == ["docker", "inspect", item["Id"]]:
return json.dumps([item]).encode()
assert arguments == ["docker", "image", "inspect", IMAGE_ID]
return json.dumps([{"Id": IMAGE_ID, "Config": {"Labels": None}}]).encode()
monkeypatch.setattr(module, "_run", run)
if damage in {"extra_bind", "wrong_volume", "read_only"}:
with pytest.raises(module.VolumeTransitionError, match="writer_mount_invalid"):
module._inspect_writer(item["Id"], IMAGE_ID, REVISION, kind="config")
else:
module._inspect_writer(item["Id"], IMAGE_ID, REVISION, kind="config")
@pytest.mark.parametrize("name", ["foreign-project", "", None, "${COMPOSE_PROJECT_NAME}"])
def test_source_compose_name_cannot_be_overridden_by_fixed_project_flag(monkeypatch, name):
def never_compose(*args, **kwargs):
pytest.fail("Foreign source project reached Compose")
monkeypatch.setattr(module, "_run", never_compose)
source = f"name: {json.dumps(name)}\nservices:\n deploy-control:\n image: immutable-controller\n".encode()
with pytest.raises(module.VolumeTransitionError, match="project_invalid"):
module.rendered_model(source, REVISION, IMAGE_ID)
def test_compose_without_explicit_name_retains_canonical_project(monkeypatch):
def run(arguments, *, data=None, **kwargs):
if arguments[:3] == ["docker", "image", "inspect"]:
return IMAGE_ID.encode()
normalized = model()
normalized["name"] = (
arguments[arguments.index("--project-name") + 1]
if "--project-name" in arguments else "data"
)
return json.dumps(normalized).encode()
monkeypatch.setattr(module, "_run", run)
original = b"services:\n deploy-control:\n image: immutable-controller\n"
rendered = json.loads(module.rendered_model(original, REVISION, IMAGE_ID))
assert rendered["name"] == "gitea-deploy-runners"
def model():
return {
"name": "gitea-deploy-runners",
"services": {
"deploy-control": {
"container_name": module.CONTAINER,
"image": "immutable-controller",
"volumes": [
{
"type": "bind",
"source": module.DOCKER_ROOT.as_posix(),
"target": module.DOCKER_ROOT.as_posix(),
"read_only": True,
},
{
"type": "bind",
"source": module.CONTROLLER_DIR.as_posix(),
"target": module.EXECUTABLE_TARGET,
"read_only": True,
},
],
"environment": {"PRIVATE_SETTING": "unchanged"},
},
"runner": {
"image": "pinned-runner",
"volumes": [
{"type": "bind", "source": "/fixed/data", "target": "/data"}
],
},
},
}
@pytest.fixture
def compose(monkeypatch):
baseline = model()
calls = []
def run(arguments, *, data=None, **_kwargs):
calls.append((arguments, data))
if arguments[:3] == ["docker", "image", "inspect"]:
return IMAGE_ID.encode("ascii")
if arguments[-2:] == ["--format", "json"]:
return data if data and data.startswith(b"{") else json.dumps(baseline).encode()
return b""
monkeypatch.setattr(module, "_run", run)
return baseline, calls
def test_compose_uses_protected_code_and_two_config_views(compose):
baseline, calls = compose
rendered = json.loads(module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID))
service = rendered["services"]["deploy-control"]
assert service["image"] == IMAGE_ID
assert service["pull_policy"] == "never"
volumes = service["volumes"]
assert {
"type": "bind",
"source": module._expected_revision_path(REVISION).as_posix(),
"target": module.EXECUTABLE_TARGET,
"read_only": True,
"bind": {"propagation": "rprivate"},
} in volumes
assert {
"type": "volume",
"source": module.VOLUME,
"target": module.TARGET,
"volume": {"nocopy": True},
} in volumes
assert {
"type": "volume",
"source": "itworx-forge-master-key-backup",
"target": "/forge-master-key-backup",
"volume": {"nocopy": True},
} in volumes
assert {
"type": "bind",
"source": module._expected_config_path().as_posix(),
"target": module.CONFIG_TARGET,
"read_only": True,
"bind": {"propagation": "rprivate"},
} in volumes
assert len(volumes) == 5
volumes.remove(next(item for item in volumes if item["target"] == module.EXECUTABLE_TARGET))
volumes.remove(next(item for item in volumes if item["target"] == module.TARGET))
volumes.remove(next(item for item in volumes if item["target"] == "/forge-master-key-backup"))
volumes.remove(next(item for item in volumes if item["target"] == module.CONFIG_TARGET))
assert rendered["volumes"] == {
module.VOLUME: {"name": module.VOLUME, "external": True},
"itworx-forge-master-key-backup": {
"name": "itworx-forge-master-key-backup", "external": True
},
}
assert rendered["services"]["runner"] == baseline["services"]["runner"]
assert service["environment"] == baseline["services"]["deploy-control"]["environment"]
compose_calls = [
arguments for arguments, _ in calls if arguments[:2] == ["docker", "compose"]
]
assert compose_calls
assert all("--project-directory" in arguments for arguments in compose_calls)
assert module.ROOT.as_posix() in compose_calls[0]
assert all(module.MOUNTPOINT.as_posix() in arguments for arguments in compose_calls[1:])
assert all("--env-file" in arguments and "/dev/null" in arguments for arguments in compose_calls)
assert all("-f" in arguments and "-" in arguments for arguments in compose_calls)
assert calls[0][1] == ORIGINAL_COMPOSE
def test_compose_migrates_legacy_config_alias_to_explicit_private_bind(compose):
baseline, _calls = compose
baseline["services"][module.SERVICE]["volumes"].append(
{
"type": "bind",
"source": module._expected_config_path().as_posix(),
"target": module.CONFIG_TARGET,
"read_only": True,
}
)
rendered = json.loads(module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID))
aliases = [
mount for mount in rendered["services"][module.SERVICE]["volumes"]
if mount.get("target") == module.CONFIG_TARGET
]
assert aliases == [
{
"type": "bind",
"source": module._expected_config_path().as_posix(),
"target": module.CONFIG_TARGET,
"read_only": True,
"bind": {"propagation": "rprivate"},
}
]
def test_compose_accepts_empty_bind_normalization_and_canonicalizes_both_managed_binds(compose):
baseline, _calls = compose
volumes = baseline["services"][module.SERVICE]["volumes"]
next(item for item in volumes if item["target"] == module.EXECUTABLE_TARGET)["bind"] = {}
volumes.append(
{
"type": "bind",
"source": module._expected_config_path().as_posix(),
"target": module.CONFIG_TARGET,
"read_only": True,
"bind": {},
}
)
rendered = json.loads(module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID))
managed = [
mount for mount in rendered["services"][module.SERVICE]["volumes"]
if mount.get("target") in {module.EXECUTABLE_TARGET, module.CONFIG_TARGET}
]
assert {item["target"]: item["bind"] for item in managed} == {
module.EXECUTABLE_TARGET: {"propagation": "rprivate"},
module.CONFIG_TARGET: {"propagation": "rprivate"},
}
def test_compose_normalizes_all_observed_live_generated_bind_defaults(compose):
baseline, _calls = compose
volumes = baseline["services"][module.SERVICE]["volumes"]
for mount in volumes:
mount["bind"] = {"create_host_path": True}
rendered = json.loads(module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID))
graph = next(mount for mount in rendered["services"][module.SERVICE]["volumes"]
if mount["target"] == module.DOCKER_ROOT.as_posix())
assert graph == {
"type": "bind", "source": module.DOCKER_ROOT.as_posix(),
"target": module.DOCKER_ROOT.as_posix(), "read_only": True,
}
module._bound_model(json.dumps(rendered).encode())
@pytest.mark.parametrize("bind", [
{"create_host_path": 1}, {"create_host_path": "true"},
{"create_host_path": False},
{"create_host_path": True, "propagation": "rslave"},
{"create_host_path": True, "unknown": True},
])
def test_compose_rejects_unsafe_generated_docker_root_bind(compose, bind):
baseline, _calls = compose
graph = next(mount for mount in baseline["services"][module.SERVICE]["volumes"]
if mount["target"] == module.DOCKER_ROOT.as_posix())
graph["bind"] = bind
with pytest.raises(module.VolumeTransitionError):
module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID)
@pytest.mark.parametrize("target", [module.EXECUTABLE_TARGET, module.CONFIG_TARGET])
@pytest.mark.parametrize("private", [False, True])
def test_compose_normalizes_generated_create_host_path_for_fixed_readonly_mounts(
compose, target, private
):
baseline, _calls = compose
volumes = baseline["services"][module.SERVICE]["volumes"]
bind = {"create_host_path": True}
if private:
bind["propagation"] = "rprivate"
if target == module.EXECUTABLE_TARGET:
mount = next(item for item in volumes if item["target"] == target)
mount["bind"] = bind
else:
volumes.append({
"type": "bind", "source": module._expected_config_path().as_posix(),
"target": target, "read_only": True, "bind": bind,
})
rendered = json.loads(module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID))
managed = [mount for mount in rendered["services"][module.SERVICE]["volumes"]
if mount["target"] in {module.EXECUTABLE_TARGET, module.CONFIG_TARGET}]
assert len(managed) == 2
assert all(mount["read_only"] is True for mount in managed)
assert all(mount["bind"] == {"propagation": "rprivate"} for mount in managed)
assert rendered["services"]["runner"] == baseline["services"]["runner"]
# Only captured source metadata is normalized. Activation stays canonical.
module._bound_model(json.dumps(rendered).encode())
next(mount for mount in managed if mount["target"] == target)["bind"]["create_host_path"] = True
with pytest.raises(module.VolumeTransitionError):
module._bound_model(json.dumps(rendered).encode())
@pytest.mark.parametrize("target", [module.EXECUTABLE_TARGET, module.CONFIG_TARGET])
@pytest.mark.parametrize("bind", [
{"create_host_path": 1},
{"create_host_path": "true"},
{"create_host_path": True, "propagation": "rslave"},
{"create_host_path": True, "selinux": "z"},
])
def test_compose_rejects_unsafe_or_untyped_generated_bind_metadata(compose, target, bind):
baseline, _calls = compose
volumes = baseline["services"][module.SERVICE]["volumes"]
if target == module.EXECUTABLE_TARGET:
next(mount for mount in volumes if mount["target"] == target)["bind"] = bind
else:
volumes.append({
"type": "bind", "source": module._expected_config_path().as_posix(),
"target": target, "read_only": True, "bind": bind,
})
with pytest.raises(module.VolumeTransitionError):
module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID)
@pytest.mark.parametrize(
("target", "bind"),
[
(module.EXECUTABLE_TARGET, {"propagation": "rslave"}),
(module.EXECUTABLE_TARGET, {"propagation": "shared"}),
(module.EXECUTABLE_TARGET, {"propagation": "rprivate", "create_host_path": False}),
(module.CONFIG_TARGET, {"propagation": "rslave"}),
(module.CONFIG_TARGET, {"propagation": "shared"}),
(module.CONFIG_TARGET, {"propagation": "rprivate", "create_host_path": False}),
],
)
def test_compose_rejects_noncanonical_controller_bind_propagation(compose, target, bind):
baseline, _calls = compose
volumes = baseline["services"][module.SERVICE]["volumes"]
if target == module.EXECUTABLE_TARGET:
mount = next(item for item in volumes if item["target"] == target)
mount["bind"] = bind
else:
volumes.append(
{
"type": "bind",
"source": module._expected_config_path().as_posix(),
"target": target,
"read_only": True,
"bind": bind,
}
)
with pytest.raises(module.VolumeTransitionError):
module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID)
def test_compose_accepts_already_canonical_controller_mounts_without_duplicates(compose):
baseline, _calls = compose
volumes = baseline["services"][module.SERVICE]["volumes"]
next(item for item in volumes if item["target"] == module.EXECUTABLE_TARGET)["bind"] = {
"propagation": "rprivate"
}
volumes.extend(
[
{
"type": "volume",
"source": module.VOLUME,
"target": module.TARGET,
"volume": {"nocopy": True},
},
{
"type": "volume",
"source": module.BACKUP_VOLUME,
"target": module.BACKUP_TARGET,
"volume": {"nocopy": True},
},
{
"type": "bind",
"source": module._expected_config_path().as_posix(),
"target": module.CONFIG_TARGET,
"read_only": True,
"bind": {"propagation": "rprivate"},
},
]
)
baseline["volumes"] = {
module.VOLUME: {"name": module.VOLUME, "external": True},
module.BACKUP_VOLUME: {"name": module.BACKUP_VOLUME, "external": True},
}
rendered = json.loads(module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID))
final_mounts = rendered["services"][module.SERVICE]["volumes"]
assert len(final_mounts) == len(volumes)
assert sum(item.get("target") == module.EXECUTABLE_TARGET for item in final_mounts) == 1
assert sum(item.get("target") == module.TARGET for item in final_mounts) == 1
assert sum(item.get("target") == module.BACKUP_TARGET for item in final_mounts) == 1
assert sum(item.get("target") == module.CONFIG_TARGET for item in final_mounts) == 1
def test_activation_and_rollback_models_both_pin_managed_bind_propagation(compose):
_baseline, _calls = compose
old_model = json.loads(module.rendered_model(ORIGINAL_COMPOSE, "b" * 40, IMAGE_ID))
next_model = json.loads(module.rendered_model(ORIGINAL_COMPOSE, "c" * 40, IMAGE_ID))
for rendered in (old_model, next_model):
mounts = rendered["services"][module.SERVICE]["volumes"]
managed = {
mount["target"]: mount
for mount in mounts
if mount.get("target") in {module.EXECUTABLE_TARGET, module.CONFIG_TARGET}
}
assert set(managed) == {module.EXECUTABLE_TARGET, module.CONFIG_TARGET}
assert managed[module.EXECUTABLE_TARGET]["bind"] == {"propagation": "rprivate"}
assert managed[module.CONFIG_TARGET]["bind"] == {"propagation": "rprivate"}
def test_compose_allows_only_the_fixed_managed_appdata_volume_mount(compose):
baseline, _calls = compose
mount = {
"type": "volume",
"source": module.MANAGED_APPDATA_VOLUME,
"target": module.MANAGED_APPDATA_MOUNTPOINT.as_posix(),
}
baseline["services"][module.SERVICE]["volumes"].append(mount)
baseline["volumes"] = {
module.MANAGED_APPDATA_VOLUME: {
"name": module.MANAGED_APPDATA_VOLUME,
"external": True,
}
}
rendered = json.loads(module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID))
assert mount in rendered["services"][module.SERVICE]["volumes"]
assert rendered["volumes"][module.MANAGED_APPDATA_VOLUME] == {
"name": module.MANAGED_APPDATA_VOLUME,
"external": True,
}
def test_compose_rejects_a_managed_appdata_target_alias(compose):
baseline, _calls = compose
baseline["services"][module.SERVICE]["volumes"].append({
"type": "volume",
"source": module.MANAGED_APPDATA_VOLUME,
"target": "/var/lib/docker/volumes/foreign/_data",
})
baseline["volumes"] = {
module.MANAGED_APPDATA_VOLUME: {
"name": module.MANAGED_APPDATA_VOLUME,
"external": True,
}
}
with pytest.raises(module.VolumeTransitionError, match="managed_appdata"):
module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID)
def test_source_relative_controller_bind_uses_original_compose_directory(monkeypatch):
def run(arguments, *, data=None, **kwargs):
if arguments[:3] == ["docker", "image", "inspect"]:
return IMAGE_ID.encode()
normalized = model()
directory = arguments[arguments.index("--project-directory") + 1]
if arguments[-2:] == ["--format", "json"]:
normalized["services"][module.SERVICE]["volumes"][1]["source"] = directory + "/controller"
return json.dumps(normalized).encode()
monkeypatch.setattr(module, "_run", run)
original = ORIGINAL_COMPOSE + b" volumes:\n - ./controller:/opt/gitea-deploy:ro\n"
rendered = json.loads(module.rendered_model(original, REVISION, IMAGE_ID))
mounts = rendered["services"][module.SERVICE]["volumes"]
assert next(m["source"] for m in mounts if m["target"] == module.EXECUTABLE_TARGET) == module._mount_source_for_revision(REVISION)
@pytest.mark.parametrize(
"damage",
[
"target_collision", "foreign_volume", "graph_rw", "relative_bind", "env_file",
"backup_target_collision", "backup_volume_alias", "backup_target_child_alias",
"backup_definition_collision",
],
)
def test_compose_rejects_unsafe_or_unpinned_activation_inputs(compose, damage):
baseline, _ = compose
if damage == "target_collision":
baseline["services"]["deploy-control"]["volumes"].append(
{"type": "bind", "source": "/unsafe", "target": module.TARGET}
)
elif damage == "foreign_volume":
baseline["services"]["runner"]["volumes"].append(
{"type": "volume", "source": module.VOLUME, "target": "/foreign"}
)
elif damage == "graph_rw":
baseline["services"]["deploy-control"]["volumes"][0]["read_only"] = False
elif damage == "relative_bind":
baseline["services"]["runner"]["volumes"][0]["source"] = "./unsafe"
elif damage == "env_file":
baseline["services"]["deploy-control"]["env_file"] = [".env"]
elif damage == "backup_target_collision":
baseline["services"]["deploy-control"]["volumes"].append(
{"type": "bind", "source": "/foreign", "target": "/forge-master-key-backup"}
)
elif damage == "backup_volume_alias":
baseline["services"]["runner"]["volumes"].append(
{
"type": "volume", "source": "itworx-forge-master-key-backup",
"target": "/foreign-backup",
}
)
elif damage == "backup_target_child_alias":
baseline["services"]["runner"]["volumes"].append(
{"type": "bind", "source": "/foreign", "target": "/forge-master-key-backup/state"}
)
else:
baseline["volumes"] = {
"itworx-forge-master-key-backup": {"name": "foreign", "external": True}
}
with pytest.raises(module.VolumeTransitionError):
module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID)
def test_pre_promotion_accepts_the_healthy_controller_without_the_new_mount(compose):
baseline, _calls = compose
reference = "registry.example/controller:stable"
baseline["services"][module.SERVICE]["image"] = reference
current = {
"Image": IMAGE_ID,
"Config": {
"Image": reference,
"Labels": {
"com.docker.compose.project": "gitea-deploy-runners",
"com.docker.compose.service": module.SERVICE,
},
},
"State": {"Health": {"Status": "healthy"}},
"Mounts": [
{
"Type": "volume", "Name": module.VOLUME,
"Source": module.MOUNTPOINT.as_posix(), "Destination": module.TARGET,
"RW": True,
}
],
}
original = f"name: gitea-deploy-runners\nservices:\n deploy-control:\n image: {reference}\n".encode()
rendered = json.loads(
module.rendered_model(original, REVISION, IMAGE_ID, current_container=current)
)
assert not any(mount.get("Destination") == "/forge-master-key-backup" for mount in current["Mounts"])
assert any(
mount.get("target") == "/forge-master-key-backup"
and mount.get("source") == "itworx-forge-master-key-backup"
for mount in rendered["services"][module.SERVICE]["volumes"]
)
def test_compose_tag_replacement_cannot_change_controller_startup_image(compose, monkeypatch):
_baseline, calls = compose
original_run = module._run
def replaced_tag(arguments, **kwargs):
if arguments[:3] == ["docker", "image", "inspect"]:
return ("sha256:" + "c" * 64).encode("ascii")
return original_run(arguments, **kwargs)
monkeypatch.setattr(module, "_run", replaced_tag)
with pytest.raises(module.VolumeTransitionError, match="image_reference_changed"):
module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID)
assert not any(
arguments[-1:] == ["deploy-control"] and "up" in arguments
for arguments, _ in calls
)
@pytest.mark.parametrize("damage", [None, "foreign_reference", "wrong_project", "wrong_service", "unhealthy", "digest_reference", "wrong_image", "normalized_reference", "interpolation"])
def test_drifted_tag_requires_healthy_current_controller_provenance(compose, monkeypatch, damage):
baseline, calls = compose
reference = "registry.example/controller:stable"
if damage == "digest_reference":
reference = "registry.example/controller@sha256:" + "c" * 64
baseline["services"][module.SERVICE]["image"] = reference
current = {
"Image": IMAGE_ID,
"Config": {"Image": reference, "Labels": {
"com.docker.compose.project": "gitea-deploy-runners",
"com.docker.compose.service": module.SERVICE,
}},
"State": {"Health": {"Status": "healthy"}},
}
if damage == "foreign_reference":
current["Config"]["Image"] = "registry.example/foreign:stable"
elif damage == "wrong_project":
current["Config"]["Labels"]["com.docker.compose.project"] = "foreign"
elif damage == "wrong_service":
current["Config"]["Labels"]["com.docker.compose.service"] = "foreign"
elif damage == "unhealthy":
current["State"]["Health"]["Status"] = "unhealthy"
elif damage == "wrong_image":
current["Image"] = "sha256:" + "c" * 64
original_run = module._run
def drifted_tag(arguments, **kwargs):
if arguments[:3] == ["docker", "image", "inspect"]:
return ("sha256:" + "c" * 64).encode("ascii")
return original_run(arguments, **kwargs)
monkeypatch.setattr(module, "_run", drifted_tag)
source_reference = reference
if damage == "normalized_reference":
source_reference = "registry.example/foreign:stable"
elif damage == "interpolation":
source_reference = "${CONTROLLER_IMAGE}"
original = f"name: gitea-deploy-runners\nservices:\n deploy-control:\n image: {source_reference}\n".encode()
if damage:
with pytest.raises(module.VolumeTransitionError, match="image_reference_(changed|invalid)"):
module.rendered_model(original, REVISION, IMAGE_ID, current_container=current)
else:
rendered = json.loads(module.rendered_model(original, REVISION, IMAGE_ID, current_container=current))
assert rendered["services"][module.SERVICE]["image"] == IMAGE_ID
assert rendered["services"][module.SERVICE]["pull_policy"] == "never"
assert not any("up" in arguments or "pull" in arguments for arguments, _ in calls)
def test_config_snapshot_has_exact_allowlist_and_broker_token():
entries = {
"projects.json": (b"{}", 0o600),
"broker-token": (b"secret", 0o600),
"gitea-validation.json": (b"{}", 0o600),
"gitea-validation-token": (b"secret", 0o600),
"private-tls.json": (b"{}", 0o600),
"appops-enrollment-bootstrap": (b"secret", 0o600),
"webhook-secrets/forge": (b"secret", 0o600),
"appops-deployment-secrets/repopulse": (b"secret", 0o600),
"status_server.py": (b"code", 0o644),
}
directories = {
"": 0o755,
"webhook-secrets": 0o700,
"appops-deployment-secrets": 0o700,
}
config, config_dirs = module._config_tree(entries, directories)
assert set(config) == set(entries) - {"status_server.py"}
assert "broker-token" in config
assert config_dirs == directories
def _forge_secret_entries():
return {
name: (f'fixture:{index}'.encode(), module.FORGE_SECRET_MODE)
for index, name in enumerate(sorted(module.FORGE_SECRET_OWNERS))
}
def _release_with_config(*, forge_secrets=False):
release = {
name: (f'fixed:{name}\n'.encode(), 0o644)
for name in module.RELEASE_FILES
}
manifest = {
'apiVersion': 'itworx.controller-artifact/v1',
'controllerVersion': '2.1.14',
'sourceRepository': 'jens/unraid-appops-gateway',
'sourceRevision': REVISION,
'sourceTree': 'c' * 40,
'artifactSha256': module._artifact_digest(release),
'builtAt': '2026-10-04T00:00:00Z',
}
release['controller-release.json'] = (json.dumps(manifest).encode(), 0o644)
config = {'broker-token': (b'fixture-token', 0o600)}
directories = {'': 0o755}
if forge_secrets:
config.update(_forge_secret_entries())
directories[module.FORGE_SECRET_DIRECTORY] = 0o700
return (release, {'': 0o755}), (release | config, directories)
def _fake_release_writer(monkeypatch, state):
writer_id = 'f' * 64
calls = []
def run(arguments, *, data=None, **_kwargs):
calls.append(arguments)
if arguments[:2] == ['docker', 'create']:
return writer_id.encode()
if arguments[:2] == ['docker', 'wait']:
return b'0'
if arguments[:2] == ['docker', 'cp']:
state['tree'] = module._read_tar_tree(data)
with tarfile.open(fileobj=io.BytesIO(data), mode='r:') as archive:
archive_mode = '-a' in arguments
state['owners'] = {
member.name: ((0, 0) if archive_mode else (member.uid, member.gid))
for member in archive.getmembers() if member.isfile()
}
return b''
monkeypatch.setattr(module, 'ensure_volume', lambda **_kwargs: None)
monkeypatch.setattr(module, '_remove_stale_writer', lambda _image_id: None)
monkeypatch.setattr(module, '_inspect_writer', lambda *_args, **_kwargs: {})
monkeypatch.setattr(module, '_run', run)
return calls
def test_existing_exact_release_subset_is_augmented_and_read_back(monkeypatch):
old, expected = _release_with_config()
state = {'tree': old}
monkeypatch.setattr(module, '_read_release_leaf', lambda _revision: state['tree'])
calls = _fake_release_writer(monkeypatch, state)
module._install_release_leaf(REVISION, expected, IMAGE_ID)
assert state['tree'] == expected
assert len([call for call in calls if call[:2] == ['docker', 'create']]) == 1
assert len([call for call in calls if call[:2] == ['docker', 'cp']]) == 1
def test_divergent_existing_release_refuses_before_writer(monkeypatch):
old, expected = _release_with_config()
damaged = dict(old[0])
damaged['status_server.py'] = (b'tampered', 0o644)
monkeypatch.setattr(module, '_read_release_leaf', lambda _revision: (damaged, old[1]))
calls = _fake_release_writer(monkeypatch, {'tree': None})
with pytest.raises(module.VolumeTransitionError, match='release_collision'):
module._install_release_leaf(REVISION, expected, IMAGE_ID)
assert not any(call[:2] == ['docker', 'create'] for call in calls)
def test_complete_existing_release_uses_no_writer(monkeypatch):
_old, expected = _release_with_config()
monkeypatch.setattr(module, '_read_release_leaf', lambda _revision: expected)
calls = _fake_release_writer(monkeypatch, {'tree': None})
module._install_release_leaf(REVISION, expected, IMAGE_ID)
assert not any(call[:2] == ['docker', 'create'] for call in calls)
def test_release_copy_preserves_fixed_forge_file_owners_on_readback(monkeypatch):
_old, expected = _release_with_config(forge_secrets=True)
state = {'tree': None, 'owners': {}}
def read_leaf(_revision):
if state['tree'] is not None:
for name, owner in module.FORGE_SECRET_OWNERS.items():
if state['owners'].get(name) != owner:
raise module.VolumeTransitionError('controller_volume_tree_file_unsafe')
return state['tree']
monkeypatch.setattr(module, '_read_release_leaf', read_leaf)
calls = _fake_release_writer(monkeypatch, state)
module._install_release_leaf(REVISION, expected, IMAGE_ID)
assert state['tree'] == expected
assert all(state['owners'][name] == owner
for name, owner in module.FORGE_SECRET_OWNERS.items())
assert len([call for call in calls if call[:2] == ['docker', 'cp']]) == 1
assert next(call for call in calls if call[:2] == ['docker', 'cp']) == [
'docker', 'cp', '-',
f"{'f' * 64}:{module.TARGET}/{module.ARTIFACTS}/{REVISION}",
]
def test_complete_forge_secret_bundle_has_exact_export_modes_and_owners():
entries = {
'projects.json': (b'{}', 0o600),
'broker-token': (b'fixture-token', 0o600),
'gitea-validation.json': (b'{}', 0o600),
'gitea-validation-token': (b'fixture-validation-token', 0o600),
} | _forge_secret_entries()
directories = {'': 0o755, module.FORGE_SECRET_DIRECTORY: 0o700}
config, config_dirs = module._config_tree(entries, directories)
archive_bytes = module._release_tar_bytes(REVISION, (config, config_dirs))
restored, restored_dirs = module._read_tar_tree(archive_bytes)
assert restored == config
assert restored_dirs == config_dirs
with tarfile.open(fileobj=io.BytesIO(archive_bytes), mode='r:') as archive:
for name, owner in module.FORGE_SECRET_OWNERS.items():
member = archive.getmember(name)
assert (member.uid, member.gid) == owner
assert member.mode == module.FORGE_SECRET_MODE
assert archive.getmember('broker-token').uid == 0
assert archive.getmember('broker-token').gid == 0
@pytest.mark.parametrize('damage', ['partial', 'unknown', 'wrong_mode', 'nested'])
def test_forge_secret_snapshot_requires_the_complete_exact_private_tree(damage):
entries = {
'projects.json': (b'{}', 0o600),
'broker-token': (b'fixture-token', 0o600),
'gitea-validation.json': (b'{}', 0o600),
'gitea-validation-token': (b'fixture-validation-token', 0o600),
} | _forge_secret_entries()
directories = {'': 0o755, module.FORGE_SECRET_DIRECTORY: 0o700}
if damage == 'partial':
entries.pop(min(module.FORGE_SECRET_OWNERS))
elif damage == 'unknown':
entries[f'{module.FORGE_SECRET_DIRECTORY}/unexpected.secret'] = (b'x', 0o400)
elif damage == 'wrong_mode':
name = min(module.FORGE_SECRET_OWNERS)
entries[name] = (entries[name][0], 0o600)
else:
directories[f'{module.FORGE_SECRET_DIRECTORY}/nested'] = 0o700
with pytest.raises(module.VolumeTransitionError):
module._config_tree(entries, directories)
def test_forge_secret_tar_owner_drift_is_rejected():
output = io.BytesIO()
with tarfile.open(fileobj=output, mode='w', format=tarfile.USTAR_FORMAT) as archive:
member = tarfile.TarInfo(
f'{module.FORGE_SECRET_DIRECTORY}/runtime_database_url.secret'
)
member.size = 1
member.mode = module.FORGE_SECRET_MODE
member.uid = member.gid = 0
archive.addfile(member, io.BytesIO(b'x'))
with pytest.raises(module.VolumeTransitionError, match='entry_unsafe'):
module._read_tar_tree(output.getvalue())
def test_config_writer_uses_tar_ownership_without_copy_archive_mode(monkeypatch):
tree = (
{
'projects.json': (b'{}', 0o600),
'broker-token': (b'fixture-token', 0o600),
'gitea-validation.json': (b'{}', 0o600),
'gitea-validation-token': (b'fixture-validation-token', 0o600),
} | _forge_secret_entries(),
{'': 0o755, module.FORGE_SECRET_DIRECTORY: 0o700},
)
writer_id = 'f' * 64
expected_archive = module._release_tar_bytes('a' * 40, tree)
commands = []
copy_payloads = []
copy_owners = {}
def run(arguments, *, data=None, **_kwargs):
commands.append(arguments)
if arguments == ['docker', 'create']:
return writer_id.encode()
if arguments[:2] == ['docker', 'wait']:
return b'0'
if arguments[:2] == ['docker', 'cp']:
copy_payloads.append(data)
with tarfile.open(fileobj=io.BytesIO(data), mode='r:') as archive:
archive_mode = '-a' in arguments
copy_owners.update({
member.name: ((0, 0) if archive_mode else (member.uid, member.gid))
for member in archive.getmembers() if member.isfile()
})
return b''
monkeypatch.setattr(module, '_read_partial_config_leaf', lambda: None)
monkeypatch.setattr(module, 'ensure_volume', lambda **_kwargs: None)
monkeypatch.setattr(module, '_release_tar_bytes', lambda *_args, **_kwargs: expected_archive)
monkeypatch.setattr(module, '_remove_stale_writer', lambda *_args, **_kwargs: None)
monkeypatch.setattr(module, '_writer_spec', lambda *_args, **_kwargs: ['docker', 'create'])
monkeypatch.setattr(module, '_inspect_writer', lambda *_args, **_kwargs: {})
monkeypatch.setattr(module, '_read_config_leaf', lambda: tree)
monkeypatch.setattr(module, '_run', run)
module._install_config_tree(tree, IMAGE_ID)
assert [command for command in commands if command[:2] == ['docker', 'cp']] == [[
'docker', 'cp', '-', f'{writer_id}:{module.TARGET}/{module.CONFIG_DIR}',
]]
assert copy_payloads == [expected_archive]
assert all(copy_owners[name] == owner
for name, owner in module.FORGE_SECRET_OWNERS.items())
def test_partial_forge_bundle_fails_during_promotion_preparation_before_stop(
monkeypatch, tmp_path
):
valid_config_tree = module._config_tree
events = _prepare_promotion_doubles(monkeypatch, tmp_path)
monkeypatch.setattr(module, '_config_tree', valid_config_tree)
partial = {
'projects.json': (b'{}', 0o600),
'broker-token': (b'fixture-token', 0o600),
'gitea-validation.json': (b'{}', 0o600),
'gitea-validation-token': (b'fixture-validation-token', 0o600),
min(module.FORGE_SECRET_OWNERS): (b'fixture', module.FORGE_SECRET_MODE),
}
directories = {'': 0o755, module.FORGE_SECRET_DIRECTORY: 0o700}
identity = {
'sourceRevision': 'b' * 40,
'sourceTree': 'c' * 40,
'controllerVersion': '2.1.0',
}
monkeypatch.setattr(
module, '_snapshot_attested_controller',
lambda *_args, **_kwargs: (partial, directories, identity),
)
with pytest.raises(module.VolumeTransitionError, match='forge_secret_bundle_incomplete'):
module.prepare_promotion(
tmp_path / 'stage',
source_revision='d' * 40,
source_tree='e' * 40,
controller_version='2.2.0',
image_id=IMAGE_ID,
)
assert 'backup_volume_prepared' not in events
@pytest.mark.skipif(os.name == 'nt' or not hasattr(os, 'geteuid') or os.geteuid() != 0,
reason='fixed Forge ownership roundtrip requires Linux root')
def test_forge_secret_filesystem_capture_and_private_stage_keep_exact_owner(tmp_path):
config_root = tmp_path / 'config'
config_root.mkdir(mode=0o700)
secret_root = config_root / module.FORGE_SECRET_DIRECTORY
secret_root.mkdir(mode=0o700)
for index, (relative, owner) in enumerate(sorted(module.FORGE_SECRET_OWNERS.items())):
path = config_root / relative
path.write_bytes(f'fixture:{index}'.encode())
os.chown(path, *owner)
path.chmod(module.FORGE_SECRET_MODE)
descriptor = os.open(config_root, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
try:
entries, directories = module._read_tree_fd(descriptor, allow_forge_secrets=True)
finally:
os.close(descriptor)
config, config_dirs = module._config_tree(entries, directories, allow_partial=True)
payload = module._release_tar_bytes(REVISION, (config, config_dirs))
roundtrip, roundtrip_dirs = module._read_tar_tree(payload)
assert roundtrip == config
assert roundtrip_dirs == config_dirs
staged = tmp_path / 'private-stage'
module._write_private_tree(staged, config, config_dirs)
for relative, owner in module.FORGE_SECRET_OWNERS.items():
info = (staged / relative).stat(follow_symlinks=False)
assert (info.st_uid, info.st_gid) == owner
assert stat.S_IMODE(info.st_mode) == module.FORGE_SECRET_MODE
def test_unknown_config_file_is_rejected():
entries = {
"projects.json": (b"{}", 0o600),
"broker-token": (b"secret", 0o600),
"gitea-validation.json": (b"{}", 0o600),
"gitea-validation-token": (b"secret", 0o600),
"shell.py": (b"unexpected", 0o600),
}
with pytest.raises(module.VolumeTransitionError, match="config_snapshot_unknown"):
module._config_tree(entries, {"": 0o755})
def test_partial_config_tree_is_accepted_only_for_crash_resume():
partial = {"projects.json": (b"{}", 0o600), "broker-token": (b"secret", 0o600)}
assert module._config_tree(partial, {"": 0o755}, allow_partial=True) == (
partial, {"": 0o755}
)
with pytest.raises(module.VolumeTransitionError, match="snapshot_incomplete"):
module._config_tree(partial, {"": 0o755})
def test_config_snapshot_requires_private_mode_for_every_file():
entries = {
"projects.json": (b"{}", 0o644),
"broker-token": (b"secret", 0o600),
"gitea-validation.json": (b"{}", 0o600),
"gitea-validation-token": (b"secret", 0o600),
}
with pytest.raises(module.VolumeTransitionError, match="config_file_unsafe"):
module._config_tree(entries, {"": 0o755})
def test_previous_private_tls_is_validated_from_the_attested_config_snapshot(
tmp_path, monkeypatch
):
previous = tmp_path / "previous-controller"
previous.mkdir(mode=0o700)
(previous / "status_server.py").write_text("# captured validator\n", encoding="utf-8")
(previous / "private-tls.json").write_bytes(b"captured private config")
calls = []
monkeypatch.setattr(module, "_run", lambda arguments, **kwargs: calls.append((arguments, kwargs)) or b"")
module._validate_previous_private_tls(tmp_path)
assert calls == [(
["python3", str(previous / "status_server.py"), "--check-private-tls-config"],
{"max_output": 4096},
)]
(previous / "private-tls.json").unlink()
calls.clear()
module._validate_previous_private_tls(tmp_path)
assert calls == []
def test_staged_config_checks_run_new_artifact_with_copied_config_and_bounded_output(
tmp_path, monkeypatch
):
stage_root = tmp_path / "source"
stage_root.mkdir(mode=0o700)
release_tree = (
{
"status_server.py": (b"new status server", 0o644),
"controller-release.json": (b"new release identity", 0o644),
},
{"": 0o755},
)
config_tree = (
{
"projects.json": (b"{\"repo\":{}}", 0o600),
"broker-token": (b"private broker token", 0o600),
"gitea-validation.json": (b"{}", 0o600),
"gitea-validation-token": (b"private validation token", 0o600),
"private-tls.json": (b"private TLS config", 0o600),
"webhook-secrets/repo": (b"private webhook secret", 0o600),
},
{"": 0o755, "webhook-secrets": 0o700},
)
calls = []
validation_root = stage_root / "validation"
def run(arguments, *, data=None, max_output=module.MAX_BYTES):
calls.append((arguments, max_output))
assert (validation_root / "status_server.py").read_bytes() == b"new status server"
assert (validation_root / "projects.json").read_bytes() == b"{\"repo\":{}}"
assert (validation_root / "broker-token").read_bytes() == b"private broker token"
assert (validation_root / "webhook-secrets" / "repo").read_bytes() == b"private webhook secret"
return b"validated"
monkeypatch.setattr(module, "_write_private_tree", _write_test_tree)
monkeypatch.setattr(module, "_run", run)
validator = getattr(module, "_validate_staged_configuration", None)
assert callable(validator), "staged configuration validation helper is required"
validator(stage_root, release_tree, config_tree)
assert calls == [
(["python3", str(validation_root / "status_server.py"), "--check-artifact"], 4096),
(["python3", str(validation_root / "status_server.py"), "--check-config"], 4096),
(["python3", str(validation_root / "status_server.py"), "--check-validation-config"], 4096),
(["python3", str(validation_root / "status_server.py"), "--check-validation-api"], 4096),
(["python3", str(validation_root / "status_server.py"), "--check-private-tls-config"], 4096),
]
assert not validation_root.exists()
def test_staged_config_check_failure_is_sanitized_and_cleans_private_validation_tree(
tmp_path, monkeypatch
):
stage_root = tmp_path / "source"
stage_root.mkdir(mode=0o700)
release_tree = (
{
"status_server.py": (b"new status server", 0o644),
"controller-release.json": (b"new release identity", 0o644),
},
{"": 0o755},
)
config_tree = (
{
"projects.json": (b"{}", 0o600),
"broker-token": (b"top-secret-broker", 0o600),
"gitea-validation.json": (b"{}", 0o600),
"gitea-validation-token": (b"top-secret-validation", 0o600),
},
{"": 0o755},
)
class FailedProcess:
returncode = 1
stdout = b"token=top-secret-validation"
stderr = b"secret=top-secret-broker"
monkeypatch.setattr(module, "_write_private_tree", _write_test_tree)
monkeypatch.setattr(module.subprocess, "run", lambda *args, **kwargs: FailedProcess())
validator = getattr(module, "_validate_staged_configuration", None)
assert callable(validator), "staged configuration validation helper is required"
with pytest.raises(module.VolumeTransitionError, match="command_failed") as error:
validator(stage_root, release_tree, config_tree)
assert "top-secret" not in str(error.value)
assert not (stage_root / "validation").exists()
def _write_test_tree(path, entries, directories):
path.mkdir(mode=0o700)
for relative, _mode in sorted(directories.items(), key=lambda item: item[0].count("/")):
if relative:
(path / relative).mkdir(parents=True, exist_ok=True)
for relative, (contents, _mode) in entries.items():
destination = path / relative
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_bytes(contents)
@pytest.mark.skipif(os.name == "nt", reason="private TLS snapshot guard requires POSIX symlink semantics")
def test_previous_private_tls_rejects_symlinked_snapshot(tmp_path):
previous = tmp_path / "previous-controller"
previous.mkdir(mode=0o700)
(previous / "status_server.py").write_text("# captured validator\n", encoding="utf-8")
target = tmp_path / "tls.json"
target.write_bytes(b"unsafe")
(previous / "private-tls.json").symlink_to(target)
with pytest.raises(module.VolumeTransitionError, match="private_tls_unsafe"):
module._validate_previous_private_tls(tmp_path)
@pytest.mark.parametrize("kind", ["artifacts", "config"])
def test_writer_is_exact_image_only_bounded_and_has_no_host_paths(kind):
command = module._writer_spec(IMAGE_ID, REVISION, kind=kind)
assert command[0:3] == ["docker", "create", "--name"]
assert module.WRITER_NAME in command
assert IMAGE_ID in command
assert "--network" in command and command[command.index("--network") + 1] == "none"
assert command[command.index("--user") + 1] == "0:0"
assert "--read-only" in command and "--cap-drop" in command
assert "--cpu-quota" in command and "--memory" in command and "--pids-limit" in command
assert "--mount" in command
mount = command[command.index("--mount") + 1]
assert mount == f"type=volume,source={module.VOLUME},target={module.TARGET},volume-nocopy"
assert not any(item.startswith(("/mnt/", "/var/lib/")) for item in command)
@pytest.mark.parametrize("action", ["writer_copy", "config_writer_copy"])
def test_copy_diagnostic_classifies_missing_passwd_user_without_retaining_user_value(action):
stderr = (b'Error response from daemon: getent unable to find entry '
b'"fixture-user-name" in passwd database')
assert module._command_stderr_condition(stderr, action, max_bytes=1024) == (
"passwd_user_lookup_failed"
)
diagnostic = module._safe_command_diagnostic(
(action, "exit_1", "passwd_user_lookup_failed")
)
assert diagnostic == f"{action}_exit_1_passwd_user_lookup_failed"
assert "fixture-user-name" not in diagnostic
def test_promoted_compose_actions_use_only_stdin_and_fixed_service(compose):
baseline, calls = compose
rendered = module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID)
module.compose_action(rendered, "stop")
module.compose_action(rendered, "up")
assert calls[-2][0][-2:] == ["stop", module.SERVICE]
up_index = calls[-1][0].index("up")
assert calls[-1][0][up_index:] == [
"up", "-d", "--force-recreate", "--pull", "never", "--no-deps", module.SERVICE
]
assert calls[-2][1] == rendered
assert calls[-1][1] == rendered
assert baseline["name"] == "gitea-deploy-runners"
def test_rollback_uses_no_dependencies_when_recreating_prior_controller(compose, tmp_path, monkeypatch):
_baseline, calls = compose
previous = REVISION
target = "c" * 40
previous_compose = module.rendered_model(ORIGINAL_COMPOSE, previous, IMAGE_ID)
target_compose = module.rendered_model(ORIGINAL_COMPOSE, target, IMAGE_ID)
receipt = {
"phase": "activated",
"imageId": IMAGE_ID,
"previousRevision": previous,
"sourceRevision": target,
}
values = {
"controller-before.tar": module._release_tar_bytes(
previous,
({"controller-release.json": (b"previous release", 0o644)}, {"": 0o755}),
),
"controller-after.tar": b"unused by isolated rollback test",
"mirror-before.tar": b"unused by isolated rollback test",
"compose-rollback.json": previous_compose,
"compose-after.json": target_compose,
}
current = {
"Mounts": [{
"Destination": module.EXECUTABLE_TARGET,
"Source": module._expected_revision_path(previous).as_posix(),
}]
}
monkeypatch.setattr(module, "_load_transaction", lambda _backup: (1, receipt, values))
monkeypatch.setattr(module.os, "close", lambda _descriptor: None)
monkeypatch.setattr(module, "_controller_image_inspect", lambda *args, **kwargs: current)
monkeypatch.setattr(module, "_verify_release_leaf", lambda *args, **kwargs: True)
monkeypatch.setattr(module, "_stop_current_controller", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_sync_controller_mirror", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_ensure_root_compose", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_verify_compose_image", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_verify_active_mount", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_set_promotion_phase", lambda *args, **kwargs: None)
module.rollback_promotion(tmp_path / "backup")
up_calls = [
arguments for arguments, _data in calls
if arguments[:2] == ["docker", "compose"] and "up" in arguments
]
assert len(up_calls) == 1
up_index = up_calls[0].index("up")
assert up_calls[0][up_index:] == [
"up", "-d", "--force-recreate", "--pull", "never", "--no-deps", module.SERVICE
]
def test_installer_has_no_shared_executable_stage_or_compose_path():
source = (ROOT / "scripts/upgrade_deployment_controller.sh").read_text(encoding="utf-8")
assert "mktemp -d -p /tmp \"itworx-controller-upgrade-" in source
assert 'docker compose --project-directory "$ROOT" -f "$COMPOSE_FILE"' not in source
assert 'python3 "${work}/${PREPARER_NAME}"' in source
assert 'mv -- "$CONTROLLER_DIR"' not in source
assert 'cp -a -- "$CONTROLLER_DIR"' not in source
assert "ops/gitea-deploy-broker/mcp_hub_release_store.py" in source
def test_installer_release_order_matches_renderer_contract():
renderer_spec = importlib.util.spec_from_file_location(
"controller_renderer", ROOT / "scripts/prepare_controller_artifact.py"
)
renderer = importlib.util.module_from_spec(renderer_spec)
renderer_spec.loader.exec_module(renderer)
assert tuple(module.RELEASE_FILES) == tuple(renderer._ARTIFACT_FILES)
assert len(renderer._ARTIFACT_FILES) == len(set(renderer._ARTIFACT_FILES))
assert module.RELEASE_FILES[:3] == (
"controller_paths.py", "mcp_hub_release_store.py", "hub_permission_preparation.py"
)
candidate_helper = "prepare_mcp_hub_candidate.py"
assert candidate_helper in module.RELEASE_FILES
assert candidate_helper in renderer._ARTIFACT_FILES
assert module.RELEASE_FILES.count(candidate_helper) == 1
previous_files = module.LEGACY_RELEASE_FILES_PRE_CANDIDATE_HELPER
assert len(previous_files) == 20
previous_with_helper = module.LEGACY_RELEASE_FILES_WITH_CANDIDATE_HELPER
assert len(previous_with_helper) == 21
assert previous_with_helper == tuple(
[*previous_files[:previous_files.index("status_server.py") + 1], candidate_helper,
*previous_files[previous_files.index("status_server.py") + 1:]]
)
assert module.LEGACY_RELEASE_FILES_WITH_CONFIG_ROOT == HISTORICAL_CONFIG_ROOT_RELEASE_FILES
assert module._KNOWN_RELEASE_FILE_SETS == (
module.RELEASE_FILES,
module.LEGACY_RELEASE_FILES_PRE_COMPONENT,
module.LEGACY_RELEASE_FILES_WITH_ROLE_HELPER,
module.LEGACY_RELEASE_FILES_WITHOUT_ROLE_HELPER,
module.LEGACY_RELEASE_FILES_BEFORE_FORGE,
previous_files,
previous_with_helper,
HISTORICAL_CONFIG_ROOT_RELEASE_FILES,
module.LEGACY_RELEASE_FILES_2_1_13,
module.LEGACY_RELEASE_FILES_2_1_5,
)
assert all(
candidate_helper not in legacy
for legacy in (
previous_files,
module.LEGACY_RELEASE_FILES_WITH_CONFIG_ROOT,
module.LEGACY_RELEASE_FILES_2_1_13,
module.LEGACY_RELEASE_FILES_2_1_5,
)
)
assert module.RELEASE_FILES[module.RELEASE_FILES.index("forge_first_install.py") + 1] == (
"forge_database_credentials.py"
)
helper_index = module.RELEASE_FILES.index("forge_database_credentials.py") + 1
assert module.RELEASE_FILES[helper_index : helper_index + 2] == (
"forge_backup_paths.py", "forge_secret_bundle.py"
)
assert module.LEGACY_RELEASE_FILES_WITH_ROLE_HELPER == tuple(
name for name in module.RELEASE_FILES
if name not in {"forge_backup_paths.py", "forge_secret_bundle.py", "immutable_service_promotion.py", "registered_legacy_adoption.py", "registered_legacy_transport.py", "registered_build_context.py"}
)
assert module.LEGACY_RELEASE_FILES_WITHOUT_ROLE_HELPER == tuple(
name for name in module.LEGACY_RELEASE_FILES_WITH_ROLE_HELPER
if name != "forge_database_credentials.py"
)
installer = (ROOT / "scripts/upgrade_deployment_controller.sh").read_text(encoding="utf-8")
module_list = installer.split("module_files=(", 1)[1].split(")", 1)[0].split()
module_helper_index = module_list.index("forge_database_credentials.py") + 1
assert module_list[module_helper_index : module_helper_index + 2] == [
"forge_backup_paths.py", "forge_secret_bundle.py"
]
source_list = installer.split("source_paths=(", 1)[1].split(")", 1)[0].split()
source_helper_index = source_list.index("ops/gitea-deploy-broker/forge_database_credentials.py") + 1
assert source_list[source_helper_index : source_helper_index + 2] == [
"ops/gitea-deploy-broker/forge_backup_paths.py",
"ops/gitea-deploy-broker/forge_secret_bundle.py",
]
def test_pre_candidate_20_file_manifest_remains_accepted_and_current_digest_requires_helper():
files = HISTORICAL_PRE_CANDIDATE_RELEASE_FILES
assert module.LEGACY_RELEASE_FILES_PRE_CANDIDATE_HELPER == files
entries = {name: (f"pre-candidate:{name}".encode(), 0o644) for name in files}
manifest = {
"apiVersion": "itworx.controller-artifact/v1",
"controllerVersion": "2.1.14",
"sourceRepository": "jens/unraid-appops-gateway",
"sourceRevision": "b" * 40,
"sourceTree": "c" * 40,
"artifactSha256": module._artifact_digest(entries, files),
"builtAt": "2026-10-02T00:00:00.000Z",
}
entries["controller-release.json"] = (
json.dumps(manifest, separators=(",", ":")).encode(), 0o644
)
identity = module._release_identity(entries)
assert identity["artifactSha256"] == manifest["artifactSha256"]
assert module._matching_release_files(entries, manifest["artifactSha256"]) == files
current_entries = {name: (f"current:{name}".encode(), 0o644) for name in module.RELEASE_FILES}
assert module._artifact_digest(current_entries) == module._artifact_digest(
current_entries, module.RELEASE_FILES
)
del current_entries["prepare_mcp_hub_candidate.py"]
with pytest.raises(module.VolumeTransitionError, match="release_incomplete"):
module._artifact_digest(current_entries)
def test_previous_21_file_helper_manifest_remains_accepted():
files = HISTORICAL_CANDIDATE_HELPER_RELEASE_FILES
assert module.LEGACY_RELEASE_FILES_WITH_CANDIDATE_HELPER == files
assert len(files) == 21
assert files.count("prepare_mcp_hub_candidate.py") == 1
entries = {name: (f"previous-helper:{name}".encode(), 0o644) for name in files}
manifest = {
"apiVersion": "itworx.controller-artifact/v1",
"controllerVersion": "2.1.14",
"sourceRepository": "jens/unraid-appops-gateway",
"sourceRevision": "d" * 40,
"sourceTree": "e" * 40,
"artifactSha256": module._artifact_digest(entries, files),
"builtAt": "2026-10-02T00:00:00.000Z",
}
entries["controller-release.json"] = (
json.dumps(manifest, separators=(",", ":")).encode(), 0o644
)
identity = module._release_identity(entries)
assert identity["artifactSha256"] == manifest["artifactSha256"]
assert module._matching_release_files(entries, manifest["artifactSha256"]) == files
def test_candidate_preparation_helper_is_staged_archived_and_digest_bound(tmp_path):
if os.name == "nt":
pytest.skip("release staging requires POSIX descriptor-based file checks")
helper_name = "prepare_mcp_hub_candidate.py"
helper_source = ROOT / "ops" / "gitea-deploy-broker" / helper_name
helper_bytes = helper_source.read_bytes()
stage_root = tmp_path / "source"
ops = stage_root / "ops" / "gitea-deploy-broker"
scripts = stage_root / "scripts"
ops.mkdir(parents=True, mode=0o700)
scripts.mkdir(mode=0o700)
for name in module.RELEASE_FILES:
folder = scripts if name == "run_repopulse_adoption.py" else ops
contents = helper_bytes if name == helper_name else f"source:{name}\n".encode()
(folder / name).write_bytes(contents)
staged = module._stage_source_files(stage_root)
assert staged[helper_name] == (helper_bytes, 0o644)
revision = "a" * 40
tree = "b" * 40
entries, directories = module._prepare_merged_tree(
stage_root, ({}, {"": 0o755}), revision, tree, "2.1.14"
)
artifact_digest = module._release_identity(entries)["artifactSha256"]
assert artifact_digest == module._artifact_digest(entries)
archive = module._release_tar_bytes(revision, (entries, directories))
extracted, _ = module._read_tar_tree(archive)
assert extracted[helper_name][0] == helper_bytes
assert module._release_identity(extracted)["artifactSha256"] == artifact_digest
tampered = dict(extracted)
tampered[helper_name] = (helper_bytes + b"# altered after manifest\n", 0o644)
with pytest.raises(module.VolumeTransitionError, match="release_identity_invalid"):
module._release_identity(tampered)
def test_installer_explicit_archive_and_install_contract_includes_candidate_helper():
source = (ROOT / "scripts/upgrade_deployment_controller.sh").read_text(encoding="utf-8")
archive_paths = source.split("source_paths=(", 1)[1].split("\n)", 1)[0]
installed_modules = source.split("module_files=(", 1)[1].split("\n)", 1)[0]
assert "ops/gitea-deploy-broker/prepare_mcp_hub_candidate.py" in archive_paths
assert "prepare_mcp_hub_candidate.py" in installed_modules
powershell = (ROOT / "scripts/upgrade_deployment_controller.ps1").read_text(encoding="utf-8")
assert "ops/gitea-deploy-broker" in powershell.split("& git archive", 1)[1].splitlines()[0]
def test_current_release_digest_binds_the_role_password_helper():
entries = {name: (f"source:{name}".encode(), 0o644) for name in module.RELEASE_FILES}
expected = module._artifact_digest(entries)
assert module._matching_release_files(entries, expected) == module.RELEASE_FILES
entries["forge_database_credentials.py"] = (b"tampered helper", 0o644)
with pytest.raises(module.VolumeTransitionError, match="release_identity_invalid"):
module._matching_release_files(entries, expected)
def test_previous_role_helper_release_digest_remains_recognized_and_bound():
files = module.LEGACY_RELEASE_FILES_WITH_ROLE_HELPER
entries = {name: (f"source:{name}".encode(), 0o644) for name in files}
expected = module._artifact_digest(entries, files)
assert module._matching_release_files(entries, expected) == files
entries["forge_database_credentials.py"] = (b"tampered role helper", 0o644)
with pytest.raises(module.VolumeTransitionError, match="release_identity_invalid"):
module._matching_release_files(entries, expected)
def test_historical_config_root_manifest_order_and_digest_remain_exact():
files = HISTORICAL_CONFIG_ROOT_RELEASE_FILES
assert module.LEGACY_RELEASE_FILES_WITH_CONFIG_ROOT == files
entries = {name: (f"source:{name}".encode(), 0o644) for name in files}
expected = module._artifact_digest(entries, files)
assert module._matching_release_files(entries, expected) == files
entries["status_server.py"] = (b"tampered historical file", 0o644)
with pytest.raises(module.VolumeTransitionError, match="release_identity_invalid"):
module._matching_release_files(entries, expected)
def test_activated_controller_must_have_the_exact_backup_mount(monkeypatch):
mounts = [
{
"Type": "bind", "Source": module._expected_revision_path(REVISION).as_posix(),
"Destination": module.EXECUTABLE_TARGET, "RW": False,
},
{
"Type": "volume", "Name": module.VOLUME,
"Source": module.MOUNTPOINT.as_posix(), "Destination": module.TARGET, "RW": True,
},
{
"Type": "bind", "Source": module._expected_config_path().as_posix(),
"Destination": module.CONFIG_TARGET, "RW": False,
},
{
"Type": "bind", "Source": module.DOCKER_ROOT.as_posix(),
"Destination": module.DOCKER_ROOT.as_posix(), "RW": False,
},
{
"Type": "volume", "Name": "itworx-forge-master-key-backup",
"Source": "/var/lib/docker/volumes/itworx-forge-master-key-backup/_data",
"Destination": "/forge-master-key-backup", "RW": True,
},
]
item = {"Mounts": mounts}
monkeypatch.setattr(module, "_controller_image_inspect", lambda _image_id: item)
monkeypatch.setattr(module, "_read_release_leaf", lambda _revision: ({}, {"": 0o755}))
monkeypatch.setattr(module, "_release_identity", lambda _entries: {"sourceRevision": REVISION})
monkeypatch.setattr(module, "_read_config_leaf", lambda: ({}, {"": 0o755}))
module._verify_active_mount(REVISION, IMAGE_ID)
mounts.pop()
with pytest.raises(module.VolumeTransitionError, match="backup_mount"):
module._verify_active_mount(REVISION, IMAGE_ID)
@pytest.mark.parametrize(
"damage",
["backup_readonly", "backup_name", "backup_source", "backup_alias", "docker_root_rw"],
)
def test_activated_controller_rejects_backup_or_docker_root_mount_drift(
monkeypatch, damage
):
mounts = [
{
"Type": "bind", "Source": module._expected_revision_path(REVISION).as_posix(),
"Destination": module.EXECUTABLE_TARGET, "RW": False,
},
{
"Type": "volume", "Name": module.VOLUME,
"Source": module.MOUNTPOINT.as_posix(), "Destination": module.TARGET, "RW": True,
},
{
"Type": "bind", "Source": module._expected_config_path().as_posix(),
"Destination": module.CONFIG_TARGET, "RW": False,
},
{
"Type": "bind", "Source": module.DOCKER_ROOT.as_posix(),
"Destination": module.DOCKER_ROOT.as_posix(), "RW": False,
},
{
"Type": "volume", "Name": module.BACKUP_VOLUME,
"Source": module.BACKUP_MOUNTPOINT.as_posix(),
"Destination": module.BACKUP_TARGET, "RW": True,
},
]
if damage == "backup_readonly":
mounts[-1]["RW"] = False
elif damage == "backup_name":
mounts[-1]["Name"] = "foreign-volume"
elif damage == "backup_source":
mounts[-1]["Source"] = "/var/lib/docker/volumes/foreign/_data"
elif damage == "backup_alias":
mounts.append({
"Type": "bind", "Source": "/foreign", "Destination": f"{module.BACKUP_TARGET}/child",
"RW": True,
})
else:
mounts[-2]["RW"] = True
monkeypatch.setattr(module, "_controller_image_inspect", lambda _image_id: {"Mounts": mounts})
monkeypatch.setattr(module, "_read_release_leaf", lambda _revision: ({}, {"": 0o755}))
monkeypatch.setattr(module, "_release_identity", lambda _entries: {"sourceRevision": REVISION})
monkeypatch.setattr(module, "_read_config_leaf", lambda: ({}, {"": 0o755}))
with pytest.raises(module.VolumeTransitionError):
module._verify_active_mount(REVISION, IMAGE_ID)
@pytest.mark.skipif(os.name == "nt", reason="dirfd swap test requires POSIX openat")
def test_mirror_updates_stay_on_the_open_directory_after_path_swap(tmp_path, monkeypatch):
original = tmp_path / "controller"
moved = tmp_path / "controller-pinned"
original.mkdir()
before_entries = {
"controller_paths.py": (b"prior paths", 0o644),
"mcp_hub_release_store.py": (b"prior Hub helper", 0o644),
}
for name, (value, _mode) in before_entries.items():
(original / name).write_bytes(value)
prior = {
name: (f"prior:{name}".encode(), 0o644)
for name in module.LEGACY_RELEASE_FILES_2_1_13
}
prior["controller-release.json"] = (b"prior manifest", 0o644)
updated = {
name: (f"updated:{name}".encode(), 0o644)
for name in module.RELEASE_FILES
}
updated["controller-release.json"] = (b"updated manifest", 0o644)
prior_tar = module._release_tar_bytes("b" * 40, (prior, {"": 0o755}))
updated_tar = module._release_tar_bytes("c" * 40, (updated, {"": 0o755}))
mirror_tar = module._release_tar_bytes("a" * 40, (before_entries, {"": 0o755}))
current_path = original
monkeypatch.setattr(
module, "_directory",
lambda _path, _anchor: os.open(current_path, os.O_RDONLY | os.O_DIRECTORY),
)
read_item = module._file_info_at_optional
swapped = False
def swap_after_first_read(descriptor, name):
nonlocal swapped
value = read_item(descriptor, name)
if not swapped:
original.rename(moved)
original.mkdir()
swapped = True
return value
monkeypatch.setattr(module, "_file_info_at_optional", swap_after_first_read)
module._sync_controller_mirror(
prior_tar, updated_tar, mirror_tar, direction="forward"
)
assert swapped and list(original.iterdir()) == []
assert (moved / "controller_paths.py").read_bytes() == b"updated:controller_paths.py"
assert (moved / "mcp_hub_release_store.py").read_bytes() == b"updated:mcp_hub_release_store.py"
current_path = moved
monkeypatch.setattr(module, "_file_info_at_optional", read_item)
module._sync_controller_mirror(
updated_tar, prior_tar, mirror_tar, direction="rollback"
)
assert {path.name: path.read_bytes() for path in moved.iterdir()} == {
name: value for name, (value, _mode) in before_entries.items()
}
@pytest.mark.skipif(os.name == "nt" or not hasattr(os, "geteuid") or os.geteuid() != 0,
reason="private /tmp ownership validation requires root on POSIX")
@pytest.mark.parametrize("tmp_mode", [0o700, 0o1777])
def test_private_stage_accepts_root_owned_tmp_with_only_approved_modes(monkeypatch, tmp_mode):
import shutil
import uuid
work = Path("/tmp") / f"itworx-controller-upgrade-{uuid.uuid4().hex[:12]}.Ab3xY7"
source = work / "source"
source.mkdir(parents=True, mode=0o700)
source.chmod(0o700)
work.chmod(0o700)
try:
tmp_info = os.stat("/tmp", follow_symlinks=False)
original_fstat = os.fstat
def fstat_with_tmp_mode(descriptor):
info = original_fstat(descriptor)
if (info.st_dev, info.st_ino) == (tmp_info.st_dev, tmp_info.st_ino):
return types.SimpleNamespace(
st_uid=info.st_uid, st_gid=info.st_gid,
st_mode=stat.S_IFDIR | tmp_mode, st_dev=info.st_dev,
)
return info
monkeypatch.setattr(os, "fstat", fstat_with_tmp_mode)
module._validate_private_stage(source)
finally:
monkeypatch.undo()
shutil.rmtree(work)
@pytest.mark.skipif(os.name == "nt" or not hasattr(os, "geteuid") or os.geteuid() != 0,
reason="private /tmp ownership validation requires root on POSIX")
@pytest.mark.parametrize(
("target_name", "changes"),
[
("tmp", {"st_mode": stat.S_IFDIR | 0o777}),
("tmp", {"st_uid": 1000}),
("tmp", {"st_gid": 1000}),
("work", {"st_mode": stat.S_IFDIR | 0o755}),
("source", {"st_mode": stat.S_IFDIR | 0o755}),
("work", {"st_uid": 1000}),
("work", {"st_gid": 1000}),
("source", {"st_uid": 1000}),
("source", {"st_gid": 1000}),
("source", {"st_dev": 123456}),
],
)
def test_private_stage_rejects_unsafe_tmp_and_child_metadata(monkeypatch, target_name, changes):
import shutil
import uuid
work = Path("/tmp") / f"itworx-controller-upgrade-{uuid.uuid4().hex[:12]}.Ab3xY7"
source = work / "source"
source.mkdir(parents=True, mode=0o700)
source.chmod(0o700)
work.chmod(0o700)
targets = {"tmp": Path("/tmp"), "work": work, "source": source}
target_info = os.stat(targets[target_name], follow_symlinks=False)
original_fstat = os.fstat
def fstat_with_unsafe_metadata(descriptor):
info = original_fstat(descriptor)
if (info.st_dev, info.st_ino) == (target_info.st_dev, target_info.st_ino):
values = {
"st_uid": info.st_uid, "st_gid": info.st_gid,
"st_mode": info.st_mode, "st_dev": info.st_dev,
}
values.update(changes)
return types.SimpleNamespace(**values)
return info
try:
monkeypatch.setattr(os, "fstat", fstat_with_unsafe_metadata)
with pytest.raises(module.VolumeTransitionError, match="private_stage_invalid"):
module._validate_private_stage(source)
finally:
monkeypatch.undo()
shutil.rmtree(work)
@pytest.mark.skipif(os.name == "nt" or not hasattr(os, "geteuid") or os.geteuid() != 0,
reason="private /tmp ownership validation requires root on POSIX")
def test_private_stage_rejects_acl_and_symlinked_source_directory(monkeypatch):
import shutil
import uuid
work = Path("/tmp") / f"itworx-controller-upgrade-{uuid.uuid4().hex[:12]}.Ab3xY7"
source = work / "source"
source.mkdir(parents=True, mode=0o700)
source.chmod(0o700)
work.chmod(0o700)
source_info = os.stat(source, follow_symlinks=False)
original_listxattr = os.listxattr
def listxattr_with_acl(descriptor):
info = os.fstat(descriptor)
values = original_listxattr(descriptor)
if (info.st_dev, info.st_ino) == (source_info.st_dev, source_info.st_ino):
return [*values, "system.posix_acl_access"]
return values
try:
monkeypatch.setattr(os, "listxattr", listxattr_with_acl)
with pytest.raises(module.VolumeTransitionError, match="acl_unsafe"):
module._validate_private_stage(source)
monkeypatch.setattr(os, "listxattr", original_listxattr)
module._validate_private_stage(source)
source.rename(work / "source-real")
source.symlink_to(work / "source-real", target_is_directory=True)
with pytest.raises(module.VolumeTransitionError, match="private_stage_invalid"):
module._validate_private_stage(source)
finally:
shutil.rmtree(work)
@pytest.mark.parametrize("directive", ["env_file", "label_file"])
def test_source_compose_preflight_rejects_host_file_reads_before_compose(compose, directive):
_baseline, calls = compose
unsafe = (
b"name: gitea-deploy-runners\nservices:\n deploy-control:\n"
+ f" image: x\n {directive}: /tmp/secret\n".encode()
)
with pytest.raises(module.VolumeTransitionError, match="external_source_invalid"):
module.rendered_model(unsafe, REVISION, IMAGE_ID)
assert calls == []
@pytest.mark.parametrize(
"source",
[
b"services:\n app:\n 'env_file': /tmp/secret\n",
b"services: {app: {\"include\": ./untrusted.yml}}\n",
b"services:\n - \"build\": ./untrusted\n",
b"services:\n app:\n \"\\u0065nv_file\": /tmp/secret\n",
],
)
def test_source_compose_preflight_rejects_quoted_or_escaped_file_keys(source):
with pytest.raises(module.VolumeTransitionError):
module._preflight_source_compose(source)
def test_preparer_uses_the_pinned_bundled_yaml_parser():
assert module.yaml.__version__ == "6.0.2"
assert module.yaml.__name__.startswith("_appops_controller_yaml_")
assert module.yaml.__with_libyaml__ is False
assert Path(module.yaml.__file__).resolve().parent == ROOT / "scripts/vendor/yaml"
def test_preparer_does_not_replace_a_host_cached_yaml_module(monkeypatch):
host_yaml = types.ModuleType("yaml")
host_yaml.__file__ = "/host/site-packages/yaml/__init__.py"
monkeypatch.setitem(sys.modules, "yaml", host_yaml)
original_sys_path = sys.path.copy()
module_name = "_controller_volume_import_isolation_test"
spec = importlib.util.spec_from_file_location(
module_name, ROOT / "scripts/prepare_controller_hub_release_volume.py"
)
assert spec is not None and spec.loader is not None
isolated_module = importlib.util.module_from_spec(spec)
monkeypatch.setitem(sys.modules, module_name, isolated_module)
spec.loader.exec_module(isolated_module)
assert sys.modules["yaml"] is host_yaml
assert isolated_module.yaml.__name__.startswith("_appops_controller_yaml_")
assert isolated_module.yaml.safe_load(b"services: {}") == {"services": {}}
assert sys.path == original_sys_path
def test_source_compose_preflight_ignores_comments_strings_and_block_scalars():
source = (
b"name: gitea-deploy-runners\n"
b"# include: ./disabled.yml\n"
b"services:\n app:\n"
b" command: |\n"
b" echo 'env_file: is command text'\n"
b" echo include: is command text\n"
b" labels: {note: \"build: is a label value\"}\n"
)
module._preflight_source_compose(source)
def test_source_compose_preflight_rejects_unresolvable_alias_keys():
source = b"services:\n app:\n *unsafe-key: value\n"
with pytest.raises(module.VolumeTransitionError, match="compose_source_invalid"):
module._preflight_source_compose(source)
def test_source_compose_preflight_bounds_recursive_list_aliases():
source = b"services: {app: {image: fixed}}\nx-loop: &a [*a]\n"
module._preflight_source_compose(source)
@pytest.mark.parametrize(
"source",
[
b"services: {}\nconfigs:\n tls-material:\n file: /etc/ssl/private/key.pem\n",
b"services: {}\nsecrets: {gitea-token: {environment: GITEA_TOKEN}}\n",
],
)
def test_source_compose_preflight_rejects_local_config_and_secret_sources(source):
with pytest.raises(module.VolumeTransitionError, match="external_source_invalid"):
module._preflight_source_compose(source)
def test_source_compose_preflight_allows_external_config_and_service_environment_fields():
source = (
b"configs:\n tls-material:\n external: true\n"
b"services:\n app:\n environment:\n file: no-local-file-read\n"
)
module._preflight_source_compose(source)
def test_capture_program_is_fixed_descriptor_based_and_compiles():
legacy = module._controller_capture_source(config_root_in_code=True)
protected = module._controller_capture_source(config_root_in_code=False)
compile(legacy, "legacy-controller-capture", "exec")
compile(protected, "protected-controller-capture", "exec")
assert "config_root='/opt/gitea-deploy'" in legacy
assert "config_root='/controller-config'" in protected
assert "os.O_NOFOLLOW" in legacy and "os.fstat(fd)" in legacy
assert "open(path,'rb')" not in legacy
@pytest.mark.parametrize("legacy_config", [True, False])
def test_capture_archive_roundtrips_through_a_nonseekable_pipe(legacy_config):
source = module._controller_capture_source(config_root_in_code=legacy_config)
assert '__FORGE_' not in source
assert 'runtime_database_url.secret' in source
assert '/forge-master-key-backup' not in source
compile(source, 'controller-capture', 'exec')
statements = ast.parse(source).body
start = next(i for i, node in enumerate(statements)
if isinstance(node, ast.Assign)
and any(isinstance(target, ast.Name) and target.id == "stream"
for target in node.targets))
end = next(i for i, node in enumerate(statements[start:], start)
if isinstance(node, ast.Expr) and isinstance(node.value, ast.Call)
and isinstance(node.value.func, ast.Attribute)
and isinstance(node.value.func.value, ast.Name)
and node.value.func.value.id == "stream" and node.value.func.attr == "close")
archive_program = ast.unparse(ast.Module(body=statements[start:end + 1], type_ignores=[]))
fixture = (
"import io,sys,tarfile\n"
"records={'deploy.py':(b'fixture source',0o644),'secrets/key':(b'fixture secret',0o600)}\n"
"dirs={'secrets':0o700}\n"
"forge_secret_owners={}\n"
)
result = subprocess.run([sys.executable, "-c", fixture + archive_program],
capture_output=True, timeout=10, check=False)
assert result.returncode == 0, result.stderr.decode("utf-8", "replace")
with tarfile.open(fileobj=io.BytesIO(result.stdout), mode="r:") as archive:
assert archive.getnames() == ["secrets", "deploy.py", "secrets/key"]
assert archive.getmember("secrets").mode == 0o700
assert archive.getmember("secrets/key").mode == 0o600
assert archive.extractfile("deploy.py").read() == b"fixture source"
assert archive.extractfile("secrets/key").read() == b"fixture secret"
assert all(member.uid == member.gid == 0 for member in archive.getmembers())
@pytest.mark.parametrize("files", [
module.LEGACY_RELEASE_FILES_WITHOUT_ROLE_HELPER,
module.LEGACY_RELEASE_FILES_2_1_5,
module.LEGACY_RELEASE_FILES_WITH_CONFIG_ROOT,
module.LEGACY_RELEASE_FILES_BEFORE_FORGE,
])
def test_prior_release_capture_must_match_protected_git_tree(monkeypatch, files):
entries = {name: (f"source:{name}".encode(), 0o644) for name in files}
source_revision = "32a6eece0e3e1276946abdc04196c819daf02e2a"
source_tree = "e0d7e80b79504b964d968e51e0d21c288cac8f0d"
manifest = {
"apiVersion": "itworx.controller-artifact/v1",
"controllerVersion": "2.1.5",
"sourceRepository": "jens/unraid-appops-gateway",
"sourceRevision": source_revision,
"sourceTree": source_tree,
"artifactSha256": module._artifact_digest(entries, files),
"builtAt": "2026-10-01T00:00:00.000Z",
}
entries["controller-release.json"] = (
json.dumps(manifest, separators=(",", ":")).encode(), 0o644
)
identity = module._release_identity(entries)
outputs = {"rev-parse": (source_tree + "\n").encode()}
outputs.update({
f"ops/gitea-deploy-broker/{name}": entries[name][0]
for name in files if name != "run_repopulse_adoption.py"
})
outputs["scripts/run_repopulse_adoption.py"] = entries["run_repopulse_adoption.py"][0]
controller_id = "c" * 64
def run(arguments, **_kwargs):
assert arguments[:5] == ["docker", "exec", controller_id, "git",
f"--git-dir={module.SOURCE_GIT_DIR}"]
return outputs[arguments[-1].split(":", 1)[-1]] if arguments[-2] == "show" else outputs["rev-parse"]
monkeypatch.setattr(module, "_run", run)
module._verify_git_source_identity(entries, identity, controller_id=controller_id)
outputs["ops/gitea-deploy-broker/deploy.py"] = b"replaced source"
with pytest.raises(module.VolumeTransitionError, match="source_mismatch"):
module._verify_git_source_identity(entries, identity, controller_id=controller_id)
@pytest.mark.parametrize("controller_id", ["gitea-deploy-control", "", "a" * 63, "a" * 65])
def test_prior_release_source_verification_requires_pinned_container_id(controller_id, monkeypatch):
monkeypatch.setattr(module, "_run", lambda *_args, **_kwargs: pytest.fail("Docker must not run"))
with pytest.raises(module.VolumeTransitionError, match="container_invalid"):
module._verify_git_source_identity({}, {}, controller_id=controller_id)
@pytest.mark.parametrize(
("name", "revision", "version", "captured", "source", "accepted"),
[
("run_repopulse_adoption.py", "034c9f844d2455850ba37b31379b7edaa786583e", "2.1.12", b"one\r\ntwo\r\n", b"one\ntwo\n", True),
("deploy.py", "034c9f844d2455850ba37b31379b7edaa786583e", "2.1.12", b"one\r\ntwo\r\n", b"one\ntwo\n", False),
("run_repopulse_adoption.py", "a" * 40, "2.1.12", b"one\r\ntwo\r\n", b"one\ntwo\n", False),
("run_repopulse_adoption.py", "034c9f844d2455850ba37b31379b7edaa786583e", "2.1.13", b"one\r\ntwo\r\n", b"one\ntwo\n", False),
("run_repopulse_adoption.py", "034c9f844d2455850ba37b31379b7edaa786583e", "2.1.12", b"one\r\ntwo\n", b"one\ntwo\n", False),
("run_repopulse_adoption.py", "034c9f844d2455850ba37b31379b7edaa786583e", "2.1.12", b"one\r\nchanged\r\n", b"one\ntwo\n", False),
("run_repopulse_adoption.py", "034c9f844d2455850ba37b31379b7edaa786583e", "2.1.12", b"one\r\r\n", b"one\r\n", False),
],
)
def test_legacy_crlf_compatibility_is_limited_to_one_attested_handoff(
name, revision, version, captured, source, accepted,
):
identity = {"sourceRevision": revision, "controllerVersion": version}
assert module._matches_legacy_crlf_source(name, captured, source, identity) is accepted
def test_current_e854_crlf_capture_requires_exact_full_release_identity():
checked_out_source = (ROOT / "scripts/run_repopulse_adoption.py").read_bytes()
source = checked_out_source.replace(b"\r\n", b"\n")
captured = source.replace(b"\n", b"\r\n")
assert hashlib.sha256(source).hexdigest() == (
"9c66fae21f1eec27215af08f7bb7d24bafc6f10757393b56c42721fa99dccc66"
)
assert hashlib.sha256(captured).hexdigest() == (
"e3036cef0f6e1934297de2d35c0381f93441bbf6f28bd18c94c3f47fc116e4e1"
)
identity = {
"sourceRevision": "e854334923cca574482d22b8424ba3ce46ec6aa1",
"sourceTree": "cf2a117f9222b952deb9902c646116612f3a7ea0",
"controllerVersion": "2.1.14",
"artifactSha256": "e88739bf091c8bac77ea0db5b97300666a1baa8ba984bbf6ae91e3bf25719e08",
}
assert module._matches_legacy_crlf_source(
"run_repopulse_adoption.py", captured, source, identity
)
@pytest.mark.parametrize(
("name", "field", "value", "change_capture"),
[
("run_repopulse_adoption.py", "sourceRevision", "f" * 40, False),
("run_repopulse_adoption.py", "sourceTree", "f" * 40, False),
("run_repopulse_adoption.py", "controllerVersion", "2.1.15", False),
("run_repopulse_adoption.py", "artifactSha256", "f" * 64, False),
("other.py", "", "", False),
("run_repopulse_adoption.py", "", "", True),
],
)
def test_current_e854_crlf_compatibility_rejects_near_misses(
name, field, value, change_capture,
):
checked_out_source = (ROOT / "scripts/run_repopulse_adoption.py").read_bytes()
source = checked_out_source.replace(b"\r\n", b"\n")
captured = source.replace(b"\n", b"\r\n")
identity = {
"sourceRevision": "e854334923cca574482d22b8424ba3ce46ec6aa1",
"sourceTree": "cf2a117f9222b952deb9902c646116612f3a7ea0",
"controllerVersion": "2.1.14",
"artifactSha256": "e88739bf091c8bac77ea0db5b97300666a1baa8ba984bbf6ae91e3bf25719e08",
}
if field:
identity[field] = value
if change_capture:
captured = captured.replace(b"\r\n", b"\n", 1)
assert not module._matches_legacy_crlf_source(name, captured, source, identity)
def test_current_e854_crlf_exception_integrates_with_git_source_verification(monkeypatch):
revision = "e854334923cca574482d22b8424ba3ce46ec6aa1"
tree = "cf2a117f9222b952deb9902c646116612f3a7ea0"
artifact_sha = "e88739bf091c8bac77ea0db5b97300666a1baa8ba984bbf6ae91e3bf25719e08"
files = module.LEGACY_RELEASE_FILES_BEFORE_FORGE
assert len(files) == 30
checked_out_script = (ROOT / "scripts/run_repopulse_adoption.py").read_bytes()
runner_source = checked_out_script.replace(b"\r\n", b"\n")
runner_capture = runner_source.replace(b"\n", b"\r\n")
assert hashlib.sha256(runner_source).hexdigest() == (
"9c66fae21f1eec27215af08f7bb7d24bafc6f10757393b56c42721fa99dccc66"
)
assert hashlib.sha256(runner_capture).hexdigest() == (
"e3036cef0f6e1934297de2d35c0381f93441bbf6f28bd18c94c3f47fc116e4e1"
)
source_by_name = {}
entries = {}
for name in files:
path = (ROOT / "scripts/run_repopulse_adoption.py" if name == "run_repopulse_adoption.py"
else ROOT / "ops/gitea-deploy-broker" / name)
source = runner_source if name == "run_repopulse_adoption.py" else path.read_bytes()
source = source.replace(b"\r\n", b"\n")
source_by_name[name] = source
entries[name] = (
runner_capture if name == "run_repopulse_adoption.py" else source,
0o644,
)
identity_data = {
"apiVersion": "itworx.controller-artifact/v1",
"controllerVersion": "2.1.14",
"sourceRepository": "jens/unraid-appops-gateway",
"sourceRevision": revision,
"sourceTree": tree,
"artifactSha256": artifact_sha,
"builtAt": "2026-09-30T00:00:00Z",
}
entries["controller-release.json"] = (
json.dumps(identity_data, separators=(",", ":")).encode(), 0o644
)
identity = {key: str(value) for key, value in identity_data.items()}
git_outputs = {path: source for name, source in source_by_name.items() for path in [
"scripts/run_repopulse_adoption.py"
if name == "run_repopulse_adoption.py"
else f"ops/gitea-deploy-broker/{name}"
]}
controller_id = "c" * 64
def select_attested_files(captured, expected):
assert expected == artifact_sha
assert all(name in captured for name in files)
return files
def run(arguments, **_kwargs):
assert arguments[:5] == [
"docker", "exec", controller_id, "git", f"--git-dir={module.SOURCE_GIT_DIR}"
]
if arguments[-2] == "rev-parse":
return f"{tree}\n".encode()
return git_outputs[arguments[-1].split(":", 1)[-1]]
monkeypatch.setattr(module, "_matching_release_files", select_attested_files)
monkeypatch.setattr(module, "_run", run)
module._verify_git_source_identity(entries, identity, controller_id=controller_id)
other_file = next(name for name in files if name != "run_repopulse_adoption.py")
other_path = f"ops/gitea-deploy-broker/{other_file}"
git_outputs[other_path] = b"tampered protected Git source"
with pytest.raises(module.VolumeTransitionError, match="source_mismatch"):
module._verify_git_source_identity(entries, identity, controller_id=controller_id)
def _current_controller_with_graph_mount():
return {
"Id": "c" * 64, "Image": IMAGE_ID, "Name": "/gitea-deploy-control",
"State": {"Running": True},
"Mounts": [
{"Type": "bind", "Source": module._expected_revision_path(REVISION).as_posix(),
"Destination": module.EXECUTABLE_TARGET, "RW": False, "Propagation": "rslave"},
{"Type": "bind", "Source": "/var/lib/docker", "Destination": "/var/lib/docker",
"RW": False, "Propagation": "rslave"},
],
}
def test_current_controller_accepts_only_fixed_readonly_docker_root_ancestor(monkeypatch):
item = _current_controller_with_graph_mount()
monkeypatch.setattr(module, "_run", lambda *_args, **_kwargs: json.dumps([item]).encode())
assert module._controller_image_inspect(IMAGE_ID) == item
@pytest.mark.parametrize("damage", ["writable", "redirected_target", "child_source", "parent_source", "volume_type", "named_volume_alias", "duplicate_graph", "shared_propagation", "private_propagation"])
def test_current_controller_rejects_other_docker_root_appdata_aliases(monkeypatch, damage):
item = _current_controller_with_graph_mount()
graph = item["Mounts"][1]
if damage == "writable":
graph["RW"] = True
elif damage == "redirected_target":
graph["Destination"] = "/graph-alias"
elif damage == "child_source":
graph["Source"] = "/var/lib/docker/volumes"
elif damage == "parent_source":
graph["Source"] = "/var/lib"
elif damage == "volume_type":
graph["Type"] = "volume"
elif damage == "named_volume_alias":
graph["Name"] = module.MANAGED_APPDATA_VOLUME
elif damage == "duplicate_graph":
item["Mounts"].append(dict(graph))
elif damage == "shared_propagation":
graph["Propagation"] = "rshared"
else:
graph["Propagation"] = "rprivate"
monkeypatch.setattr(module, "_run", lambda *_args, **_kwargs: json.dumps([item]).encode())
with pytest.raises(module.VolumeTransitionError, match="managed_appdata_mount_alias"):
module._controller_image_inspect(IMAGE_ID)
@pytest.mark.parametrize("populated", [False, True])
def test_config_snapshot_preserves_fixed_appops_legacy_intent_directory(populated):
entries = {name: (b"{}", 0o600) for name in ("projects.json", "broker-token", "gitea-validation.json", "gitea-validation-token")}
if populated:
entries["appops-legacy-upgrade/unraid-appops-gateway.json"] = (b'{"schema":"itworx.appops-legacy-upgrade-intent/v1"}', 0o600)
directories = {"": 0o755, "appops-legacy-upgrade": 0o700}
assert module._config_tree(entries, directories) == (entries, directories)
assert module._read_tar_tree(module._release_tar_bytes(REVISION, (entries, directories))) == (entries, directories)
@pytest.mark.parametrize("damage", ["unknown_file", "nested_directory", "public_directory", "public_file"])
def test_config_snapshot_keeps_appops_legacy_state_contract_closed(damage):
entries = {name: (b"{}", 0o600) for name in ("projects.json", "broker-token", "gitea-validation.json", "gitea-validation-token")}
directories = {"": 0o755, "appops-legacy-upgrade": 0o700}
if damage == "unknown_file":
entries["appops-legacy-upgrade/shell.py"] = (b"unexpected", 0o600)
elif damage == "nested_directory":
directories["appops-legacy-upgrade/nested"] = 0o700
elif damage == "public_directory":
directories["appops-legacy-upgrade"] = 0o755
else:
entries["appops-legacy-upgrade/unraid-appops-gateway.json"] = (b"{}", 0o644)
with pytest.raises(module.VolumeTransitionError):
module._config_tree(entries, directories)