diff --git a/.goreleaser.yml b/.goreleaser.yml index 5f567ff7..87a24d73 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -197,7 +197,7 @@ brews: commit_author: name: goreleaser email: fred@creativeprojects.tech - folder: Formula + directory: Formula homepage: https://github.com/creativeprojects/{{ .ProjectName }} description: Configuration profiles for restic backup license: "GPL-3.0-only" diff --git a/codecov.yml b/codecov.yml index 53831104..f2ce922b 100644 --- a/codecov.yml +++ b/codecov.yml @@ -25,7 +25,7 @@ coverage: default: target: auto threshold: "2%" - patch: + patch: default: target: "70%" threshold: "2%" diff --git a/filesearch/filesearch.go b/filesearch/filesearch.go index 0d55a634..ce9d9556 100644 --- a/filesearch/filesearch.go +++ b/filesearch/filesearch.go @@ -17,7 +17,7 @@ import ( ) var ( - XDGAppName = "resticprofile" + AppName = "resticprofile" // configurationExtensions list the possible extensions for the config file configurationExtensions = []string{ @@ -40,6 +40,10 @@ var ( "/opt/local/etc/resticprofile/", } + addConfigurationLocationsDarwin = []string{ + ".config/" + AppName + "/", + } + defaultConfigurationLocationsWindows = []string{ "c:\\restic\\", "c:\\resticprofile\\", @@ -65,7 +69,7 @@ var ( } ) -var fs afero.Fs +var fs afero.Fs // we could probably change the implementation to use fs.FS instead func init() { fs = afero.NewOsFs() @@ -187,6 +191,8 @@ func FindResticBinary(configLocation string) (string, error) { return filename, nil } } + clog.Tracef("could not find restic binary %q in any of these locations: %s", binaryFile, strings.Join(paths, ", ")) + // Last resort, search from the OS PATH filename, err := exec.LookPath(binaryFile) if err != nil { @@ -211,9 +217,11 @@ func ShellExpand(filename string) (string, error) { } func getSearchConfigurationLocations() []string { - locations := []string{filepath.Join(xdg.ConfigHome, XDGAppName)} + home, _ := os.UserHomeDir() + + locations := []string{filepath.Join(xdg.ConfigHome, AppName)} for _, configDir := range xdg.ConfigDirs { - locations = append(locations, filepath.Join(configDir, XDGAppName)) + locations = append(locations, filepath.Join(configDir, AppName)) } if platform.IsWindows() { @@ -222,7 +230,11 @@ func getSearchConfigurationLocations() []string { locations = append(locations, defaultConfigurationLocationsUnix...) } - if home, err := os.UserHomeDir(); err == nil { + if platform.IsDarwin() { + locations = append(locations, addRootToRelativePaths(home, addConfigurationLocationsDarwin)...) + } + + if home != "" { locations = append(locations, home) } @@ -244,7 +256,7 @@ func getSearchBinaryLocations() []string { paths = defaultBinaryLocationsUnix } if home, err := os.UserHomeDir(); err == nil { - paths = append(paths, filepath.Join(home, ".local/bin/"), filepath.Join(home, "bin/")) + paths = append(paths, addRootToRelativePaths(home, []string{".local/bin/", "bin/"})...) } return paths } @@ -260,3 +272,22 @@ func fileExists(filename string) bool { _, err := fs.Stat(filename) return err == nil || errors.Is(err, iofs.ErrExist) } + +func addRootToRelativePaths(home string, paths []string) []string { + if platform.IsWindows() { + return paths + } + if home == "" { + return paths + } + rootedPaths := make([]string, len(paths)) + for i, path := range paths { + if filepath.IsAbs(path) { + rootedPaths[i] = path + continue + } + path = strings.TrimPrefix(path, "~/") + rootedPaths[i] = filepath.Join(home, path) + } + return rootedPaths +} diff --git a/filesearch/filesearch_test.go b/filesearch/filesearch_test.go index fbc00ee6..dac8d0c5 100644 --- a/filesearch/filesearch_test.go +++ b/filesearch/filesearch_test.go @@ -40,6 +40,8 @@ func TestMain(t *testing.M) { // ConfigDirs: [C:\ProgramData C:\Users\runneradmin\AppData\Roaming] // ApplicationDirs: [C:\Users\runneradmin\AppData\Roaming\Microsoft\Windows\Start Menu\Programs C:\ProgramData\Microsoft\Windows\Start Menu\Programs] func TestDefaultConfigDirs(t *testing.T) { + t.Parallel() + t.Log("ConfigHome:", xdg.ConfigHome) t.Log("ConfigDirs:", xdg.ConfigDirs) t.Log("ApplicationDirs:", xdg.ApplicationDirs) @@ -192,6 +194,8 @@ func testLocations(t *testing.T) []testLocation { } func TestFindConfigurationFile(t *testing.T) { + t.Parallel() + // Work from a temporary directory err := os.Chdir(os.TempDir()) require.NoError(t, err) @@ -206,7 +210,7 @@ func TestFindConfigurationFile(t *testing.T) { var err error // Install empty config file if location.realPath != "" { - err = fs.MkdirAll(location.realPath, 0700) + err = fs.MkdirAll(location.realPath, 0o700) require.NoError(t, err) } file, err := fs.Create(filepath.Join(location.realPath, location.realFile)) @@ -230,12 +234,16 @@ func TestFindConfigurationFile(t *testing.T) { } func TestCannotFindConfigurationFile(t *testing.T) { + t.Parallel() + found, err := FindConfigurationFile("some_config_file") assert.Empty(t, found) assert.Error(t, err) } func TestFindResticBinary(t *testing.T) { + t.Parallel() + binary, err := FindResticBinary("some_other_name") if binary != "" { assert.True(t, strings.HasSuffix(binary, getResticBinaryName())) @@ -246,6 +254,8 @@ func TestFindResticBinary(t *testing.T) { } func TestFindResticBinaryWithTilde(t *testing.T) { + t.Parallel() + if runtime.GOOS == "windows" { t.Skip("not supported on Windows") return @@ -253,7 +263,7 @@ func TestFindResticBinaryWithTilde(t *testing.T) { home, err := os.UserHomeDir() require.NoError(t, err) - tempFile, err := afero.TempFile(fs, home, "TestFindResticBinaryWithTilde") + tempFile, err := afero.TempFile(fs, home, t.Name()) require.NoError(t, err) tempFile.Close() defer func() { @@ -267,6 +277,8 @@ func TestFindResticBinaryWithTilde(t *testing.T) { } func TestShellExpand(t *testing.T) { + t.Parallel() + if runtime.GOOS == "windows" { t.Skip("not supported on Windows") return @@ -290,6 +302,8 @@ func TestShellExpand(t *testing.T) { for _, testItem := range testData { t.Run(testItem.source, func(t *testing.T) { + t.Parallel() + result, err := ShellExpand(testItem.source) require.NoError(t, err) assert.Equal(t, testItem.expected, result) @@ -298,6 +312,8 @@ func TestShellExpand(t *testing.T) { } func TestFindConfigurationIncludes(t *testing.T) { + t.Parallel() + testID := fmt.Sprintf("%d", uint32(time.Now().UnixNano())) tempDir := os.TempDir() files := []string{ @@ -309,7 +325,9 @@ func TestFindConfigurationIncludes(t *testing.T) { for _, file := range files { require.NoError(t, afero.WriteFile(fs, file, []byte{}, iofs.ModePerm)) - defer fs.Remove(file) // defer stack is ok for cleanup + t.Cleanup(func() { + _ = fs.Remove(file) + }) } testData := []struct { @@ -335,6 +353,8 @@ func TestFindConfigurationIncludes(t *testing.T) { for _, test := range testData { t.Run(strings.Join(test.includes, ","), func(t *testing.T) { + t.Parallel() + result, err := FindConfigurationIncludes(files[0], test.includes) if test.expected == nil { assert.Nil(t, result) @@ -350,3 +370,36 @@ func TestFindConfigurationIncludes(t *testing.T) { }) } } + +func TestAddRootToRelativePaths(t *testing.T) { + t.Parallel() + + if platform.IsWindows() { + t.Skip("not supported on Windows") + } + + testCases := []struct { + root string + inputPath []string + outputPath []string + }{ + { + root: "", + inputPath: []string{"", "dir", "~/user", "/root"}, + outputPath: []string{"", "dir", "~/user", "/root"}, + }, + { + root: "/home", + inputPath: []string{"", "dir", "~/user", "/root"}, + outputPath: []string{"/home", "/home/dir", "/home/user", "/root"}, + }, + } + for _, testCase := range testCases { + t.Run(testCase.root, func(t *testing.T) { + t.Parallel() + + result := addRootToRelativePaths(testCase.root, testCase.inputPath) + assert.Equal(t, testCase.outputPath, result) + }) + } +} diff --git a/schedule/scheduler_config.go b/schedule/scheduler_config.go index 0b8a73c6..8521dba5 100644 --- a/schedule/scheduler_config.go +++ b/schedule/scheduler_config.go @@ -7,7 +7,6 @@ import ( "github.com/creativeprojects/resticprofile/config" "github.com/creativeprojects/resticprofile/constants" - "github.com/spf13/afero" ) type SchedulerConfig interface { @@ -35,16 +34,13 @@ type SchedulerWindows struct{} func (s SchedulerWindows) Type() string { return constants.SchedulerWindows } func (s SchedulerWindows) Convert(_ string) SchedulerConfig { return s } -type SchedulerLaunchd struct { - Fs afero.Fs -} +type SchedulerLaunchd struct{} func (s SchedulerLaunchd) Type() string { return constants.SchedulerLaunchd } func (s SchedulerLaunchd) Convert(_ string) SchedulerConfig { return s } // SchedulerCrond configures crond compatible schedulers, either needs CrontabBinary or CrontabFile type SchedulerCrond struct { - Fs afero.Fs CrontabFile string CrontabBinary string Username string @@ -54,7 +50,6 @@ func (s SchedulerCrond) Type() string { return constants.Sch func (s SchedulerCrond) Convert(_ string) SchedulerConfig { return s } type SchedulerSystemd struct { - Fs afero.Fs UnitTemplate string TimerTemplate string }