utils.packages package¶
Submodules¶
utils.packages.calls module¶
The vocabulary a package plan is written in.
A ModuleCall is one Ansible module invocation, a
RetryPolicy says how often it may be repeated. Both are plain
data, so a plan can be built and asserted without an Ansible connection.
- utils.packages.calls.AUR_FETCH = RetryPolicy(attempts=6, seconds=60)¶
The AUR is one origin host with no mirror pool behind it, so a flap there lasts minutes rather than the seconds a pool member needs to rotate out; EXTERNAL_FETCH’s ~40s window has been observed losing to a single flap.
- utils.packages.calls.EXTERNAL_FETCH = RetryPolicy(attempts=3, seconds=10)¶
Applies to every call that fetches over the network, the distribution’s own repositories included: their metadata is served by mirror pools that degrade independently of the package manager’s internal retries.
- utils.packages.calls.GENERIC_PACKAGE = 'ansible.builtin.package'¶
ansible.builtin.package ships as a documentation-only stub; its logic lives in Ansible’s action plugin. The action plugin here executes modules directly, so it must swap this marker for the host’s ansible_facts.pkg_mgr.
- class utils.packages.calls.ModuleCall(module: str, args: dict[str, ~typing.Any]=<factory>, become_user: str | None = None, retry: RetryPolicy | None = None)¶
Bases:
objectOne Ansible module invocation the action plugin should execute.
- Parameters:
module – module name, or GENERIC_PACKAGE for the host’s manager.
args – module arguments; a None value is dropped before execution.
become_user – user to escalate to, None for the connection default.
retry – None when the call is run once, else its retry policy.
- become_user: str | None = None¶
- module: str¶
- retry: RetryPolicy | None = None¶
- class utils.packages.calls.RetryPolicy(attempts: int, seconds: int)¶
Bases:
objectHow often a module call may be repeated, and how long between tries.
- Parameters:
attempts – total tries, including the first.
seconds – pause between tries.
- attempts: int¶
- seconds: int¶
- utils.packages.calls.package_call(names: list[str], state: str) ModuleCall¶
Install or remove names through the host’s own package manager.
utils.packages.facts module¶
Read the gathered facts a package plan needs off either fact shape.
utils.packages.outcome module¶
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.
- utils.packages.outcome.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.
- Parameters:
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.
- utils.packages.outcome.failure_message(result: dict[str, Any]) str¶
Describe one failed module call.
- Parameters:
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 onlyfailedandexception, whose event holds the sole account of what went wrong.
utils.packages.plan module¶
Install plans for the package registry.
Turns a resolved PackageSpec into the
ordered module calls that acquire it. Keeping this pure means every
acquisition path - distribution repository, AUR, COPR, PPA, source build -
is unit-testable without an Ansible connection; the action plugin only
executes what it is handed.
- utils.packages.plan.build_plan(spec: PackageSpec, state: str) list[ModuleCall]¶
Return the ordered module calls that bring
specintostate.
utils.packages.registry module¶
Package registry SPOT.
Every installable package is declared exactly once, in one of two places:
the repository-root
meta/packages.ymlholds the shared collection - packages more than one role or an inventory bundle installs;a role’s
roles/<role>/meta/packages.ymlholds what only that role installs.
This module finds those declarations, indexes them by id and resolves one
for a target distribution. The declaration shape itself lives in
utils.packages.schema.
- class utils.packages.registry.Declaration(package_id: str, role: str | None, path: Path, mapping: dict[str, Any])¶
Bases:
objectOne package id, declared either repository-wide or by one role.
- property owner: str¶
Human-readable owner for lint and runtime messages.
- package_id: str¶
- path: Path¶
- role: str | None¶
Whether any role may install this id.
- utils.packages.registry.build_registry(project_root: Path) dict[str, Declaration]¶
Index every declaration by package id.
Raises on a duplicate id: two files declaring the same package is the missing-SPOT condition the uniqueness lint exists to prevent, so the runtime resolver must never silently pick one.
- utils.packages.registry.iter_inventory_package_lists(project_root: Path) Iterator[tuple[Path, list[str]]]¶
Yield
(path, ids)for every inventory that definesPACKAGES.
- utils.packages.registry.iter_package_files(project_root: Path) Iterator[tuple[str | None, Path]]¶
Yield
(role_or_None, path)for every package declaration file.The shared root file comes first and carries
Noneas its role.
- utils.packages.registry.load_declarations(project_root: Path) list[Declaration]¶
Read every
meta/packages.ymlwithout deduplicating.Duplicates are returned as-is so the uniqueness lint can report all offenders instead of failing on the first one.
- utils.packages.registry.project_root_from_env() Path¶
Repository root, overridable for tests via
INFINITO_PROJECT_ROOT.
- utils.packages.registry.resolve(declaration: Declaration, distro: str, family: str | None = None) PackageSpec | None¶
Resolve
distrothrough its distro override, else its os_family.familyis theos_familyansible reports for the host. Callers that have facts (the action plugin) pass it, so distributions outside the CI matrix - Manjaro, Mint, Rocky - still resolve through their family. Callers without facts (lints, the availability test) omit it and the family is derived fromDISTRO_FAMILY, which only covers the default matrix.Returns
Nonewhen neither key is declared, which is what the coverage lint reports as a gap.
utils.packages.schema module¶
Declaration shape for meta/packages.yml.
Owns what a package entry may say and how one distro entry becomes a
concrete PackageSpec. Finding and indexing the declarations is
utils.packages.registry; turning a spec into module calls is
utils.packages.plan.
The same shape serves the repository-root file, which holds the shared collection any role and any inventory bundle may install from, and a role’s own file, which holds what only that role installs.
The file is a mapping of logical package id to a distro mapping. Keys are
ansible_facts['os_family'] values; a lowercase distribution name
(ubuntu, centos, fedora, arch, debian) overrides its
family when that one distribution differs:
nfs-ganesha:
Debian: [nfs-ganesha, nfs-ganesha-vfs]
RedHat: [nfs-ganesha, nfs-ganesha-vfs]
centos:
names: [nfs-ganesha, nfs-ganesha-vfs]
repo:
bootstrap_package: centos-release-nfs-ganesha11
Archlinux:
source: aur
names: [nfs-ganesha]
repo carries the repository the names come from, defined inline on the
entry that needs it: a full definition (name/description/
baseurl/gpgkey) rendered as a yum repo, bootstrap_package for a
repo shipped as a package such as epel-release, enable_existing to
switch on a repo the distro already ships, pacman_section to uncomment
a section of pacman.conf such as multilib, copr/ppa for a
community repository, or managed_externally when the role sets it up
itself.
A bare list is shorthand for {names: [...], source: repo}. An empty
list states that the package is deliberately not installed on that family,
which counts as covered; a missing key is a coverage gap.
virtual: true records that the package manager satisfies the name
through an RPM Provides rather than a package of that name, so an index
lookup by name legitimately finds nothing while the install works.
- utils.packages.schema.DISTRO_FAMILY: dict[str, str] = {'arch': 'Archlinux', 'centos': 'RedHat', 'debian': 'Debian', 'fedora': 'RedHat', 'ubuntu': 'Debian'}¶
The default distro matrix mapped onto the
os_familyansible reports for it, read from themeta/distros.ymlSPOT. Coverage is required for every key.
- utils.packages.schema.INVENTORY_PACKAGES_VAR = 'PACKAGES'¶
Inventory variable listing extra package ids a bundle installs.
- class utils.packages.schema.PackageSpec(names: tuple[str, ...], source: str = 'repo', repo: dict[str, Any] | None = None, virtual: bool = False, build: dict[str, Any] | None = None)¶
Bases:
objectResolved install instruction for one package on one distribution.
- names: tuple[str, ...]¶
- source: str = 'repo'¶
- virtual: bool = False¶
- exception utils.packages.schema.PackagesShapeError¶
Bases:
ValueErrorRaised when a
meta/packages.ymlviolates the declared shape.
- utils.packages.schema.REPO_KEYS: frozenset[str] = frozenset({'baseurl', 'bootstrap_package', 'copr', 'enable_existing', 'managed_externally', 'pacman_section', 'ppa'})¶
A repository definition must declare at least one of these; the rest (
name,description,gpgkey,gpgcheck,state) qualify it.
- utils.packages.schema.SOURCES: tuple[str, ...] = ('repo', 'aur', 'copr', 'ppa', 'build')¶
How the names are acquired.
repois the distribution’s own package manager;aur,coprandppaare the community managers Arch, Fedora and Ubuntu ship alongside it;buildcompiles from source where no manager carries the package at all.
- utils.packages.schema.normalize_entry(package_id: str, key: str, value: Any, path: Path) PackageSpec¶
Turn a bare list or a spec mapping into a
PackageSpec.
utils.packages.sources module¶
One builder per acquisition path.
Each function returns the ordered calls that bring a package in from one
source. utils.packages.plan picks between them; none of them knows
about the others.
- utils.packages.sources.AUR_MAKEPKG_ARGS = '--skipinteg'¶
The AUR PKGBUILDs this project consumes carry a checksum on a git source, which makepkg can only reject; integrity rests on the pinned upstream tag fetched over HTTPS.
- utils.packages.sources.aur_calls(names: list[str]) list[ModuleCall]¶
Prepare the unprivileged build environment, then build through it.
makepkg refuses to run as root, so the toolchain, a build user and a passwordless pacman rule must exist before any AUR package is built.
- utils.packages.sources.build_calls(build: dict[str, Any]) list[ModuleCall]¶
Install the declared dependencies, then run the build command.
- utils.packages.sources.pacman_section_calls(section: str, names: list[str]) list[ModuleCall]¶
Uncomment a pacman.conf section, then install through it.
Arch ships multilib commented out, so the repository holding the 32-bit and Steam packages does not exist until the two lines are enabled. The regex stops matching once uncommented, which is what makes it idempotent.
- utils.packages.sources.repository_calls(repo: dict[str, Any], names: list[str]) list[ModuleCall]¶
Enable the declared repository, then install through it.