From bda84ae5f3ceaf59a0ee567ac517f80181a37b95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asger=20Geel=20Weirs=C3=B8e?= Date: Mon, 27 Apr 2026 14:50:13 +0200 Subject: [PATCH] Version is not required to have value, fallbacks or nil --- pkg/platform/facts/os/os_linux.go | 71 ++++++++++++++++++++++++------- pkg/platform/facts/os/os_test.go | 55 +++++++++++++++++++++++- 2 files changed, 110 insertions(+), 16 deletions(-) diff --git a/pkg/platform/facts/os/os_linux.go b/pkg/platform/facts/os/os_linux.go index d00f5bf0..6be0056c 100644 --- a/pkg/platform/facts/os/os_linux.go +++ b/pkg/platform/facts/os/os_linux.go @@ -15,23 +15,27 @@ import ( func gather(ctx *common.GatherContext) (api.DeviceFactsRequestOs, error) { name, version := getLinuxDistribution() - return api.DeviceFactsRequestOs{ - Arch: runtime.GOARCH, - Family: api.DEVICEFACTSOSFAMILY_LINUX, - Name: api.PtrString(name), - Version: api.PtrString(version), - }, nil + info := api.DeviceFactsRequestOs{ + Arch: runtime.GOARCH, + Family: api.DEVICEFACTSOSFAMILY_LINUX, + Name: api.PtrString(name), + } + if version != "" { + info.Version = api.PtrString(version) + } + + return info, nil } func getLinuxDistribution() (string, string) { // Try /etc/os-release first (systemd standard) - if fullVersion := parseOSRelease("/etc/os-release"); fullVersion != "" { - return extractVersion(fullVersion) + if name, version := parseOSRelease("/etc/os-release"); name != "" { + return name, version } // Try /usr/lib/os-release as fallback - if fullVersion := parseOSRelease("/usr/lib/os-release"); fullVersion != "" { - return extractVersion(fullVersion) + if name, version := parseOSRelease("/usr/lib/os-release"); name != "" { + return name, version } // Try /etc/lsb-release (Ubuntu/Debian) @@ -57,25 +61,62 @@ func getLinuxDistribution() (string, string) { return "Linux", getKernelVersion() } -func parseOSRelease(filename string) string { +func parseOSRelease(filename string) (string, string) { file, err := os.Open(filename) if err != nil { - return "" + return "", "" } defer func() { _ = file.Close() }() scanner := bufio.NewScanner(file) + var name, prettyName, versionID, version, buildID string for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) - if strings.HasPrefix(line, "PRETTY_NAME=") { - return strings.Trim(strings.TrimPrefix(line, "PRETTY_NAME="), "\"") + key, value, ok := strings.Cut(line, "=") + if !ok { + continue + } + value = strings.Trim(value, "\"'") + switch key { + case "NAME": + name = value + case "PRETTY_NAME": + prettyName = value + case "VERSION_ID": + versionID = value + case "VERSION": + version = value + case "BUILD_ID": + buildID = value } } - return "" + fullName := prettyName + if fullName == "" { + fullName = name + } + if fullName == "" { + return "", "" + } + + parsedName, parsedVersion := extractVersion(fullName) + if parsedVersion != "" { + return parsedName, parsedVersion + } + if versionID != "" { + return parsedName, versionID + } + if version != "" { + return parsedName, version + } + if buildID != "" { + return parsedName, buildID + } + + return parsedName, "" } func parseLSBRelease() (string, string) { diff --git a/pkg/platform/facts/os/os_test.go b/pkg/platform/facts/os/os_test.go index fcc3bc27..0ca0eb8e 100644 --- a/pkg/platform/facts/os/os_test.go +++ b/pkg/platform/facts/os/os_test.go @@ -1,8 +1,11 @@ package os import ( + stdos "os" + "path/filepath" "runtime" "slices" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -18,7 +21,9 @@ func TestGather(t *testing.T) { assert.NotEqual(t, info.Family, "") assert.True(t, slices.Contains(api.AllowedDeviceFactsOSFamilyEnumValues, info.Family)) assert.Equal(t, info.Arch, runtime.GOARCH) - assert.Regexp(t, `(\d+\.(?:\d+\.?)+)`, *info.Version, "Version must only contain numbers: '%s'", *info.Version) + if info.Version != nil { + assert.NotEqual(t, strings.TrimSpace(*info.Version), "") + } } func TestExtract(t *testing.T) { @@ -46,6 +51,54 @@ func TestExtract(t *testing.T) { } } +func TestParseOSRelease(t *testing.T) { + for _, tc := range []struct { + name string + content string + osName string + version string + }{ + { + name: "pretty name with embedded version", + content: `NAME="Ubuntu" +PRETTY_NAME="Ubuntu 24.04.3 LTS" +`, + osName: "Ubuntu", + version: "24.04.3 LTS", + }, + { + name: "fallback to version id", + content: `NAME="TestOS" +PRETTY_NAME="TestOS" +VERSION_ID="1.2" +VERSION="rolling" +BUILD_ID="2025.03.19" +`, + osName: "TestOS", + version: "1.2", + }, + { + name: "fallback to build id", + content: `NAME="EndeavourOS" +PRETTY_NAME="EndeavourOS" +BUILD_ID="2025.03.19" +`, + osName: "EndeavourOS", + version: "2025.03.19", + }, + } { + t.Run(tc.name, func(t *testing.T) { + path := filepath.Join(t.TempDir(), "os-release") + err := stdos.WriteFile(path, []byte(tc.content), 0o600) + assert.NoError(t, err) + + name, version := parseOSRelease(path) + assert.Equal(t, tc.osName, name) + assert.Equal(t, tc.version, version) + }) + } +} + func TestGatherLinux(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("Skipping Linux-specific test")