mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
101 lines
3.3 KiB
Python
101 lines
3.3 KiB
Python
"""Resolve which role is the active mail provider for a deploy.
|
|
|
|
``MAIL_PROVIDER`` is the operator's config flag and always wins while the role
|
|
it names is part of the deploy. It is a static inventory value, though, so a
|
|
round that deploys only the alternative provider would otherwise resolve to a
|
|
role that is not there: the provider binds no public mail ports, skips
|
|
``sys-ctl-mtn-cert-deploy``, and every consumer resolves its SMTP host against
|
|
a server that was never deployed.
|
|
|
|
Falling back to whichever declaring role IS deployed fixes that, and ordering
|
|
the fallback ``provides:`` before ``covers:`` keeps it single-valued — two
|
|
providers can never both consider themselves active and bind port 25.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from utils.roles.applications.services.registry import (
|
|
build_covered_key_to_role,
|
|
build_role_to_primary_service_key,
|
|
build_service_registry_from_roles_dir,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
MAIL_SERVICE_KEY = "email"
|
|
|
|
|
|
def _registry(roles_dir: Path) -> dict[str, dict[str, Any]]:
|
|
return build_service_registry_from_roles_dir(roles_dir)
|
|
|
|
|
|
def declaring_roles(roles_dir: Path) -> list[str]:
|
|
"""Roles that declare the mail service, ``provides:`` before ``covers:``.
|
|
|
|
Args:
|
|
roles_dir: repository ``roles/`` directory.
|
|
|
|
Returns:
|
|
Role ids in fallback priority order, without duplicates.
|
|
"""
|
|
registry = _registry(roles_dir)
|
|
ordered: list[str] = []
|
|
|
|
for role, primary_key in sorted(
|
|
build_role_to_primary_service_key(registry).items()
|
|
):
|
|
if primary_key == MAIL_SERVICE_KEY and role not in ordered:
|
|
ordered.append(role)
|
|
|
|
covering = build_covered_key_to_role(registry).get(MAIL_SERVICE_KEY, "")
|
|
if covering and covering not in ordered:
|
|
ordered.append(covering)
|
|
|
|
return ordered
|
|
|
|
|
|
def deployed_roles(groups: dict[str, Any] | None) -> list[str]:
|
|
"""Roles with at least one host anywhere in the inventory.
|
|
|
|
Cluster-wide, never per-host: in swarm the provider is manager-pinned, so a
|
|
worker's ``group_names`` lacks it even though the whole cluster relays
|
|
through it. Resolving from ``group_names`` would hand workers a different
|
|
provider than the manager and render their relay config against a server
|
|
they are not supposed to use.
|
|
|
|
Args:
|
|
groups: Ansible's ``groups`` mapping of group name to member hosts.
|
|
|
|
Returns:
|
|
Group names that have members.
|
|
"""
|
|
return [name for name, hosts in (groups or {}).items() if hosts]
|
|
|
|
|
|
def resolve_active_provider(
|
|
configured: str, deployed: list[str] | tuple[str, ...], roles_dir: Path
|
|
) -> str:
|
|
"""The role acting as mail provider for this deploy.
|
|
|
|
Args:
|
|
configured: the ``MAIL_PROVIDER`` value from the inventory.
|
|
deployed: roles present cluster-wide, from :func:`deployed_roles`.
|
|
roles_dir: repository ``roles/`` directory.
|
|
|
|
Returns:
|
|
``configured`` when that role is deployed, otherwise the
|
|
highest-priority declaring role that is; ``configured`` when none is.
|
|
"""
|
|
present = set(deployed or ())
|
|
if configured and configured in present:
|
|
return configured
|
|
|
|
for role in declaring_roles(roles_dir):
|
|
if role in present:
|
|
return role
|
|
|
|
return configured
|