From c25408651f076c59c13f35f27016461ada8a9093 Mon Sep 17 00:00:00 2001 From: Anand Hegde Date: Fri, 25 Sep 2026 21:43:37 +0530 Subject: [PATCH] feat: show number of pending package updates (#2357) --- agent/agent.go | 7 + agent/package_updates.go | 267 ++++++++++++++++++ agent/package_updates_test.go | 151 ++++++++++ .../apk_alpine320_list_upgradable.txt | 10 + .../package_updates/apt_debian12.txt | 101 +++++++ .../package_updates/apt_ubuntu2204.txt | 130 +++++++++ .../dnf4_rocky9_check_update.txt | 111 ++++++++ .../dnf4_rocky9_check_update_security.txt | 54 ++++ .../dnf5_fedora42_check_update.txt | 20 ++ .../dnf5_fedora42_check_update_security.txt | 5 + .../package_updates/pacman_checkupdates.txt | 4 + .../zypper_leap155_list_patches_security.txt | 15 + .../zypper_leap155_list_updates.txt | 29 ++ .../zypper_leap156_list_updates_none.txt | 3 + internal/entities/system/system.go | 1 + .../systems-table/systems-table-columns.tsx | 49 ++++ internal/site/src/types.d.ts | 2 + 17 files changed, 959 insertions(+) create mode 100644 agent/package_updates.go create mode 100644 agent/package_updates_test.go create mode 100644 agent/test-data/package_updates/apk_alpine320_list_upgradable.txt create mode 100644 agent/test-data/package_updates/apt_debian12.txt create mode 100644 agent/test-data/package_updates/apt_ubuntu2204.txt create mode 100644 agent/test-data/package_updates/dnf4_rocky9_check_update.txt create mode 100644 agent/test-data/package_updates/dnf4_rocky9_check_update_security.txt create mode 100644 agent/test-data/package_updates/dnf5_fedora42_check_update.txt create mode 100644 agent/test-data/package_updates/dnf5_fedora42_check_update_security.txt create mode 100644 agent/test-data/package_updates/pacman_checkupdates.txt create mode 100644 agent/test-data/package_updates/zypper_leap155_list_patches_security.txt create mode 100644 agent/test-data/package_updates/zypper_leap155_list_updates.txt create mode 100644 agent/test-data/package_updates/zypper_leap156_list_updates_none.txt diff --git a/agent/agent.go b/agent/agent.go index 998a32a7..7b75bd16 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -50,6 +50,7 @@ type Agent struct { systemdManager *systemdManager // Manages systemd services monitorManager *MonitorManager // Manages network monitors storagePoolManager *StoragePoolManager // Manages storage pool and dataset data + packageUpdates *packageUpdatesManager // Checks for pending package updates } // NewAgent creates a new agent with the given data directory for persisting data. @@ -155,6 +156,8 @@ func NewAgent(dataDir ...string) (agent *Agent, err error) { slog.Debug("SMART", "err", err) } + agent.packageUpdates = newPackageUpdatesManager() + // initialize GPU manager agent.gpuManager, err = NewGPUManager() if err != nil { @@ -219,6 +222,10 @@ func (a *Agent) gatherStats(options common.DataRequestOptions) *system.CombinedD } } + if a.packageUpdates != nil { + data.Info.PackageUpdates = a.packageUpdates.get(time.Now()) + } + data.Stats.ExtraFs = make(map[string]*system.FsStats) data.Info.ExtraFsPct = make(map[string]float64) for name, stats := range a.fsStats { diff --git a/agent/package_updates.go b/agent/package_updates.go new file mode 100644 index 00000000..2e521651 --- /dev/null +++ b/agent/package_updates.go @@ -0,0 +1,267 @@ +package agent + +import ( + "bufio" + "context" + "errors" + "log/slog" + "os" + "os/exec" + "runtime" + "slices" + "strings" + "sync" + "time" + + "github.com/henrygd/beszel/agent/utils" +) + +const ( + defaultPackageUpdatesInterval = time.Hour + packageUpdatesTimeout = 5 * time.Minute +) + +// packageUpdatesCheck returns [total] or [total, security] pending package updates. +type packageUpdatesCheck func(ctx context.Context) ([]uint16, error) + +// packageUpdatesManager periodically checks the host package manager for pending +// updates in the background and caches the result, so checks never delay metrics. +type packageUpdatesManager struct { + sync.Mutex + check packageUpdatesCheck + interval time.Duration + counts []uint16 + checkedAt time.Time + running bool +} + +// newPackageUpdatesManager returns nil if disabled or no supported package manager +// is found. Agents running in a container are skipped because the container's +// package database is not the host's. +func newPackageUpdatesManager() *packageUpdatesManager { + if runtime.GOOS != "linux" || runningInContainer() { + return nil + } + interval := defaultPackageUpdatesInterval + if env, exists := utils.GetEnv("PACKAGE_UPDATES_INTERVAL"); exists { + duration, err := time.ParseDuration(env) + switch { + case err == nil && duration == 0: + return nil + case err == nil && duration > 0: + interval = duration + default: + slog.Warn("Invalid PACKAGE_UPDATES_INTERVAL", "value", env) + } + } + name, check := detectPackageManager() + if check == nil { + return nil + } + slog.Debug("Package updates", "manager", name, "interval", interval) + return &packageUpdatesManager{check: check, interval: interval} +} + +// get returns the last cached counts and starts a background check if they are stale. +func (pm *packageUpdatesManager) get(now time.Time) []uint16 { + pm.Lock() + defer pm.Unlock() + if !pm.running && (pm.checkedAt.IsZero() || now.Sub(pm.checkedAt) >= pm.interval) { + pm.running = true + go pm.refresh() + } + return pm.counts +} + +func (pm *packageUpdatesManager) refresh() { + ctx, cancel := context.WithTimeout(context.Background(), packageUpdatesTimeout) + defer cancel() + counts, err := pm.check(ctx) + if err != nil { + slog.Debug("Package updates check failed", "err", err) + counts = nil + } + pm.Lock() + pm.counts = counts + pm.checkedAt = time.Now() + pm.running = false + pm.Unlock() +} + +func runningInContainer() bool { + for _, path := range []string{"/.dockerenv", "/run/.containerenv"} { + if _, err := os.Stat(path); err == nil { + return true + } + } + return false +} + +func detectPackageManager() (string, packageUpdatesCheck) { + switch { + case commandExists("apt-get"): + return "apt", checkApt + case commandExists("dnf"): + return "dnf", checkDnf + case commandExists("zypper"): + return "zypper", checkZypper + case commandExists("checkupdates"): + return "pacman", checkPacman + case commandExists("apk"): + return "apk", checkApk + } + return "", nil +} + +func commandExists(name string) bool { + _, err := exec.LookPath(name) + return err == nil +} + +// runPackageCommand runs a read-only package manager command and returns stdout. +// okCodes lists non-zero exit codes that still mean success. +func runPackageCommand(ctx context.Context, okCodes []int, name string, args ...string) (string, error) { + cmd := exec.CommandContext(ctx, name, args...) + cmd.Env = append(os.Environ(), "LC_ALL=C") + out, err := cmd.Output() + if exitErr, ok := errors.AsType[*exec.ExitError](err); ok && slices.Contains(okCodes, exitErr.ExitCode()) { + return string(out), nil + } + return string(out), err +} + +// checkApt simulates a full upgrade against the current package lists. +// It never refreshes the lists; apt-daily or the user does that. +func checkApt(ctx context.Context) ([]uint16, error) { + out, err := runPackageCommand(ctx, nil, "apt-get", "-s", "dist-upgrade") + if err != nil { + return nil, err + } + total, security := parseAptSimulate(out) + return []uint16{total, security}, nil +} + +// checkDnf uses the system metadata cache only (-C), so it never downloads metadata. +func checkDnf(ctx context.Context) ([]uint16, error) { + out, err := runPackageCommand(ctx, []int{100}, "dnf", "-q", "-C", "check-update") + if err != nil { + return nil, err + } + total := parseDnfCheckUpdate(out) + out, err = runPackageCommand(ctx, []int{100}, "dnf", "-q", "-C", "check-update", "--security") + if err != nil { + return []uint16{total}, nil + } + return []uint16{total, parseDnfCheckUpdate(out)}, nil +} + +func checkZypper(ctx context.Context) ([]uint16, error) { + out, err := runPackageCommand(ctx, nil, "zypper", "--no-refresh", "-q", "list-updates") + if err != nil { + return nil, err + } + total := parseZypperTable(out) + out, err = runPackageCommand(ctx, nil, "zypper", "--no-refresh", "-q", "list-patches", "--category", "security") + if err != nil { + return []uint16{total}, nil + } + return []uint16{total, parseZypperTable(out)}, nil +} + +// checkPacman uses checkupdates (pacman-contrib), which syncs a private copy of +// the databases and never touches pacman's own. Exit code 2 means no updates. +func checkPacman(ctx context.Context) ([]uint16, error) { + out, err := runPackageCommand(ctx, []int{2}, "checkupdates") + if err != nil { + return nil, err + } + return []uint16{parsePacmanCheckUpdates(out)}, nil +} + +func checkApk(ctx context.Context) ([]uint16, error) { + out, err := runPackageCommand(ctx, nil, "apk", "--no-network", "-u", "list") + if err != nil { + return nil, err + } + return []uint16{parseApkUpgradable(out)}, nil +} + +// parseAptSimulate counts upgrades in `apt-get -s` output. Upgrade lines look like +// "Inst libc6 [2.35-0ubuntu3.4] (2.35-0ubuntu3.15 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64])". +// New dependencies have no "[old version]" and are not counted. +func parseAptSimulate(out string) (total, security uint16) { + scanner := bufio.NewScanner(strings.NewReader(out)) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) < 4 || fields[0] != "Inst" || !strings.HasPrefix(fields[2], "[") { + continue + } + total++ + start := strings.IndexByte(line, '(') + end := strings.IndexByte(line, ')') + if start >= 0 && end > start && strings.Contains(line[start:end], "-security") { + security++ + } + } + return total, security +} + +// parseDnfCheckUpdate counts "name.arch version repo" lines, stopping at the +// obsoletes section so obsoleted packages are not counted twice. +func parseDnfCheckUpdate(out string) (count uint16) { + scanner := bufio.NewScanner(strings.NewReader(out)) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "Obsoleting") { + break + } + fields := strings.Fields(line) + if len(fields) == 3 && strings.Contains(fields[0], ".") { + count++ + } + } + return count +} + +// parseZypperTable counts the data rows of a zypper table (the lines after the +// "---+---" separator). +func parseZypperTable(out string) (count uint16) { + inTable := false + scanner := bufio.NewScanner(strings.NewReader(out)) + for scanner.Scan() { + line := scanner.Text() + switch { + case !inTable: + inTable = strings.HasPrefix(line, "--") && strings.Contains(line, "-+-") + case strings.Contains(line, "|"): + count++ + default: + return count + } + } + return count +} + +// parsePacmanCheckUpdates counts "name old -> new" lines. +func parsePacmanCheckUpdates(out string) (count uint16) { + scanner := bufio.NewScanner(strings.NewReader(out)) + for scanner.Scan() { + if strings.Contains(scanner.Text(), " -> ") { + count++ + } + } + return count +} + +// parseApkUpgradable counts lines of `apk -u list`, which look like +// "musl-1.2.5-r3 aarch64 {musl} (MIT) [upgradable from: musl-1.2.5-r0]". +func parseApkUpgradable(out string) (count uint16) { + scanner := bufio.NewScanner(strings.NewReader(out)) + for scanner.Scan() { + if strings.Contains(scanner.Text(), "[upgradable from:") { + count++ + } + } + return count +} diff --git a/agent/package_updates_test.go b/agent/package_updates_test.go new file mode 100644 index 00000000..7c4a84f7 --- /dev/null +++ b/agent/package_updates_test.go @@ -0,0 +1,151 @@ +//go:build testing + +package agent + +import ( + "context" + "errors" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func readPackageUpdatesTestData(t *testing.T, name string) string { + t.Helper() + data, err := os.ReadFile(filepath.Join("test-data", "package_updates", name)) + require.NoError(t, err) + return string(data) +} + +// Test data files are real command outputs captured in containers. + +func TestParseAptSimulate(t *testing.T) { + tests := []struct { + file string + total, security uint16 + }{ + {"apt_debian12.txt", 44, 5}, + {"apt_ubuntu2204.txt", 58, 45}, + } + for _, tt := range tests { + t.Run(tt.file, func(t *testing.T) { + total, security := parseAptSimulate(readPackageUpdatesTestData(t, tt.file)) + assert.Equal(t, tt.total, total) + assert.Equal(t, tt.security, security) + }) + } + + t.Run("new dependencies and trailing brackets", func(t *testing.T) { + out := `Inst linux-image-6.8.0-50-generic (6.8.0-50.51 Ubuntu:24.04/noble-updates, Ubuntu:24.04/noble-security [amd64]) +Inst linux-image-generic [6.8.0-49.49] (6.8.0-50.50 Ubuntu:24.04/noble-updates, Ubuntu:24.04/noble-security [amd64]) +Inst gcc-12-base [12.3.0-1ubuntu1~22.04] (12.3.0-1ubuntu1~22.04.3 Ubuntu:22.04/jammy-updates [arm64]) [libstdc++6:arm64 libgcc-s1:arm64 ] +Conf linux-image-generic (6.8.0-50.50 Ubuntu:24.04/noble-updates, Ubuntu:24.04/noble-security [amd64]) +Remv oldpkg [1.0]` + total, security := parseAptSimulate(out) + assert.Equal(t, uint16(2), total) + assert.Equal(t, uint16(1), security) + }) + + t.Run("no updates", func(t *testing.T) { + total, security := parseAptSimulate("Reading package lists...\n0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.\n") + assert.Zero(t, total) + assert.Zero(t, security) + }) +} + +func TestParseDnfCheckUpdate(t *testing.T) { + tests := []struct { + file string + count uint16 + }{ + {"dnf4_rocky9_check_update.txt", 110}, + {"dnf4_rocky9_check_update_security.txt", 53}, + {"dnf5_fedora42_check_update.txt", 20}, + {"dnf5_fedora42_check_update_security.txt", 5}, + } + for _, tt := range tests { + t.Run(tt.file, func(t *testing.T) { + assert.Equal(t, tt.count, parseDnfCheckUpdate(readPackageUpdatesTestData(t, tt.file))) + }) + } + + t.Run("obsoletes section and notices", func(t *testing.T) { + out := ` +kernel.x86_64 5.14.0-503.el9 baseos +Security: kernel-core-5.14.0-427.el9.x86_64 is an installed security update +Obsoleting Packages +grub2-tools.x86_64 1:2.06-80.el9 baseos + grub2-tools.x86_64 1:2.06-77.el9 @baseos +` + assert.Equal(t, uint16(1), parseDnfCheckUpdate(out)) + }) +} + +func TestParseZypperTable(t *testing.T) { + tests := []struct { + file string + count uint16 + }{ + {"zypper_leap155_list_updates.txt", 22}, + {"zypper_leap155_list_patches_security.txt", 4}, + {"zypper_leap156_list_updates_none.txt", 0}, + } + for _, tt := range tests { + t.Run(tt.file, func(t *testing.T) { + assert.Equal(t, tt.count, parseZypperTable(readPackageUpdatesTestData(t, tt.file))) + }) + } +} + +func TestParsePacmanCheckUpdates(t *testing.T) { + assert.Equal(t, uint16(4), parsePacmanCheckUpdates(readPackageUpdatesTestData(t, "pacman_checkupdates.txt"))) + assert.Zero(t, parsePacmanCheckUpdates("")) +} + +func TestParseApkUpgradable(t *testing.T) { + assert.Equal(t, uint16(10), parseApkUpgradable(readPackageUpdatesTestData(t, "apk_alpine320_list_upgradable.txt"))) + assert.Zero(t, parseApkUpgradable("")) +} + +func TestPackageUpdatesManagerCaching(t *testing.T) { + calls := make(chan struct{}, 10) + result := []uint16{3, 1} + var resultErr error + pm := &packageUpdatesManager{ + interval: time.Hour, + check: func(context.Context) ([]uint16, error) { + calls <- struct{}{} + return result, resultErr + }, + } + waitIdle := func() { + require.Eventually(t, func() bool { + pm.Lock() + defer pm.Unlock() + return !pm.running + }, time.Second, time.Millisecond) + } + + now := time.Now() + // first call starts a background check and returns nothing yet + assert.Nil(t, pm.get(now)) + waitIdle() + assert.Len(t, calls, 1) + + // cached result within interval, no new check + assert.Equal(t, []uint16{3, 1}, pm.get(now.Add(time.Minute))) + assert.Len(t, calls, 1) + + // stale after interval: returns cached value and refreshes in background + result, resultErr = nil, errors.New("boom") + assert.Equal(t, []uint16{3, 1}, pm.get(now.Add(2*time.Hour))) + waitIdle() + assert.Len(t, calls, 2) + + // failed check clears the counts + assert.Nil(t, pm.get(time.Now())) +} diff --git a/agent/test-data/package_updates/apk_alpine320_list_upgradable.txt b/agent/test-data/package_updates/apk_alpine320_list_upgradable.txt new file mode 100644 index 00000000..02a16d16 --- /dev/null +++ b/agent/test-data/package_updates/apk_alpine320_list_upgradable.txt @@ -0,0 +1,10 @@ +apk-tools-2.14.4-r1 aarch64 {apk-tools} (GPL-2.0-only) [upgradable from: apk-tools-2.14.4-r0] +busybox-1.36.1-r31 aarch64 {busybox} (GPL-2.0-only) [upgradable from: busybox-1.36.1-r28] +busybox-binsh-1.36.1-r31 aarch64 {busybox} (GPL-2.0-only) [upgradable from: busybox-binsh-1.36.1-r28] +ca-certificates-bundle-20260413-r0 aarch64 {ca-certificates} (MPL-2.0 AND MIT) [upgradable from: ca-certificates-bundle-20240226-r0] +libcrypto3-3.3.7-r0 aarch64 {openssl} (Apache-2.0) [upgradable from: libcrypto3-3.3.0-r2] +libssl3-3.3.7-r0 aarch64 {openssl} (Apache-2.0) [upgradable from: libssl3-3.3.0-r2] +musl-1.2.5-r3 aarch64 {musl} (MIT) [upgradable from: musl-1.2.5-r0] +musl-utils-1.2.5-r3 aarch64 {musl} (MIT AND BSD-2-Clause AND GPL-2.0-or-later) [upgradable from: musl-utils-1.2.5-r0] +ssl_client-1.36.1-r31 aarch64 {busybox} (GPL-2.0-only) [upgradable from: ssl_client-1.36.1-r28] +zlib-1.3.2-r0 aarch64 {zlib} (Zlib) [upgradable from: zlib-1.3.1-r1] diff --git a/agent/test-data/package_updates/apt_debian12.txt b/agent/test-data/package_updates/apt_debian12.txt new file mode 100644 index 00000000..ec03f308 --- /dev/null +++ b/agent/test-data/package_updates/apt_debian12.txt @@ -0,0 +1,101 @@ +Reading package lists... +Building dependency tree... +Reading state information... +Calculating upgrade... +The following packages will be upgraded: + base-files bash bsdutils debian-archive-keyring dpkg e2fsprogs gcc-12-base + gpgv init-system-helpers libblkid1 libc-bin libc6 libcap2 libcom-err2 + libext2fs2 libgcc-s1 libgcrypt20 libgnutls30 liblzma5 libmount1 + libpam-modules libpam-modules-bin libpam-runtime libpam0g libpcre2-8-0 + libseccomp2 libsmartcols1 libss2 libstdc++6 libsystemd0 libtasn1-6 libudev1 + libuuid1 login logsave mount passwd perl-base sed tar tzdata usr-is-merged + util-linux util-linux-extra +44 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. +Inst base-files [12.4+deb12u4] (12.4+deb12u15 Debian:12.15/oldstable [arm64]) +Conf base-files (12.4+deb12u15 Debian:12.15/oldstable [arm64]) +Inst bash [5.2.15-2+b2] (5.2.15-2+b13 Debian:12.15/oldstable [arm64]) +Conf bash (5.2.15-2+b13 Debian:12.15/oldstable [arm64]) +Inst bsdutils [1:2.38.1-5+b1] (1:2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Conf bsdutils (1:2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Inst tar [1.34+dfsg-1.2] (1.34+dfsg-1.2+deb12u1 Debian:12.15/oldstable [arm64]) +Conf tar (1.34+dfsg-1.2+deb12u1 Debian:12.15/oldstable [arm64]) +Inst dpkg [1.21.22] (1.21.23 Debian:12.15/oldstable [arm64]) +Conf dpkg (1.21.23 Debian:12.15/oldstable [arm64]) +Inst login [1:4.13+dfsg1-1+b1] (1:4.13+dfsg1-1+deb12u2 Debian:12.15/oldstable [arm64]) +Conf login (1:4.13+dfsg1-1+deb12u2 Debian:12.15/oldstable [arm64]) +Inst perl-base [5.36.0-7+deb12u1] (5.36.0-7+deb12u3 Debian:12.15/oldstable [arm64]) +Conf perl-base (5.36.0-7+deb12u3 Debian:12.15/oldstable [arm64]) +Inst sed [4.9-1] (4.9-1+deb12u1 Debian:12.15/oldstable [arm64]) +Conf sed (4.9-1+deb12u1 Debian:12.15/oldstable [arm64]) +Inst gcc-12-base [12.2.0-14] (12.2.0-14+deb12u1 Debian:12.15/oldstable [arm64]) [libstdc++6:arm64 libgcc-s1:arm64 ] +Conf gcc-12-base (12.2.0-14+deb12u1 Debian:12.15/oldstable [arm64]) [libstdc++6:arm64 libgcc-s1:arm64 ] +Inst libgcc-s1 [12.2.0-14] (12.2.0-14+deb12u1 Debian:12.15/oldstable [arm64]) [libstdc++6:arm64 ] +Conf libgcc-s1 (12.2.0-14+deb12u1 Debian:12.15/oldstable [arm64]) [libstdc++6:arm64 ] +Inst libstdc++6 [12.2.0-14] (12.2.0-14+deb12u1 Debian:12.15/oldstable [arm64]) +Conf libstdc++6 (12.2.0-14+deb12u1 Debian:12.15/oldstable [arm64]) +Inst libc6 [2.36-9+deb12u3] (2.36-9+deb12u14 Debian:12.15/oldstable [arm64]) +Conf libc6 (2.36-9+deb12u14 Debian:12.15/oldstable [arm64]) +Inst libsmartcols1 [2.38.1-5+b1] (2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Conf libsmartcols1 (2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Inst util-linux-extra [2.38.1-5+b1] (2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Conf util-linux-extra (2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Inst util-linux [2.38.1-5+b1] (2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Conf util-linux (2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Inst usr-is-merged [35] (37~deb12u1 Debian:12.15/oldstable [all]) +Conf usr-is-merged (37~deb12u1 Debian:12.15/oldstable [all]) +Inst init-system-helpers [1.65.2] (1.65.2+deb12u1 Debian:12.15/oldstable [all]) +Conf init-system-helpers (1.65.2+deb12u1 Debian:12.15/oldstable [all]) +Inst libc-bin [2.36-9+deb12u3] (2.36-9+deb12u14 Debian:12.15/oldstable [arm64]) +Conf libc-bin (2.36-9+deb12u14 Debian:12.15/oldstable [arm64]) +Inst libpam0g [1.5.2-6+deb12u1] (1.5.2-6+deb12u2 Debian:12.15/oldstable [arm64]) +Conf libpam0g (1.5.2-6+deb12u2 Debian:12.15/oldstable [arm64]) +Inst libpam-modules-bin [1.5.2-6+deb12u1] (1.5.2-6+deb12u2 Debian:12.15/oldstable [arm64]) [libpam-modules:arm64 on libpam-modules-bin:arm64] [libpam-modules:arm64 ] +Conf libpam-modules-bin (1.5.2-6+deb12u2 Debian:12.15/oldstable [arm64]) [libpam-modules:arm64 ] +Inst libpam-modules [1.5.2-6+deb12u1] (1.5.2-6+deb12u2 Debian:12.15/oldstable [arm64]) +Conf libpam-modules (1.5.2-6+deb12u2 Debian:12.15/oldstable [arm64]) +Inst logsave [1.47.0-2] (1.47.0-2+b2 Debian:12.15/oldstable [arm64]) +Inst libext2fs2 [1.47.0-2] (1.47.0-2+b2 Debian:12.15/oldstable [arm64]) [e2fsprogs:arm64 on libext2fs2:arm64] [e2fsprogs:arm64 ] +Conf libext2fs2 (1.47.0-2+b2 Debian:12.15/oldstable [arm64]) [e2fsprogs:arm64 ] +Inst e2fsprogs [1.47.0-2] (1.47.0-2+b2 Debian:12.15/oldstable [arm64]) +Inst mount [2.38.1-5+b1] (2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Inst libpam-runtime [1.5.2-6+deb12u1] (1.5.2-6+deb12u2 Debian:12.15/oldstable [all]) +Conf libpam-runtime (1.5.2-6+deb12u2 Debian:12.15/oldstable [all]) +Inst passwd [1:4.13+dfsg1-1+b1] (1:4.13+dfsg1-1+deb12u2 Debian:12.15/oldstable [arm64]) +Conf passwd (1:4.13+dfsg1-1+deb12u2 Debian:12.15/oldstable [arm64]) +Inst debian-archive-keyring [2023.3+deb12u1] (2023.3+deb12u2 Debian:12.15/oldstable [all]) +Conf debian-archive-keyring (2023.3+deb12u2 Debian:12.15/oldstable [all]) +Inst libgcrypt20 [1.10.1-3] (1.10.1-3+deb12u1 Debian:12.15/oldstable, Debian-Security:12/oldstable-security [arm64]) +Conf libgcrypt20 (1.10.1-3+deb12u1 Debian:12.15/oldstable, Debian-Security:12/oldstable-security [arm64]) +Inst gpgv [2.2.40-1.1] (2.2.40-1.1+deb12u2 Debian:12.15/oldstable [arm64]) +Conf gpgv (2.2.40-1.1+deb12u2 Debian:12.15/oldstable [arm64]) +Inst libblkid1 [2.38.1-5+b1] (2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Conf libblkid1 (2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Inst libcap2 [1:2.66-4] (1:2.66-4+deb12u3+b1 Debian:12.15/oldstable [arm64]) +Conf libcap2 (1:2.66-4+deb12u3+b1 Debian:12.15/oldstable [arm64]) +Inst libtasn1-6 [4.19.0-2] (4.19.0-2+deb12u1 Debian:12.15/oldstable, Debian-Security:12/oldstable-security [arm64]) +Conf libtasn1-6 (4.19.0-2+deb12u1 Debian:12.15/oldstable, Debian-Security:12/oldstable-security [arm64]) +Inst libgnutls30 [3.7.9-2+deb12u1] (3.7.9-2+deb12u7 Debian:12.15/oldstable, Debian-Security:12/oldstable-security [arm64]) +Conf libgnutls30 (3.7.9-2+deb12u7 Debian:12.15/oldstable, Debian-Security:12/oldstable-security [arm64]) +Inst liblzma5 [5.4.1-0.2] (5.4.1-1+deb12u2 Debian-Security:12/oldstable-security [arm64]) +Conf liblzma5 (5.4.1-1+deb12u2 Debian-Security:12/oldstable-security [arm64]) +Inst libmount1 [2.38.1-5+b1] (2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Conf libmount1 (2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Inst libpcre2-8-0 [10.42-1] (10.42-1+deb12u1 Debian-Security:12/oldstable-security [arm64]) +Conf libpcre2-8-0 (10.42-1+deb12u1 Debian-Security:12/oldstable-security [arm64]) +Inst libseccomp2 [2.5.4-1+b3] (2.5.4-1+deb12u1 Debian:12.15/oldstable [arm64]) +Conf libseccomp2 (2.5.4-1+deb12u1 Debian:12.15/oldstable [arm64]) +Inst libsystemd0 [252.19-1~deb12u1] (252.39-1~deb12u2 Debian:12.15/oldstable [arm64]) +Conf libsystemd0 (252.39-1~deb12u2 Debian:12.15/oldstable [arm64]) +Inst libudev1 [252.19-1~deb12u1] (252.39-1~deb12u2 Debian:12.15/oldstable [arm64]) +Conf libudev1 (252.39-1~deb12u2 Debian:12.15/oldstable [arm64]) +Inst libuuid1 [2.38.1-5+b1] (2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Conf libuuid1 (2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Inst tzdata [2023c-5+deb12u1] (2026b-0+deb12u1 Debian:12.15/oldstable [all]) +Inst libcom-err2 [1.47.0-2] (1.47.0-2+b2 Debian:12.15/oldstable [arm64]) +Inst libss2 [1.47.0-2] (1.47.0-2+b2 Debian:12.15/oldstable [arm64]) +Conf logsave (1.47.0-2+b2 Debian:12.15/oldstable [arm64]) +Conf e2fsprogs (1.47.0-2+b2 Debian:12.15/oldstable [arm64]) +Conf mount (2.38.1-5+deb12u3 Debian:12.15/oldstable [arm64]) +Conf tzdata (2026b-0+deb12u1 Debian:12.15/oldstable [all]) +Conf libcom-err2 (1.47.0-2+b2 Debian:12.15/oldstable [arm64]) +Conf libss2 (1.47.0-2+b2 Debian:12.15/oldstable [arm64]) diff --git a/agent/test-data/package_updates/apt_ubuntu2204.txt b/agent/test-data/package_updates/apt_ubuntu2204.txt new file mode 100644 index 00000000..6c683eb4 --- /dev/null +++ b/agent/test-data/package_updates/apt_ubuntu2204.txt @@ -0,0 +1,130 @@ +Reading package lists... +Building dependency tree... +Reading state information... +Calculating upgrade... +The following packages will be upgraded: + apt base-files bash bsdutils coreutils diffutils dpkg e2fsprogs gcc-12-base + gpgv gzip libapt-pkg6.0 libattr1 libblkid1 libbz2-1.0 libc-bin libc6 libcap2 + libcom-err2 libext2fs2 libgcc-s1 libgcrypt20 libgnutls30 libgssapi-krb5-2 + libk5crypto3 libkrb5-3 libkrb5support0 liblzma5 libmount1 libncurses6 + libncursesw6 libp11-kit0 libpam-modules libpam-modules-bin libpam-runtime + libpam0g libprocps8 libseccomp2 libsmartcols1 libss2 libssl3 libstdc++6 + libsystemd0 libtasn1-6 libtinfo6 libudev1 libuuid1 login logsave mount + ncurses-base ncurses-bin passwd perl-base procps sed tar util-linux +58 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. +Inst gcc-12-base [12.3.0-1ubuntu1~22.04] (12.3.0-1ubuntu1~22.04.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) [libstdc++6:arm64 libgcc-s1:arm64 ] +Conf gcc-12-base (12.3.0-1ubuntu1~22.04.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) [libstdc++6:arm64 libgcc-s1:arm64 ] +Inst libgcc-s1 [12.3.0-1ubuntu1~22.04] (12.3.0-1ubuntu1~22.04.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) [libstdc++6:arm64 ] +Conf libgcc-s1 (12.3.0-1ubuntu1~22.04.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) [libstdc++6:arm64 ] +Inst libstdc++6 [12.3.0-1ubuntu1~22.04] (12.3.0-1ubuntu1~22.04.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libstdc++6 (12.3.0-1ubuntu1~22.04.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libc6 [2.35-0ubuntu3.4] (2.35-0ubuntu3.15 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libc6 (2.35-0ubuntu3.15 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst base-files [12ubuntu4.4] (12ubuntu4.7 Ubuntu:22.04/jammy-updates [arm64]) +Conf base-files (12ubuntu4.7 Ubuntu:22.04/jammy-updates [arm64]) +Inst bash [5.1-6ubuntu1] (5.1-6ubuntu1.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf bash (5.1-6ubuntu1.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst bsdutils [1:2.37.2-4ubuntu3] (1:2.37.2-4ubuntu3.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf bsdutils (1:2.37.2-4ubuntu3.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst coreutils [8.32-4.1ubuntu1] (8.32-4.1ubuntu1.4 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf coreutils (8.32-4.1ubuntu1.4 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst diffutils [1:3.8-0ubuntu2] (1:3.8-0ubuntu2.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf diffutils (1:3.8-0ubuntu2.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libbz2-1.0 [1.0.8-5build1] (1.0.8-5ubuntu0.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libbz2-1.0 (1.0.8-5ubuntu0.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libgcrypt20 [1.9.4-3ubuntu3] (1.9.4-3ubuntu3.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libgcrypt20 (1.9.4-3ubuntu3.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst liblzma5 [5.2.5-2ubuntu1] (5.2.5-2ubuntu1.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf liblzma5 (5.2.5-2ubuntu1.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libsystemd0 [249.11-0ubuntu3.10] (249.11-0ubuntu3.22 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libsystemd0 (249.11-0ubuntu3.22 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libudev1 [249.11-0ubuntu3.10] (249.11-0ubuntu3.22 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libudev1 (249.11-0ubuntu3.22 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libapt-pkg6.0 [2.4.10] (2.4.14 Ubuntu:22.04/jammy-updates [arm64]) +Conf libapt-pkg6.0 (2.4.14 Ubuntu:22.04/jammy-updates [arm64]) +Inst tar [1.34+dfsg-1ubuntu0.1.22.04.1] (1.34+dfsg-1ubuntu0.1.22.04.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf tar (1.34+dfsg-1ubuntu0.1.22.04.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst dpkg [1.21.1ubuntu2.2] (1.21.1ubuntu2.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf dpkg (1.21.1ubuntu2.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst gzip [1.10-4ubuntu4.1] (1.10-4ubuntu4.2 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf gzip (1.10-4ubuntu4.2 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst login [1:4.8.1-2ubuntu2.1] (1:4.8.1-2ubuntu2.2 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf login (1:4.8.1-2ubuntu2.2 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst ncurses-bin [6.3-2ubuntu0.1] (6.3-2ubuntu0.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf ncurses-bin (6.3-2ubuntu0.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst perl-base [5.34.0-3ubuntu1.2] (5.34.0-3ubuntu1.9 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf perl-base (5.34.0-3ubuntu1.9 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst sed [4.8-1ubuntu2] (4.8-1ubuntu2.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf sed (4.8-1ubuntu2.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst util-linux [2.37.2-4ubuntu3] (2.37.2-4ubuntu3.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf util-linux (2.37.2-4ubuntu3.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libc-bin [2.35-0ubuntu3.4] (2.35-0ubuntu3.15 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libc-bin (2.35-0ubuntu3.15 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst ncurses-base [6.3-2ubuntu0.1] (6.3-2ubuntu0.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [all]) +Conf ncurses-base (6.3-2ubuntu0.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [all]) +Inst gpgv [2.2.27-3ubuntu2.1] (2.2.27-3ubuntu2.5 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf gpgv (2.2.27-3ubuntu2.5 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libp11-kit0 [0.24.0-6build1] (0.24.0-6ubuntu0.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libp11-kit0 (0.24.0-6ubuntu0.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libtasn1-6 [4.18.0-4build1] (4.18.0-4ubuntu0.2 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libtasn1-6 (4.18.0-4ubuntu0.2 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libgnutls30 [3.7.3-4ubuntu1.2] (3.7.3-4ubuntu1.9 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libgnutls30 (3.7.3-4ubuntu1.9 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libseccomp2 [2.5.3-2ubuntu2] (2.5.3-2ubuntu3~22.04.1 Ubuntu:22.04/jammy-updates [arm64]) +Conf libseccomp2 (2.5.3-2ubuntu3~22.04.1 Ubuntu:22.04/jammy-updates [arm64]) +Inst apt [2.4.10] (2.4.14 Ubuntu:22.04/jammy-updates [arm64]) +Conf apt (2.4.14 Ubuntu:22.04/jammy-updates [arm64]) +Inst libpam0g [1.4.0-11ubuntu2.3] (1.4.0-11ubuntu2.8 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libpam0g (1.4.0-11ubuntu2.8 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libpam-modules-bin [1.4.0-11ubuntu2.3] (1.4.0-11ubuntu2.8 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) [libpam-modules:arm64 on libpam-modules-bin:arm64] [libpam-modules:arm64 ] +Conf libpam-modules-bin (1.4.0-11ubuntu2.8 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) [libpam-modules:arm64 ] +Inst libpam-modules [1.4.0-11ubuntu2.3] (1.4.0-11ubuntu2.8 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libpam-modules (1.4.0-11ubuntu2.8 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst logsave [1.46.5-2ubuntu1.1] (1.46.5-2ubuntu1.2 Ubuntu:22.04/jammy-updates [arm64]) +Inst libext2fs2 [1.46.5-2ubuntu1.1] (1.46.5-2ubuntu1.2 Ubuntu:22.04/jammy-updates [arm64]) [e2fsprogs:arm64 on libext2fs2:arm64] [e2fsprogs:arm64 ] +Conf libext2fs2 (1.46.5-2ubuntu1.2 Ubuntu:22.04/jammy-updates [arm64]) [e2fsprogs:arm64 ] +Inst e2fsprogs [1.46.5-2ubuntu1.1] (1.46.5-2ubuntu1.2 Ubuntu:22.04/jammy-updates [arm64]) +Inst mount [2.37.2-4ubuntu3] (2.37.2-4ubuntu3.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libattr1 [1:2.5.1-1build1] (1:2.5.1-1ubuntu0.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libattr1 (1:2.5.1-1ubuntu0.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libblkid1 [2.37.2-4ubuntu3] (2.37.2-4ubuntu3.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libblkid1 (2.37.2-4ubuntu3.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libcap2 [1:2.44-1ubuntu0.22.04.1] (1:2.44-1ubuntu0.22.04.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libcap2 (1:2.44-1ubuntu0.22.04.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libcom-err2 [1.46.5-2ubuntu1.1] (1.46.5-2ubuntu1.2 Ubuntu:22.04/jammy-updates [arm64]) +Conf libcom-err2 (1.46.5-2ubuntu1.2 Ubuntu:22.04/jammy-updates [arm64]) +Inst libk5crypto3 [1.19.2-2ubuntu0.2] (1.19.2-2ubuntu0.10 Ubuntu:22.04/jammy-updates [arm64]) +Conf libk5crypto3 (1.19.2-2ubuntu0.10 Ubuntu:22.04/jammy-updates [arm64]) +Inst libkrb5support0 [1.19.2-2ubuntu0.2] (1.19.2-2ubuntu0.10 Ubuntu:22.04/jammy-updates [arm64]) [libkrb5-3:arm64 ] +Conf libkrb5support0 (1.19.2-2ubuntu0.10 Ubuntu:22.04/jammy-updates [arm64]) [libkrb5-3:arm64 ] +Inst libkrb5-3 [1.19.2-2ubuntu0.2] (1.19.2-2ubuntu0.10 Ubuntu:22.04/jammy-updates [arm64]) [libgssapi-krb5-2:arm64 ] +Conf libkrb5-3 (1.19.2-2ubuntu0.10 Ubuntu:22.04/jammy-updates [arm64]) [libgssapi-krb5-2:arm64 ] +Inst libgssapi-krb5-2 [1.19.2-2ubuntu0.2] (1.19.2-2ubuntu0.10 Ubuntu:22.04/jammy-updates [arm64]) +Conf libgssapi-krb5-2 (1.19.2-2ubuntu0.10 Ubuntu:22.04/jammy-updates [arm64]) +Inst libssl3 [3.0.2-0ubuntu1.10] (3.0.2-0ubuntu1.29 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libssl3 (3.0.2-0ubuntu1.29 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libmount1 [2.37.2-4ubuntu3] (2.37.2-4ubuntu3.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libmount1 (2.37.2-4ubuntu3.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libpam-runtime [1.4.0-11ubuntu2.3] (1.4.0-11ubuntu2.8 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [all]) +Conf libpam-runtime (1.4.0-11ubuntu2.8 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [all]) +Inst libsmartcols1 [2.37.2-4ubuntu3] (2.37.2-4ubuntu3.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libsmartcols1 (2.37.2-4ubuntu3.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libncurses6 [6.3-2ubuntu0.1] (6.3-2ubuntu0.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) [] +Inst libncursesw6 [6.3-2ubuntu0.1] (6.3-2ubuntu0.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) [] +Inst libtinfo6 [6.3-2ubuntu0.1] (6.3-2ubuntu0.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libtinfo6 (6.3-2ubuntu0.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libuuid1 [2.37.2-4ubuntu3] (2.37.2-4ubuntu3.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libuuid1 (2.37.2-4ubuntu3.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst passwd [1:4.8.1-2ubuntu2.1] (1:4.8.1-2ubuntu2.2 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf passwd (1:4.8.1-2ubuntu2.2 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libprocps8 [2:3.3.17-6ubuntu2] (2:3.3.17-6ubuntu2.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Inst libss2 [1.46.5-2ubuntu1.1] (1.46.5-2ubuntu1.2 Ubuntu:22.04/jammy-updates [arm64]) +Inst procps [2:3.3.17-6ubuntu2] (2:3.3.17-6ubuntu2.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf logsave (1.46.5-2ubuntu1.2 Ubuntu:22.04/jammy-updates [arm64]) +Conf e2fsprogs (1.46.5-2ubuntu1.2 Ubuntu:22.04/jammy-updates [arm64]) +Conf mount (2.37.2-4ubuntu3.6 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libncurses6 (6.3-2ubuntu0.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libncursesw6 (6.3-2ubuntu0.3 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libprocps8 (2:3.3.17-6ubuntu2.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) +Conf libss2 (1.46.5-2ubuntu1.2 Ubuntu:22.04/jammy-updates [arm64]) +Conf procps (2:3.3.17-6ubuntu2.1 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [arm64]) diff --git a/agent/test-data/package_updates/dnf4_rocky9_check_update.txt b/agent/test-data/package_updates/dnf4_rocky9_check_update.txt new file mode 100644 index 00000000..260e457f --- /dev/null +++ b/agent/test-data/package_updates/dnf4_rocky9_check_update.txt @@ -0,0 +1,111 @@ + +alternatives.aarch64 1.24-2.el9 baseos +audit-libs.aarch64 3.1.5-8.el9 baseos +basesystem.noarch 11-13.el9.0.1 baseos +bash.aarch64 5.1.8-9.el9 baseos +binutils.aarch64 2.35.2-72.el9 baseos +binutils-gold.aarch64 2.35.2-72.el9 baseos +bzip2-libs.aarch64 1.0.8-11.el9 baseos +ca-certificates.noarch 2025.2.80_v9.0.305-91.el9 baseos +coreutils-single.aarch64 8.32-41.el9_8.1 baseos +cracklib.aarch64 2.9.6-28.el9 baseos +cracklib-dicts.aarch64 2.9.6-28.el9 baseos +crypto-policies.noarch 20260224-1.gitea0f072.el9 baseos +crypto-policies-scripts.noarch 20260224-1.gitea0f072.el9 baseos +curl-minimal.aarch64 7.76.1-40.el9_8.5 baseos +cyrus-sasl-lib.aarch64 2.1.27-22.el9_7 baseos +dnf.noarch 4.14.0-34.el9_8.rocky.0.1 baseos +dnf-data.noarch 4.14.0-34.el9_8.rocky.0.1 baseos +elfutils-debuginfod-client.aarch64 0.194-1.el9.rocky.0.1 baseos +elfutils-default-yama-scope.noarch 0.194-1.el9.rocky.0.1 baseos +elfutils-libelf.aarch64 0.194-1.el9.rocky.0.1 baseos +elfutils-libs.aarch64 0.194-1.el9.rocky.0.1 baseos +expat.aarch64 2.5.0-6.el9_8.3 baseos +file-libs.aarch64 5.39-17.el9 baseos +filesystem.aarch64 3.16-5.el9 baseos +findutils.aarch64 1:4.8.0-7.el9 baseos +gdbm-libs.aarch64 1:1.23-1.el9 baseos +glib2.aarch64 2.68.4-19.el9_8.10 baseos +glibc.aarch64 2.34-275.el9_8 baseos +glibc-common.aarch64 2.34-275.el9_8 baseos +glibc-minimal-langpack.aarch64 2.34-275.el9_8 baseos +gnupg2.aarch64 2.3.3-5.el9_7 baseos +gnutls.aarch64 3.8.10-8.el9_8 baseos +gzip.aarch64 1.12-2.el9_8 baseos +ima-evm-utils.aarch64 1.6.2-2.el9.rocky.0.2 baseos +krb5-libs.aarch64 1.21.1-10.el9_8 baseos +less.aarch64 590-6.el9 baseos +libacl.aarch64 2.4.0-1.el9_8 baseos +libarchive.aarch64 3.5.3-11.el9_8 baseos +libatomic.aarch64 11.5.0-14.el9 baseos +libattr.aarch64 2.6.0-1.el9_8 baseos +libblkid.aarch64 2.37.4-25.el9 baseos +libcap.aarch64 2.48-10.el9_7.1 baseos +libcom_err.aarch64 1.46.5-8.el9 baseos +libcurl-minimal.aarch64 7.76.1-40.el9_8.5 baseos +libdb.aarch64 5.3.28-57.el9_6 baseos +libdnf.aarch64 0.69.0-18.el9.rocky.0.1 baseos +libeconf.aarch64 0.4.1-7.el9_8 baseos +libevent.aarch64 2.1.13-1.el9_8 baseos +libfdisk.aarch64 2.37.4-25.el9 baseos +libgcc.aarch64 11.5.0-14.el9 baseos +libgcrypt.aarch64 1.10.0-13.el9_8 baseos +libgomp.aarch64 11.5.0-14.el9 baseos +libksba.aarch64 1.5.1-7.el9 baseos +libmount.aarch64 2.37.4-25.el9 baseos +libnghttp2.aarch64 1.43.0-6.el9_8.2 baseos +librepo.aarch64 1.19.0-1.el9 baseos +libselinux.aarch64 3.6-3.el9 baseos +libsemanage.aarch64 3.6-5.el9_6 baseos +libsepol.aarch64 3.6-3.el9 baseos +libsmartcols.aarch64 2.37.4-25.el9 baseos +libsolv.aarch64 0.7.24-6.el9_8 baseos +libstdc++.aarch64 11.5.0-14.el9 baseos +libtasn1.aarch64 4.16.0-10.el9_8 baseos +libusbx.aarch64 1.0.30-1.el9_8 baseos +libuser.aarch64 0.63-17.el9 baseos +libuuid.aarch64 2.37.4-25.el9 baseos +libxml2.aarch64 2.9.13-14.el9_8.4 baseos +libzstd.aarch64 1.5.5-1.el9 baseos +mpfr.aarch64 4.1.0-10.el9 baseos +ncurses-base.noarch 6.2-12.20210508.el9 baseos +ncurses-libs.aarch64 6.2-12.20210508.el9 baseos +nettle.aarch64 3.10.1-1.el9 baseos +openldap.aarch64 2.6.8-4.el9.0.1 baseos +openssl.aarch64 1:3.5.8-1.el9_8 baseos +openssl-libs.aarch64 1:3.5.8-1.el9_8 baseos +p11-kit.aarch64 0.26.4-1.el9_8 baseos +p11-kit-trust.aarch64 0.26.4-1.el9_8 baseos +pam.aarch64 1.5.1-28.el9_8.1 baseos +pcre.aarch64 8.44-4.el9 baseos +pcre2.aarch64 10.40-6.el9 baseos +pcre2-syntax.noarch 10.40-6.el9 baseos +python3.aarch64 3.9.25-7.el9_8.3 baseos +python3-dnf.noarch 4.14.0-34.el9_8.rocky.0.1 baseos +python3-hawkey.aarch64 0.69.0-18.el9.rocky.0.1 baseos +python3-libdnf.aarch64 0.69.0-18.el9.rocky.0.1 baseos +python3-libs.aarch64 3.9.25-7.el9_8.3 baseos +python3-pip-wheel.noarch 21.3.1-2.el9_8.rocky.0.1 baseos +python3-rpm.aarch64 4.16.1.3-40.el9 baseos +python3-setuptools-wheel.noarch 53.0.0-15.el9 baseos +rocky-gpg-keys.noarch 9.8-1.2.el9 baseos +rocky-release.noarch 9.8-1.2.el9 baseos +rocky-repos.noarch 9.8-1.2.el9 baseos +rootfiles.noarch 8.1-35.el9 baseos +rpm.aarch64 4.16.1.3-40.el9 baseos +rpm-build-libs.aarch64 4.16.1.3-40.el9 baseos +rpm-libs.aarch64 4.16.1.3-40.el9 baseos +rpm-sign-libs.aarch64 4.16.1.3-40.el9 baseos +sed.aarch64 4.8-10.el9_8 baseos +setup.noarch 2.13.7-10.el9 baseos +shadow-utils.aarch64 2:4.9-16.el9 baseos +sqlite-libs.aarch64 3.34.1-11.el9_8 baseos +systemd-libs.aarch64 252-67.el9_8.6.rocky.0.1 baseos +tar.aarch64 2:1.34-13.el9_8 baseos +tpm2-tss.aarch64 3.2.3-1.el9 baseos +tzdata.noarch 2026c-1.el9_8 baseos +usermode.aarch64 1.114-7.el9 baseos +util-linux.aarch64 2.37.4-25.el9 baseos +util-linux-core.aarch64 2.37.4-25.el9 baseos +vim-minimal.aarch64 2:8.2.2637-26.el9_8.21 baseos +yum.noarch 4.14.0-34.el9_8.rocky.0.1 baseos diff --git a/agent/test-data/package_updates/dnf4_rocky9_check_update_security.txt b/agent/test-data/package_updates/dnf4_rocky9_check_update_security.txt new file mode 100644 index 00000000..7be87a69 --- /dev/null +++ b/agent/test-data/package_updates/dnf4_rocky9_check_update_security.txt @@ -0,0 +1,54 @@ + +binutils.aarch64 2.35.2-72.el9 baseos +binutils-gold.aarch64 2.35.2-72.el9 baseos +bzip2-libs.aarch64 1.0.8-11.el9 baseos +coreutils-single.aarch64 8.32-41.el9_8.1 baseos +curl-minimal.aarch64 7.76.1-40.el9_8.5 baseos +expat.aarch64 2.5.0-6.el9_8.3 baseos +file-libs.aarch64 5.39-17.el9 baseos +glib2.aarch64 2.68.4-19.el9_8.10 baseos +glibc.aarch64 2.34-275.el9_8 baseos +glibc-common.aarch64 2.34-275.el9_8 baseos +glibc-minimal-langpack.aarch64 2.34-275.el9_8 baseos +gnupg2.aarch64 2.3.3-5.el9_7 baseos +gnutls.aarch64 3.8.10-8.el9_8 baseos +gzip.aarch64 1.12-2.el9_8 baseos +krb5-libs.aarch64 1.21.1-10.el9_8 baseos +less.aarch64 590-6.el9 baseos +libacl.aarch64 2.4.0-1.el9_8 baseos +libarchive.aarch64 3.5.3-11.el9_8 baseos +libatomic.aarch64 11.5.0-14.el9 baseos +libattr.aarch64 2.6.0-1.el9_8 baseos +libblkid.aarch64 2.37.4-25.el9 baseos +libcap.aarch64 2.48-10.el9_7.1 baseos +libcurl-minimal.aarch64 7.76.1-40.el9_8.5 baseos +libevent.aarch64 2.1.13-1.el9_8 baseos +libfdisk.aarch64 2.37.4-25.el9 baseos +libgcc.aarch64 11.5.0-14.el9 baseos +libgcrypt.aarch64 1.10.0-13.el9_8 baseos +libgomp.aarch64 11.5.0-14.el9 baseos +libmount.aarch64 2.37.4-25.el9 baseos +libnghttp2.aarch64 1.43.0-6.el9_8.2 baseos +libsmartcols.aarch64 2.37.4-25.el9 baseos +libsolv.aarch64 0.7.24-6.el9_8 baseos +libstdc++.aarch64 11.5.0-14.el9 baseos +libtasn1.aarch64 4.16.0-10.el9_8 baseos +libuuid.aarch64 2.37.4-25.el9 baseos +libxml2.aarch64 2.9.13-14.el9_8.4 baseos +ncurses-base.noarch 6.2-12.20210508.el9 baseos +ncurses-libs.aarch64 6.2-12.20210508.el9 baseos +openssl.aarch64 1:3.5.8-1.el9_8 baseos +openssl-libs.aarch64 1:3.5.8-1.el9_8 baseos +p11-kit.aarch64 0.26.4-1.el9_8 baseos +p11-kit-trust.aarch64 0.26.4-1.el9_8 baseos +pam.aarch64 1.5.1-28.el9_8.1 baseos +python3.aarch64 3.9.25-7.el9_8.3 baseos +python3-libs.aarch64 3.9.25-7.el9_8.3 baseos +python3-setuptools-wheel.noarch 53.0.0-15.el9 baseos +shadow-utils.aarch64 2:4.9-16.el9 baseos +sqlite-libs.aarch64 3.34.1-11.el9_8 baseos +systemd-libs.aarch64 252-67.el9_8.6.rocky.0.1 baseos +tar.aarch64 2:1.34-13.el9_8 baseos +util-linux.aarch64 2.37.4-25.el9 baseos +util-linux-core.aarch64 2.37.4-25.el9 baseos +vim-minimal.aarch64 2:8.2.2637-26.el9_8.21 baseos diff --git a/agent/test-data/package_updates/dnf5_fedora42_check_update.txt b/agent/test-data/package_updates/dnf5_fedora42_check_update.txt new file mode 100644 index 00000000..562c3265 --- /dev/null +++ b/agent/test-data/package_updates/dnf5_fedora42_check_update.txt @@ -0,0 +1,20 @@ +dnf5.aarch64 5.2.18.0-3.fc42 updates +dnf5-plugins.aarch64 5.2.18.0-3.fc42 updates +elfutils-default-yama-scope.noarch 0.195-1.fc42 updates +elfutils-libelf.aarch64 0.195-1.fc42 updates +elfutils-libs.aarch64 0.195-1.fc42 updates +fedora-release-common.noarch 42-31 updates +fedora-release-container.noarch 42-31 updates +fedora-release-identity-container.noarch 42-31 updates +glibc.aarch64 2.41-18.fc42 updates +glibc-common.aarch64 2.41-18.fc42 updates +glibc-minimal-langpack.aarch64 2.41-18.fc42 updates +krb5-libs.aarch64 1.21.3-7.fc42 updates +libdnf5.aarch64 5.2.18.0-3.fc42 updates +libdnf5-cli.aarch64 5.2.18.0-3.fc42 updates +libsolv.aarch64 0.7.37-2.fc42 updates +openssl-libs.aarch64 1:3.2.6-4.fc42 updates +rpm-sequoia.aarch64 1.10.2-2.fc42 updates +tzdata.noarch 2026b-1.fc42 updates +vim-data.noarch 2:9.2.390-1.fc42 updates +vim-minimal.aarch64 2:9.2.390-1.fc42 updates diff --git a/agent/test-data/package_updates/dnf5_fedora42_check_update_security.txt b/agent/test-data/package_updates/dnf5_fedora42_check_update_security.txt new file mode 100644 index 00000000..a2bf3d16 --- /dev/null +++ b/agent/test-data/package_updates/dnf5_fedora42_check_update_security.txt @@ -0,0 +1,5 @@ +krb5-libs.aarch64 1.21.3-7.fc42 updates +openssl-libs.aarch64 1:3.2.6-4.fc42 updates +rpm-sequoia.aarch64 1.10.2-2.fc42 updates +vim-data.noarch 2:9.2.390-1.fc42 updates +vim-minimal.aarch64 2:9.2.390-1.fc42 updates diff --git a/agent/test-data/package_updates/pacman_checkupdates.txt b/agent/test-data/package_updates/pacman_checkupdates.txt new file mode 100644 index 00000000..8a3ccd92 --- /dev/null +++ b/agent/test-data/package_updates/pacman_checkupdates.txt @@ -0,0 +1,4 @@ +libpcap 1.10.7-1 -> 1.11.0-1 +libsecret 0.21.7-1 -> 0.21.8.2-1 +libtirpc 1.3.7-1 -> 1.3.8-1 +tzdata 2026c-1 -> 2026d-1 diff --git a/agent/test-data/package_updates/zypper_leap155_list_patches_security.txt b/agent/test-data/package_updates/zypper_leap155_list_patches_security.txt new file mode 100644 index 00000000..df5ff57c --- /dev/null +++ b/agent/test-data/package_updates/zypper_leap155_list_patches_security.txt @@ -0,0 +1,15 @@ +Warning: Repository 'Update repository of openSUSE Backports' metadata expired since 2025-03-02 19:18:12 UTC. +Warning: Repository 'Main Update Repository' metadata expired since 2025-08-30 08:17:31 UTC. +Warning: Repository 'Update Repository (Non-Oss)' metadata expired since 2025-04-10 11:03:28 UTC. + + + +Repository | Name | Category | Severity | Interactive | Status | Summary +-------------------------------------------------------------+-----------------------------+----------+-----------+-------------+--------+-------------------------------- +Update repository with updates from SUSE Linux Enterprise 15 | openSUSE-SLE-15.5-2024-3765 | security | moderate | --- | needed | Security update for openssl-1_1 +Update repository with updates from SUSE Linux Enterprise 15 | openSUSE-SLE-15.5-2024-3926 | security | moderate | --- | needed | Security update for curl +Update repository with updates from SUSE Linux Enterprise 15 | openSUSE-SLE-15.5-2024-4078 | security | important | --- | needed | Security update for glib2 +Update repository with updates from SUSE Linux Enterprise 15 | openSUSE-SLE-15.5-2024-4359 | security | moderate | --- | needed | Security update for curl + +4 patches needed (4 security patches) + diff --git a/agent/test-data/package_updates/zypper_leap155_list_updates.txt b/agent/test-data/package_updates/zypper_leap155_list_updates.txt new file mode 100644 index 00000000..ee90e813 --- /dev/null +++ b/agent/test-data/package_updates/zypper_leap155_list_updates.txt @@ -0,0 +1,29 @@ +Warning: Repository 'Update repository of openSUSE Backports' metadata expired since 2025-03-02 19:18:12 UTC. +Warning: Repository 'Main Update Repository' metadata expired since 2025-08-30 08:17:31 UTC. +Warning: Repository 'Update Repository (Non-Oss)' metadata expired since 2025-04-10 11:03:28 UTC. + + +S | Repository | Name | Current Version | Available Version | Arch +---+--------------------------------------------------------------+--------------------+------------------------------------------+------------------------------------------+-------- +v | Update repository with updates from SUSE Linux Enterprise 15 | aaa_base | 84.87+git20180409.04c9dae-150300.10.20.1 | 84.87+git20180409.04c9dae-150300.10.23.1 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | bash | 4.4-150400.25.22 | 4.4-150400.27.3.2 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | bash-sh | 4.4-150400.25.22 | 4.4-150400.27.3.2 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | crypto-policies | 20210917.c9d86d1-150400.3.6.1 | 20210917.c9d86d1-150400.3.8.1 | noarch +v | Update repository with updates from SUSE Linux Enterprise 15 | curl | 8.0.1-150400.5.50.1 | 8.0.1-150400.5.59.1 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | glibc | 2.31-150300.86.3 | 2.31-150300.89.2 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | libcom_err2 | 1.46.4-150400.3.6.2 | 1.46.4-150400.3.9.2 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | libcurl4 | 8.0.1-150400.5.50.1 | 8.0.1-150400.5.59.1 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | libgcc_s1 | 13.3.0+git8781-150000.1.12.1 | 14.2.0+git10526-150000.1.6.1 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | libglib-2_0-0 | 2.70.5-150400.3.14.1 | 2.70.5-150400.3.17.1 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | libopenssl1_1 | 1.1.1l-150500.17.34.1 | 1.1.1l-150500.17.37.1 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | libopenssl1_1-hmac | 1.1.1l-150500.17.34.1 | 1.1.1l-150500.17.37.1 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | libreadline7 | 7.0-150400.25.22 | 7.0-150400.27.3.2 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | libsolv-tools | 0.7.30-150400.3.27.2 | 0.7.31-150500.6.5.1 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | libsolv-tools-base | 0.7.30-150400.3.27.2 | 0.7.31-150500.6.5.1 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | libstdc++6 | 13.3.0+git8781-150000.1.12.1 | 14.2.0+git10526-150000.1.6.1 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | libudev1 | 249.17-150400.8.43.1 | 249.17-150400.8.46.1 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | libzypp | 17.35.8-150500.6.13.1 | 17.35.16-150500.6.31.1 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | login_defs | 4.8.1-150400.10.21.1 | 4.8.1-150400.10.24.1 | noarch +v | Update repository with updates from SUSE Linux Enterprise 15 | openssl-1_1 | 1.1.1l-150500.17.34.1 | 1.1.1l-150500.17.37.1 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | shadow | 4.8.1-150400.10.21.1 | 4.8.1-150400.10.24.1 | aarch64 +v | Update repository with updates from SUSE Linux Enterprise 15 | zypper | 1.14.76-150500.6.6.15 | 1.14.78-150500.6.14.1 | aarch64 diff --git a/agent/test-data/package_updates/zypper_leap156_list_updates_none.txt b/agent/test-data/package_updates/zypper_leap156_list_updates_none.txt new file mode 100644 index 00000000..f841f980 --- /dev/null +++ b/agent/test-data/package_updates/zypper_leap156_list_updates_none.txt @@ -0,0 +1,3 @@ +Warning: Repository 'Update repository of openSUSE Backports' metadata expired since 2026-07-10 11:19:15 UTC. + + diff --git a/internal/entities/system/system.go b/internal/entities/system/system.go index c6e8d0ea..aeaf7899 100644 --- a/internal/entities/system/system.go +++ b/internal/entities/system/system.go @@ -184,6 +184,7 @@ type Info struct { Services []uint16 `json:"sv,omitempty" cbor:"22,keyasint,omitempty"` // [totalServices, numFailedServices] Battery Battery `json:"bat,omitzero" cbor:"23,keyasint,omitzero"` // [percent, charge state] RootDiskName string `json:"rdn,omitempty" cbor:"24,keyasint,omitempty"` // custom name for root disk (set via FILESYSTEM=device__name) + PackageUpdates []uint16 `json:"pu,omitempty" cbor:"25,keyasint,omitempty"` // [totalUpdates, securityUpdates] (security omitted if unknown) } // Data that does not change during process lifetime and is not needed in All Systems table diff --git a/internal/site/src/components/systems-table/systems-table-columns.tsx b/internal/site/src/components/systems-table/systems-table-columns.tsx index c49b8ca6..6f60f97c 100644 --- a/internal/site/src/components/systems-table/systems-table-columns.tsx +++ b/internal/site/src/components/systems-table/systems-table-columns.tsx @@ -14,6 +14,7 @@ import { HardDriveIcon, MemoryStickIcon, MoreHorizontalIcon, + PackageIcon, PauseCircleIcon, PenBoxIcon, PlayCircleIcon, @@ -80,6 +81,15 @@ const STATUS_COLORS = { [SystemStatus.Pending]: "bg-yellow-500", } as const +/** Rank of the updates dot color for sorting: 2 security (red), 1 regular (yellow), 0 up to date (green), -1 no data */ +function getUpdatesRank(pu: SystemRecord["info"]["pu"]): number { + if (!pu) { + return -1 + } + const [total, security = 0] = pu + return security > 0 ? 2 : total > 0 ? 1 : 0 +} + function getMeterStateByThresholds(value: number, warn = 65, crit = 90): MeterState { return value >= crit ? MeterState.Crit : value >= warn ? MeterState.Warn : MeterState.Good } @@ -393,6 +403,45 @@ export function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef info.pu?.[0], + id: "updates", + name: () => t`Updates`, + size: 50, + Icon: PackageIcon, + header: sortableHeader, + hideSort: true, + sortingFn: (a, b) => { + // sort priorities: 1) dot color (security > regular > up to date), 2) total updates + const puA = a.original.info.pu + const puB = b.original.info.pu + const rankA = getUpdatesRank(puA) + const rankB = getUpdatesRank(puB) + if (rankA !== rankB) { + return rankA - rankB + } + return (puA?.[0] ?? 0) - (puB?.[0] ?? 0) + }, + cell(info) { + const sys = info.row.original + if (sys.status !== SystemStatus.Up || !sys.info.pu) { + return null + } + const [total, security = 0] = sys.info.pu + return ( + + 0, + [STATUS_COLORS[SystemStatus.Pending]]: security === 0 && total > 0, + [STATUS_COLORS[SystemStatus.Up]]: total === 0, + })} + /> + {total === 0 ? t`Up to date` : plural(total, { one: "# update", other: "# updates" })} + + ) + }, + }, { accessorFn: ({ info }) => info.u || undefined, id: "uptime", diff --git a/internal/site/src/types.d.ts b/internal/site/src/types.d.ts index bdf14707..6efbc1a6 100644 --- a/internal/site/src/types.d.ts +++ b/internal/site/src/types.d.ts @@ -80,6 +80,8 @@ export interface SystemInfo { sv?: [number, number] /** custom root disk name */ rdn?: string + /** pending package updates [total, security] (security omitted if unknown) */ + pu?: [number, number?] } export interface SystemStats {