mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
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:
437 files changed
+12903
-1724
No files matched your search
-91
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user