Files
computer-playbook/utils/install/lint/ansible/collections.py
kevinveenbirkenbachandClaude Opus 5 26e54b8f8e feat(install): pin the collections and stop refetching what is already there
The requirements named seven collections without versions, so every
install re-resolved them against galaxy.ansible.com and --force-with-deps
redownloaded all 37 MB even when nothing had changed. Exact pins make the
installed set checkable, and the check short-circuits the whole roundtrip:
make install-ansible drops from 8m24s to 0.077s, and a Galaxy outage stops
mattering once the pinned set is present.

The git fallback is pinned to the same versions so it installs what the
Galaxy file declares rather than whatever the branches currently hold.
kewlfft.aur pins a commit because its tags carry no galaxy.yml and master
has moved past the released version.

The lint installer kept a second, weaker truth about the same directory,
guessing from directory presence for three hardcoded collections; it now
shares the version-exact check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 20:38:50 +02:00

68 lines
1.9 KiB
Python

"""Install required Ansible collections via ansible-galaxy with retry + git fallback."""
from __future__ import annotations
import random
import subprocess
import time
from pathlib import Path
from utils.cache import PROJECT_ROOT
from utils.install.collections import unsatisfied
from utils.install.primitives import log, warn
_MAX_ATTEMPTS = 5
def _galaxy_install(requirements_file: Path, base_dir: Path) -> bool:
try:
subprocess.run(
[
"ansible-galaxy",
"collection",
"install",
"-r",
str(requirements_file),
"-p",
str(base_dir),
"--force-with-deps",
],
check=True,
)
except (subprocess.CalledProcessError, FileNotFoundError):
return False
return True
def ensure() -> None:
collections_base_dir = Path("~/.ansible/collections").expanduser()
repo_root = Path(PROJECT_ROOT)
req_galaxy = repo_root / "requirements" / "requirements.galaxy.yml"
req_git = repo_root / "requirements" / "requirements.git.yml"
missing = unsatisfied(req_galaxy, collections_base_dir)
if not missing:
return
attempt = 1
while True:
log(
f"Installing missing Ansible collections: {' '.join(missing)} "
f"(attempt {attempt}/{_MAX_ATTEMPTS})"
)
if _galaxy_install(req_galaxy, collections_base_dir):
return
warn(f"Galaxy install failed on attempt {attempt}. Trying git fallback.")
if _galaxy_install(req_git, collections_base_dir):
return
if attempt >= _MAX_ATTEMPTS:
raise RuntimeError("Unable to install required Ansible collections.")
sleep_time = 60 + random.randint(0, 60) # noqa: S311 - jitter, not crypto
warn(f"Retrying collection installation in {sleep_time}s.")
time.sleep(sleep_time)
attempt += 1