mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
A module that dies on an unhandled exception carries no msg: the AnsiballZ wrapper returns only failed and exception, whose event holds the sole account of what went wrong. The aggregation defaulted to the literal string package install failed and discarded it, so an AUR fetch that threw in run 30713666732 reported nothing at all and took a full investigation to place. Walk msg, exception.event.msg, exception, module_stderr, stderr instead, and tag every result with the module that produced it. This applies to apt, dnf, pacman and every other module the plugin drives, not only to the AUR. The aur module fetches the RPC record and the snapshot tarball through bare open_url calls with no error handling, so one blip kills a deploy; distribution repositories need none of this because apt, dnf and pacman retry inside their own transport. Give the calls that reach an endpoint of their own a single shared policy. Only the AUR path is declared today; copr, ppa and build carry it against the day they are. Split the result along its responsibilities so no file passes 250 lines: calls.py holds the vocabulary, sources.py one builder per acquisition path, plan.py only the dispatcher, outcome.py the reporting and facts.py the fact shapes. The action plugin is left with executing modules. Tests follow the code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
67 lines
2.1 KiB
Python
67 lines
2.1 KiB
Python
"""Turn the module results of one plan into a single task result.
|
|
|
|
Pure data in, pure data out, so the reporting is testable without an
|
|
Ansible connection.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
|
|
def failure_message(result: dict[str, Any]) -> str:
|
|
"""Describe one failed module call.
|
|
|
|
Args:
|
|
result: the module's own return dict, tagged with the module that
|
|
produced it. A module that dies on an unhandled exception never
|
|
carries ``msg`` -- the AnsiballZ wrapper returns only ``failed``
|
|
and ``exception``, whose event holds the sole account of what
|
|
went wrong.
|
|
"""
|
|
module = result["module"]
|
|
exception = result.get("exception")
|
|
event_msg = getattr(getattr(exception, "event", None), "msg", None)
|
|
for value in (
|
|
result.get("msg"),
|
|
event_msg,
|
|
exception,
|
|
result.get("module_stderr"),
|
|
result.get("stderr"),
|
|
):
|
|
if value:
|
|
return f"{module}: {str(value).strip()}"
|
|
return f"{module}: failed without reporting anything"
|
|
|
|
|
|
def aggregate(
|
|
results: list[dict[str, Any]], skipped: list[str], distribution: str
|
|
) -> dict[str, Any]:
|
|
"""Fold every module result of one package_install task into one result.
|
|
|
|
Args:
|
|
results: the module results, in execution order.
|
|
skipped: package ids that declared nothing for this distribution.
|
|
distribution: the target distribution, named in the skip reason.
|
|
"""
|
|
if not results:
|
|
return {
|
|
"changed": False,
|
|
"skipped": True,
|
|
"skip_reason": (
|
|
f"{', '.join(skipped)} declare nothing to install on {distribution}"
|
|
),
|
|
}
|
|
|
|
failed = [result for result in results if result.get("failed")]
|
|
aggregated: dict[str, Any] = {
|
|
"changed": any(bool(result.get("changed")) for result in results),
|
|
"results": results,
|
|
}
|
|
if skipped:
|
|
aggregated["skipped_ids"] = skipped
|
|
if failed:
|
|
aggregated["failed"] = True
|
|
aggregated["msg"] = "; ".join(failure_message(r) for r in failed)
|
|
return aggregated
|