mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-09 04:06:42 +00:00
Every stage looped over every role of its group and skipped the ones this host
does not deploy, one task at a time, plus an unconditional handler flush behind
each. Measured on a one-app deploy on 2026-09-23: 132 of 139 entries skipped,
278 task deltas, 41.8 seconds of nothing.
The lookup now takes the `groups` magic variable and drops the entries no host
in the inventory carries, so they never reach the loop.
Which roles get deployed does not change, and that is provable rather than
hoped for: the body deploys a role exactly when application_allowed holds,
which requires the app to be in that host's group_names; if it is, that host is
in groups[app], so groups[app] is non-empty and the lookup keeps the entry. The
filter is therefore strictly weaker than the condition it precedes and can drop
nothing the body would have run.
The filter deliberately reads `groups` and not `group_names`. 8aea5a9b35
measured that a per-host condition above the loop makes ansible insert the
included blocks in host order instead of loop order, after which
svc-storage-nfs-client mounted an export svc-storage-nfs-server had not
deployed yet and the play died on 'mount.nfs4: Connection refused'
(run 31645153977). `groups` is the same mapping on every host, so every host
still walks one identical list, and tasks/utils/setup/role.yml keeps its `when`
exactly where that commit put it. Two of the new cases pin this: a role only
one host carries still enters the loop, and a group_names passed to the lookup
changes nothing.
First written the other way, with group_names in the lookup and the `when`
removed. A single-host deploy cannot show that defect, and it came back green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
57 lines
2.1 KiB
Python
57 lines
2.1 KiB
Python
"""Lookup `group_roles`: the roles of a role group in run_after order.
|
|
|
|
Returns one ``{'role': …, 'app': …}`` mapping per role, so a stage can loop
|
|
over the group instead of including a generated task file per group:
|
|
|
|
loop: "{{ lookup('group_roles', 'web-app') }}"
|
|
|
|
`role` is the directory under ``roles/``, `app` the role's application_id --
|
|
the value the deployment whitelist and `application_allowed` speak in.
|
|
|
|
Passing ``inventory_groups`` (the ``groups`` magic variable) drops the roles no
|
|
host in the inventory carries, so they never enter the loop at all. That list
|
|
is identical on every host, which is the whole point: 8aea5a9b35 measured that
|
|
a per-host condition above the loop makes ansible insert blocks in host order
|
|
rather than loop order, after which svc-storage-nfs-client mounted an export
|
|
svc-storage-nfs-server had not deployed yet. Narrowing to a single host stays
|
|
in the body's ``when``, never here.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from ansible.errors import AnsibleError
|
|
from ansible.plugins.lookup import LookupBase
|
|
|
|
from utils.roles.order import ordered_roles
|
|
|
|
|
|
class LookupModule(LookupBase):
|
|
def run(
|
|
self,
|
|
terms: list[Any] | None,
|
|
variables: dict[str, Any] | None = None,
|
|
**kwargs: Any,
|
|
) -> list[list[dict[str, str]]]:
|
|
if not terms or len(terms) != 1:
|
|
raise AnsibleError(
|
|
"group_roles lookup requires exactly one term: the group"
|
|
)
|
|
|
|
group = str(terms[0]).strip()
|
|
if not group:
|
|
raise AnsibleError("group_roles lookup: the group must not be empty")
|
|
|
|
roles_dir = str(kwargs.get("roles_dir") or Path.cwd() / "roles")
|
|
inventory_groups = kwargs.get("inventory_groups")
|
|
try:
|
|
entries = [dict(entry) for entry in ordered_roles(roles_dir, group)]
|
|
except Exception as exc:
|
|
raise AnsibleError(f"group_roles: {exc}") from exc
|
|
|
|
if inventory_groups is None:
|
|
return [entries]
|
|
return [[entry for entry in entries if inventory_groups.get(entry["app"])]]
|