From 555d50bfd10b1ac983aec17cf5b3cdf3a9cb99d7 Mon Sep 17 00:00:00 2001 From: Dennis Fink Date: Sun, 27 Sep 2026 13:14:07 +0200 Subject: fix(commands): classify command failures for retries Allow command invocations to define which child exit codes are considered successful, including checkupdates exit status 2 when no updates are available. Classify unexpected child command failures as exporter exit status 1 so systemd treats them as runtime failures eligible for retry. Keep exit status 2 for CLI usage errors, which are configuration problems and should not be retried. Update the systemd service with RestartPreventExitStatus=2 so only retryable runtime failures trigger the configured restart policy. --- prometheus_pacman_exporter/__init__.py | 40 ++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 12 deletions(-) (limited to 'prometheus_pacman_exporter/__init__.py') diff --git a/prometheus_pacman_exporter/__init__.py b/prometheus_pacman_exporter/__init__.py index 23ce5a2..ac76dc6 100644 --- a/prometheus_pacman_exporter/__init__.py +++ b/prometheus_pacman_exporter/__init__.py @@ -80,7 +80,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.""") @click.pass_context def run_command( - ctx: click.Context, command: list[str] + ctx: click.Context, command: list[str], success_codes: tuple[int, ...] = (0,) ) -> subprocess.CompletedProcess[str]: """Run a shell command and return the result. @@ -89,18 +89,21 @@ 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. :return: Completed process with stdout and stderr captured. """ debug("Running command", " ".join(command), err=True) - try: - return subprocess.run( - command, capture_output=True, encoding="utf-8", check=True + result = subprocess.run(command, capture_output=True, encoding="utf-8", check=False) + + if result.returncode not in success_codes: + error( + f"Command ({' '.join(command)}) failed with exit code {result.returncode}" ) - except subprocess.CalledProcessError as e: - error(f"Command ({' '.join(command)}) failed with exit code {e.returncode}") - if e.stderr: - error("stderr:", e.stderr.strip()) - ctx.exit(e.returncode) + if result.stderr: + error("stderr:", result.stderr.strip()) + ctx.exit(1) + + return result @click.command( @@ -193,36 +196,47 @@ def prometheus_pacman_exporter( pacman_base_command = ["pacman", "--query"] queries = [ - ([*pacman_base_command], "installed packages", metrics.INSTALLED_PACKAGES), + ( + [*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"] @@ -230,6 +244,7 @@ def prometheus_pacman_exporter( else [*pacman_base_command, "--upgrades"], "updateable packages", metrics.UPDATEABLE_PACKAGES, + (0, 2) if use_checkupdates_flag else (0, 1), ), ] @@ -239,11 +254,12 @@ def prometheus_pacman_exporter( shlex.split(cast(str, aur_command)), "AUR updateable packages", metrics.AUR_UPDATEABLE_PACKAGES, + (0,), ) ) - for command, description, metric in queries: - output = run_command(command) + for command, description, metric, success_codes in queries: + output = run_command(command, success_codes) value = len(output.stdout.splitlines()) verbose(f"Setting {description}:", str(value), err=True) metric.set(value) -- cgit v1.3.1