diff --git a/cmd/root.go b/cmd/root.go index fd367a1..4460397 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,10 +5,10 @@ import ( "path/filepath" "strings" - "github.com/cupcakearmy/autorestic/internal" "github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/flags" "github.com/cupcakearmy/autorestic/internal/lock" + "github.com/cupcakearmy/autorestic/internal/version" "github.com/spf13/cobra" homedir "github.com/mitchellh/go-homedir" @@ -26,7 +26,7 @@ func CheckErr(err error) { var cfgFile string var rootCmd = &cobra.Command{ - Version: internal.VERSION, + Version: version.VERSION, Use: "autorestic", Short: "CLI Wrapper for restic", Long: "Documentation:\thttps://autorestic.vercel.app\nSupport:\thttps://discord.gg/wS7RpYTYd2", @@ -41,7 +41,7 @@ func init() { rootCmd.PersistentFlags().BoolVar(&flags.CI, "ci", false, "CI mode disabled interactive mode and colors and enables verbosity") rootCmd.PersistentFlags().BoolVarP(&flags.VERBOSE, "verbose", "v", false, "verbose mode") rootCmd.PersistentFlags().StringVar(&flags.RESTIC_BIN, "restic-bin", "restic", "specify custom restic binary") - rootCmd.PersistentFlags().StringVar(&flags.DOCKER_IMAGE, "docker-image", "cupcakearmy/autorestic:"+internal.VERSION, "specify a custom docker image") + rootCmd.PersistentFlags().StringVar(&flags.DOCKER_IMAGE, "docker-image", "cupcakearmy/autorestic:"+version.VERSION, "specify a custom docker image") cobra.OnInitialize(initConfig) } diff --git a/internal/backend.go b/internal/backend.go index 1742fda..d90dff3 100644 --- a/internal/backend.go +++ b/internal/backend.go @@ -10,6 +10,8 @@ import ( "github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/flags" + "github.com/cupcakearmy/autorestic/internal/options" + "github.com/cupcakearmy/autorestic/internal/utils" ) type BackendRest struct { @@ -24,7 +26,7 @@ type Backend struct { Key string `mapstructure:"key,omitempty"` Env map[string]string `mapstructure:"env,omitempty"` Rest BackendRest `mapstructure:"rest,omitempty"` - Options Options `mapstructure:"options,omitempty"` + Options options.Options `mapstructure:"options,omitempty"` } func GetBackend(name string) (Backend, bool) { @@ -120,11 +122,15 @@ func (b Backend) validate() error { if err != nil { return err } - options := ExecuteOptions{Envs: env, Silent: true} + options := utils.ExecuteOptions{ + Envs: env, + Silent: true, + Global: GetConfig().Global, + } // Check if already initialized cmd := []string{"check"} cmd = append(cmd, combineBackendOptions("check", b)...) - _, _, err = ExecuteResticCommand(options, cmd...) + _, _, err = utils.ExecuteResticCommand(options, cmd...) if err == nil { return nil } else { @@ -132,7 +138,7 @@ func (b Backend) validate() error { colors.Body.Printf("Initializing backend \"%s\"...\n", b.name) cmd := []string{"init"} cmd = append(cmd, combineBackendOptions("init", b)...) - _, _, err := ExecuteResticCommand(options, cmd...) + _, _, err := utils.ExecuteResticCommand(options, cmd...) return err } } @@ -142,8 +148,11 @@ func (b Backend) Exec(args []string) error { if err != nil { return err } - options := ExecuteOptions{Envs: env} - _, out, err := ExecuteResticCommand(options, args...) + options := utils.ExecuteOptions{ + Envs: env, + Global: GetConfig().Global, + } + _, out, err := utils.ExecuteResticCommand(options, args...) if err != nil { colors.Error.Println(out) return err @@ -157,7 +166,7 @@ func (b Backend) ExecDocker(l Location, args []string) (int, string, error) { return -1, "", err } volume := l.From[0] - options := ExecuteOptions{ + options := utils.ExecuteOptions{ Command: "docker", Envs: env, } @@ -185,7 +194,7 @@ func (b Backend) ExecDocker(l Location, args []string) (int, string, error) { // No additional setup needed case "rclone": // Read host rclone config and mount it into the container - code, configFile, err := ExecuteCommand(ExecuteOptions{Command: "rclone"}, "config", "file") + code, configFile, err := utils.ExecuteCommand(utils.ExecuteOptions{Command: "rclone"}, "config", "file") if err != nil { return code, "", err } @@ -200,5 +209,5 @@ func (b Backend) ExecDocker(l Location, args []string) (int, string, error) { } docker = append(docker, flags.DOCKER_IMAGE, "-c", strings.Join(args, " ")) - return ExecuteCommand(options, docker...) + return utils.ExecuteCommand(options, docker...) } diff --git a/internal/bins/bins.go b/internal/bins/bins.go index 111df3a..fe5e7eb 100644 --- a/internal/bins/bins.go +++ b/internal/bins/bins.go @@ -14,9 +14,10 @@ import ( "strings" "github.com/blang/semver/v4" - "github.com/cupcakearmy/autorestic/internal" "github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/flags" + "github.com/cupcakearmy/autorestic/internal/utils" + "github.com/cupcakearmy/autorestic/internal/version" ) const INSTALL_PATH = "/usr/local/bin" @@ -115,7 +116,7 @@ func downloadAndInstallAsset(body GithubRelease, name string) error { } func InstallRestic() error { - installed := internal.CheckIfCommandIsCallable("restic") + installed := utils.CheckIfCommandIsCallable("restic") if installed { colors.Body.Println("restic already installed") return nil @@ -129,7 +130,7 @@ func InstallRestic() error { } func upgradeRestic() error { - _, _, err := internal.ExecuteCommand(internal.ExecuteOptions{ + _, _, err := utils.ExecuteCommand(utils.ExecuteOptions{ Command: flags.RESTIC_BIN, }, "self-update") return err @@ -147,7 +148,7 @@ func Upgrade(restic bool) error { } // Upgrade self - current, err := semver.ParseTolerant(internal.VERSION) + current, err := semver.ParseTolerant(version.VERSION) if err != nil { return err } diff --git a/internal/config.go b/internal/config.go index b66fb2f..4a951d7 100644 --- a/internal/config.go +++ b/internal/config.go @@ -11,23 +11,20 @@ import ( "github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/flags" "github.com/cupcakearmy/autorestic/internal/lock" + "github.com/cupcakearmy/autorestic/internal/options" + "github.com/cupcakearmy/autorestic/internal/utils" "github.com/joho/godotenv" "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" ) -const VERSION = "1.7.7" - -type OptionMap map[string][]interface{} -type Options map[string]OptionMap - type Config struct { Version string `mapstructure:"version"` Extras interface{} `mapstructure:"extras"` Locations map[string]Location `mapstructure:"locations"` Backends map[string]Backend `mapstructure:"backends"` - Global Options `mapstructure:"global"` + Global options.Options `mapstructure:"global"` } var once sync.Once @@ -184,7 +181,7 @@ func CheckConfig() error { if c == nil { return fmt.Errorf("config could not be loaded/found") } - if !CheckIfResticIsCallable() { + if !utils.CheckIfResticIsCallable() { return fmt.Errorf(`%s was not found. Install either with "autorestic install" or manually`, flags.RESTIC_BIN) } for name, backend := range c.Backends { @@ -263,7 +260,7 @@ func AddFlagsToCommand(cmd *cobra.Command, backend bool) { func (c *Config) SaveConfig() error { file := viper.ConfigFileUsed() - if err := CopyFile(file, file+".old"); err != nil { + if err := utils.CopyFile(file, file+".old"); err != nil { return err } colors.Secondary.Println("Saved a backup copy of your file next to the original.") @@ -274,40 +271,11 @@ func (c *Config) SaveConfig() error { return viper.WriteConfig() } -func optionToString(option string) string { - if !strings.HasPrefix(option, "-") { - return "--" + option - } - return option -} - -func appendOptionsToSlice(str *[]string, options OptionMap) { - for key, values := range options { - for _, value := range values { - // Bool - asBool, ok := value.(bool) - if ok && asBool { - *str = append(*str, optionToString(key)) - continue - } - *str = append(*str, optionToString(key), fmt.Sprint(value)) - } - } -} - -func getOptions(options Options, keys []string) []string { - var selected []string - for _, key := range keys { - appendOptionsToSlice(&selected, options[key]) - } - return selected -} - func combineBackendOptions(key string, b Backend) []string { // Priority: backend > global var options []string - gFlags := getOptions(GetConfig().Global, []string{key}) - bFlags := getOptions(b.Options, []string{"all", key}) + gFlags := GetConfig().Global.GetOptions([]string{key}) + bFlags := b.Options.GetOptions([]string{"all", key}) options = append(options, gFlags...) options = append(options, bFlags...) return options @@ -316,9 +284,9 @@ func combineBackendOptions(key string, b Backend) []string { func combineAllOptions(key string, l Location, b Backend) []string { // Priority: location > backend > global var options []string - gFlags := getOptions(GetConfig().Global, []string{key}) - bFlags := getOptions(b.Options, []string{"all", key}) - lFlags := getOptions(l.Options, []string{"all", key}) + gFlags := GetConfig().Global.GetOptions([]string{key}) + bFlags := b.Options.GetOptions([]string{"all", key}) + lFlags := l.Options.GetOptions([]string{"all", key}) options = append(options, gFlags...) options = append(options, bFlags...) options = append(options, lFlags...) diff --git a/internal/location.go b/internal/location.go index bf8deb5..55a1a4c 100644 --- a/internal/location.go +++ b/internal/location.go @@ -12,6 +12,8 @@ import ( "github.com/cupcakearmy/autorestic/internal/flags" "github.com/cupcakearmy/autorestic/internal/lock" "github.com/cupcakearmy/autorestic/internal/metadata" + "github.com/cupcakearmy/autorestic/internal/utils" + "github.com/cupcakearmy/autorestic/internal/options" "github.com/robfig/cron" ) @@ -49,7 +51,7 @@ type Location struct { To []string `mapstructure:"to,omitempty"` Hooks Hooks `mapstructure:"hooks,omitempty"` Cron string `mapstructure:"cron,omitempty"` - Options Options `mapstructure:"options,omitempty"` + Options options.Options `mapstructure:"options,omitempty"` ForgetOption LocationForgetOption `mapstructure:"forget,omitempty"` CopyOption LocationCopy `mapstructure:"copy,omitempty"` } @@ -101,14 +103,14 @@ func (l Location) validate() error { if _, ok := GetBackend(copyFrom); !ok { return fmt.Errorf(`location "%s" has an invalid backend "%s" in copy option`, l.name, copyFrom) } - if !ArrayContains(l.To, copyFrom) { + if !utils.ArrayContains(l.To, copyFrom) { return fmt.Errorf(`location "%s" has an invalid copy from "%s"`, l.name, copyFrom) } for _, copyToTarget := range copyTo { if _, ok := GetBackend(copyToTarget); !ok { return fmt.Errorf(`location "%s" has an invalid backend "%s" in copy option`, l.name, copyToTarget) } - if ArrayContains(l.To, copyToTarget) { + if utils.ArrayContains(l.To, copyToTarget) { return fmt.Errorf(`location "%s" cannot copy to "%s" as it's already a target`, l.name, copyToTarget) } } @@ -123,7 +125,7 @@ func (l Location) validate() error { return nil } -func (l Location) ExecuteHooks(commands []string, options ExecuteOptions) error { +func (l Location) ExecuteHooks(commands []string, options utils.ExecuteOptions) error { if len(commands) == 0 { return nil } @@ -137,7 +139,7 @@ func (l Location) ExecuteHooks(commands []string, options ExecuteOptions) error colors.Secondary.Println("\nRunning hooks") for _, command := range commands { colors.Body.Println("> " + command) - _, out, err := ExecuteCommand(options, "-c", command) + _, out, err := utils.ExecuteCommand(options, "-c", command) if err != nil { colors.Error.Println(out) return err @@ -176,7 +178,7 @@ func (l Location) Backup(cron bool, specificBackend string) []error { return errors } cwd, _ := GetPathRelativeToConfig(".") - options := ExecuteOptions{ + options := utils.ExecuteOptions{ Command: "bash", Dir: cwd, Envs: map[string]string{ @@ -221,8 +223,9 @@ func (l Location) Backup(cron bool, specificBackend string) []error { cmd = append(cmd, "--tag", buildTag("cron")) } cmd = append(cmd, "--tag", l.getLocationTags()) - backupOptions := ExecuteOptions{ + backupOptions := utils.ExecuteOptions{ Envs: env, + Global: GetConfig().Global, } var code int = 0 @@ -237,9 +240,9 @@ func (l Location) Backup(cron bool, specificBackend string) []error { } cmd = append(cmd, path) } - code, out, err = ExecuteResticCommand(backupOptions, cmd...) + code, out, err = utils.ExecuteResticCommand(backupOptions, cmd...) case TypeVolume: - ok := CheckIfVolumeExists(l.From[0]) + ok := utils.CheckIfVolumeExists(l.From[0]) if !ok { errors = append(errors, fmt.Errorf("volume \"%s\" does not exist", l.From[0])) continue @@ -277,8 +280,9 @@ func (l Location) Backup(cron bool, specificBackend string) []error { for k, v := range env2 { env[k+"2"] = v } - _, _, err := ExecuteResticCommand(ExecuteOptions{ + _, _, err := utils.ExecuteResticCommand(utils.ExecuteOptions{ Envs: env, + Global: GetConfig().Global, }, "copy", md.SnapshotID) if err != nil { @@ -335,8 +339,9 @@ func (l Location) Forget(prune bool, dry bool) error { if err != nil { return nil } - options := ExecuteOptions{ + options := utils.ExecuteOptions{ Envs: env, + Global: GetConfig().Global, } cmd := []string{"forget", "--tag", l.getLocationTags()} if prune { @@ -346,7 +351,7 @@ func (l Location) Forget(prune bool, dry bool) error { cmd = append(cmd, "--dry-run") } cmd = append(cmd, combineAllOptions("forget", l, backend)...) - _, _, err = ExecuteResticCommand(options, cmd...) + _, _, err = utils.ExecuteResticCommand(options, cmd...) if err != nil { return err } diff --git a/internal/options/options.go b/internal/options/options.go new file mode 100644 index 0000000..b09bf4d --- /dev/null +++ b/internal/options/options.go @@ -0,0 +1,38 @@ +package options + +import ( + "fmt" + "strings" +) + +type OptionMap map[string][]interface{} +type Options map[string]OptionMap + +func (o Options) GetOptions(keys []string) []string { + var selected []string + for _, key := range keys { + o[key].AppendOptionsToSlice(&selected) + } + return selected +} + +func (m OptionMap) AppendOptionsToSlice(str *[]string) { + for key, values := range m { + for _, value := range values { + // Bool + asBool, ok := value.(bool) + if ok && asBool { + *str = append(*str, optionToString(key)) + continue + } + *str = append(*str, optionToString(key), fmt.Sprint(value)) + } + } +} + +func optionToString(option string) string { + if !strings.HasPrefix(option, "-") { + return "--" + option + } + return option +} diff --git a/internal/utils.go b/internal/utils/utils.go similarity index 93% rename from internal/utils.go rename to internal/utils/utils.go index c8bf799..602bb90 100644 --- a/internal/utils.go +++ b/internal/utils/utils.go @@ -1,4 +1,4 @@ -package internal +package utils import ( "bytes" @@ -9,6 +9,7 @@ import ( "github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/flags" + "github.com/cupcakearmy/autorestic/internal/options" "github.com/fatih/color" ) @@ -26,6 +27,7 @@ type ExecuteOptions struct { Envs map[string]string Dir string Silent bool + Global options.Options } type ColoredWriter struct { @@ -78,8 +80,7 @@ func ExecuteCommand(options ExecuteOptions, args ...string) (int, string, error) func ExecuteResticCommand(options ExecuteOptions, args ...string) (int, string, error) { options.Command = flags.RESTIC_BIN - var c = GetConfig() - var optionsAsString = getOptions(c.Global, []string{"all"}) + var optionsAsString = options.Global.GetOptions([]string{"all"}) args = append(optionsAsString, args...) return ExecuteCommand(options, args...) } diff --git a/internal/version/version.go b/internal/version/version.go new file mode 100644 index 0000000..50922bf --- /dev/null +++ b/internal/version/version.go @@ -0,0 +1,3 @@ +package version + +const VERSION = "1.7.7"