diff --git a/pkg/limayaml/defaults.go b/pkg/limayaml/defaults.go index d9d2c6229d3..629cbcc3f97 100644 --- a/pkg/limayaml/defaults.go +++ b/pkg/limayaml/defaults.go @@ -135,26 +135,6 @@ func MACAddress(uniqueID string) string { return hw.String() } -func hostTimeZone() string { - // WSL2 will automatically set the timezone - if runtime.GOOS != "windows" { - tz, err := os.ReadFile("/etc/timezone") - if err == nil { - return strings.TrimSpace(string(tz)) - } - zoneinfoFile, err := filepath.EvalSymlinks("/etc/localtime") - if err == nil { - for baseDir := filepath.Dir(zoneinfoFile); baseDir != "/"; baseDir = filepath.Dir(baseDir) { - if _, err = os.Stat(filepath.Join(baseDir, "Etc/UTC")); err == nil { - return strings.TrimPrefix(zoneinfoFile, baseDir+"/") - } - } - logrus.Warnf("could not locate zoneinfo directory from %q", zoneinfoFile) - } - } - return "" -} - func defaultCPUs() int { const x = 4 if hostCPUs := runtime.NumCPU(); hostCPUs < x { diff --git a/pkg/limayaml/defaults_unix.go b/pkg/limayaml/defaults_unix.go new file mode 100644 index 00000000000..fdce36cbb56 --- /dev/null +++ b/pkg/limayaml/defaults_unix.go @@ -0,0 +1,58 @@ +//go:build !windows + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package limayaml + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/sirupsen/logrus" +) + +func hostTimeZone() string { + if tzBytes, err := os.ReadFile("/etc/timezone"); err == nil { + if tz := strings.TrimSpace(string(tzBytes)); tz != "" { + if _, err := time.LoadLocation(tz); err != nil { + logrus.Warnf("invalid timezone found in /etc/timezone: %v", err) + } else { + return tz + } + } + } + + if zoneinfoFile, err := filepath.EvalSymlinks("/etc/localtime"); err == nil { + if tz, err := extractTZFromPath(zoneinfoFile); err != nil { + logrus.Warnf("failed to extract timezone from %s: %v", zoneinfoFile, err) + } else { + return tz + } + } + + logrus.Warn("unable to determine host timezone, falling back to default value") + return "" +} + +func extractTZFromPath(zoneinfoFile string) (string, error) { + if zoneinfoFile == "" { + return "", errors.New("invalid zoneinfo file path") + } + + if _, err := os.Stat(zoneinfoFile); os.IsNotExist(err) { + return "", fmt.Errorf("zoneinfo file does not exist: %s", zoneinfoFile) + } + + for dir := filepath.Dir(zoneinfoFile); dir != filepath.Dir(dir); dir = filepath.Dir(dir) { + if _, err := os.Stat(filepath.Join(dir, "Etc", "UTC")); err == nil { + return filepath.Rel(dir, zoneinfoFile) + } + } + + return "", errors.New("timezone base directory not found") +} diff --git a/pkg/limayaml/defaults_unix_test.go b/pkg/limayaml/defaults_unix_test.go new file mode 100644 index 00000000000..26f33228489 --- /dev/null +++ b/pkg/limayaml/defaults_unix_test.go @@ -0,0 +1,75 @@ +//go:build !windows + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package limayaml + +import ( + "os" + "path/filepath" + "testing" + + "gotest.tools/v3/assert" +) + +func TestExtractTimezoneFromPath(t *testing.T) { + tmpDir := t.TempDir() + + // Create test timezone directory structure + assert.NilError(t, os.MkdirAll(filepath.Join(tmpDir, "Etc"), 0o755)) + assert.NilError(t, os.WriteFile(filepath.Join(tmpDir, "Etc", "UTC"), []byte{}, 0o644)) + assert.NilError(t, os.WriteFile(filepath.Join(tmpDir, "UTC"), []byte{}, 0o644)) + assert.NilError(t, os.MkdirAll(filepath.Join(tmpDir, "Antarctica"), 0o755)) + assert.NilError(t, os.WriteFile(filepath.Join(tmpDir, "Antarctica", "Troll"), []byte{}, 0o644)) + + tests := []struct { + name string + path string + want string + wantErr bool + }{ + { + "valid_timezone", + filepath.Join(tmpDir, "Antarctica", "Troll"), + "Antarctica/Troll", + false, + }, + { + "root_level_zone", + filepath.Join(tmpDir, "UTC"), + "UTC", + false, + }, + { + "outside_zoneinfo", + "/tmp/somefile", + "", + true, + }, + { + "empty_path", + "", + "", + true, + }, + { + "nonexistent_file", + filepath.Join(tmpDir, "Invalid", "Zone"), + "", + true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := extractTZFromPath(tt.path) + if tt.wantErr { + assert.Assert(t, err != nil, "expected error but got none") + } else { + assert.NilError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/limayaml/defaults_windows.go b/pkg/limayaml/defaults_windows.go new file mode 100644 index 00000000000..f2a43775361 --- /dev/null +++ b/pkg/limayaml/defaults_windows.go @@ -0,0 +1,11 @@ +//go:build windows + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package limayaml + +func hostTimeZone() string { + // WSL2 will automatically set the timezone + return "" +}