From 61fa959d522e245da6a8634dc2f18a7a4a32e352 Mon Sep 17 00:00:00 2001 From: Carsten Date: Tue, 21 Apr 2026 22:35:32 +0200 Subject: [PATCH] feat(agent): system info collector for pveversion/zfs/apt --- .../proxmox_agent/collectors/system_info.ex | 45 +++++++++++++++++++ agent/test/fixtures/system/apt_upgradable.txt | 3 ++ agent/test/fixtures/system/pveversion.txt | 1 + agent/test/fixtures/system/zfs_version.txt | 2 + .../collectors/system_info_test.exs | 38 ++++++++++++++++ 5 files changed, 89 insertions(+) create mode 100644 agent/lib/proxmox_agent/collectors/system_info.ex create mode 100644 agent/test/fixtures/system/apt_upgradable.txt create mode 100644 agent/test/fixtures/system/pveversion.txt create mode 100644 agent/test/fixtures/system/zfs_version.txt create mode 100644 agent/test/proxmox_agent/collectors/system_info_test.exs diff --git a/agent/lib/proxmox_agent/collectors/system_info.ex b/agent/lib/proxmox_agent/collectors/system_info.ex new file mode 100644 index 0000000..de62ca7 --- /dev/null +++ b/agent/lib/proxmox_agent/collectors/system_info.ex @@ -0,0 +1,45 @@ +defmodule ProxmoxAgent.Collectors.SystemInfo do + @moduledoc "Slow-path system metadata: pveversion, zfs version, apt upgradable count." + + @spec collect(keyword()) :: %{ + pve_version: String.t() | nil, + zfs_version: String.t() | nil, + pending_updates: non_neg_integer(), + agent_version: String.t(), + errors: [map()] + } + def collect(opts \\ []) do + runner = Keyword.get(opts, :runner, &ProxmoxAgent.Shell.run/2) + + {pve, e1} = trim_output(runner.("pveversion", []), :pveversion) + {zfs, e2} = trim_output(runner.("zfs", ["--version"]), :zfs_version) + {apt, e3} = runner.("apt", ["list", "--upgradable"]) |> count_upgrades() + + %{ + pve_version: pve, + zfs_version: zfs, + pending_updates: apt, + agent_version: ProxmoxAgent.version(), + errors: Enum.filter([e1, e2, e3], & &1) + } + end + + defp trim_output({:ok, text}, _tag), do: {String.trim(text) |> first_line(), nil} + + defp trim_output({:error, reason}, tag), + do: {nil, %{tag: Atom.to_string(tag), message: inspect(reason)}} + + defp first_line(str), do: str |> String.split("\n", parts: 2) |> hd() + + defp count_upgrades({:ok, text}) do + count = + text + |> String.split("\n", trim: true) + |> Enum.count(&String.contains?(&1, "[upgradable")) + + {count, nil} + end + + defp count_upgrades({:error, reason}), + do: {0, %{tag: "apt_upgradable", message: inspect(reason)}} +end diff --git a/agent/test/fixtures/system/apt_upgradable.txt b/agent/test/fixtures/system/apt_upgradable.txt new file mode 100644 index 0000000..a967a3a --- /dev/null +++ b/agent/test/fixtures/system/apt_upgradable.txt @@ -0,0 +1,3 @@ +Listing... +libssl3/stable 3.0.11-1~deb12u2 amd64 [upgradable from: 3.0.11-1~deb12u1] +openssh-server/stable 1:9.2p1-2+deb12u3 amd64 [upgradable from: 1:9.2p1-2+deb12u2] diff --git a/agent/test/fixtures/system/pveversion.txt b/agent/test/fixtures/system/pveversion.txt new file mode 100644 index 0000000..95f2b17 --- /dev/null +++ b/agent/test/fixtures/system/pveversion.txt @@ -0,0 +1 @@ +pve-manager/8.3.1/abc123 (running kernel: 6.8.12-1-pve) diff --git a/agent/test/fixtures/system/zfs_version.txt b/agent/test/fixtures/system/zfs_version.txt new file mode 100644 index 0000000..60855e0 --- /dev/null +++ b/agent/test/fixtures/system/zfs_version.txt @@ -0,0 +1,2 @@ +zfs-2.3.0-pve2 +zfs-kmod-2.3.0-pve2 diff --git a/agent/test/proxmox_agent/collectors/system_info_test.exs b/agent/test/proxmox_agent/collectors/system_info_test.exs new file mode 100644 index 0000000..54a609f --- /dev/null +++ b/agent/test/proxmox_agent/collectors/system_info_test.exs @@ -0,0 +1,38 @@ +defmodule ProxmoxAgent.Collectors.SystemInfoTest do + use ExUnit.Case, async: true + + alias ProxmoxAgent.Collectors.SystemInfo + + @fixtures Path.expand("../../fixtures/system", __DIR__) + + defp fake_runner do + fn + "pveversion", [] -> {:ok, File.read!(Path.join(@fixtures, "pveversion.txt"))} + "zfs", ["--version"] -> {:ok, File.read!(Path.join(@fixtures, "zfs_version.txt"))} + "apt", ["list", "--upgradable"] -> {:ok, File.read!(Path.join(@fixtures, "apt_upgradable.txt"))} + end + end + + test "collects pveversion, zfs version and pending upgrade count" do + sample = SystemInfo.collect(runner: fake_runner()) + + assert sample.pve_version =~ "pve-manager/8.3.1" + assert sample.zfs_version =~ "2.3.0" + assert sample.pending_updates == 2 + assert sample.errors == [] + end + + test "partial sample when one command fails" do + partial = fn + "pveversion", [] -> {:ok, "pve-manager/8.3.1/abc (running kernel: 6.8.12-1-pve)\n"} + "zfs", ["--version"] -> {:error, {:enoent, "zfs"}} + "apt", ["list", "--upgradable"] -> {:ok, "Listing...\n"} + end + + sample = SystemInfo.collect(runner: partial) + assert sample.pve_version =~ "8.3.1" + assert sample.zfs_version == nil + assert sample.pending_updates == 0 + assert length(sample.errors) == 1 + end +end