From 4f9c5a1106ed7ed0667008b6efecc294cfb9dbe0 Mon Sep 17 00:00:00 2001 From: Public Source Publisher Date: Fri, 2 Oct 2026 21:31:41 +0000 Subject: [PATCH] Publish curated Unraid-AppOps-Gateway source from f4a918d9dba3663faa1f675dcc335c9ec951f30c --- PUBLIC_SOURCE_EXPORT.md | 2 +- PUBLIC_SOURCE_MANIFEST.json | 11 ++- .../test_controller_host_dependencies.py | 25 +++++++ .../test_controller_hub_release_volume.py | 68 ++++++++++++++++++- 4 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 backend/tests/test_controller_host_dependencies.py diff --git a/PUBLIC_SOURCE_EXPORT.md b/PUBLIC_SOURCE_EXPORT.md index 100c5f8..ece1b6e 100644 --- a/PUBLIC_SOURCE_EXPORT.md +++ b/PUBLIC_SOURCE_EXPORT.md @@ -1,3 +1,3 @@ # Public source export -This source snapshot was generated from the private canonical repository at revision `4f99d8f6ea4fb8ae105b83570344b25fa05c31fd`. 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 `f4a918d9dba3663faa1f675dcc335c9ec951f30c`. It contains no private Git history or operational evidence. Changes are published from the canonical repository. diff --git a/PUBLIC_SOURCE_MANIFEST.json b/PUBLIC_SOURCE_MANIFEST.json index 8ef23ae..7c575d0 100644 --- a/PUBLIC_SOURCE_MANIFEST.json +++ b/PUBLIC_SOURCE_MANIFEST.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceRevision": "4f99d8f6ea4fb8ae105b83570344b25fa05c31fd", + "sourceRevision": "f4a918d9dba3663faa1f675dcc335c9ec951f30c", "files": [ { "path": ".dockerignore", @@ -842,10 +842,15 @@ "sha256": "9cbe851651abec8c5a792d9843ceca308993e71f67bdcbc081ae7eb9a96a0888", "bytes": 5207 }, + { + "path": "backend/tests/test_controller_host_dependencies.py", + "sha256": "1b615295c70fd7917b7e6117b2bd3b82e06da0ec3d955157f614f14ddedf880f", + "bytes": 1095 + }, { "path": "backend/tests/test_controller_hub_release_volume.py", - "sha256": "d527b5ac287a02bc5e9f4c89ddf69638b6f8a12c5a34ad2e65db2721b8e05303", - "bytes": 28619 + "sha256": "8c7dd4f95407c9b274f8ee6ca9c98088ee0e842afa0c537cbd1d7311ec9bfa30", + "bytes": 32553 }, { "path": "backend/tests/test_controller_root_procedure_contract.py", diff --git a/backend/tests/test_controller_host_dependencies.py b/backend/tests/test_controller_host_dependencies.py new file mode 100644 index 0000000..508f9d6 --- /dev/null +++ b/backend/tests/test_controller_host_dependencies.py @@ -0,0 +1,25 @@ +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] + + +def test_source_preflight_handles_recursive_yaml_lists_without_host_packages(): + source = ROOT / 'scripts/prepare_controller_hub_release_volume.py' + program = """ +import importlib.util, sys +spec = importlib.util.spec_from_file_location('controller', sys.argv[1]) +module = importlib.util.module_from_spec(spec) +spec.loader.exec_module(module) +module._preflight_source_compose(b'services: {deploy-control: {image: fixed}}\\nx-loop: &a [*a]\\n') +try: + module._preflight_source_compose(b'services: {deploy-control: {image: fixed}}\\nx-loop: &a [*a, {env_file: /tmp/secret}]\\n') +except module.VolumeTransitionError as error: + assert str(error) == 'controller_volume_external_source_invalid' +else: + raise AssertionError('external source inside alias accepted') +""" + result = subprocess.run([sys.executable, '-I', '-S', '-c', program, str(source)], + capture_output=True, text=True, timeout=15, check=False) + assert result.returncode == 0, result.stderr diff --git a/backend/tests/test_controller_hub_release_volume.py b/backend/tests/test_controller_hub_release_volume.py index f224c01..065a28b 100644 --- a/backend/tests/test_controller_hub_release_volume.py +++ b/backend/tests/test_controller_hub_release_volume.py @@ -1,9 +1,13 @@ from __future__ import annotations +import ast import importlib.util +import io import json import os +import subprocess import sys +import tarfile import types from pathlib import Path @@ -668,6 +672,37 @@ def test_capture_program_is_fixed_descriptor_based_and_compiles(): 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) + 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" + ) + 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()) + + def test_prior_release_capture_must_match_protected_git_tree(monkeypatch): files = module.LEGACY_RELEASE_FILES_2_1_5 entries = {name: (f"source:{name}".encode(), 0o644) for name in files} @@ -692,12 +727,41 @@ def test_prior_release_capture_must_match_protected_git_tree(monkeypatch): 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) + 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) + 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