aboutsummaryrefslogtreecommitdiff
path: root/prometheus_pacman_exporter/__init__.py
diff options
context:
space:
mode:
authorDennis Fink2026-09-27 15:19:59 +0200
committerDennis Fink2026-09-27 15:21:53 +0200
commita9934b8a44ccd6f482e7399f931412e8b43033f4 (patch)
treea07bb151d120161f9f69fcddde9cb9531cc22478 /prometheus_pacman_exporter/__init__.py
parentf898ed214f19155b842a537756a22c6dcc64b379 (diff)
downloadprometheus-pacman-exporter-a9934b8a44ccd6f482e7399f931412e8b43033f4.tar.gz
prometheus-pacman-exporter-a9934b8a44ccd6f482e7399f931412e8b43033f4.zip
refactor(commands): model package queries explicitly
Introduce a PackageQuery type to group each command with its description, metric, and accepted exit codes. Move the static pacman queries into module-level constants and construct only the update and optional AUR queries at runtime. Use immutable command tuples throughout and rename success_codes to accepted_exit_codes to better describe the command execution contract.
Diffstat (limited to '')
-rw-r--r--prometheus_pacman_exporter/__init__.py139
1 files changed, 74 insertions, 65 deletions
diff --git a/prometheus_pacman_exporter/__init__.py b/prometheus_pacman_exporter/__init__.py
index cb19032..73f0945 100644
--- a/prometheus_pacman_exporter/__init__.py
+++ b/prometheus_pacman_exporter/__init__.py
@@ -8,11 +8,11 @@ import os
import shlex
import subprocess
from pathlib import Path
-from typing import cast
+from typing import NamedTuple, cast
import click_extra as click
from cloup.constraints import If, require_all
-from prometheus_client import write_to_textfile
+from prometheus_client import Gauge, write_to_textfile
from . import metrics
from .cli import FlexibleColorOption, debug, error, msg, verbose
@@ -25,6 +25,47 @@ AUTHOR = "Dennis Fink <me+coding@dennisfink.me>"
LICENSE = "BSD-3-Clause"
+class PackageQuery(NamedTuple):
+ command: tuple[str, ...]
+ description: str
+ metric: Gauge
+ accepted_exit_codes: tuple[int, ...] = (0,)
+
+
+PACMAN_BASE_COMMAND = ("pacman", "--query")
+BASE_QUERIES: tuple[PackageQuery, ...] = (
+ PackageQuery(PACMAN_BASE_COMMAND, "installed packages", metrics.INSTALLED_PACKAGES),
+ PackageQuery(
+ (*PACMAN_BASE_COMMAND, "--explicit"),
+ "explicitly installed packages",
+ metrics.EXPLICIT_PACKAGES,
+ ),
+ PackageQuery(
+ (*PACMAN_BASE_COMMAND, "--deps"),
+ "packages installed as dependencies",
+ metrics.DEPENDS_PACKAGES,
+ ),
+ PackageQuery(
+ (*PACMAN_BASE_COMMAND, "--unrequired"),
+ "unrequired packages",
+ metrics.UNREQUIRED_PACKAGES,
+ ),
+ PackageQuery(
+ (*PACMAN_BASE_COMMAND, "--foreign"),
+ "foreign packages",
+ metrics.FOREIGN_PACKAGES,
+ ),
+ PackageQuery(
+ (*PACMAN_BASE_COMMAND, "--native"), "native packages", metrics.NATIVE_PACKAGES
+ ),
+ PackageQuery(
+ (*PACMAN_BASE_COMMAND, "--deps", "--unrequired"),
+ "orphan packages",
+ metrics.ORPHAN_PACKAGES,
+ ),
+)
+
+
def print_version(
ctx: click.Context, param: click.Parameter | None, value: bool
) -> None:
@@ -80,7 +121,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.""")
@click.pass_context
def run_command(
- ctx: click.Context, command: list[str], success_codes: tuple[int, ...] = (0,)
+ ctx: click.Context,
+ command: tuple[str, ...],
+ accepted_exit_codes: tuple[int, ...] = (0,),
) -> subprocess.CompletedProcess[str]:
"""Run a shell command and return the result.
@@ -89,13 +132,13 @@ def run_command(
:param ctx: Click context injected by :func:`click.pass_context`.
:param command: Command and its arguments.
- :param success_codes: Exit codes considered successful.
+ :param accepted_exit_codes: Exit codes considered successful.
:return: Completed process with stdout and stderr captured.
"""
debug("Running command", " ".join(command), err=True)
result = subprocess.run(command, capture_output=True, encoding="utf-8", check=False)
- if result.returncode not in success_codes:
+ if result.returncode not in accepted_exit_codes:
error(
f"Command ({' '.join(command)}) failed with exit code {result.returncode}"
)
@@ -201,75 +244,41 @@ def prometheus_pacman_exporter(
debug(" verbose:", str(verbose_flag), err=True)
debug(" quiet:", str(quiet_flag), err=True)
- pacman_base_command = ["pacman", "--query"]
- queries = [
- (
- [*pacman_base_command],
- "installed packages",
- metrics.INSTALLED_PACKAGES,
- (0,),
- ),
- (
- [*pacman_base_command, "--explicit"],
- "explicitly installed packages",
- metrics.EXPLICIT_PACKAGES,
- (0,),
- ),
- (
- [*pacman_base_command, "--deps"],
- "packages installed as dependencies",
- metrics.DEPENDS_PACKAGES,
- (0,),
- ),
- (
- [*pacman_base_command, "--unrequired"],
- "unrequired packages",
- metrics.UNREQUIRED_PACKAGES,
- (0,),
- ),
- (
- [*pacman_base_command, "--foreign"],
- "foreign packages",
- metrics.FOREIGN_PACKAGES,
- (0,),
- ),
- (
- [*pacman_base_command, "--native"],
- "native packages",
- metrics.NATIVE_PACKAGES,
- (0,),
- ),
- (
- [*pacman_base_command, "--deps", "--unrequired"],
- "orphan packages",
- metrics.ORPHAN_PACKAGES,
- (0,),
- ),
- (
- ["checkupdates"]
- if use_checkupdates_flag
- else [*pacman_base_command, "--upgrades"],
- "updateable packages",
- metrics.UPDATEABLE_PACKAGES,
- (0, 2) if use_checkupdates_flag else (0, 1),
- ),
- ]
+ queries: list[PackageQuery] = [*BASE_QUERIES]
+
+ if use_checkupdates_flag:
+ queries.append(
+ PackageQuery(
+ ("checkupdates",),
+ "updateable packages",
+ metrics.UPDATEABLE_PACKAGES,
+ (0, 2),
+ )
+ )
+ else:
+ queries.append(
+ PackageQuery(
+ (*PACMAN_BASE_COMMAND, "--upgrades"),
+ "updateable packages",
+ metrics.UPDATEABLE_PACKAGES,
+ (0, 1),
+ )
+ )
if aur_flag:
queries.append(
- (
- shlex.split(cast(str, aur_command)),
+ PackageQuery(
+ tuple(shlex.split(cast(str, aur_command))),
"AUR updateable packages",
metrics.AUR_UPDATEABLE_PACKAGES,
- (0,),
)
)
- for command, description, metric, success_codes in queries:
- output = run_command(command, success_codes)
+ for query in queries:
+ output = run_command(query.command, query.accepted_exit_codes)
value = len(output.stdout.splitlines())
- verbose(f"Setting {description}:", str(value), err=True)
- metric.set(value)
+ verbose(f"Setting {query.description}:", str(value), err=True)
+ query.metric.set(value)
metrics_file = textfile_collector_dir / "pacman.prom"
write_to_textfile(str(metrics_file.absolute()), metrics.REGISTRY)