mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-11 13:17:07 +00:00
The same failure class as 3dee332350, in the places that only became
visible once the louder messages were gone. Nine of them are class
docstrings, which the detector used to find the first round skipped
because it walked modules and functions only.
restart_condition carried a section title inside its docstring, where
docutils allows none, and the indented block below it held a second.
cert_utils indented the closing brace of its example shallower than the
body it closes.
Two are regressions of my own first round: filters lacked the blank line
between its new literal block and the paragraph after it, and
fediwall_active had the wrong one of its two colons corrected.
make i18n-extract domain=docs now finishes with zero warnings under -W.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
171 lines
5.8 KiB
Python
171 lines
5.8 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from ansible.errors import AnsibleError
|
|
from ansible.plugins.loader import lookup_loader
|
|
from ansible.plugins.lookup import LookupBase
|
|
|
|
from utils.get_url import get_url
|
|
from utils.roles.applications.config import (
|
|
AppConfigKeyError,
|
|
ConfigEntryNotSetError,
|
|
get,
|
|
)
|
|
from utils.tls_common import resolve_enabled
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Iterable, Sequence
|
|
|
|
DEFAULT_FEATURES = ("services.sso.enabled",)
|
|
DEFAULT_WILDCARD = "/*"
|
|
|
|
|
|
def _stable_dedup(items: Sequence[str]) -> list[str]:
|
|
seen = set()
|
|
out: list[str] = []
|
|
for x in items:
|
|
if x not in seen:
|
|
seen.add(x)
|
|
out.append(x)
|
|
return out
|
|
|
|
|
|
def _iter_domains(value: Any) -> Iterable[str]:
|
|
"""Yield domains from str | list/tuple[str] | dict[*, str|list|tuple]."""
|
|
if value is None:
|
|
return
|
|
if isinstance(value, str):
|
|
yield value
|
|
elif isinstance(value, dict):
|
|
for val in value.values():
|
|
yield from _iter_domains(val)
|
|
elif isinstance(value, (list, tuple)):
|
|
for val in value:
|
|
yield from _iter_domains(val)
|
|
else:
|
|
raise AnsibleError(
|
|
"redirect_uris: domain value must be str, list/tuple[str], or dict mapping to those"
|
|
)
|
|
|
|
|
|
def build_redirect_uris(
|
|
domains: dict,
|
|
applications: dict,
|
|
tls_enabled: bool,
|
|
*,
|
|
wildcard: str = DEFAULT_WILDCARD,
|
|
features: Iterable[str] = DEFAULT_FEATURES,
|
|
dedup: bool = True,
|
|
deployed: Iterable[str] | None = None,
|
|
) -> list[str]:
|
|
"""Registered redirect URIs for every deployed SSO consumer, one per domain.
|
|
|
|
Args:
|
|
domains: app_id -> domain, list of domains, or nested mapping of those.
|
|
applications: merged applications view, used for feature gating and for
|
|
each consumer's ``server.tls.enabled`` override.
|
|
tls_enabled: global TLS default, overridden per consumer by
|
|
``resolve_enabled``; an .onion domain always resolves to plaintext.
|
|
wildcard: suffix appended to every URI.
|
|
features: config paths ORed together to decide whether an app gets a URI.
|
|
dedup: drop repeated URIs, preserving first-seen order.
|
|
deployed: application ids this play deploys (``group_names``); an app
|
|
outside it gets no URI. None registers every app in ``domains``.
|
|
"""
|
|
if not isinstance(domains, dict):
|
|
raise AnsibleError(
|
|
"redirect_uris: 'domains' must be a dict mapping app_id -> domain or list of domains"
|
|
)
|
|
|
|
deployed_ids = None if deployed is None else set(deployed)
|
|
uris: list[str] = []
|
|
for app_id, domain_value in domains.items():
|
|
if deployed_ids is not None and app_id not in deployed_ids:
|
|
continue
|
|
try:
|
|
has_feature = any(
|
|
bool(get(applications, app_id, f, False)) for f in features
|
|
)
|
|
except (AppConfigKeyError, ConfigEntryNotSetError):
|
|
has_feature = False
|
|
if not has_feature:
|
|
continue
|
|
|
|
for domain in _iter_domains(domain_value):
|
|
proto = (
|
|
"https"
|
|
if resolve_enabled(
|
|
applications.get(app_id) or {}, tls_enabled, primary_domain=domain
|
|
)
|
|
else "http"
|
|
)
|
|
try:
|
|
url = get_url({app_id: domain}, app_id, proto)
|
|
except Exception as exc:
|
|
raise AnsibleError(
|
|
f"redirect_uris: get_url failed for app '{app_id}' "
|
|
f"with domain '{domain}': {exc}"
|
|
) from exc
|
|
uris.append(f"{url}{wildcard}")
|
|
|
|
return _stable_dedup(uris) if dedup else uris
|
|
|
|
|
|
class LookupModule(LookupBase):
|
|
"""
|
|
Usage::
|
|
|
|
{{ lookup('redirect_uris') }}
|
|
{{ lookup('redirect_uris', wildcard='/cb') }}
|
|
|
|
Returns the redirect URIs Keycloak must whitelist: one per domain of every
|
|
application this play deploys (``group_names``) whose config enables SSO.
|
|
Each URI's scheme is resolved for the CONSUMER -- its own
|
|
``server.tls.enabled`` over the global ``TLS_ENABLED``, and always
|
|
plaintext for an .onion -- never from Keycloak's own TLS state.
|
|
|
|
Parameters::
|
|
|
|
wildcard: suffix appended to every URI (default '/*')
|
|
features: config paths ORed to gate an application (default services.sso.enabled)
|
|
dedup: drop repeated URIs (default true)
|
|
"""
|
|
|
|
def run(
|
|
self,
|
|
terms: list[Any] | None,
|
|
variables: dict[str, Any] | None = None,
|
|
**kwargs: Any,
|
|
) -> list[list[str]]:
|
|
if terms:
|
|
raise AnsibleError("lookup('redirect_uris') expects no terms.")
|
|
|
|
variables = variables or getattr(self._templar, "available_variables", {}) or {}
|
|
if "TLS_ENABLED" not in variables:
|
|
raise AnsibleError("redirect_uris: TLS_ENABLED is not defined.")
|
|
if "group_names" not in variables:
|
|
raise AnsibleError("redirect_uris: group_names is not defined.")
|
|
|
|
loader = getattr(self, "_loader", None)
|
|
templar = getattr(self, "_templar", None)
|
|
domains = lookup_loader.get("domains", loader=loader, templar=templar).run(
|
|
[], variables=variables
|
|
)[0]
|
|
applications = lookup_loader.get(
|
|
"applications", loader=loader, templar=templar
|
|
).run([], variables=variables)[0]
|
|
|
|
features = kwargs.get("features") or DEFAULT_FEATURES
|
|
return [
|
|
build_redirect_uris(
|
|
domains,
|
|
applications,
|
|
bool(variables["TLS_ENABLED"]),
|
|
wildcard=kwargs.get("wildcard", DEFAULT_WILDCARD),
|
|
features=features,
|
|
dedup=bool(kwargs.get("dedup", True)),
|
|
deployed=variables["group_names"],
|
|
)
|
|
]
|