Publish curated Unraid-AppOps-Gateway source from 451646fee806a47e3256731766980a05a262dcdd
This commit is contained in:
1 parent
532f3d9f26
commit
0a82153d95
43 files changed
+17722
-93
No files matched your search
@@ -28,6 +28,7 @@ REVISION = "a" * 40
|
||||
TREE = "b" * 40
|
||||
CALLER_ID = "c" * 64
|
||||
MODERN_HELPER_ID = "f" * 64
|
||||
RETIRED_HELPER_ID = bootstrap.RETIRED_FAILED_MODERN_HELPER_ID
|
||||
HOST_HOSTNAME = "Tower"
|
||||
|
||||
|
||||
@@ -99,7 +100,7 @@ bash "$work/upgrade.sh" \\
|
||||
'''
|
||||
|
||||
|
||||
def _legacy_helper_spec() -> dict[str, Any]:
|
||||
def _legacy_helper_spec(revision: str = REVISION) -> dict[str, Any]:
|
||||
tree = ast.parse(LEGACY_HELPER_SOURCE_FIXTURE)
|
||||
function = next(
|
||||
node
|
||||
@@ -121,7 +122,7 @@ def _legacy_helper_spec() -> dict[str, Any]:
|
||||
compile(ast.Module(body=[function], type_ignores=[]), "legacy-promoter", "exec"),
|
||||
namespace,
|
||||
)
|
||||
return namespace["helper_container_spec"](revision=REVISION, image_id=CONTROLLER_IMAGE_ID)
|
||||
return namespace["helper_container_spec"](revision=revision, image_id=CONTROLLER_IMAGE_ID)
|
||||
|
||||
|
||||
def _image(labels: dict[str, str]) -> dict[str, Any]:
|
||||
@@ -409,14 +410,43 @@ class FakeDocker:
|
||||
self.existing_child = existing_child
|
||||
self.created: list[dict[str, Any]] = []
|
||||
self.started: list[str] = []
|
||||
self.removed: list[str] = []
|
||||
self.inspected: list[str] = []
|
||||
self.host_name = HOST_HOSTNAME
|
||||
self.log_bytes = b"controller_v2_ready revision=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"
|
||||
self.named_inspect_override: dict[str, Any] | None = None
|
||||
self.named_inspect_override_at = 0
|
||||
self.retired_id_inspect_override: dict[str, Any] | None = None
|
||||
self.appops_inspect_count = 0
|
||||
self.appops_inspect_override: dict[str, Any] | None = None
|
||||
self.appops_inspect_override_at = 0
|
||||
self._inspect_counts: dict[str, int] = {}
|
||||
|
||||
def inspect_container(self, reference: str) -> dict[str, Any] | None:
|
||||
self.inspected.append(reference)
|
||||
self._inspect_counts[reference] = self._inspect_counts.get(reference, 0) + 1
|
||||
if reference == "itworx-controller-artifact-promoter-modern":
|
||||
if (
|
||||
self.named_inspect_override is not None
|
||||
and self._inspect_counts[reference] == self.named_inspect_override_at
|
||||
):
|
||||
return self.named_inspect_override
|
||||
return self.existing_child
|
||||
if (
|
||||
reference == RETIRED_HELPER_ID
|
||||
and self.retired_id_inspect_override is not None
|
||||
):
|
||||
return self.retired_id_inspect_override
|
||||
if (
|
||||
reference == RETIRED_HELPER_ID
|
||||
and self.existing_child is not None
|
||||
and self.existing_child.get("Id") == RETIRED_HELPER_ID
|
||||
):
|
||||
return self.existing_child
|
||||
if reference == APPOPS_CONTAINER_ID:
|
||||
self.appops_inspect_count += 1
|
||||
if self.appops_inspect_count == self.appops_inspect_override_at:
|
||||
return self.appops_inspect_override
|
||||
return self.containers.get(reference)
|
||||
|
||||
def daemon_name(self) -> str:
|
||||
@@ -444,8 +474,15 @@ class FakeDocker:
|
||||
source_revision = self.created[-1]["Labels"]["itworx.source.revision"]
|
||||
self.containers["gitea-deploy-control"] = _modern_controller_inspect(source_revision)
|
||||
|
||||
def remove_retired_failed_container(self, identifier: str) -> None:
|
||||
assert identifier == RETIRED_HELPER_ID
|
||||
assert self.existing_child is not None
|
||||
assert self.existing_child["Id"] == RETIRED_HELPER_ID
|
||||
self.removed.append(identifier)
|
||||
self.existing_child = None
|
||||
|
||||
def container_logs(self, identifier: str, *, tail: int, max_bytes: int) -> bytes:
|
||||
assert identifier == MODERN_HELPER_ID
|
||||
assert identifier in {MODERN_HELPER_ID, RETIRED_HELPER_ID}
|
||||
assert tail <= 40
|
||||
assert max_bytes <= 8192
|
||||
return self.log_bytes[:max_bytes]
|
||||
@@ -458,9 +495,13 @@ class FakeProtectedGit:
|
||||
self.helper_source = helper_source
|
||||
self.blobs_read: list[tuple[str, str]] = []
|
||||
self.main_checks = 0
|
||||
self.fail_current_main_at: int | None = None
|
||||
self.retired_tree_valid = True
|
||||
|
||||
def verify_current_main(self, revision: str, tree: str) -> None:
|
||||
self.main_checks += 1
|
||||
if self.fail_current_main_at == self.main_checks:
|
||||
raise bootstrap.BootstrapError("controller_promoter_source_not_current_main")
|
||||
if (revision, tree) != (self.revision, self.tree):
|
||||
raise bootstrap.BootstrapError("controller_promoter_source_not_current_main")
|
||||
|
||||
@@ -468,9 +509,18 @@ class FakeProtectedGit:
|
||||
if (revision, tree) != (LEGACY_REVISION, LEGACY_TREE):
|
||||
raise bootstrap.BootstrapError("controller_promoter_legacy_provenance_invalid")
|
||||
|
||||
def verify_retired_modern_revision(self, revision: str, tree: str) -> None:
|
||||
if (revision, tree) != (
|
||||
bootstrap.RETIRED_FAILED_MODERN_REVISION,
|
||||
bootstrap.RETIRED_FAILED_MODERN_TREE,
|
||||
) or not self.retired_tree_valid:
|
||||
raise bootstrap.BootstrapError(
|
||||
"controller_promoter_retired_helper_provenance_invalid"
|
||||
)
|
||||
|
||||
def read_blob(self, revision: str, path: str) -> bytes:
|
||||
self.blobs_read.append((revision, path))
|
||||
if revision != self.revision or path != "backend/app/services/controller_artifact_helper_spec.py":
|
||||
if revision not in {self.revision, bootstrap.RETIRED_FAILED_MODERN_REVISION} or path != "backend/app/services/controller_artifact_helper_spec.py":
|
||||
raise bootstrap.BootstrapError("controller_promoter_source_blob_invalid")
|
||||
return self.helper_source.encode("utf-8")
|
||||
|
||||
@@ -518,7 +568,10 @@ def _run_bootstrap(
|
||||
*,
|
||||
health: dict[str, Any] | None = None,
|
||||
initial_health: dict[str, Any] | None = None,
|
||||
health_sequence: list[dict[str, Any]] | None = None,
|
||||
hostname: str = HOST_HOSTNAME,
|
||||
source_revision: str = REVISION,
|
||||
source_tree: str = TREE,
|
||||
):
|
||||
source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_text(
|
||||
encoding="utf-8"
|
||||
@@ -530,15 +583,15 @@ def _run_bootstrap(
|
||||
version="2.1.12",
|
||||
artifact_sha256=bootstrap.LEGACY_CONTROLLER_ARTIFACT_SHA256,
|
||||
)
|
||||
health_reads = [initial, health or _health()]
|
||||
health_reads = health_sequence or [initial, health or _health()]
|
||||
|
||||
def read_health() -> dict[str, Any]:
|
||||
return health_reads.pop(0) if health_reads else health or _health()
|
||||
|
||||
return bootstrap.run_bootstrap(
|
||||
"jens/unraid-appops-gateway",
|
||||
REVISION,
|
||||
TREE,
|
||||
source_revision,
|
||||
source_tree,
|
||||
docker=docker,
|
||||
protected_git=git,
|
||||
hostname=hostname,
|
||||
@@ -549,6 +602,54 @@ def _run_bootstrap(
|
||||
)
|
||||
|
||||
|
||||
def _retired_failed_child(helper_source: str) -> dict[str, Any]:
|
||||
spec = bootstrap.render_modern_helper_spec(
|
||||
helper_source,
|
||||
revision=bootstrap.RETIRED_FAILED_MODERN_REVISION,
|
||||
image_id=CONTROLLER_IMAGE_ID,
|
||||
current_controller_id=CONTROLLER_CONTAINER_ID,
|
||||
current_controller_revision=CONTROLLER_REVISION,
|
||||
current_controller_tree=CONTROLLER_TREE,
|
||||
current_controller_version="2.1.12",
|
||||
)
|
||||
child = _inspect_from_spec(
|
||||
"itworx-controller-artifact-promoter-modern",
|
||||
RETIRED_HELPER_ID,
|
||||
CONTROLLER_IMAGE_ID,
|
||||
spec,
|
||||
state={"Running": False, "ExitCode": 1, "Status": "exited"},
|
||||
)
|
||||
child["Config"]["Volumes"] = None
|
||||
child["Mounts"] = [
|
||||
{"Type": "bind", "Source": "/mnt/cache", "Destination": "/mnt/cache", "RW": True, "Propagation": "rprivate"},
|
||||
{"Type": "bind", "Source": "/mnt/user", "Destination": "/mnt/user", "RW": True, "Propagation": "rprivate"},
|
||||
{"Type": "bind", "Source": "/var/lib/docker", "Destination": "/var/lib/docker", "RW": False, "Propagation": "rslave"},
|
||||
{"Type": "bind", "Source": "/srv/example/appdata/gitea/git/repositories", "Destination": "/gitea-repositories", "RW": False, "Propagation": "rprivate"},
|
||||
{"Type": "bind", "Source": "/var/run/docker.sock", "Destination": "/var/run/docker.sock", "RW": True, "Propagation": "rprivate"},
|
||||
]
|
||||
child["HostConfig"].update(
|
||||
{
|
||||
"Mounts": None,
|
||||
"CapAdd": None,
|
||||
"CapDrop": None,
|
||||
"Devices": None,
|
||||
"DeviceRequests": None,
|
||||
"PortBindings": None,
|
||||
"VolumesFrom": None,
|
||||
"PidMode": "",
|
||||
"UTSMode": "",
|
||||
"UsernsMode": "",
|
||||
"CgroupParent": "",
|
||||
"IpcMode": "private",
|
||||
"CgroupnsMode": "private",
|
||||
"Runtime": "runc",
|
||||
"AutoRemove": False,
|
||||
"PublishAllPorts": False,
|
||||
}
|
||||
)
|
||||
return child
|
||||
|
||||
|
||||
def test_initial_controller_runtime_accepts_exact_current_compose_container():
|
||||
docker = FakeDocker(_legacy_helper_spec())
|
||||
base_image_labels = bootstrap._inspect_controller_base_image(docker)
|
||||
@@ -876,6 +977,187 @@ def test_running_modern_child_returns_unknown_without_duplicate_or_stop():
|
||||
assert docker.started == []
|
||||
|
||||
|
||||
def test_exact_failed_historical_child_is_removed_before_current_helper_launch():
|
||||
helper_source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
legacy_health = _health(
|
||||
revision=CONTROLLER_REVISION,
|
||||
tree=CONTROLLER_TREE,
|
||||
version="2.1.12",
|
||||
artifact_sha256=bootstrap.LEGACY_CONTROLLER_ARTIFACT_SHA256,
|
||||
)
|
||||
failed_child = _retired_failed_child(helper_source)
|
||||
docker = FakeDocker(_legacy_helper_spec(), existing_child=failed_child)
|
||||
git = FakeProtectedGit(helper_source)
|
||||
|
||||
result = _run_bootstrap(
|
||||
docker,
|
||||
git,
|
||||
health_sequence=[legacy_health, legacy_health, legacy_health, _health()],
|
||||
)
|
||||
|
||||
assert result["status"] == "promoted"
|
||||
assert docker.removed == [RETIRED_HELPER_ID]
|
||||
assert docker.created and docker.started == [MODERN_HELPER_ID]
|
||||
assert RETIRED_HELPER_ID in docker.inspected
|
||||
assert docker.appops_inspect_count == 2
|
||||
assert git.blobs_read.count(
|
||||
(bootstrap.RETIRED_FAILED_MODERN_REVISION, bootstrap.MODERN_HELPER_SOURCE_PATH)
|
||||
) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mutation",
|
||||
[
|
||||
"running",
|
||||
"successful-exit",
|
||||
"boolean-exit",
|
||||
"other-exit",
|
||||
"wrong-id",
|
||||
"config-volumes",
|
||||
"extra-host-mount",
|
||||
"cap-add",
|
||||
"device-request",
|
||||
"docker-root-writable",
|
||||
"socket-bind-changed",
|
||||
"legacy-controller-stopped",
|
||||
"current-main-changed",
|
||||
"second-name-inspect-changed",
|
||||
"second-id-inspect-changed",
|
||||
"appops-stopped-before-delete",
|
||||
"historical-tree-proof-failed",
|
||||
],
|
||||
)
|
||||
def test_retired_helper_cleanup_fails_closed_on_any_identity_or_state_drift(mutation: str):
|
||||
helper_source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
failed_child = _retired_failed_child(helper_source)
|
||||
docker = FakeDocker(_legacy_helper_spec(), existing_child=failed_child)
|
||||
git = FakeProtectedGit(helper_source)
|
||||
if mutation == "running":
|
||||
failed_child["State"].update({"Running": True, "Status": "running"})
|
||||
elif mutation == "successful-exit":
|
||||
failed_child["State"]["ExitCode"] = 0
|
||||
elif mutation == "boolean-exit":
|
||||
failed_child["State"]["ExitCode"] = True
|
||||
elif mutation == "other-exit":
|
||||
failed_child["State"]["ExitCode"] = 2
|
||||
elif mutation == "wrong-id":
|
||||
failed_child["Id"] = "9" * 64
|
||||
elif mutation == "config-volumes":
|
||||
failed_child["Config"]["Volumes"] = {"/extra": {}}
|
||||
elif mutation == "extra-host-mount":
|
||||
failed_child["HostConfig"]["Mounts"] = [{"Type": "volume"}]
|
||||
elif mutation == "cap-add":
|
||||
failed_child["HostConfig"]["CapAdd"] = ["SYS_ADMIN"]
|
||||
elif mutation == "device-request":
|
||||
failed_child["HostConfig"]["DeviceRequests"] = [{"Capabilities": [["gpu"]]}]
|
||||
elif mutation == "docker-root-writable":
|
||||
failed_child["HostConfig"]["Binds"][2] = "/var/lib/docker:/var/lib/docker:rw"
|
||||
failed_child["Mounts"][2]["RW"] = True
|
||||
elif mutation == "socket-bind-changed":
|
||||
failed_child["HostConfig"]["Binds"][4] = "/tmp/docker.sock:/var/run/docker.sock"
|
||||
failed_child["Mounts"][4]["Source"] = "/tmp/docker.sock"
|
||||
elif mutation == "legacy-controller-stopped":
|
||||
docker.containers["gitea-deploy-control"]["State"]["Running"] = False
|
||||
elif mutation == "current-main-changed":
|
||||
git.fail_current_main_at = 2
|
||||
elif mutation == "second-name-inspect-changed":
|
||||
docker.named_inspect_override = json.loads(json.dumps(failed_child))
|
||||
docker.named_inspect_override["Config"]["Cmd"].append("unexpected")
|
||||
docker.named_inspect_override_at = 2
|
||||
elif mutation == "second-id-inspect-changed":
|
||||
docker.retired_id_inspect_override = json.loads(json.dumps(failed_child))
|
||||
docker.retired_id_inspect_override["State"]["ExitCode"] = 0
|
||||
elif mutation == "appops-stopped-before-delete":
|
||||
docker.appops_inspect_override = json.loads(
|
||||
json.dumps(docker.containers[APPOPS_CONTAINER_ID])
|
||||
)
|
||||
docker.appops_inspect_override["State"]["Running"] = False
|
||||
docker.appops_inspect_override_at = 2
|
||||
elif mutation == "historical-tree-proof-failed":
|
||||
git.retired_tree_valid = False
|
||||
|
||||
legacy_health = _health(
|
||||
revision=CONTROLLER_REVISION,
|
||||
tree=CONTROLLER_TREE,
|
||||
version="2.1.12",
|
||||
artifact_sha256=bootstrap.LEGACY_CONTROLLER_ARTIFACT_SHA256,
|
||||
)
|
||||
with pytest.raises(bootstrap.BootstrapError):
|
||||
_run_bootstrap(
|
||||
docker,
|
||||
git,
|
||||
health_sequence=[legacy_health, legacy_health, legacy_health],
|
||||
)
|
||||
|
||||
assert docker.removed == []
|
||||
assert docker.created == []
|
||||
assert docker.started == []
|
||||
|
||||
|
||||
def test_retired_helper_remove_uses_non_force_non_volume_docker_delete():
|
||||
client = bootstrap.DockerSocketClient()
|
||||
calls: list[tuple[str, str]] = []
|
||||
|
||||
def request(method: str, path: str, **_kwargs: Any) -> tuple[int, bytes]:
|
||||
calls.append((method, path))
|
||||
return 204, b""
|
||||
|
||||
client._request = request # type: ignore[method-assign]
|
||||
client.remove_retired_failed_container(RETIRED_HELPER_ID)
|
||||
|
||||
assert calls == [
|
||||
("DELETE", f"/containers/{RETIRED_HELPER_ID}?force=false&v=false")
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status", [404, 409])
|
||||
def test_retired_helper_delete_refuses_unexpected_docker_status(status: int):
|
||||
client = bootstrap.DockerSocketClient()
|
||||
|
||||
def request(_method: str, _path: str, **_kwargs: Any) -> tuple[int, bytes]:
|
||||
return status, b""
|
||||
|
||||
client._request = request # type: ignore[method-assign]
|
||||
with pytest.raises(bootstrap.BootstrapError):
|
||||
client.remove_retired_failed_container(RETIRED_HELPER_ID)
|
||||
|
||||
|
||||
def test_same_failed_source_revision_is_never_retried_or_removed():
|
||||
helper_source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
docker = FakeDocker(
|
||||
_legacy_helper_spec(bootstrap.RETIRED_FAILED_MODERN_REVISION),
|
||||
existing_child=_retired_failed_child(helper_source),
|
||||
)
|
||||
git = FakeProtectedGit(helper_source)
|
||||
git.revision = bootstrap.RETIRED_FAILED_MODERN_REVISION
|
||||
git.tree = bootstrap.RETIRED_FAILED_MODERN_TREE
|
||||
legacy_health = _health(
|
||||
revision=CONTROLLER_REVISION,
|
||||
tree=CONTROLLER_TREE,
|
||||
version="2.1.12",
|
||||
artifact_sha256=bootstrap.LEGACY_CONTROLLER_ARTIFACT_SHA256,
|
||||
)
|
||||
|
||||
result = _run_bootstrap(
|
||||
docker,
|
||||
git,
|
||||
health_sequence=[legacy_health],
|
||||
source_revision=git.revision,
|
||||
source_tree=git.tree,
|
||||
)
|
||||
|
||||
assert result["status"] == "failed"
|
||||
assert result["exitCode"] == 1
|
||||
assert docker.removed == []
|
||||
assert docker.created == []
|
||||
|
||||
|
||||
def test_completed_modern_child_with_wrong_artifact_is_not_relaunched():
|
||||
helper_source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_text(
|
||||
encoding="utf-8"
|
||||
|
||||
Reference in new issue
Block a user