Merge origin/main into feature/svc-net-wireguard

Brings in 225 commits, including the merged web-app-openbao work and the
LMStudio loopback fix.

Seven conflicts:

- The legacy svc-net-wireguard-core/plain/firewalled roles this branch deleted
  in e706170a0 were modified upstream. The deletion stands, which is the whole
  premise of the branch.
- The MCP audit takes upstream's list plus svc-net-firewall, with the three
  legacy wireguard entries collapsed into the single svc-net-wireguard: 23.
- web-app-openbao/README.md takes main's reviewed version wholesale.
- matrix.py takes both sides: upstream's app_id argument to _reset_credentials
  and this branch's _write_mesh call after it.

The merge pushed matrix.py to 511 lines against the 500 cap, so the step
runner moved to utils/tests/swarm/run.py, which is self-contained and leaves
matrix.py at 427. Its test moved with it.
This commit is contained in:
evangelostsak committed 2026-09-24 21:14:25 +02:00
commit 0a6700e378
437 files changed
+12903 -1724

No files matched your search

@@ -1,91 +0,0 @@
"""Unit tests for `cli.administration.deploy.development.inventory.nested_overrides`.
The service registry is stubbed: what matters is which role a claim is
attributed to, not how a service key resolves to its provider.
"""
from __future__ import annotations
import unittest
from unittest.mock import patch
from cli.administration.deploy.development.inventory.nested_overrides import (
NestedOverrideConflictError,
collect_provider_overrides,
)
_REGISTRY = {
"openwebui": {"role": "web-app-openwebui"},
"gitea": {"role": "web-app-gitea"},
}
_PATCH = (
"cli.administration.deploy.development.inventory.nested_overrides"
".build_service_registry_from_roles_dir"
)
class TestCollectProviderOverrides(unittest.TestCase):
def _collect(self, payloads, registry=None):
with patch(_PATCH, autospec=True) as build:
build.return_value = _REGISTRY if registry is None else registry
return collect_provider_overrides(payloads, roles_dir="/roles")
def test_a_named_topic_reaches_its_provider(self) -> None:
payloads = {
"web-app-moodle": {"services": {"openwebui": {"services": None}}},
}
self.assertEqual(
{"web-app-openwebui": {"services": None}}, self._collect(payloads)
)
def test_several_topics_of_one_provider_are_collected_together(self) -> None:
payloads = {
"web-app-moodle": {
"services": {
"openwebui": {"services": None, "addons": {"whisper": False}},
},
},
}
self.assertEqual(
{"web-app-openwebui": {"services": None, "addons": {"whisper": False}}},
self._collect(payloads),
)
def test_a_roles_own_entry_is_not_an_override(self) -> None:
payloads = {"web-app-gitea": {"services": {"gitea": {"users": {"a": 1}}}}}
self.assertEqual({}, self._collect(payloads))
def test_an_unregistered_service_key_is_skipped(self) -> None:
payloads = {"web-app-moodle": {"services": {"nowhere": {"services": None}}}}
self.assertEqual({}, self._collect(payloads))
def test_two_roles_agreeing_is_not_a_conflict(self) -> None:
payloads = {
"web-app-moodle": {"services": {"openwebui": {"services": None}}},
"web-app-gitea": {"services": {"openwebui": {"services": None}}},
}
self.assertEqual(
{"web-app-openwebui": {"services": None}}, self._collect(payloads)
)
def test_two_roles_demanding_different_values_raises(self) -> None:
payloads = {
"web-app-moodle": {"services": {"openwebui": {"services": None}}},
"web-app-gitea": {"services": {"openwebui": {"services": {"a": 1}}}},
}
with self.assertRaises(NestedOverrideConflictError):
self._collect(payloads)
def test_a_round_without_overrides_never_builds_the_registry(self) -> None:
payloads = {"web-app-moodle": {"services": {"openwebui": {"enabled": True}}}}
with patch(_PATCH, autospec=True) as build:
build.return_value = _REGISTRY
self.assertEqual(
{}, collect_provider_overrides(payloads, roles_dir="/roles")
)
build.assert_not_called()
if __name__ == "__main__":
unittest.main()
@@ -53,6 +53,10 @@ users:
6e6577730a
biber:
password: VeryUnsecurePassword!1
keycloak-bot:
password: !vault |
$ANSIBLE_VAULT;1.1;AES256
626f740a
"""
DECLARED_USERS = """\
@@ -63,6 +67,14 @@ newsletter:
uid: 1002
"""
KEYCLOAK_USERS = (
DECLARED_USERS
+ """\
keycloak-bot:
uid: 1003
"""
)
class TestResetCredentials(unittest.TestCase):
def setUp(self):
@@ -70,10 +82,14 @@ class TestResetCredentials(unittest.TestCase):
self.addCleanup(shutil.rmtree, self.workdir, ignore_errors=True)
self.roles_dir = self.workdir / "roles"
for application_id in ("web-app-a", "web-app-keycloak"):
declarations = {
"web-app-a": DECLARED_USERS,
"web-app-keycloak": KEYCLOAK_USERS,
}
for application_id, declared in declarations.items():
users_file = self.roles_dir / application_id / ROLE_FILE_META_USERS
users_file.parent.mkdir(parents=True, exist_ok=True)
users_file.write_text(DECLARED_USERS, encoding="utf-8")
users_file.write_text(declared, encoding="utf-8")
self.host_vars_file = self.workdir / "host_vars" / "localhost.yml"
self.host_vars_file.parent.mkdir(parents=True, exist_ok=True)
@@ -125,6 +141,32 @@ class TestResetCredentials(unittest.TestCase):
self._reset(exclude={"administrator", "web-app-keycloak"})
self.assertIn("database_password", self._credentials("web-app-keycloak"))
def test_an_application_outside_the_rotation_keeps_its_credentials(self):
"""Dropping must not outrun regeneration.
The drop side walks the document while the generate side walks
`credential_ids`. A matrix host_vars file carries a block per mirror
artefact too, so a document-wide drop would delete credentials the
caller never writes back.
"""
_, credentials, _ = self._reset(credential_ids=["web-app-a"])
self.assertIn("database_password", self._credentials("web-app-keycloak"))
self.assertNotIn("api_key", self._credentials("web-app-a"))
self.assertEqual(credentials.call_args.kwargs["application_ids"], ["web-app-a"])
def test_narrowing_the_credentials_still_rotates_every_user(self):
"""A narrowed credential scope must not narrow the user passwords.
Both halves used to share one id list, so scoping the credentials to
the round silently stopped rotating the users of every other role.
"""
_, _, users = self._reset(credential_ids=["web-app-a"])
self.assertNotIn("password", self._document()["users"]["keycloak-bot"])
self.assertEqual(
users.call_args.kwargs["application_ids"],
["web-app-a", "web-app-keycloak"],
)
def test_an_excluded_user_keeps_its_password(self):
self._reset()
users = self._document()["users"]
@@ -164,7 +206,7 @@ class TestResetCredentials(unittest.TestCase):
def test_the_rotated_count_covers_both_sections(self):
rotated, _, _ = self._reset()
self.assertEqual(rotated, 4)
self.assertEqual(rotated, 5)
def test_rotating_nothing_aborts(self):
with self.assertRaises(SystemExit):
@@ -188,6 +188,36 @@ class TestApplyServicesDisabled(unittest.TestCase):
dump_yaml(role_dir / ROLE_FILE_META_SERVICES, services)
dump_yaml(role_dir / ROLE_FILE_VARS_MAIN, {"application_id": role_name})
def test_a_role_without_shared_in_its_contract_does_not_get_one(self):
self._write_host_vars(
{"applications": {"web-svc-cdn": {"services": {"dashboard": {}}}}}
)
self._make_role("web-svc-cdn", {"dashboard": {"enabled": False}})
apply_services_disabled(self.host_vars, ["dashboard"], self.roles_dir)
service = self._read_host_vars()["applications"]["web-svc-cdn"]["services"][
"dashboard"
]
self.assertEqual(False, service["enabled"])
self.assertNotIn("shared", service)
def test_a_role_declaring_both_toggles_still_gets_both(self):
self._write_host_vars(
{"applications": {"web-app-nextcloud": {"services": {"dashboard": {}}}}}
)
self._make_role(
"web-app-nextcloud", {"dashboard": {"enabled": True, "shared": True}}
)
apply_services_disabled(self.host_vars, ["dashboard"], self.roles_dir)
service = self._read_host_vars()["applications"]["web-app-nextcloud"][
"services"
]["dashboard"]
self.assertEqual(False, service["enabled"])
self.assertEqual(False, service["shared"])
def test_disables_service_in_host_vars_and_removes_from_inventory(self):
self._write_host_vars(
{
@@ -7,7 +7,7 @@ import unittest
from cli.meta.ci.dev_runtime import image_label, workspace_matrix
from utils.symbol_glossary import to_emoji
IMAGES = ["debian:bookworm", "fedora:latest", "ubuntu:latest", "quay.io/centos:latest"]
IMAGES = ["debian:13", "fedora:latest", "ubuntu:latest", "quay.io/centos:latest"]
class TestWorkspaceMatrix(unittest.TestCase):
@@ -55,7 +55,7 @@ class TestWorkspaceMatrix(unittest.TestCase):
def test_a_label_keeps_only_the_last_two_segments(self) -> None:
self.assertEqual(image_label("quay.io/centos/centos:latest"), "centos:latest")
self.assertEqual(image_label("manjarolinux/base"), "manjarolinux/base")
self.assertEqual(image_label("debian:bookworm"), "debian:bookworm")
self.assertEqual(image_label("debian:13"), "debian:13")
self.assertEqual(image_label("ghcr.io/foo/bar/baz:1.2"), "baz:1.2")
self.assertEqual(image_label("plainname"), "plainname")
@@ -54,6 +54,11 @@ class TestProblems(unittest.TestCase):
self.assertEqual(errors, [])
self.assertIn("web-app-gone", warnings[0])
def test_a_pinned_role_the_run_never_discovers_only_warns(self) -> None:
errors, warnings = _problems("web-app-gone#0,2")
self.assertEqual(errors, [])
self.assertIn("web-app-gone", warnings[0])
def test_a_bare_role_the_run_discovers_is_silent(self) -> None:
self.assertEqual(_problems("web-app-a"), ([], []))
@@ -13,6 +13,7 @@ from utils.cache.yaml import dump_yaml
from utils.roles.mapping import (
ROLE_FILE_META_MAIN,
ROLE_FILE_META_SERVICES,
ROLE_FILE_META_VARIANTS,
ROLE_FILE_VARS_MAIN,
)
@@ -38,6 +39,22 @@ def _write_run_after(root: Path, role: str, run_after: list[str]) -> None:
)
def _write_sso_consumer(root: Path, role: str, variants: list[bool]) -> None:
"""A role consuming shared `sso`, with one variant per `variants` entry
that either keeps the consumption or switches it off."""
dump_yaml(
root / "roles" / role / ROLE_FILE_META_SERVICES,
{role: {}, "sso": {"enabled": True, "shared": True, "flavor": "oidc"}},
)
dump_yaml(
root / "roles" / role / ROLE_FILE_META_VARIANTS,
[
{"services": {"sso": {"enabled": consumes, "shared": consumes}}}
for consumes in variants
],
)
def _write_dependencies(root: Path, role: str, deps: list[str]) -> None:
dump_yaml(
root / "roles" / role / ROLE_FILE_META_MAIN,
@@ -104,6 +121,59 @@ class TestAffected(unittest.TestCase):
self.assertIn("web-app-keycloak", got)
self.assertIn("web-app-consumer", got)
def test_only_the_variants_reaching_the_seed_are_kept(self) -> None:
with tempfile.TemporaryDirectory() as td:
root = Path(td)
_mk_app_role(root, "web-app-keycloak", "keycloak")
_mk_app_role(root, "web-app-consumer", "consumer")
_write_sso_consumer(root, "web-app-consumer", [True, False, True])
with patch.object(repo_paths, "PROJECT_ROOT", root):
got = affected_main.affected_selection(["web-app-keycloak"])
self.assertIn("web-app-consumer#0,2", got)
def test_a_role_whose_variants_all_reach_the_seed_stays_bare(self) -> None:
with tempfile.TemporaryDirectory() as td:
root = Path(td)
_mk_app_role(root, "web-app-keycloak", "keycloak")
_mk_app_role(root, "web-app-consumer", "consumer")
_write_sso_consumer(root, "web-app-consumer", [True, True])
with patch.object(repo_paths, "PROJECT_ROOT", root):
got = affected_main.affected_selection(["web-app-keycloak"])
self.assertIn("web-app-consumer", got)
def test_the_narrowing_follows_a_parents_variant_one_generation_up(self) -> None:
with tempfile.TemporaryDirectory() as td:
root = Path(td)
_mk_app_role(root, "web-app-keycloak", "keycloak")
_mk_app_role(root, "web-app-matomo", "matomo")
_mk_app_role(root, "web-app-consumer", "consumer")
_write_sso_consumer(root, "web-app-matomo", [True, False])
dump_yaml(
root / "roles" / "web-app-consumer" / ROLE_FILE_META_SERVICES,
{"consumer": {}, "matomo": {"enabled": True, "shared": True}},
)
dump_yaml(
root / "roles" / "web-app-consumer" / ROLE_FILE_META_VARIANTS,
[{"services": {"matomo": {"enabled": True, "shared": True}}}] * 2,
)
with patch.object(repo_paths, "PROJECT_ROOT", root):
got = affected_main.affected_selection(["web-app-keycloak"])
self.assertIn("web-app-consumer#0", got)
def test_a_seed_keeps_every_variant_of_its_own_role(self) -> None:
with tempfile.TemporaryDirectory() as td:
root = Path(td)
_mk_app_role(root, "web-app-keycloak", "keycloak")
_mk_app_role(root, "web-app-consumer", "consumer")
_write_sso_consumer(root, "web-app-consumer", [True, False])
with patch.object(repo_paths, "PROJECT_ROOT", root):
got = affected_main.affected_selection(["web-app-consumer"])
self.assertEqual(got, ["web-app-consumer"])
def test_unknown_role_raises(self) -> None:
with tempfile.TemporaryDirectory() as td:
root = Path(td)