Merge branch 'main' into feature/store-i18n

Fourteen real conflicts plus the 66 generated READMEs.

utils/cache/files.py and its test: both sides added a skip-dir rule, resolved
as a union so the root `build/` prune and the per-root walk key coexist.

plugins/lookup/database.py and postgres_libraries.py: main's added lookups kept,
with get_entity_name renamed to entity_name as this branch renamed it.

roles/web-app-{mailu,matrix}/meta/services.yml: main's `covers: [email]` and its
MAIL_PROVIDER-gated email entry, plus this branch's gitlab/jira/n8n/jitsi
consumers, which the merge base carried on neither side.

group_vars/all/17_resource.yml: this branch's lookup('resource', ...) refactor;
main's single added literal is already covered by _POSTGRES_MAX_LOCKS_PER_TRANSACTION.

docs/requirements/035-mcp-proxy-expansion.md: this branch moved the design into
ADR 0003, so the requirement keeps only the user story and the criteria. Main's
delta inside the moved block was two new roles, carried into the ADR audit:
web-app-stalwart as adapter_candidate, svc-net-tor-smtp as privileged_control_plane.
The no_surface total said 99 while its own subcategories summed to 112.

inventories/development/default.yml: biber keeps no explicit email. Both sides fixed
the same CI failure; this branch fixed it in _apply_account_defaults, which renders
biber@{{ DOMAIN_PRIMARY }} (utils/cache/users/__init__.py:68) — byte-identical to
the address main hardcoded, so the inventory line is a second copy of one fact.

roles/web-app-{gitlab,keycloak}/files/playwright/playwright.spec.js and
roles/web-app-gitlab/templates/playwright.env.j2: union of both import sets, with
requireDotenvValue kept over decodeDotenvQuotedValue for CANONICAL_DOMAIN because
an empty value must fail rather than match every substring.

The 66 role READMEs take this side: aceb5295ef moved Cosmos, Quick Setup and
Credits to the documentation build, and test_readme.py now rejects them in a
committed README. Main's prose edits inside them are carried over by hand (the
Mailu deprecation banner, Mailu -> Stalwart in erpnext and zammad), and the two
READMEs main added fresh are stripped of the three generated sections.
roles/web-app-sphinx/README.md stays deleted; 6eb9a59d14 merged that role into
web-app-docs. The root README's generated role table was regenerated from the
merged metadata.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-10-04 01:57:17 +02:00
commit f2fedfc677
351 files changed
+9968 -416

No files matched your search

