Publish curated Unraid-AppOps-Gateway source from ba66029223865607deddc234e283c11127324eb8
This commit is contained in:
1 parent
820244dbb3
commit
8dd4b40252
52 files changed
+11814
-288
No files matched your search
@@ -0,0 +1,177 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from email.message import Message
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from test_deploy_broker import BROKER_ROOT, _load_module
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def controller(monkeypatch):
|
||||
module = _load_module("component_transport_test", BROKER_ROOT / "status_server.py")
|
||||
monkeypatch.setattr(module, "_self_upgrade_signature_is_valid", lambda *_a, **_k: True)
|
||||
monkeypatch.setattr(module, "_self_upgrade_secret", lambda: b"transport-test-key-" + b"x" * 48)
|
||||
projects = {module.REGISTERED_AUTO_APPLY_REPOSITORY: {"branch": "main", "validation": {"required": True}},
|
||||
"other/unchanged": {"branch": "main"}}
|
||||
monkeypatch.setattr(module, "_load_projects", lambda: projects)
|
||||
monkeypatch.setattr(module, "_write_projects", lambda updated: (projects.clear(), projects.update(updated)))
|
||||
monkeypatch.setattr(module, "_validation_policy", lambda *_a: {"required": True})
|
||||
return module, projects
|
||||
|
||||
|
||||
def call(module, path, payload, method="_handle_registered_auto_apply"):
|
||||
handler = module.Handler.__new__(module.Handler)
|
||||
body = json.dumps(payload).encode()
|
||||
handler.path = path
|
||||
handler.headers = Message()
|
||||
handler.headers["Content-Length"] = str(len(body))
|
||||
handler.headers["Content-Type"] = "application/json"
|
||||
handler.rfile = io.BytesIO(body)
|
||||
handler._trusted_client = lambda: True
|
||||
responses = []
|
||||
handler._send_json = lambda status, data: responses.append((status, json.loads(data)))
|
||||
getattr(handler, method)()
|
||||
return responses[0]
|
||||
|
||||
|
||||
def intent(module, projects):
|
||||
project = projects[module.REGISTERED_AUTO_APPLY_REPOSITORY]
|
||||
return {"repository": module.REGISTERED_AUTO_APPLY_REPOSITORY, "held": True,
|
||||
"expectedRegistrySha256": hashlib.sha256(json.dumps(project, sort_keys=True, separators=(",", ":")).encode()).hexdigest(),
|
||||
"reason": "Limit rollout to the validated Gitea component", "requestId": "11111111-1111-4111-8111-111111111111"}
|
||||
|
||||
|
||||
def test_hold_is_cas_bound_and_retains_registered_policy(controller):
|
||||
module, projects = controller
|
||||
payload = intent(module, projects)
|
||||
original = dict(projects[module.REGISTERED_AUTO_APPLY_REPOSITORY])
|
||||
code, report = call(module, module.REGISTERED_AUTO_APPLY_PATH, payload)
|
||||
assert code == 200 and report["status"] == "held" and report["autoApply"] is False
|
||||
updated = projects[module.REGISTERED_AUTO_APPLY_REPOSITORY]
|
||||
assert updated["branch"] == original["branch"]
|
||||
assert updated["validation"] == original["validation"]
|
||||
assert projects["other/unchanged"] == {"branch": "main"}
|
||||
assert payload["reason"] not in json.dumps(updated)
|
||||
assert call(module, module.REGISTERED_AUTO_APPLY_PATH, payload)[1]["reason"] == "registered_target_policy_changed"
|
||||
code, preflight = call(module, module.REGISTERED_AUTO_APPLY_PATH + "/preflight", {"repository": payload["repository"]})
|
||||
assert code == 200 and preflight["autoApply"] is False
|
||||
payload.update(held=False, expectedRegistrySha256=preflight["registrySha256"])
|
||||
assert call(module, module.REGISTERED_AUTO_APPLY_PATH, payload)[1]["status"] == "released"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("change", [{"repository": "other/unchanged"}, {"held": "true"}, {"image": "caller/image"},
|
||||
{"requestId": "bad"}, {"expectedRegistrySha256": "0" * 64}])
|
||||
def test_hold_rejects_unknown_target_input_and_stale_fingerprint(controller, change):
|
||||
module, projects = controller
|
||||
before = json.dumps(projects, sort_keys=True)
|
||||
code, _ = call(module, module.REGISTERED_AUTO_APPLY_PATH, {**intent(module, projects), **change})
|
||||
assert code in {400, 409}
|
||||
assert json.dumps(projects, sort_keys=True) == before
|
||||
|
||||
|
||||
def test_hold_authentication_and_inflight_deployment_remain_fail_closed(controller, monkeypatch):
|
||||
module, projects = controller
|
||||
payload = intent(module, projects)
|
||||
monkeypatch.setattr(module, "_self_upgrade_signature_is_valid", lambda *_a, **_k: False)
|
||||
assert call(module, module.REGISTERED_AUTO_APPLY_PATH, payload)[0] == 403
|
||||
monkeypatch.setattr(module, "_self_upgrade_signature_is_valid", lambda *_a, **_k: True)
|
||||
module._registered_auto_apply_lock.acquire()
|
||||
try:
|
||||
assert call(module, module.REGISTERED_AUTO_APPLY_PATH, payload)[1]["reason"] == "registered_target_deployment_active"
|
||||
finally:
|
||||
module._registered_auto_apply_lock.release()
|
||||
|
||||
|
||||
def test_execution_rechecks_hold_under_same_lock(controller, monkeypatch):
|
||||
module, projects = controller
|
||||
invoked = []
|
||||
def execute(command, **_kwargs):
|
||||
assert module._registered_auto_apply_lock.acquire(blocking=False) is False
|
||||
invoked.append(command)
|
||||
return subprocess.CompletedProcess(command, 0)
|
||||
monkeypatch.setattr(module.subprocess, "run", execute)
|
||||
projects[module.REGISTERED_AUTO_APPLY_REPOSITORY]["auto_apply"] = False
|
||||
assert module._run_registered_deploy(module.REGISTERED_AUTO_APPLY_REPOSITORY, "a" * 40).returncode == 4
|
||||
assert invoked == []
|
||||
projects[module.REGISTERED_AUTO_APPLY_REPOSITORY]["auto_apply"] = True
|
||||
assert module._run_registered_deploy(module.REGISTERED_AUTO_APPLY_REPOSITORY, "a" * 40).returncode == 0
|
||||
assert invoked == [["python3", str(module.ROOT / "deploy.py"), "deploy", module.REGISTERED_AUTO_APPLY_REPOSITORY, "a" * 40]]
|
||||
|
||||
|
||||
def test_component_authentication_blocks_before_reading_or_mutating(controller, monkeypatch):
|
||||
module, _ = controller
|
||||
monkeypatch.setattr(module, "_self_upgrade_signature_is_valid", lambda *_a, **_k: False)
|
||||
assert call(module, "/v1/appops/hub-components/apply", {"image": "caller/image"}, "_handle_hub_component")[0] == 403
|
||||
|
||||
|
||||
def test_component_worker_serializes_hold_and_records_sanitized_failure(controller, monkeypatch):
|
||||
module, _ = controller
|
||||
failures = []
|
||||
plan_id = "a" * 64
|
||||
def apply(*_args, **_kwargs):
|
||||
assert module._registered_auto_apply_lock.acquire(blocking=False) is False
|
||||
raise RuntimeError("secret runtime configuration must never escape")
|
||||
fake = SimpleNamespace(apply=apply, PromotionRejected=ValueError,
|
||||
record_worker_failure=lambda *args, **_kw: failures.append(args))
|
||||
monkeypatch.setitem(sys.modules, "immutable_service_promotion", fake)
|
||||
module._component_worker_plans.add(plan_id)
|
||||
module._run_component_worker({"planId": plan_id}, b"x" * 64)
|
||||
assert failures == [(plan_id, "component_worker_failed")]
|
||||
assert plan_id not in module._component_worker_plans
|
||||
assert module._registered_auto_apply_lock.acquire(blocking=False)
|
||||
module._registered_auto_apply_lock.release()
|
||||
|
||||
|
||||
def test_component_queue_is_idempotent_and_failed_start_is_durable(controller, monkeypatch):
|
||||
module, _ = controller
|
||||
failures, starts = [], []
|
||||
plan_id = "a" * 64
|
||||
fake = SimpleNamespace(accept_apply=lambda *_a, **_k: {"status": "queued", "planId": plan_id},
|
||||
PromotionRejected=ValueError,
|
||||
record_worker_failure=lambda *args, **_kw: failures.append(args))
|
||||
monkeypatch.setitem(sys.modules, "immutable_service_promotion", fake)
|
||||
monkeypatch.setattr(module.threading, "Thread", lambda **kwargs: SimpleNamespace(start=lambda: starts.append(kwargs)))
|
||||
payload = {"planId": plan_id}
|
||||
assert call(module, "/v1/appops/hub-components/apply", payload, "_handle_hub_component")[0] == 202
|
||||
assert call(module, "/v1/appops/hub-components/apply", payload, "_handle_hub_component")[0] == 202
|
||||
assert len(starts) == 1
|
||||
module._component_worker_plans.clear()
|
||||
def fail_start(**_kwargs):
|
||||
raise RuntimeError("thread unavailable")
|
||||
monkeypatch.setattr(module.threading, "Thread", fail_start)
|
||||
assert call(module, "/v1/appops/hub-components/apply", payload, "_handle_hub_component")[1]["reason"] == "component_worker_start_failed"
|
||||
assert failures == [(plan_id, "component_worker_start_failed")]
|
||||
assert not module._component_worker_plans
|
||||
|
||||
|
||||
@pytest.mark.parametrize("validated", [False, True])
|
||||
def test_hold_retains_push_validation_and_candidate_preparation_without_deploy(controller, monkeypatch, tmp_path, validated):
|
||||
module, projects = controller
|
||||
repository = module.REGISTERED_AUTO_APPLY_REPOSITORY
|
||||
projects[repository]["auto_apply"] = False
|
||||
revision = "a" * 40
|
||||
semantic = module._webhook_semantic_digest(34, "refs/heads/main", revision)
|
||||
marker = tmp_path / (semantic + ".json")
|
||||
record = {"schema": module._WEBHOOK_SCHEMA, "repository": repository, "gitea_repository_id": 34,
|
||||
"ref": "refs/heads/main", "revision": revision, "semantic_sha256": semantic,
|
||||
"body_sha256": "b" * 64, "status": "validation_passed" if validated else "awaiting_validation"}
|
||||
monkeypatch.setattr(module, "_read_inbox_record", lambda _path: record)
|
||||
monkeypatch.setattr(module, "_webhook_policy", lambda *_a: (34, "main"))
|
||||
monkeypatch.setattr(module, "_record_policy_for_processing", lambda *_a: True)
|
||||
monkeypatch.setattr(module, "_process_validation", lambda *_a: False)
|
||||
monkeypatch.setattr(module, "_atomic_inbox_update", lambda *_a: None)
|
||||
monkeypatch.setattr(module, "_supersede_older_deliveries", lambda *_a: None)
|
||||
prepared = []
|
||||
monkeypatch.setattr(module, "_prepare_hub_candidate_delivery", lambda *_a: prepared.append(revision) or True)
|
||||
monkeypatch.setattr(module, "_run_registered_deploy", lambda *_a: pytest.fail("held target must never deploy"))
|
||||
module._process_webhook_record(marker)
|
||||
assert prepared == ([revision] if validated else [])
|
||||
assert record["status"] == ("validation_passed" if validated else "awaiting_validation")
|
||||
if validated:
|
||||
assert record["reason"] == "registered_target_auto_apply_held"
|
||||
Reference in new issue
Block a user