Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
and `networking.scope6` when no primary IPv6 address is available.
- Mountpoint entries with no stat, device, filesystem, or option data are now
omitted instead of rendering empty maps such as `mountpoints."/": {}`.
- Linux `os.release` is now omitted when `/etc/os-release` has no
`VERSION_ID`, including Arch Linux; `BUILD_ID=rolling` is not treated as an
operating system release.
- NixOS, Rocky Linux, and AlmaLinux `os.release` now split dotted
`VERSION_ID` values into `major` and `minor`, matching `os.distro.release`.

## v0.0.3 - 2026-06-18

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ lima-docker-distro-facts: lima-docker-build-amd64
*) echo "missing expected os.distro.id for $$image" >&2; exit 2 ;; \
esac; \
echo "==> distro fact smoke $$image"; \
$(LIMACTL) shell '$(LIMA_DOCKER_INSTANCE)' -- sh -lc "set -eu; cd '$(CURDIR)'; out=\$$(docker run --rm --platform linux/amd64 -e CI=true -v \"\$$PWD/dist/facts-linux-amd64:/usr/local/bin/facts:ro\" $$image /usr/local/bin/facts --json os.name os.family os.distro.id os.distro.description os.release.major os.distro.release.major kernel.name virtual); printf '%s\n' \"\$$out\"; printf '%s\n' \"\$$out\" | grep -Eq '\"kernel.name\"[[:space:]]*:[[:space:]]*\"Linux\"'; printf '%s\n' \"\$$out\" | grep -Eq '\"os.distro.id\"[[:space:]]*:[[:space:]]*\"$$expected_id\"'" || exit $$?; \
$(LIMACTL) shell '$(LIMA_DOCKER_INSTANCE)' -- sh -lc "set -eu; cd '$(CURDIR)'; out=\$$(docker run --rm --platform linux/amd64 -e CI=true -v \"\$$PWD/dist/facts-linux-amd64:/usr/local/bin/facts:ro\" $$image /usr/local/bin/facts --json os.name os.family os.distro.id os.distro.description kernel.name virtual); printf '%s\n' \"\$$out\"; printf '%s\n' \"\$$out\" | grep -Eq '\"kernel.name\"[[:space:]]*:[[:space:]]*\"Linux\"'; printf '%s\n' \"\$$out\" | grep -Eq '\"os.distro.id\"[[:space:]]*:[[:space:]]*\"$$expected_id\"'" || exit $$?; \
done

