Publish curated Unraid-AppOps-Gateway source from 0f89e1506065c5365c48968b1d7845d666a18be8
This commit is contained in:
1 parent
3eed2d6657
commit
a0d1a1224a
8 files changed
+152
-31
No files matched your search
@@ -1,3 +1,3 @@
|
|||||||
# Public source export
|
# Public source export
|
||||||
|
|
||||||
This source snapshot was generated from the private canonical repository at revision `25057d17d4995d4bd407ff0516ef69ddaec878c6`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
|
This source snapshot was generated from the private canonical repository at revision `0f89e1506065c5365c48968b1d7845d666a18be8`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
|
||||||
+10
-10
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"sourceRevision": "25057d17d4995d4bd407ff0516ef69ddaec878c6",
|
"sourceRevision": "0f89e1506065c5365c48968b1d7845d666a18be8",
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"path": ".dockerignore",
|
"path": ".dockerignore",
|
||||||
@@ -524,7 +524,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/app/services/controller_artifact_helper_spec.py",
|
"path": "backend/app/services/controller_artifact_helper_spec.py",
|
||||||
"sha256": "232048b69a06e5fe4edc1c5fab27c3032bdd829bbbe9589b7d47b1a288207ca8",
|
"sha256": "e345e51a81eb483ffe8532592deb197296bd28db99e1e5bc358c590a59f1b94d",
|
||||||
"bytes": 6029
|
"bytes": 6029
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -949,7 +949,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/tests/test_controller_artifact_bootstrap.py",
|
"path": "backend/tests/test_controller_artifact_bootstrap.py",
|
||||||
"sha256": "46ceb18a80b1f413da98325bea69c80422057354fb54569cc5563fccfffdda64",
|
"sha256": "6774b7fdf9fd91c463661970512b93027af93979fd4b6d61d89476f0465cc48b",
|
||||||
"bytes": 82644
|
"bytes": 82644
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -964,7 +964,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/tests/test_controller_artifact_promoter.py",
|
"path": "backend/tests/test_controller_artifact_promoter.py",
|
||||||
"sha256": "284389dc63da8305927e80bf4ed4120e1acac6b0eb86b2b29f5cbbc360fa3778",
|
"sha256": "1a274fb06503ce5568e2096898e1a98ded10dee333f49bd45060bbc82adf00cb",
|
||||||
"bytes": 7570
|
"bytes": 7570
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -989,8 +989,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/tests/test_controller_hub_release_volume.py",
|
"path": "backend/tests/test_controller_hub_release_volume.py",
|
||||||
"sha256": "e13f1f01799fe74821eab9622d7eb4cf9add65196a67bde386f1c11231e04c5d",
|
"sha256": "c3de3b47673a5fa365bf20aedc020a2ca733804adc9cd05d15b26aca2a1a2b05",
|
||||||
"bytes": 161342
|
"bytes": 161657
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/tests/test_controller_root_procedure_contract.py",
|
"path": "backend/tests/test_controller_root_procedure_contract.py",
|
||||||
@@ -1624,8 +1624,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/tests/test_runner_fleet_specialize.py",
|
"path": "backend/tests/test_runner_fleet_specialize.py",
|
||||||
"sha256": "9de45904f4a4fd254d5e3b4c502b3d2c235f2ce220fd8f122a5ed840388b2eb1",
|
"sha256": "6771e7ae22ddb0d39e19272d311f9c77b898273c7106eef02ba0c76fc6e48873",
|
||||||
"bytes": 113743
|
"bytes": 119270
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/tests/test_runner_fleet_v2_execution_surface.py",
|
"path": "backend/tests/test_runner_fleet_v2_execution_surface.py",
|
||||||
@@ -1639,8 +1639,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/tests/test_runner_resource_envelope.py",
|
"path": "backend/tests/test_runner_resource_envelope.py",
|
||||||
"sha256": "a0e469bc980dbec76cedda9a1ae5011fc1444d49ffed4c067268a10f1905a5a1",
|
"sha256": "9bb731508ac3cc4d36ba5eae7839c0f73b411880b92a45118dfaa4b62c4e019f",
|
||||||
"bytes": 40504
|
"bytes": 40972
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/tests/test_runner_scheduler_liveness_recovery.py",
|
"path": "backend/tests/test_runner_scheduler_liveness_recovery.py",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from typing import Any
|
|||||||
|
|
||||||
SOURCE_REPOSITORY = "jens/unraid-appops-gateway"
|
SOURCE_REPOSITORY = "jens/unraid-appops-gateway"
|
||||||
HELPER_NAME = "itworx-controller-artifact-promoter-modern"
|
HELPER_NAME = "itworx-controller-artifact-promoter-modern"
|
||||||
CONTROLLER_VERSION = "2.1.18"
|
CONTROLLER_VERSION = "2.1.19"
|
||||||
HELPER_LABEL = "itworx.controller.promoter"
|
HELPER_LABEL = "itworx.controller.promoter"
|
||||||
_REVISION = re.compile(r"^[0-9a-f]{40}$")
|
_REVISION = re.compile(r"^[0-9a-f]{40}$")
|
||||||
_IMAGE_ID = re.compile(r"^sha256:[0-9a-f]{64}$")
|
_IMAGE_ID = re.compile(r"^sha256:[0-9a-f]{64}$")
|
||||||
|
|||||||
@@ -589,7 +589,7 @@ def _health(
|
|||||||
*,
|
*,
|
||||||
revision: str = REVISION,
|
revision: str = REVISION,
|
||||||
tree: str = TREE,
|
tree: str = TREE,
|
||||||
version: str = "2.1.18",
|
version: str = "2.1.19",
|
||||||
artifact_sha256: str = "1" * 64,
|
artifact_sha256: str = "1" * 64,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
@@ -1347,7 +1347,7 @@ def test_current_214_baseline_is_verified_and_bound_to_new_helper():
|
|||||||
"itworx.controller.promoter": "true",
|
"itworx.controller.promoter": "true",
|
||||||
"itworx.source.repository": "jens/unraid-appops-gateway",
|
"itworx.source.repository": "jens/unraid-appops-gateway",
|
||||||
"itworx.source.revision": REVISION,
|
"itworx.source.revision": REVISION,
|
||||||
"itworx.controller.version": "2.1.18",
|
"itworx.controller.version": "2.1.19",
|
||||||
"itworx.controller.bootstrap.target.id": MODERN_CONTROLLER_CONTAINER_ID,
|
"itworx.controller.bootstrap.target.id": MODERN_CONTROLLER_CONTAINER_ID,
|
||||||
"itworx.controller.bootstrap.target.image": CONTROLLER_IMAGE_ID,
|
"itworx.controller.bootstrap.target.image": CONTROLLER_IMAGE_ID,
|
||||||
"itworx.controller.bootstrap.target.revision": MODERN_CONTROLLER_REVISION,
|
"itworx.controller.bootstrap.target.revision": MODERN_CONTROLLER_REVISION,
|
||||||
@@ -1826,7 +1826,7 @@ def test_successful_child_requires_exact_source_tree_version_and_health():
|
|||||||
|
|
||||||
assert result["status"] == "promoted"
|
assert result["status"] == "promoted"
|
||||||
assert result["revision"] == REVISION
|
assert result["revision"] == REVISION
|
||||||
assert result["controllerVersion"] == "2.1.18"
|
assert result["controllerVersion"] == "2.1.19"
|
||||||
assert result["artifactSha256"] == "1" * 64
|
assert result["artifactSha256"] == "1" * 64
|
||||||
assert docker.started == [MODERN_HELPER_ID]
|
assert docker.started == [MODERN_HELPER_ID]
|
||||||
assert docker.created[0]["Image"] == CONTROLLER_IMAGE_ID
|
assert docker.created[0]["Image"] == CONTROLLER_IMAGE_ID
|
||||||
@@ -2086,32 +2086,32 @@ def test_modern_upgrade_path_still_requires_private_archive_and_image_id(
|
|||||||
def test_current_helper_version_is_distinct_from_fixed_controller_baselines():
|
def test_current_helper_version_is_distinct_from_fixed_controller_baselines():
|
||||||
source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_bytes()
|
source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_bytes()
|
||||||
namespace = bootstrap._safe_modern_spec_namespace(source)
|
namespace = bootstrap._safe_modern_spec_namespace(source)
|
||||||
assert namespace["CONTROLLER_VERSION"] == "2.1.18"
|
assert namespace["CONTROLLER_VERSION"] == "2.1.19"
|
||||||
assert bootstrap._verified_historical_helper_version(source) == "2.1.18"
|
assert bootstrap._verified_historical_helper_version(source) == "2.1.19"
|
||||||
assert [baseline.version for baseline in bootstrap.CONTROLLER_BASELINES] == ["2.1.12", "2.1.14"]
|
assert [baseline.version for baseline in bootstrap.CONTROLLER_BASELINES] == ["2.1.12", "2.1.14"]
|
||||||
with pytest.raises(bootstrap.BootstrapError, match="controller_promoter_modern_spec_identity_invalid"):
|
with pytest.raises(bootstrap.BootstrapError, match="controller_promoter_modern_spec_identity_invalid"):
|
||||||
bootstrap._safe_modern_spec_namespace(RETIRED_MODERN_HELPER_SOURCE.encode("utf-8"))
|
bootstrap._safe_modern_spec_namespace(RETIRED_MODERN_HELPER_SOURCE.encode("utf-8"))
|
||||||
assert bootstrap._verified_historical_helper_version(RETIRED_MODERN_HELPER_SOURCE.encode("utf-8")) == "2.1.14"
|
assert bootstrap._verified_historical_helper_version(RETIRED_MODERN_HELPER_SOURCE.encode("utf-8")) == "2.1.14"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("version", ["2.1.16", "2.1.19", "9.9.9"])
|
@pytest.mark.parametrize("version", ["2.1.16", "2.1.20", "9.9.9"])
|
||||||
def test_current_and_historical_helper_reject_unreviewed_target_versions(version):
|
def test_current_and_historical_helper_reject_unreviewed_target_versions(version):
|
||||||
source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_bytes()
|
source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_bytes()
|
||||||
foreign = source.replace(b'CONTROLLER_VERSION = "2.1.18"', f'CONTROLLER_VERSION = "{version}"'.encode())
|
foreign = source.replace(b'CONTROLLER_VERSION = "2.1.19"', f'CONTROLLER_VERSION = "{version}"'.encode())
|
||||||
with pytest.raises(bootstrap.BootstrapError, match="controller_promoter_modern_spec_identity_invalid"):
|
with pytest.raises(bootstrap.BootstrapError, match="controller_promoter_modern_spec_identity_invalid"):
|
||||||
bootstrap._safe_modern_spec_namespace(foreign)
|
bootstrap._safe_modern_spec_namespace(foreign)
|
||||||
with pytest.raises(bootstrap.BootstrapError, match="controller_promoter_modern_spec_identity_invalid"):
|
with pytest.raises(bootstrap.BootstrapError, match="controller_promoter_modern_spec_identity_invalid"):
|
||||||
bootstrap._verified_historical_helper_version(foreign)
|
bootstrap._verified_historical_helper_version(foreign)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("version", ["2.1.14", "2.1.16", "2.1.19"])
|
@pytest.mark.parametrize("version", ["2.1.14", "2.1.16", "2.1.20"])
|
||||||
def test_promoted_health_cannot_substitute_a_baseline_or_unknown_version(version):
|
def test_promoted_health_cannot_substitute_a_baseline_or_unknown_version(version):
|
||||||
assert not bootstrap._health_matches(_health(version=version), REVISION, TREE)
|
assert not bootstrap._health_matches(_health(version=version), REVISION, TREE)
|
||||||
|
|
||||||
|
|
||||||
def test_previous_reviewed_helper_is_historical_only():
|
def test_previous_reviewed_helper_is_historical_only():
|
||||||
source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_bytes()
|
source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_bytes()
|
||||||
prior = source.replace(b'CONTROLLER_VERSION = "2.1.18"', b'CONTROLLER_VERSION = "2.1.17"')
|
prior = source.replace(b'CONTROLLER_VERSION = "2.1.19"', b'CONTROLLER_VERSION = "2.1.18"')
|
||||||
with pytest.raises(bootstrap.BootstrapError, match="controller_promoter_modern_spec_identity_invalid"):
|
with pytest.raises(bootstrap.BootstrapError, match="controller_promoter_modern_spec_identity_invalid"):
|
||||||
bootstrap._safe_modern_spec_namespace(prior)
|
bootstrap._safe_modern_spec_namespace(prior)
|
||||||
assert bootstrap._verified_historical_helper_version(prior) == "2.1.17"
|
assert bootstrap._verified_historical_helper_version(prior) == "2.1.18"
|
||||||
@@ -46,7 +46,7 @@ def test_controller_promoter_helper_spec_is_fixed_and_bounded():
|
|||||||
spec = helper_container_spec(revision=revision, image_id=image_id)
|
spec = helper_container_spec(revision=revision, image_id=image_id)
|
||||||
|
|
||||||
assert HELPER_NAME == "itworx-controller-artifact-promoter-modern"
|
assert HELPER_NAME == "itworx-controller-artifact-promoter-modern"
|
||||||
assert CONTROLLER_VERSION == "2.1.18"
|
assert CONTROLLER_VERSION == "2.1.19"
|
||||||
assert re.fullmatch(r"2\.1\.\d+", CONTROLLER_VERSION)
|
assert re.fullmatch(r"2\.1\.\d+", CONTROLLER_VERSION)
|
||||||
assert spec["Image"] == image_id
|
assert spec["Image"] == image_id
|
||||||
assert spec["HostConfig"]["Privileged"] is False
|
assert spec["HostConfig"]["Privileged"] is False
|
||||||
@@ -79,7 +79,7 @@ def test_controller_promoter_helper_spec_is_fixed_and_bounded():
|
|||||||
assert revision in command
|
assert revision in command
|
||||||
assert "--image-id" in command
|
assert "--image-id" in command
|
||||||
assert CONTROLLER_VERSION in command
|
assert CONTROLLER_VERSION in command
|
||||||
assert "version='2.1.18'" in command
|
assert "version='2.1.19'" in command
|
||||||
assert "/gitea-repositories/jens/unraid-appops-gateway.git" in command
|
assert "/gitea-repositories/jens/unraid-appops-gateway.git" in command
|
||||||
archive_start = command.index('git --git-dir="$repo" archive')
|
archive_start = command.index('git --git-dir="$repo" archive')
|
||||||
archive_end = command.index('> "$work/controller.tar"', archive_start)
|
archive_end = command.index('> "$work/controller.tar"', archive_start)
|
||||||
@@ -110,7 +110,7 @@ def test_controller_promoter_bootstrap_child_spec_binds_legacy_target_identity()
|
|||||||
"itworx.controller.promoter": "true",
|
"itworx.controller.promoter": "true",
|
||||||
"itworx.source.repository": "jens/unraid-appops-gateway",
|
"itworx.source.repository": "jens/unraid-appops-gateway",
|
||||||
"itworx.source.revision": revision,
|
"itworx.source.revision": revision,
|
||||||
"itworx.controller.version": "2.1.18",
|
"itworx.controller.version": "2.1.19",
|
||||||
"itworx.controller.bootstrap.target.id": target_id,
|
"itworx.controller.bootstrap.target.id": target_id,
|
||||||
"itworx.controller.bootstrap.target.image": image_id,
|
"itworx.controller.bootstrap.target.image": image_id,
|
||||||
"itworx.controller.bootstrap.target.revision": target_revision,
|
"itworx.controller.bootstrap.target.revision": target_revision,
|
||||||
|
|||||||
@@ -34,8 +34,9 @@ def compose_config_root(monkeypatch, tmp_path):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("operation", ["config", "stop", "up"])
|
@pytest.mark.parametrize("operation", ["config", "stop", "up"])
|
||||||
|
@pytest.mark.parametrize("source_directory", [False, True])
|
||||||
def test_compose_uses_private_ephemeral_cli_config_with_readonly_root(
|
def test_compose_uses_private_ephemeral_cli_config_with_readonly_root(
|
||||||
monkeypatch, tmp_path, operation,
|
monkeypatch, tmp_path, operation, source_directory,
|
||||||
):
|
):
|
||||||
directories = []
|
directories = []
|
||||||
|
|
||||||
@@ -57,12 +58,15 @@ def test_compose_uses_private_ephemeral_cli_config_with_readonly_root(
|
|||||||
assert arguments[-1] == operation
|
assert arguments[-1] == operation
|
||||||
assert arguments[arguments.index("--env-file") + 1] == "/dev/null"
|
assert arguments[arguments.index("--env-file") + 1] == "/dev/null"
|
||||||
assert arguments[arguments.index("-f") + 1] == "-"
|
assert arguments[arguments.index("-f") + 1] == "-"
|
||||||
|
assert ("--profile" in arguments) is (source_directory and operation == "config")
|
||||||
|
if "--profile" in arguments:
|
||||||
|
assert arguments[arguments.index("--profile") + 1] == "runner"
|
||||||
(directory / "generated-client-state").write_bytes(b"private-client-state")
|
(directory / "generated-client-state").write_bytes(b"private-client-state")
|
||||||
return b"ok"
|
return b"ok"
|
||||||
|
|
||||||
monkeypatch.setattr(module, "_run", run)
|
monkeypatch.setattr(module, "_run", run)
|
||||||
for _ in range(2):
|
for _ in range(2):
|
||||||
assert module._compose([operation], b"captured-private-compose") == b"ok"
|
assert module._compose([operation], b"captured-private-compose", source_directory=source_directory) == b"ok"
|
||||||
assert directories[0] != directories[1]
|
assert directories[0] != directories[1]
|
||||||
assert all(not directory.exists() for directory in directories)
|
assert all(not directory.exists() for directory in directories)
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,113 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def controller_only_source(specialize, tmp_path, monkeypatch):
|
||||||
|
image = "sha256:" + "a" * 64
|
||||||
|
code = f"{specialize.RELEASE_VOLUME_HOST_ROOT}/controller-artifacts/{'b' * 40}"
|
||||||
|
model = {"name": specialize.PROJECT, "services": {"deploy-control": {
|
||||||
|
"container_name": specialize.CONTROLLER_CONTAINER, "image": image,
|
||||||
|
"volumes": [{"type": "bind", "source": code, "target": "/opt/gitea-deploy",
|
||||||
|
"read_only": True, "bind": {"propagation": "rslave"}}],
|
||||||
|
}}}
|
||||||
|
compose = tmp_path / "docker-compose.yml"
|
||||||
|
archive_dir = tmp_path / "backups" / "controller-v2-fixed"
|
||||||
|
archive_dir.mkdir(parents=True, mode=0o700)
|
||||||
|
archive_dir.parent.chmod(0o700)
|
||||||
|
archive = archive_dir / "compose-before.bin"
|
||||||
|
archived = b"reviewed complete legacy v1 source"
|
||||||
|
archive.write_bytes(archived)
|
||||||
|
archive.chmod(0o600)
|
||||||
|
monkeypatch.setattr(specialize, "ROOT", tmp_path)
|
||||||
|
monkeypatch.setattr(specialize, "COMPOSE_PATH", compose)
|
||||||
|
monkeypatch.setattr(specialize, "LEGACY_V1_COMPOSE_BACKUP", archive)
|
||||||
|
monkeypatch.setattr(specialize, "_KNOWN_V1_COMPOSE_SHA256", hashlib.sha256(archived).hexdigest())
|
||||||
|
monkeypatch.setattr(specialize, "_controller_runtime_identity", lambda: (image, code))
|
||||||
|
return model, compose, archive, archived
|
||||||
|
|
||||||
|
|
||||||
|
def test_controller_only_source_uses_fixed_pinned_archive_without_rewriting_live_compose(
|
||||||
|
specialize, controller_only_source,
|
||||||
|
):
|
||||||
|
model, compose, _archive, archived = controller_only_source
|
||||||
|
current = json.dumps(model).encode()
|
||||||
|
compose.write_bytes(current)
|
||||||
|
result, current_sha = specialize._read_fleet_base_compose()
|
||||||
|
assert result == archived
|
||||||
|
assert current_sha == hashlib.sha256(current).hexdigest()
|
||||||
|
assert compose.read_bytes() == current
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mutation", ["project", "image", "container", "code", "writable", "shared", "extra_code"])
|
||||||
|
def test_controller_only_source_rejects_identity_and_code_binding_drift(
|
||||||
|
specialize, controller_only_source, mutation,
|
||||||
|
):
|
||||||
|
model, compose, _archive, _archived = controller_only_source
|
||||||
|
controller = model["services"]["deploy-control"]
|
||||||
|
mount = controller["volumes"][0]
|
||||||
|
if mutation == "project":
|
||||||
|
model["name"] = "other"
|
||||||
|
elif mutation == "image":
|
||||||
|
controller["image"] = "sha256:" + "c" * 64
|
||||||
|
elif mutation == "container":
|
||||||
|
controller["container_name"] = "other"
|
||||||
|
elif mutation == "code":
|
||||||
|
mount["source"] = "/mnt/shared/code"
|
||||||
|
elif mutation == "writable":
|
||||||
|
mount["read_only"] = False
|
||||||
|
elif mutation == "shared":
|
||||||
|
mount["bind"]["propagation"] = "rshared"
|
||||||
|
else:
|
||||||
|
controller["volumes"].append(copy.deepcopy(mount))
|
||||||
|
compose.write_text(json.dumps(model))
|
||||||
|
with pytest.raises(specialize.RunnerFleetSpecializeError, match="controller_compose_untrusted"):
|
||||||
|
specialize._read_fleet_base_compose()
|
||||||
|
|
||||||
|
|
||||||
|
def test_controller_only_source_rejects_modified_fixed_archive(specialize, controller_only_source):
|
||||||
|
model, compose, archive, _archived = controller_only_source
|
||||||
|
compose.write_text(json.dumps(model))
|
||||||
|
archive.write_bytes(b"different legacy source")
|
||||||
|
with pytest.raises(specialize.RunnerFleetSpecializeError, match="legacy_backup_changed"):
|
||||||
|
specialize._read_fleet_base_compose()
|
||||||
|
|
||||||
|
|
||||||
|
def test_legacy_live_source_does_not_open_promotion_backup(specialize, monkeypatch):
|
||||||
|
current = b"services:\n runner: {}\n"
|
||||||
|
monkeypatch.setattr(specialize, "_read_source_file", lambda *_args, **_kwargs: current)
|
||||||
|
monkeypatch.setattr(specialize, "_trusted_private_file", lambda *_args, **_kwargs: pytest.fail("archive must remain unopened"))
|
||||||
|
assert specialize._read_fleet_base_compose() == (current, None)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("separate_controller_hash", [False, True])
|
||||||
|
def test_controller_compose_cas_blocks_rollback_before_any_lifecycle_action(
|
||||||
|
specialize, controller_only_source, monkeypatch, separate_controller_hash,
|
||||||
|
):
|
||||||
|
model, compose, _archive, _archived = controller_only_source
|
||||||
|
compose.write_text(json.dumps(model))
|
||||||
|
original_sha = hashlib.sha256(compose.read_bytes()).hexdigest()
|
||||||
|
journal = {"baseComposeSha256": original_sha}
|
||||||
|
if separate_controller_hash:
|
||||||
|
journal["controllerComposeSha256"] = original_sha
|
||||||
|
compose.write_bytes(compose.read_bytes() + b"\n")
|
||||||
|
monkeypatch.setattr(specialize, "_guard_state_unchanged", lambda *_args: pytest.fail("must reject before lifecycle"))
|
||||||
|
assert specialize._rollback_before_activation(journal) is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("reviewed,user,image,expected", [
|
||||||
|
(True, None, True, "rootless"), (False, None, True, None),
|
||||||
|
(True, "root", True, "root"), (True, None, False, None),
|
||||||
|
])
|
||||||
|
def test_only_byte_pinned_v1_pinned_image_default_user_is_normalized(
|
||||||
|
specialize, monkeypatch, reviewed, user, image, expected,
|
||||||
|
):
|
||||||
|
source = b"reviewed v1 source" if reviewed else b"different source"
|
||||||
|
monkeypatch.setattr(specialize, "_KNOWN_V1_COMPOSE_SHA256", hashlib.sha256(b"reviewed v1 source").hexdigest())
|
||||||
|
model = {"services": {"runner-dind": {"image": specialize._DIND_IMAGE_REFERENCE if image else "unknown", "user": user}}}
|
||||||
|
monkeypatch.setattr(specialize, "_run_compose", lambda *_args: json.dumps(model))
|
||||||
|
assert specialize._compose_model(source)["services"]["runner-dind"]["user"] == expected
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[2]
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
BROKER = ROOT / "ops" / "gitea-deploy-broker"
|
BROKER = ROOT / "ops" / "gitea-deploy-broker"
|
||||||
SPECIALIZE_PATH = BROKER / "runner_fleet_specialize.py"
|
SPECIALIZE_PATH = BROKER / "runner_fleet_specialize.py"
|
||||||
@@ -2145,6 +2252,10 @@ def test_migration_failure_restores_captured_dind_state_before_cgroup_cleanup(
|
|||||||
|
|
||||||
base_bytes = json.dumps(base_model, sort_keys=True, separators=(",", ":")).encode()
|
base_bytes = json.dumps(base_model, sort_keys=True, separators=(",", ":")).encode()
|
||||||
specialized_bytes = json.dumps(specialized_model, sort_keys=True, separators=(",", ":")).encode()
|
specialized_bytes = json.dumps(specialized_model, sort_keys=True, separators=(",", ":")).encode()
|
||||||
|
live_compose = tmp_path / "docker-compose.yml"
|
||||||
|
live_compose.write_bytes(base_bytes)
|
||||||
|
monkeypatch.setattr(specialize, "COMPOSE_PATH", live_compose)
|
||||||
|
journal["baseComposeSha256"] = hashlib.sha256(base_bytes).hexdigest()
|
||||||
monkeypatch.setattr(specialize, "_guard_state_unchanged", lambda _journal: True)
|
monkeypatch.setattr(specialize, "_guard_state_unchanged", lambda _journal: True)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
specialize,
|
specialize,
|
||||||
|
|||||||
@@ -478,7 +478,7 @@ def test_host_probe_contract_is_bounded_and_never_mounts_host_proc_or_docker_soc
|
|||||||
assert "target=/host-cgroup" in mount
|
assert "target=/host-cgroup" in mount
|
||||||
assert "readonly" in mount
|
assert "readonly" in mount
|
||||||
assert mounts[1] == (
|
assert mounts[1] == (
|
||||||
f"type=bind,source={code_source},target=/opt/gitea-deploy,readonly,bind-propagation=rprivate"
|
f"type=bind,source={code_source},target=/opt/gitea-deploy,readonly,bind-propagation=rslave"
|
||||||
)
|
)
|
||||||
assert not any("/proc" in item or "docker.sock" in item for item in args)
|
assert not any("/proc" in item or "docker.sock" in item for item in args)
|
||||||
assert args[-3:] == [image_id, "/opt/gitea-deploy/runner_resource_envelope.py", "--host-probe"]
|
assert args[-3:] == [image_id, "/opt/gitea-deploy/runner_resource_envelope.py", "--host-probe"]
|
||||||
@@ -528,7 +528,7 @@ def test_host_probe_inspection_rejects_namespace_mount_and_resource_drift():
|
|||||||
"Source": code_source,
|
"Source": code_source,
|
||||||
"Target": "/opt/gitea-deploy",
|
"Target": "/opt/gitea-deploy",
|
||||||
"ReadOnly": True,
|
"ReadOnly": True,
|
||||||
"BindOptions": {"Propagation": "rprivate"},
|
"BindOptions": {"Propagation": "rslave"},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -547,7 +547,7 @@ def test_host_probe_inspection_rejects_namespace_mount_and_resource_drift():
|
|||||||
"Destination": "/opt/gitea-deploy",
|
"Destination": "/opt/gitea-deploy",
|
||||||
"Mode": "",
|
"Mode": "",
|
||||||
"RW": False,
|
"RW": False,
|
||||||
"Propagation": "rprivate",
|
"Propagation": "rslave",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -585,6 +585,12 @@ def test_host_probe_inspection_rejects_namespace_mount_and_resource_drift():
|
|||||||
),
|
),
|
||||||
lambda item: item["Mounts"][0].update(RW=True),
|
lambda item: item["Mounts"][0].update(RW=True),
|
||||||
lambda item: item["Mounts"][1].update(RW=True),
|
lambda item: item["Mounts"][1].update(RW=True),
|
||||||
|
lambda item: item["Mounts"][0].update(Propagation="rslave"),
|
||||||
|
lambda item: item["Mounts"][1].update(Propagation="rprivate"),
|
||||||
|
lambda item: item["Mounts"][1].update(Propagation="rshared"),
|
||||||
|
lambda item: item["Mounts"][1].update(Propagation="slave"),
|
||||||
|
lambda item: item["HostConfig"]["Mounts"][0]["BindOptions"].update(Propagation="rslave"),
|
||||||
|
lambda item: item["HostConfig"]["Mounts"][1]["BindOptions"].update(Propagation="rprivate"),
|
||||||
lambda item: item["Mounts"][0].update(Mode="ro"),
|
lambda item: item["Mounts"][0].update(Mode="ro"),
|
||||||
lambda item: item["Mounts"][1].update(Mode="ro"),
|
lambda item: item["Mounts"][1].update(Mode="ro"),
|
||||||
lambda item: item["Mounts"][1].update(Source="/mnt/shared/code"),
|
lambda item: item["Mounts"][1].update(Source="/mnt/shared/code"),
|
||||||
@@ -734,7 +740,7 @@ def test_host_probe_rw_modes_are_fixed_and_limited_to_ensure_or_rollback():
|
|||||||
)
|
)
|
||||||
assert all(
|
assert all(
|
||||||
code_mount
|
code_mount
|
||||||
== f"type=bind,source={code_source},target=/opt/gitea-deploy,readonly,bind-propagation=rprivate"
|
== f"type=bind,source={code_source},target=/opt/gitea-deploy,readonly,bind-propagation=rslave"
|
||||||
for code_mount in (snapshot_code_mount, ensure_code_mount, rollback_code_mount, family_code_mount)
|
for code_mount in (snapshot_code_mount, ensure_code_mount, rollback_code_mount, family_code_mount)
|
||||||
)
|
)
|
||||||
for args in (snapshot, ensure, rollback, family):
|
for args in (snapshot, ensure, rollback, family):
|
||||||
|
|||||||
Reference in new issue
Block a user