mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-10 12:47:04 +00:00
perf(web-opt-rdr-domains): render redirect vhosts without deep-copying the variables
ansible-core 2.20's template lookup deep-copies all task variables on every call. The redirect loop renders two templates per source domain, so on galaxyserver this copy dominated the resolve and render step. A role-local lookup, template_shallow, hands the builtin template lookup a top-level copy of the variables whose values stay shared with the caller; nothing on the reachable template and lookup paths mutates them in place. Rendered output is identical across 12 variants (TLS, Tor, logout, self-signed, wildcard keep_path), per-item failures and undefined-variable messages included. A real playbook with 65 items went from 10.5 s to 5.8 s. 🎯 Fix: 92 % 💥 Break: 3 % 🌱 Efficiency: – ⚡ Speed: – 🛡️ Security: ~100 % 🏆 Score: 89 🚦 Gate: ✅ 90/5, efficiency and speed <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
025ebe9754
commit
4a6bec9cf0
5 files changed
+142
-2
No files matched your search
@@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from ansible.plugins.loader import lookup_loader
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
|
||||
|
||||
class _SharedVariables(dict):
|
||||
def __deepcopy__(self, memo: dict[int, Any]) -> dict[str, Any]:
|
||||
return dict(self)
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
def run(
|
||||
self, terms: list[Any], variables: dict[str, Any], **kwargs: Any
|
||||
) -> list[Any]:
|
||||
"""
|
||||
Args:
|
||||
terms: the template paths, resolved like ``ansible.builtin.template``.
|
||||
variables: the caller's variables; every term renders over a fresh
|
||||
top-level copy whose values are shared with the caller.
|
||||
kwargs: the ``ansible.builtin.template`` options, such as
|
||||
``template_vars``.
|
||||
|
||||
Returns:
|
||||
One rendered string per term.
|
||||
"""
|
||||
template = lookup_loader.get(
|
||||
"template", loader=self._loader, templar=self._templar
|
||||
)
|
||||
return template.run(terms, variables=_SharedVariables(variables), **kwargs)
|
||||
@@ -26,11 +26,11 @@
|
||||
- "{{ lookup('cert', item.source, 'files.key') }}"
|
||||
files: >-
|
||||
{{ [{'path': lookup('nginx', 'files.domain', item.source),
|
||||
'content': lookup('template', 'redirect-domain.conf.j2',
|
||||
'content': lookup('template_shallow', 'redirect-domain.conf.j2',
|
||||
template_vars={'domain': item.source, 'target': item.target,
|
||||
'keep_path': item.get('keep_path', true)})}]
|
||||
+ ([{'path': lookup('nginx', 'files.domain', item.source, protocol='http'),
|
||||
'content': lookup('template', lookup('path_absolute', 'roles/sys-front-tls/templates/http.conf.j2'),
|
||||
'content': lookup('template_shallow', lookup('path_absolute', 'roles/sys-front-tls/templates/http.conf.j2'),
|
||||
template_vars={'domain': item.source})}]
|
||||
if redirect_source_tls else []) }}
|
||||
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[6]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -0,0 +1,103 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.parsing.dataloader import DataLoader
|
||||
from ansible.plugins.loader import lookup_loader
|
||||
from ansible.template import Templar, trust_as_template
|
||||
|
||||
from utils.cache.yaml import load_yaml_any
|
||||
from utils.roles.mapping import ROLE_FILE_TASKS_MAIN
|
||||
|
||||
from . import PROJECT_ROOT
|
||||
|
||||
ROLE = PROJECT_ROOT / "roles" / "web-opt-rdr-domains"
|
||||
PROBE_PLUGIN = """
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
def run(self, terms, variables, **kwargs):
|
||||
return [variables["domain"]]
|
||||
"""
|
||||
TEMPLATE = "{{ domain }} {{ lookup('rdr_probe_variables_domain') }}\n"
|
||||
|
||||
|
||||
class _NoDeepCopy:
|
||||
def __deepcopy__(self, memo):
|
||||
raise AssertionError("the variables were deep-copied")
|
||||
|
||||
|
||||
class TestTemplateShallow(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls._tmp = tempfile.TemporaryDirectory()
|
||||
tmp = Path(cls._tmp.name)
|
||||
(tmp / "plugins").mkdir()
|
||||
(tmp / "plugins" / "rdr_probe_variables_domain.py").write_text(PROBE_PLUGIN)
|
||||
(tmp / "templates").mkdir()
|
||||
(tmp / "templates" / "vhost.j2").write_text(TEMPLATE)
|
||||
lookup_loader.add_directory(str(ROLE / "lookup_plugins"))
|
||||
lookup_loader.add_directory(str(tmp / "plugins"))
|
||||
cls.search_path = [str(tmp)]
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls) -> None:
|
||||
cls._tmp.cleanup()
|
||||
|
||||
def _variables(self) -> dict:
|
||||
return {"domain": "play.example", "ansible_search_path": self.search_path}
|
||||
|
||||
def _render(self, plugin: str, variables: dict) -> str:
|
||||
return Templar(loader=DataLoader(), variables=variables).template(
|
||||
trust_as_template(
|
||||
"{{ lookup('" + plugin + "', 'vhost.j2', "
|
||||
"template_vars={'domain': 'source.example'}) }}"
|
||||
)
|
||||
)
|
||||
|
||||
def test_a_lookup_inside_the_template_reads_the_per_call_domain(self) -> None:
|
||||
rendered = self._render("template_shallow", self._variables())
|
||||
self.assertEqual(rendered, "source.example source.example\n")
|
||||
self.assertEqual(rendered, self._render("template", self._variables()))
|
||||
|
||||
def test_the_caller_variables_keep_their_values(self) -> None:
|
||||
variables = self._variables()
|
||||
self._render("template_shallow", variables)
|
||||
self.assertEqual(variables, self._variables())
|
||||
|
||||
def test_no_value_of_the_caller_is_deep_copied(self) -> None:
|
||||
variables = {**self._variables(), "untouchable": _NoDeepCopy()}
|
||||
self.assertEqual(
|
||||
self._render("template_shallow", variables),
|
||||
"source.example source.example\n",
|
||||
)
|
||||
with self.assertRaises(AnsibleError) as raised:
|
||||
self._render("template", variables)
|
||||
causes = []
|
||||
error: BaseException | None = raised.exception
|
||||
while error is not None:
|
||||
causes.append(str(error))
|
||||
error = error.__cause__
|
||||
self.assertTrue(any("deep-copied" in cause for cause in causes), causes)
|
||||
|
||||
|
||||
class TestRedirectLoopRendersShallow(unittest.TestCase):
|
||||
def test_both_files_of_every_source_render_through_template_shallow(
|
||||
self,
|
||||
) -> None:
|
||||
tasks = load_yaml_any(str(ROLE / ROLE_FILE_TASKS_MAIN))
|
||||
[loop_task] = [
|
||||
task for task in tasks if "redirect_source" in task.get("vars", {})
|
||||
]
|
||||
files = loop_task["vars"]["redirect_source"]["files"]
|
||||
self.assertEqual(files.count("lookup('template_shallow', "), 2)
|
||||
self.assertNotIn("lookup('template', ", json.dumps(loop_task))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user