lima-docker-workloads: lima-docker-go-containers lima-docker-distro-facts
Expand Down
17 changes: 13 additions & 4 deletions internal/engine/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,10 @@ func currentMacOSModel(goos string, run commandRunner) string {

func probeOSRelease(s *Session) any {
if runtime.GOOS == "windows" {
return currentWindowsOSRelease(s.cachedWindowsOSVersionInput())
if release := currentWindowsOSRelease(s.cachedWindowsOSVersionInput()); len(release) > 0 {
return release
}
return nil
}
return currentOSRelease(s, runtime.GOOS, s.readFile, s.commandOutput)
}
Expand All @@ -225,7 +228,10 @@ func currentOSRelease(s *Session, goos string, readFile fileReader, run commandR
if release := specificLinuxOSRelease(id, readFile, run); len(release) > 0 {
return release
}
return parseLinuxOSRelease(string(data))
if release := parseLinuxOSRelease(string(data)); len(release) > 0 {
return release
}
return nil
case "freebsd":
versions := parseFreeBSDVersions(run("/bin/freebsd-version", "-k"), run("/bin/freebsd-version", "-ru"))
if versions.InstalledUserland != "" {
Expand All @@ -246,7 +252,10 @@ func currentOSRelease(s *Session, goos string, readFile fileReader, run commandR
case "darwin":
return parseDarwinOSRelease(run("uname", "-r"))
case "windows":
return currentWindowsOSRelease(windowsWMIOutput(run, "os", "OtherTypeDescription,ProductType,Version"))
if release := currentWindowsOSRelease(windowsWMIOutput(run, "os", "OtherTypeDescription,ProductType,Version")); len(release) > 0 {
return release
}
return nil
}
return s.cachedKernelRelease()
}
Expand Down Expand Up @@ -711,7 +720,7 @@ func linuxOSReleaseMap(id, full string) map[string]any {
return debianReleaseMap(full)
}
switch strings.ToLower(id) {
case "mariner", "azurelinux", "linuxmint", "gentoo", "mageia":
case "mariner", "azurelinux", "linuxmint", "gentoo", "mageia", "nixos", "rocky", "almalinux":
return releaseHashFromString(full, false)
}
return map[string]any{"full": full, "major": full}
Expand Down
49 changes: 48 additions & 1 deletion internal/engine/os_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,34 @@ func TestParseLinuxOSRelease_padsDebianVersionIDLikeRubyResolver(t *testing.T) {
}
}

func TestParseLinuxOSRelease_splitsNixOSVersionID(t *testing.T) {
got := parseLinuxOSRelease("ID=nixos\nVERSION_ID=26.05\n")

want := map[string]any{"full": "26.05", "major": "26", "minor": "05"}
if !reflect.DeepEqual(got, want) {
t.Fatalf("parseLinuxOSRelease() = %#v, want %#v", got, want)
}
}

func TestParseLinuxOSRelease_splitsRockyAndAlmaVersionID(t *testing.T) {
tests := []struct {
id string
}{
{id: "rocky"},
{id: "almalinux"},
}
for _, tt := range tests {
t.Run(tt.id, func(t *testing.T) {
got := parseLinuxOSRelease("ID=" + tt.id + "\nVERSION_ID=9.8\n")

want := map[string]any{"full": "9.8", "major": "9", "minor": "8"}
if !reflect.DeepEqual(got, want) {
t.Fatalf("parseLinuxOSRelease() = %#v, want %#v", got, want)
}
})
}
}

func TestParseLinuxDistroOSRelease_trimsDebianMinorLeadingZero(t *testing.T) {
got := parseLinuxDistroOSRelease("ID=debian\nVERSION_ID=10.02\n")

Expand Down Expand Up @@ -1251,15 +1279,34 @@ func TestParseLinuxDistroOSRelease_normalizesSLESNameAndSAPID(t *testing.T) {
func TestParseLinuxDistroOSRelease_normalizesArchLinuxName(t *testing.T) {
t.Parallel()

got := parseLinuxDistroOSRelease("NAME=\"Arch Linux\"\nID=arch\nPRETTY_NAME=\"Arch Linux\"\n")
got := parseLinuxDistroOSRelease("NAME=\"Arch Linux\"\nID=arch\nBUILD_ID=rolling\nPRETTY_NAME=\"Arch Linux\"\n")
if got.Name != "Archlinux" {
t.Fatalf("parseLinuxDistroOSRelease().Name = %q, want Archlinux", got.Name)
}
if len(got.Release) != 0 || got.ReleaseKnown {
t.Fatalf("parseLinuxDistroOSRelease().Release = %#v, ReleaseKnown = %v, want no release from BUILD_ID", got.Release, got.ReleaseKnown)
}
if name := osName("linux", got); name != "Archlinux" {
t.Fatalf("osName(linux, arch) = %q, want Archlinux", name)
}
}

func TestCurrentOSRelease_omitsArchRollingBuildID(t *testing.T) {
t.Parallel()

readFile := func(path string) ([]byte, error) {
if path != "/etc/os-release" {
return nil, os.ErrNotExist
}
return []byte("NAME=\"Arch Linux\"\nPRETTY_NAME=\"Arch Linux\"\nID=arch\nBUILD_ID=rolling\n"), nil
}

got := currentOSRelease(testSession, "linux", readFile, func(string, ...string) string { return "" })
if got != nil {
t.Fatalf("currentOSRelease(testSession, arch) = %#v, want nil because BUILD_ID is not a release", got)
}
}

func TestParseLinuxDistroOSRelease_normalizesManjaroLinuxName(t *testing.T) {
t.Parallel()

Expand Down
Loading