Files
computer-playbook/plugins/lookup/email.py
T
kevinveenbirkenbachandClaude Opus 4.7 c55ad69c41 refactor: drop requirement-file references from code, add doc rule + lint
Strip every `req <NNN>`, `req-<NNN>`, `Requirement <NNN>`,
`requirement-<NNN>`, and `docs/requirements/<NNN>-...md` mention from
non-Markdown source files. Comments anchored to requirement numbers
rot when requirements are renumbered or rewritten and conflate
present-state behaviour (which the comment should describe) with the
audit trail of how the code got there (which belongs in commit
messages, PR descriptions, and the requirement file itself).

* Document the rule in `docs/contributing/documentation.md` under
  "Comments". Code MUST describe the *why* in present-state language;
  contract enforcement lives in tests, not folklore.
* Clean 254 source files across `roles/`, `tests/`, `plugins/`,
  `cli/`, `scripts/`, `utils/`, `group_vars/`, and the repo-root
  `eslint.config.js`. Pure comment / docstring / log-message edits;
  no behavioural change.
* Add `tests/lint/repository/test_no_req_references_in_code.py` to
  enforce the rule going forward across `.js`, `.py`, `.yml`,
  `.yaml`, `.j2`, and `.sh` files. The legacy
  `test_no_stale_requirement_refs.py` is exempt as the only file
  whose purpose is to detect these very tokens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:25:22 +02:00

219 lines
7.5 KiB
Python

from __future__ import annotations
from typing import Any
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
from plugins.lookup.applications import LookupModule as ApplicationsLookup
from plugins.lookup.domain import LookupModule as DomainLookup
from plugins.lookup.users import LookupModule as UsersLookup
SYSTEM_EMAIL_PREFIX = "SYSTEM_EMAIL_"
# Declared in resolution order so each computed default sees already-resolved
# predecessors (for example ``port`` depends on ``tls``).
RESOLUTION_ORDER = (
"enabled",
"timeout",
"external",
"environment",
"domain",
"tls",
"port",
"host",
"auth",
"start_tls",
"smtp",
"from",
"username",
"password",
)
_TRUE_STRINGS = frozenset({"true", "yes", "1", "on"})
_FALSE_STRINGS = frozenset({"false", "no", "0", "off"})
def _short_to_var(short_key: str) -> str:
return SYSTEM_EMAIL_PREFIX + short_key.upper()
def _as_bool(value: Any) -> bool:
if isinstance(value, bool):
return value
if isinstance(value, (int, float)):
return bool(value)
if isinstance(value, str):
lowered = value.strip().lower()
if lowered in _TRUE_STRINGS:
return True
if lowered in _FALSE_STRINGS:
return False
return bool(value)
def _render(value: Any, templar: Any | None) -> Any:
if templar is None:
return value
if not isinstance(value, str) or "{{" not in value:
return value
try:
return templar.template(value, fail_on_undefined=False)
except Exception:
return value
class LookupModule(LookupBase):
def run(
self,
terms: list[Any] | None,
variables: dict[str, Any] | None = None,
**kwargs: Any,
) -> list[Any]:
terms = terms or []
if len(terms) > 1:
raise AnsibleError(
"email: expected 0 or 1 terms: lookup('email'[, application_id])"
)
variables = variables or getattr(self._templar, "available_variables", {}) or {}
self._kwargs = kwargs
templar = getattr(self, "_templar", None)
resolved: dict[str, Any] = {}
for short_key in RESOLUTION_ORDER:
var_name = _short_to_var(short_key)
raw = variables.get(var_name)
if raw is None or raw == "":
resolved[short_key] = _render(
self._compute(short_key, resolved, variables), templar
)
else:
resolved[short_key] = _render(raw, templar)
if len(terms) == 0:
return [resolved]
application_id = str(terms[0]).strip()
overrides = self._app_email_overrides(application_id, variables)
merged = dict(resolved)
for key, value in overrides.items():
merged[str(key).lower()] = _render(value, templar)
return [merged]
def _compute(
self,
short_key: str,
resolved: dict[str, Any],
variables: dict[str, Any],
) -> Any:
if short_key == "enabled":
return True
if short_key == "timeout":
return "30"
if short_key == "external":
group_names = variables.get("group_names") or []
return bool("web-app-mailu" in group_names)
if short_key == "environment":
external = _as_bool(resolved.get("external"))
tls_enabled = _as_bool(variables.get("TLS_ENABLED"))
docker_in_container = _as_bool(variables.get("DOCKER_IN_CONTAINER"))
base = "external" if (external or tls_enabled) else "localhost"
suffix = "_container" if (docker_in_container and not tls_enabled) else ""
return base + suffix
if short_key == "domain":
return variables.get("DOMAIN_PRIMARY")
if short_key == "tls":
external = _as_bool(resolved.get("external"))
if not external:
return False
return _as_bool(variables.get("TLS_ENABLED"))
if short_key == "port":
external = _as_bool(resolved.get("external"))
tls = _as_bool(resolved.get("tls"))
return 465 if (external and tls) else 25
if short_key == "host":
env = resolved.get("environment")
if env in ("external_container", "localhost", "localhost_container"):
return "localhost"
return self._lookup_mailu_domain(variables)
if short_key == "auth":
env = resolved.get("environment")
if env in ("external_container", "localhost"):
return False
return _as_bool(resolved.get("tls"))
if short_key == "start_tls":
return False
if short_key == "smtp":
return True
if short_key == "from":
external = _as_bool(resolved.get("external"))
if external:
no_reply = self._lookup_no_reply_user(variables)
if isinstance(no_reply, dict):
email = no_reply.get("email")
if email:
return email
inventory_hostname = variables.get("inventory_hostname") or "localhost"
return f"root@{inventory_hostname}.localdomain"
if short_key == "username":
return resolved.get("from")
if short_key == "password":
no_reply = self._lookup_no_reply_user(variables)
if isinstance(no_reply, dict):
tokens = no_reply.get("tokens") or {}
if isinstance(tokens, dict):
return tokens.get("web-app-mailu", "") or ""
return ""
raise AnsibleError(f"email: unknown key {short_key!r}")
def _lookup_mailu_domain(self, variables: dict[str, Any]) -> Any:
domain_lookup = DomainLookup()
domain_lookup._templar = getattr(self, "_templar", None)
try:
return domain_lookup.run(["web-app-mailu"], variables=variables)[0]
except Exception:
return "localhost"
def _lookup_no_reply_user(self, variables: dict[str, Any]) -> dict[str, Any]:
users_lookup = UsersLookup()
users_lookup._templar = getattr(self, "_templar", None)
forwarded = {
k: v for k, v in getattr(self, "_kwargs", {}).items() if k == "roles_dir"
}
try:
entry = users_lookup.run(
["no-reply", {}], variables=variables, **forwarded
)[0]
except AnsibleError:
return {}
return entry if isinstance(entry, dict) else {}
def _app_email_overrides(
self,
application_id: str,
variables: dict[str, Any],
) -> dict[str, Any]:
apps = ApplicationsLookup()
apps._templar = getattr(self, "_templar", None)
forwarded = {
k: v for k, v in getattr(self, "_kwargs", {}).items() if k == "roles_dir"
}
try:
entry = apps.run(
[application_id, {}],
variables=variables,
**forwarded,
)[0]
except AnsibleError:
return {}
if not isinstance(entry, dict):
return {}
# Per the materialised payload exposes services under the
# bare ``services`` key (no ``compose.services`` envelope).
services = entry.get("services") or {}
email = services.get("email") or {} if isinstance(services, dict) else {}
if not isinstance(email, dict):
return {}
return email