mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
fix(cache): keep a mid-render domain map out of the finished one
26 roles resolve a domain in their metadata, so lookup('tls') and lookup('domains') run inside the applications render. Under the re-entry guard those calls receive the unrendered tree, whose service flags are still Jinja strings, and _as_bool reads "{{ 'svc-net-tor' in group_names }}" as false. The resulting map skipped onion injection for every app that does not enable tor literally, and it was cached, so it answered every later read in that process.
Before the applications carrier each worker rendered and poisoned its own cache the same way, which is why the shared Keycloak client registered 5 onion origins then and 65 now. The render state joins the cache key, so the render's own call sites still share one map while a later read builds the finished one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b764b00ba3
commit
fa3797540a
3 files changed
+40
-2
No files matched your search
+32
@@ -18,6 +18,7 @@ from unittest.mock import patch
|
||||
|
||||
from utils.cache import _reset_cache_for_tests
|
||||
from utils.cache import domains as cache_domains
|
||||
from utils.cache.base import _RENDER_GUARD
|
||||
from utils.roles.mapping import ROLE_FILE_META_SERVICES, ROLE_FILE_META_USERS
|
||||
|
||||
from . import PROJECT_ROOT
|
||||
@@ -114,6 +115,37 @@ class TestCachingPerVariablesSignature(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(mocked.call_count, 2)
|
||||
|
||||
def test_a_map_built_during_the_render_never_becomes_the_finished_one(self):
|
||||
"""Under the guard the applications view is still unrendered, so its
|
||||
map must not answer a later read; it may still serve the render."""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
roles = _seed_minimal_role(Path(tmp))
|
||||
variables = {"DOMAIN_PRIMARY": "infinito.test"}
|
||||
with patch(
|
||||
"utils.cache.applications.get_merged_applications",
|
||||
return_value={},
|
||||
) as mocked:
|
||||
_RENDER_GUARD.applications = True
|
||||
try:
|
||||
cache_domains.get_merged_domains(
|
||||
variables=variables, roles_dir=roles, templar=None
|
||||
)
|
||||
cache_domains.get_merged_domains(
|
||||
variables=variables, roles_dir=roles, templar=None
|
||||
)
|
||||
self.assertEqual(mocked.call_count, 1)
|
||||
finally:
|
||||
_RENDER_GUARD.applications = False
|
||||
cache_domains.get_merged_domains(
|
||||
variables=variables, roles_dir=roles, templar=None
|
||||
)
|
||||
self.assertEqual(mocked.call_count, 2)
|
||||
self.assertEqual(len(cache_domains._MERGED_DOMAINS_CACHE), 2)
|
||||
self.assertEqual(
|
||||
sorted(key[-1] for key in cache_domains._MERGED_DOMAINS_CACHE),
|
||||
[False, True],
|
||||
)
|
||||
|
||||
|
||||
class TestResetClearsDomainsCache(unittest.TestCase):
|
||||
def test_reset_evicts_cached_entry(self):
|
||||
|
||||
Vendored
+1
-1
@@ -12,7 +12,7 @@ This package contains the in-process cache layers Infinito.Nexus uses to keep CL
|
||||
| [applications.py](applications.py) | Per-app variants + defaults + `get_merged_applications`. **Strictly ansible-free at import time** so the GitHub Actions runner-host CLI path (`cli.administration.deploy.development.init` → `plan_dev_inventory_matrix` → `get_variants`) keeps working without ansible installed. |
|
||||
| [carrier.py](carrier.py) | The play-scoped carrier of the rendered applications payload: the fact name `_INFINITO_APPLICATIONS_RENDERED`, `merged_applications_cache_key`, and the reader `get_merged_applications` consults before it renders. |
|
||||
| [users/](users/) | User definitions, token store hydration, alias materialization, `get_user_defaults`, `get_merged_users` (package; `users/placeholders.py` substitutes `DOMAIN_PRIMARY` / `ORGANIZATION` / `SOFTWARE_NAME` ahead of the templar render pass). |
|
||||
| [domains.py](domains.py) | Canonical-domains map derived from the merged applications view: `get_merged_domains`. |
|
||||
| [domains.py](domains.py) | Canonical-domains map derived from the merged applications view: `get_merged_domains`. A read issued while the applications render is in progress (the re-entry guard is set) sees the unrendered view, so it is cached under its own key and never answers a later read. |
|
||||
| [`__init__.py`](__init__.py) | Owns the package-level `_reset_cache_for_tests()` orchestrator that clears every cache plus the shared fingerprint memo in one call. |
|
||||
|
||||
## When To Use Which 🎯
|
||||
|
||||
Vendored
+7
-1
@@ -12,6 +12,7 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from .base import (
|
||||
_RENDER_GUARD,
|
||||
_cache_key,
|
||||
_resolve_roles_dir,
|
||||
_stable_variables_signature,
|
||||
@@ -136,7 +137,11 @@ def get_merged_domains(
|
||||
(canonical/aliases) and flow through the regular applications-merge
|
||||
pipeline.
|
||||
|
||||
Cached keyed on (roles_dir, variables_signature).
|
||||
Cached keyed on (roles_dir, variables_signature, render_state). A read
|
||||
issued while the applications render is in progress sees the unrendered
|
||||
tree, whose service flags are still Jinja strings; its map is cached under
|
||||
its own key so the render's own call sites reuse it without it ever being
|
||||
served as the finished map.
|
||||
"""
|
||||
from plugins.filter.canonical_domains_map import (
|
||||
FilterModule as _CanonicalDomainsFilter,
|
||||
@@ -150,6 +155,7 @@ def get_merged_domains(
|
||||
cache_key = (
|
||||
_cache_key(resolved_roles_dir),
|
||||
_stable_variables_signature(variables),
|
||||
bool(getattr(_RENDER_GUARD, "applications", False)),
|
||||
)
|
||||
cached = _MERGED_DOMAINS_CACHE.get(cache_key)
|
||||
if cached is not None:
|
||||
|
||||
Reference in new issue
Block a user