|
13 | 13 |
|
14 | 14 | from .. import logger |
15 | 15 |
|
| 16 | +def _capture_packages(pools: list[Pool]) -> dict[Host, set[str]]: |
| 17 | + """Snapshot the installed packages of every host in the pools.""" |
| 18 | + return {h: set(h.packages()) for p in pools for h in p.hosts} |
| 19 | + |
| 20 | +def _filter_packages(pkgs: set[str]) -> set[str]: |
| 21 | + return {p for p in pkgs if not p.startswith("gpg-pubkey-")} |
| 22 | + |
| 23 | +def _format_packages(pkgs: list[str]) -> str: |
| 24 | + return "\n".join(f" - {p}" for p in pkgs) |
| 25 | + |
| 26 | +def _report_updated(before: dict[Host, set[str]], after: dict[Host, set[str]]) -> None: |
| 27 | + """Log a summary of the packages that were updated on each host.""" |
| 28 | + updated = { |
| 29 | + h: _filter_packages(after[h] - pkgs) for h, pkgs in before.items() |
| 30 | + } |
| 31 | + common_updated = set.intersection(*updated.values()) if updated else set() |
| 32 | + |
| 33 | + if not common_updated: |
| 34 | + logger.info("No packages were updated on any host.") |
| 35 | + return |
| 36 | + logger.info( |
| 37 | + f"Updated packages on all hosts ({len(common_updated)}):\n" |
| 38 | + f"{_format_packages(sorted(common_updated))}" |
| 39 | + ) |
| 40 | + for h, pkgs in updated.items(): |
| 41 | + extra = sorted(pkgs - common_updated) |
| 42 | + if extra: |
| 43 | + logger.info( |
| 44 | + f"Additional packages on [{h}] ({len(extra)}):\n" |
| 45 | + f"{_format_packages(extra)}" |
| 46 | + ) |
| 47 | + |
| 48 | +def _check_consistency(packages: dict[Host, set[str]]) -> None: |
| 49 | + """Warn if not all hosts end up with the same set of packages.""" |
| 50 | + common_set = set.intersection(*packages.values()) |
| 51 | + inconsistent = { |
| 52 | + h: _filter_packages(p) - _filter_packages(common_set) |
| 53 | + for h, p in packages.items() if _filter_packages(p) != _filter_packages(common_set) |
| 54 | + } |
| 55 | + if inconsistent: |
| 56 | + lines = [ |
| 57 | + f"Not all hosts have the same set of packages " |
| 58 | + f"(reference: common set of {len(packages)} hosts):" |
| 59 | + ] |
| 60 | + for h, extra_pkgs in inconsistent.items(): |
| 61 | + lines.append(f" [{h}] additional packages:\n{_format_packages(sorted(extra_pkgs))}") |
| 62 | + logger.warning("\n".join(lines)) |
| 63 | + |
16 | 64 | def update_pools(inventory: Inventory) -> None: |
17 | 65 | """Updates hosts in pool(s). |
18 | 66 |
|
@@ -43,6 +91,8 @@ def update_pools(inventory: Inventory) -> None: |
43 | 91 | except NotAMasterHostError: |
44 | 92 | logger.warning(f"[{host}] Skipping: not a master host") |
45 | 93 |
|
| 94 | + before_packages = _capture_packages(pools) |
| 95 | + |
46 | 96 | # update master hosts |
47 | 97 | with ThreadPoolExecutor() as executor: |
48 | 98 | future_masters = {executor.submit( |
@@ -80,6 +130,10 @@ def update_pools(inventory: Inventory) -> None: |
80 | 130 | ) |
81 | 131 | raise exc |
82 | 132 |
|
| 133 | + after_packages = _capture_packages(pools) |
| 134 | + _report_updated(before_packages, after_packages) |
| 135 | + _check_consistency(after_packages) |
| 136 | + |
83 | 137 | # Snapshot creation |
84 | 138 | for hosting_pool, nested in nested_hosts.items(): |
85 | 139 | pool = Pool(hosting_pool) # mandatory for getting an host instance |
|
0 commit comments