From 26899f834510cbd72a071e073455daf7a7cf9f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Budai?= Date: Wed, 10 Apr 2024 16:34:29 +0200 Subject: [PATCH] distro: add ReadOSReleaseFromTree This new method supports reading os-release from an arbitrary tree, and it also correctly handles multiple os-release locations (see os-release(5)). It's public so we can reuse it in multiple places. --- pkg/distro/host.go | 24 ++++++++++++++++++++++++ pkg/distro/host_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/pkg/distro/host.go b/pkg/distro/host.go index 294e9109f4..f3578abe9a 100644 --- a/pkg/distro/host.go +++ b/pkg/distro/host.go @@ -3,8 +3,10 @@ package distro import ( "bufio" "errors" + "fmt" "io" "os" + "path" "strings" ) @@ -48,3 +50,25 @@ func readOSRelease(r io.Reader) (map[string]string, error) { return osrelease, nil } + +// ReadOSReleaseFromTree reads the os-release file from the given root directory. +// +// According to os-release(5), the os-release file should be located in either /etc/os-release or /usr/lib/os-release, +// so both locations are tried, with the former taking precedence. +func ReadOSReleaseFromTree(root string) (map[string]string, error) { + locations := []string{ + "etc/os-release", + "usr/lib/os-release", + } + var errs []string + for _, location := range locations { + f, err := os.Open(path.Join(root, location)) + if err == nil { + defer f.Close() + return readOSRelease(f) + } + errs = append(errs, fmt.Sprintf("cannot read %s: %v", location, err)) + } + + return nil, fmt.Errorf("failed to read os-release:\n%s", strings.Join(errs, "\n")) +} diff --git a/pkg/distro/host_test.go b/pkg/distro/host_test.go index 490040bf32..c6f500cb3c 100644 --- a/pkg/distro/host_test.go +++ b/pkg/distro/host_test.go @@ -1,9 +1,13 @@ package distro import ( + "os" + "path" "reflect" "strings" "testing" + + "github.com/stretchr/testify/require" ) func TestOSRelease(t *testing.T) { @@ -52,3 +56,36 @@ VARIANT_ID=workstation`, } } } + +func TestReadOSReleaseFromTree(t *testing.T) { + tree := t.TempDir() + + // initialize dirs + require.NoError(t, os.MkdirAll(path.Join(tree, "usr/lib"), 0755)) + require.NoError(t, os.MkdirAll(path.Join(tree, "etc"), 0755)) + + // firstly, let's write a simple /usr/lib/os-release + require.NoError(t, + os.WriteFile(path.Join(tree, "usr/lib/os-release"), []byte("ID=toucan\n"), 0600), + ) + + osRelease, err := ReadOSReleaseFromTree(tree) + require.NoError(t, err) + require.Equal(t, "toucan", osRelease["ID"]) + + // secondly, let's override it with /etc/os-release + require.NoError(t, + os.WriteFile(path.Join(tree, "etc/os-release"), []byte("ID=kingfisher\n"), 0600), + ) + + osRelease, err = ReadOSReleaseFromTree(tree) + require.NoError(t, err) + require.Equal(t, "kingfisher", osRelease["ID"]) +} + +func TestReadOSReleaseFromTreeUnhappy(t *testing.T) { + tree := t.TempDir() + + _, err := ReadOSReleaseFromTree(tree) + require.ErrorContains(t, err, "failed to read os-release") +}