mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 22:12:23 +00:00
Detect Proxmox VE host agent OS identity
This commit is contained in:
@@ -198,6 +198,11 @@ redeclared in host-agent or settings-table branches. Host-profile detection is
|
||||
an identity fact and must not depend on optional storage probes succeeding; an
|
||||
Unraid host still reports the governed `unraid` profile and `linux` runtime
|
||||
platform when `mdcmd` or array-topology collection is unavailable.
|
||||
First-class platform hosts that also run the Pulse Agent must keep the same
|
||||
operator-facing system identity split: a Proxmox VE node may report a Debian
|
||||
runtime platform underneath, but the host-agent OS identity and infrastructure
|
||||
System badge must resolve and present `Proxmox VE` / `PVE` from PVE runtime
|
||||
evidence instead of exposing the Debian base distro as the primary system label.
|
||||
The lifecycle-owned infrastructure source manager also owns platform/system
|
||||
grouping as source-management content, but not its table band presentation:
|
||||
`frontend-modern/src/components/Settings/InfrastructureSourceManager.tsx` must
|
||||
|
||||
@@ -78,6 +78,128 @@ func TestNew_AllowsInsecureRemoteHTTPPulseURL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew_ResolvesProxmoxVEHostIdentity(t *testing.T) {
|
||||
mc := &mockCollector{
|
||||
hostInfoFn: func(context.Context) (*gohost.InfoStat, error) {
|
||||
return &gohost.InfoStat{
|
||||
Hostname: "pve-host",
|
||||
HostID: "hid",
|
||||
Platform: "debian",
|
||||
PlatformFamily: "debian",
|
||||
PlatformVersion: "13.4",
|
||||
KernelVersion: "7.0.0-3-pve",
|
||||
KernelArch: runtime.GOARCH,
|
||||
}, nil
|
||||
},
|
||||
statFn: func(name string) (os.FileInfo, error) {
|
||||
if name == "/etc/pve" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
},
|
||||
lookPathFn: func(file string) (string, error) {
|
||||
if file == "pveversion" {
|
||||
return "/usr/bin/pveversion", nil
|
||||
}
|
||||
return "", os.ErrNotExist
|
||||
},
|
||||
commandCombinedOutputFn: func(ctx context.Context, name string, arg ...string) (string, error) {
|
||||
if name != "/usr/bin/pveversion" {
|
||||
t.Fatalf("command name = %q, want /usr/bin/pveversion", name)
|
||||
}
|
||||
return "pve-manager/9.1.9/ee7bad0a3d1546c9 (running kernel: 7.0.0-3-pve)", nil
|
||||
},
|
||||
}
|
||||
|
||||
agent, err := New(Config{
|
||||
APIToken: "token",
|
||||
LogLevel: zerolog.InfoLevel,
|
||||
Collector: mc,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New() error = %v", err)
|
||||
}
|
||||
if agent.platform != "debian" {
|
||||
t.Fatalf("platform = %q, want Debian runtime platform", agent.platform)
|
||||
}
|
||||
if agent.osName != proxmoxPVEOSName {
|
||||
t.Fatalf("osName = %q, want %q", agent.osName, proxmoxPVEOSName)
|
||||
}
|
||||
if agent.osVersion != "9.1.9" {
|
||||
t.Fatalf("osVersion = %q, want 9.1.9", agent.osVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew_DoesNotUseDebianVersionForProxmoxVE(t *testing.T) {
|
||||
mc := &mockCollector{
|
||||
hostInfoFn: func(context.Context) (*gohost.InfoStat, error) {
|
||||
return &gohost.InfoStat{
|
||||
Hostname: "pve-host",
|
||||
HostID: "hid",
|
||||
Platform: "debian",
|
||||
PlatformFamily: "debian",
|
||||
PlatformVersion: "13.4",
|
||||
KernelVersion: "7.0.0-3-pve",
|
||||
KernelArch: runtime.GOARCH,
|
||||
}, nil
|
||||
},
|
||||
statFn: func(name string) (os.FileInfo, error) {
|
||||
if name == "/etc/pve" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
},
|
||||
lookPathFn: func(file string) (string, error) {
|
||||
return "", os.ErrNotExist
|
||||
},
|
||||
}
|
||||
|
||||
agent, err := New(Config{
|
||||
APIToken: "token",
|
||||
LogLevel: zerolog.InfoLevel,
|
||||
Collector: mc,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New() error = %v", err)
|
||||
}
|
||||
if agent.osName != proxmoxPVEOSName {
|
||||
t.Fatalf("osName = %q, want %q", agent.osName, proxmoxPVEOSName)
|
||||
}
|
||||
if agent.osVersion != "" {
|
||||
t.Fatalf("osVersion = %q, want empty Proxmox version when pveversion is unavailable", agent.osVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanProxmoxPVEVersion(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
raw string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "short output",
|
||||
raw: "pve-manager/9.1.9/ee7bad0a3d1546c9 (running kernel: 7.0.0-3-pve)",
|
||||
want: "9.1.9",
|
||||
},
|
||||
{
|
||||
name: "verbose output",
|
||||
raw: "proxmox-ve: 9.1-1\npve-manager/9.1.9/ee7bad0a3d1546c9\n",
|
||||
want: "9.1.9",
|
||||
},
|
||||
{
|
||||
name: "unrelated",
|
||||
raw: "Debian GNU/Linux 13",
|
||||
want: "",
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := cleanProxmoxPVEVersion(tt.raw); got != tt.want {
|
||||
t.Fatalf("cleanProxmoxPVEVersion() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew_RequiresAPITokenWhenEnrollmentEnabled(t *testing.T) {
|
||||
mc := &mockCollector{
|
||||
hostInfoFn: func(context.Context) (*gohost.InfoStat, error) {
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package hostagent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var unraidVersionPattern = regexp.MustCompile(`\b\d+(?:\.\d+)+(?:[-+._][A-Za-z0-9]+)*\b|\b\d+\b`)
|
||||
var proxmoxPVEVersionPattern = regexp.MustCompile(`(?i)\bpve-manager/([^/\s]+)`)
|
||||
|
||||
const proxmoxPVEOSName = "Proxmox VE"
|
||||
|
||||
func resolveHostOSIdentity(collector SystemCollector, osName, osVersion string) (string, string) {
|
||||
currentName := strings.TrimSpace(osName)
|
||||
@@ -16,29 +21,32 @@ func resolveHostOSIdentity(collector SystemCollector, osName, osVersion string)
|
||||
}
|
||||
|
||||
if name, version, ok := detectSynologyOSIdentity(collector); ok {
|
||||
if version == "" {
|
||||
version = currentVersion
|
||||
}
|
||||
return name, strings.TrimSpace(version)
|
||||
return resolvedDetectedHostOSIdentity(name, version, currentVersion, true)
|
||||
}
|
||||
|
||||
if name, version, ok := detectQNAPOSIdentity(collector); ok {
|
||||
if version == "" {
|
||||
version = currentVersion
|
||||
}
|
||||
return name, strings.TrimSpace(version)
|
||||
return resolvedDetectedHostOSIdentity(name, version, currentVersion, true)
|
||||
}
|
||||
|
||||
if name, version, ok := detectUnraidOSIdentity(collector); ok {
|
||||
if version == "" {
|
||||
version = currentVersion
|
||||
}
|
||||
return name, strings.TrimSpace(version)
|
||||
return resolvedDetectedHostOSIdentity(name, version, currentVersion, true)
|
||||
}
|
||||
|
||||
if name, version, ok := detectProxmoxVEOSIdentity(collector); ok {
|
||||
return resolvedDetectedHostOSIdentity(name, version, currentVersion, false)
|
||||
}
|
||||
|
||||
return currentName, currentVersion
|
||||
}
|
||||
|
||||
func resolvedDetectedHostOSIdentity(name, version, currentVersion string, allowVersionFallback bool) (string, string) {
|
||||
version = strings.TrimSpace(version)
|
||||
if version == "" && allowVersionFallback {
|
||||
version = currentVersion
|
||||
}
|
||||
return strings.TrimSpace(name), strings.TrimSpace(version)
|
||||
}
|
||||
|
||||
func detectSynologyOSIdentity(collector SystemCollector) (string, string, bool) {
|
||||
hasSynologyDir := false
|
||||
if _, err := collector.Stat("/usr/syno"); err == nil {
|
||||
@@ -163,6 +171,46 @@ func detectUnraidOSIdentity(collector SystemCollector) (string, string, bool) {
|
||||
return "Unraid", version, true
|
||||
}
|
||||
|
||||
func detectProxmoxVEOSIdentity(collector SystemCollector) (string, string, bool) {
|
||||
hasPVE := false
|
||||
if _, err := collector.Stat("/etc/pve"); err == nil {
|
||||
hasPVE = true
|
||||
}
|
||||
|
||||
pveVersionPath, err := collector.LookPath("pveversion")
|
||||
if err == nil {
|
||||
hasPVE = true
|
||||
}
|
||||
|
||||
if !hasPVE {
|
||||
if _, err := collector.LookPath("pvesh"); err != nil {
|
||||
return "", "", false
|
||||
}
|
||||
}
|
||||
|
||||
version := ""
|
||||
if pveVersionPath != "" {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
if output, err := collector.CommandCombinedOutput(ctx, pveVersionPath); err == nil {
|
||||
version = cleanProxmoxPVEVersion(output)
|
||||
}
|
||||
}
|
||||
|
||||
return proxmoxPVEOSName, version, true
|
||||
}
|
||||
|
||||
func cleanProxmoxPVEVersion(raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
if match := proxmoxPVEVersionPattern.FindStringSubmatch(raw); len(match) == 2 {
|
||||
return strings.TrimSpace(match[1])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func detectUnraidOSReleaseIdentity(collector SystemCollector) (string, string, bool) {
|
||||
data, err := collector.ReadFile("/etc/os-release")
|
||||
if err != nil || len(data) == 0 {
|
||||
|
||||
Reference in New Issue
Block a user