+13
View File
@@ -106,6 +106,19 @@ class TestIterProjectFiles(_ProjectRootFixture, unittest.TestCase):
f"cli/build is source and must survive the prune: {all_paths}",
)
def test_git_local_exclude_dirs_pruned(self) -> None:
"""Plain directory entries in .git/info/exclude are pruned; file
patterns and globs are left to git."""
_touch(
self.root / ".git/info/exclude",
"# comment\n/local-tool-out/\nsettings.local.json\n*.tmp\n",
)
_touch(self.root / "local-tool-out/report.md", "generated")
_reset()
all_paths = set(iter_project_files())
self.assertFalse(any("/local-tool-out/" in p for p in all_paths))
self.assertTrue(any(p.endswith("readme.md") for p in all_paths))
def test_extensions_filter(self) -> None:
ymls = list(iter_project_files(extensions=(".yml",)))
self.assertEqual(len(ymls), 1)
@@ -142,11 +142,33 @@ class TestOtherFlavors(unittest.TestCase):
blackhole="void@example",
)
self.assertIn("if [ ! -f /tmp/email_sent ]", argv[1])
self.assertIn("msmtp -t void@example", argv[1])
self.assertIn("msmtp --domain=app.example -t void@example", argv[1])
self.assertTrue(
argv[1].endswith("curl -f --noproxy '*' http://127.0.0.1:80/ || exit 1")
)
def test_the_ehlo_identity_is_named_so_a_strict_relay_accepts_it(self):
"""msmtp defaults EHLO to "localhost", which Stalwart answers with 550."""
argv = probe(
"msmtp_curl",
port=80,
email_enabled=True,
domain="app.example",
blackhole="void@example",
)
self.assertIn("--domain=app.example", argv[1])
def test_without_a_domain_no_empty_ehlo_flag_is_emitted(self):
argv = probe(
"msmtp_curl",
port=80,
email_enabled=True,
domain="",
blackhole="void@example",
)
self.assertNotIn("--domain=", argv[1])
self.assertIn("msmtp -t void@example", argv[1])
def test_msmtp_curl_probe_behaviour(self):
argv = probe(
"msmtp_curl",
+3
View File
@@ -0,0 +1,3 @@
from pathlib import Path
PROJECT_ROOT: Path = Path(__file__).resolve().parents[5]
@@ -0,0 +1,120 @@
"""The mail provider a deploy actually resolves to.
``MAIL_PROVIDER`` is the operator's flag and wins whenever the role it names is
deployed. The fallback exists for the round that deploys only the alternative
provider, and it must stay single-valued: if two roles could both consider
themselves active they would both bind the public SMTP ports.
"""
from __future__ import annotations
import unittest
from utils.mail.provider import (
declaring_roles,
deployed_roles,
resolve_active_provider,
)
from . import PROJECT_ROOT
ROLES_DIR = PROJECT_ROOT / "roles"
STALWART = "web-app-stalwart"
MAILU = "web-app-mailu"
class TestDeclaringRoles(unittest.TestCase):
def test_both_providers_are_discovered(self):
roles = declaring_roles(ROLES_DIR)
self.assertIn(STALWART, roles)
self.assertIn(MAILU, roles)
def test_provides_outranks_covers(self):
roles = declaring_roles(ROLES_DIR)
self.assertLess(
roles.index(STALWART),
roles.index(MAILU),
"the role declaring provides: email must win the fallback",
)
class TestResolveActiveProvider(unittest.TestCase):
def test_configured_provider_wins_when_deployed(self):
self.assertEqual(
resolve_active_provider(STALWART, [STALWART, MAILU], ROLES_DIR), STALWART
)
def test_configured_provider_wins_alone(self):
self.assertEqual(
resolve_active_provider(STALWART, [STALWART], ROLES_DIR), STALWART
)
def test_falls_back_to_the_provider_that_is_deployed(self):
self.assertEqual(resolve_active_provider(STALWART, [MAILU], ROLES_DIR), MAILU)
def test_mailu_configured_wins_over_stalwart_priority(self):
self.assertEqual(
resolve_active_provider(MAILU, [STALWART, MAILU], ROLES_DIR), MAILU
)
def test_exactly_one_provider_is_active_when_both_deployed(self):
deployed = [STALWART, MAILU]
for configured in (STALWART, MAILU, "web-app-absent"):
active = resolve_active_provider(configured, deployed, ROLES_DIR)
others = [r for r in deployed if r != active]
self.assertNotIn(
active,
others,
f"configured={configured!r} produced more than one active provider",
)
self.assertIn(active, deployed)
def test_no_provider_deployed_keeps_the_configured_value(self):
self.assertEqual(
resolve_active_provider(STALWART, ["web-app-gitea"], ROLES_DIR), STALWART
)
def test_empty_group_names_keeps_the_configured_value(self):
self.assertEqual(resolve_active_provider(STALWART, [], ROLES_DIR), STALWART)
class TestDeployedRoles(unittest.TestCase):
"""Resolution is cluster-wide, so it reads ``groups``, not ``group_names``.
A swarm worker's ``group_names`` lacks the manager-pinned provider. Deriving
presence per-host would give workers a different provider than the manager
and render their relay config against a server they must not use.
"""
def test_only_groups_with_hosts_count_as_deployed(self):
groups = {
STALWART: ["mgr-01"],
MAILU: [],
"all": ["mgr-01", "wrk-01"],
}
result = deployed_roles(groups)
self.assertIn(STALWART, result)
self.assertNotIn(MAILU, result)
def test_none_and_empty_are_tolerated(self):
self.assertEqual(deployed_roles(None), [])
self.assertEqual(deployed_roles({}), [])
def test_worker_resolves_the_same_provider_as_the_manager(self):
groups = {STALWART: ["mgr-01"], "all": ["mgr-01", "wrk-01", "wrk-02"]}
self.assertEqual(
resolve_active_provider(STALWART, deployed_roles(groups), ROLES_DIR),
STALWART,
"a manager-pinned provider must still resolve on worker nodes",
)
def test_mailu_only_cluster_resolves_mailu_on_every_node(self):
groups = {MAILU: ["mgr-01"], "all": ["mgr-01", "wrk-01"]}
self.assertEqual(
resolve_active_provider(STALWART, deployed_roles(groups), ROLES_DIR), MAILU
)
if __name__ == "__main__":
unittest.main()
@@ -204,6 +204,43 @@ class TestTemplatingRenderStrict(unittest.TestCase):
)
self.assertEqual(out, ca_cert_host("infinito.nexus"))
def test_inline_conditional_with_concat(self):
raw = (
"{{ ('mail.' ~ DOMAIN_PRIMARY) if MAIL_PROVIDER == 'web-app-mailu' "
"else ('legacy-mail.' ~ DOMAIN_PRIMARY) }}"
)
base = {"DOMAIN_PRIMARY": "example.com"}
self.assertEqual(
render_ansible_strict(
templar=None,
raw=raw,
var_name="x",
err_prefix="t",
variables={**base, "MAIL_PROVIDER": "web-app-mailu"},
),
"mail.example.com",
)
self.assertEqual(
render_ansible_strict(
templar=None,
raw=raw,
var_name="x",
err_prefix="t",
variables={**base, "MAIL_PROVIDER": "web-app-stalwart"},
),
"legacy-mail.example.com",
)
self.assertEqual(
render_ansible_strict(
templar=None,
raw=raw,
var_name="x",
err_prefix="t",
variables=base,
),
"legacy-mail.example.com",
)
def test_filters_lower_upper_default(self):
variables = {"NAME": "MiXeD", "EMPTY": ""}
out1 = render_ansible_strict(
@@ -0,0 +1,94 @@
# Ensure repo root is importable so `utils.*` resolves in all runners
import sys
import unittest
from . import PROJECT_ROOT
sys.path.insert(0, str(PROJECT_ROOT))
from utils.templating.expr import (
find_top_level_op,
is_paren_wrapped,
split_list_items,
split_top_level,
)
class TestSplitListItems(unittest.TestCase):
def test_bare_and_quoted_tokens(self):
self.assertEqual(
split_list_items("DIR_BIN, 'ca-inject'"), ["DIR_BIN", "'ca-inject'"]
)
def test_comma_inside_quotes_is_not_a_separator(self):
self.assertEqual(split_list_items("'a,b', c"), ["'a,b'", "c"])
def test_mixed_quote_styles(self):
self.assertEqual(split_list_items("\"x\", 'y'"), ['"x"', "'y'"])
def test_empty_and_whitespace_tokens_dropped(self):
self.assertEqual(split_list_items(" a ,, b , "), ["a", "b"])
def test_empty_string(self):
self.assertEqual(split_list_items(""), [])
class TestFindTopLevelOp(unittest.TestCase):
def test_finds_operator_at_depth_zero(self):
self.assertEqual(find_top_level_op("A == B", "=="), 2)
def test_skips_operator_inside_parens(self):
expr = "(A == B) != C"
self.assertEqual(find_top_level_op(expr, "!="), expr.index("!="))
self.assertEqual(find_top_level_op("(A == B)", "=="), -1)
def test_skips_operator_inside_quotes(self):
expr = "'x == y' == Z"
self.assertEqual(find_top_level_op(expr, "=="), 9)
def test_word_operator_padded_with_spaces(self):
"""A padded ` if ` must not match inside identifiers like `notify`."""
self.assertEqual(find_top_level_op("gift if COND else b", " if "), 4)
self.assertEqual(find_top_level_op("gifted", " if "), -1)
def test_not_found(self):
self.assertEqual(find_top_level_op("A ~ B", "=="), -1)
def test_bracket_kinds_all_count_as_depth(self):
self.assertEqual(find_top_level_op("[a == b] == c", "=="), 9)
self.assertEqual(find_top_level_op("{a == b} == c", "=="), 9)
class TestSplitTopLevel(unittest.TestCase):
def test_simple_split(self):
self.assertEqual(split_top_level("a ~ b ~ c", "~"), ["a ", " b ", " c"])
def test_separator_inside_quotes_kept(self):
self.assertEqual(split_top_level("'a ~ b' ~ c", "~"), ["'a ~ b' ", " c"])
def test_separator_inside_parens_kept(self):
self.assertEqual(split_top_level("(a ~ b) ~ c", "~"), ["(a ~ b) ", " c"])
def test_no_separator_returns_whole(self):
self.assertEqual(split_top_level("abc", "~"), ["abc"])
class TestIsParenWrapped(unittest.TestCase):
def test_fully_wrapped(self):
self.assertTrue(is_paren_wrapped("(a ~ b)"))
def test_adjacent_groups_are_not_wrapped(self):
self.assertFalse(is_paren_wrapped("(a) ~ (b)"))
def test_no_parens(self):
self.assertFalse(is_paren_wrapped("a ~ b"))
def test_nested_wrap(self):
self.assertTrue(is_paren_wrapped("((a) ~ (b))"))
def test_close_paren_inside_quotes_ignored(self):
self.assertTrue(is_paren_wrapped("(a ~ ')' ~ b)"))
if __name__ == "__main__": # pragma: no cover
unittest.main()