Source code for rmote.tools.facts.collectors.apt

"""Read-only Debian package inventory."""

import os
import shutil
from dataclasses import dataclass

from rmote.process import async_process
from rmote.protocol import Tool


[docs] @dataclass class AptPackage: version: str architecture: str status: str
[docs] @dataclass class AptInfo: available: bool version: str | None = None packages: dict[str, AptPackage] | None = None
[docs] class AptFacts(Tool): """Collect APT version and installed packages under the ``apt`` key. Requires both apt-get and dpkg-query. Missing binaries produce ``available=False`` and None for version/packages. Each package is indexed by dpkg's binary package name (including the architecture qualifier when applicable), retaining version, architecture and the full dpkg status. Removed/config-files-only and incompletely installed packages are excluded. Installed packages on hold are included. Queries do not refresh indexes, contact repositories or acquire a write lock. Each command has a 30-second timeout; errors from an installed utility propagate. ``version`` is the complete apt-get --version output. """ key = "apt" version = 1 result_type = AptInfo
[docs] @staticmethod def packages(output: str) -> dict[str, AptPackage]: """Parse tab-separated dpkg records, preserving multiarch identities.""" result = {} for line in output.splitlines(): name, version, architecture, status = line.split("\t", 3) if status.split()[1:] != ["ok", "installed"]: continue result[name] = AptPackage(version, architecture, status) return result
[docs] @staticmethod async def collect() -> AptInfo: """Return local package state without changing the package database.""" executable = shutil.which("apt-get") query = shutil.which("dpkg-query") result: AptInfo = AptInfo(available=executable is not None and query is not None, version=None, packages=None) if executable is None or query is None: return result options = {**os.environ, "LC_ALL": "C"} version = await async_process( executable, "--version", capture_output=True, text=True, check=True, timeout=30, env=options ) inventory = await async_process( query, "--show", "--showformat=${binary:Package}\t${Version}\t${Architecture}\t${Status}\n", capture_output=True, text=True, check=True, timeout=30, env=options, ) result.version = version.stdout.strip() result.packages = AptFacts.packages(inventory.stdout) return result
__tool_package__ = "rmote.tools.facts"