This commit is contained in:
rdelaage 2023-10-02 03:03:58 -07:00 committed by GitHub
commit 5b46897feb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 98 additions and 73 deletions

View File

@ -5,10 +5,10 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/cupcakearmy/autorestic/internal"
"github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/flags" "github.com/cupcakearmy/autorestic/internal/flags"
"github.com/cupcakearmy/autorestic/internal/lock" "github.com/cupcakearmy/autorestic/internal/lock"
"github.com/cupcakearmy/autorestic/internal/version"
"github.com/spf13/cobra" "github.com/spf13/cobra"
homedir "github.com/mitchellh/go-homedir" homedir "github.com/mitchellh/go-homedir"
@ -26,7 +26,7 @@ func CheckErr(err error) {
var cfgFile string var cfgFile string
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Version: internal.VERSION, Version: version.VERSION,
Use: "autorestic", Use: "autorestic",
Short: "CLI Wrapper for restic", Short: "CLI Wrapper for restic",
Long: "Documentation:\thttps://autorestic.vercel.app\nSupport:\thttps://discord.gg/wS7RpYTYd2", 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().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().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.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) cobra.OnInitialize(initConfig)
} }

View File

@ -10,6 +10,8 @@ import (
"github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/flags" "github.com/cupcakearmy/autorestic/internal/flags"
"github.com/cupcakearmy/autorestic/internal/options"
"github.com/cupcakearmy/autorestic/internal/utils"
) )
type BackendRest struct { type BackendRest struct {
@ -24,7 +26,7 @@ type Backend struct {
Key string `mapstructure:"key,omitempty"` Key string `mapstructure:"key,omitempty"`
Env map[string]string `mapstructure:"env,omitempty"` Env map[string]string `mapstructure:"env,omitempty"`
Rest BackendRest `mapstructure:"rest,omitempty"` Rest BackendRest `mapstructure:"rest,omitempty"`
Options Options `mapstructure:"options,omitempty"` Options options.Options `mapstructure:"options,omitempty"`
} }
func GetBackend(name string) (Backend, bool) { func GetBackend(name string) (Backend, bool) {
@ -120,11 +122,15 @@ func (b Backend) validate() error {
if err != nil { if err != nil {
return err return err
} }
options := ExecuteOptions{Envs: env, Silent: true} options := utils.ExecuteOptions{
Envs: env,
Silent: true,
Global: GetConfig().Global,
}
// Check if already initialized // Check if already initialized
cmd := []string{"check"} cmd := []string{"check"}
cmd = append(cmd, combineBackendOptions("check", b)...) cmd = append(cmd, combineBackendOptions("check", b)...)
_, _, err = ExecuteResticCommand(options, cmd...) _, _, err = utils.ExecuteResticCommand(options, cmd...)
if err == nil { if err == nil {
return nil return nil
} else { } else {
@ -132,7 +138,7 @@ func (b Backend) validate() error {
colors.Body.Printf("Initializing backend \"%s\"...\n", b.name) colors.Body.Printf("Initializing backend \"%s\"...\n", b.name)
cmd := []string{"init"} cmd := []string{"init"}
cmd = append(cmd, combineBackendOptions("init", b)...) cmd = append(cmd, combineBackendOptions("init", b)...)
_, _, err := ExecuteResticCommand(options, cmd...) _, _, err := utils.ExecuteResticCommand(options, cmd...)
return err return err
} }
} }
@ -142,8 +148,11 @@ func (b Backend) Exec(args []string) error {
if err != nil { if err != nil {
return err return err
} }
options := ExecuteOptions{Envs: env} options := utils.ExecuteOptions{
_, out, err := ExecuteResticCommand(options, args...) Envs: env,
Global: GetConfig().Global,
}
_, out, err := utils.ExecuteResticCommand(options, args...)
if err != nil { if err != nil {
colors.Error.Println(out) colors.Error.Println(out)
return err return err
@ -157,7 +166,7 @@ func (b Backend) ExecDocker(l Location, args []string) (int, string, error) {
return -1, "", err return -1, "", err
} }
volume := l.From[0] volume := l.From[0]
options := ExecuteOptions{ options := utils.ExecuteOptions{
Command: "docker", Command: "docker",
Envs: env, Envs: env,
} }
@ -185,7 +194,7 @@ func (b Backend) ExecDocker(l Location, args []string) (int, string, error) {
// No additional setup needed // No additional setup needed
case "rclone": case "rclone":
// Read host rclone config and mount it into the container // 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 { if err != nil {
return code, "", err 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, " ")) docker = append(docker, flags.DOCKER_IMAGE, "-c", strings.Join(args, " "))
return ExecuteCommand(options, docker...) return utils.ExecuteCommand(options, docker...)
} }

View File

@ -14,9 +14,10 @@ import (
"strings" "strings"
"github.com/blang/semver/v4" "github.com/blang/semver/v4"
"github.com/cupcakearmy/autorestic/internal"
"github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/flags" "github.com/cupcakearmy/autorestic/internal/flags"
"github.com/cupcakearmy/autorestic/internal/utils"
"github.com/cupcakearmy/autorestic/internal/version"
) )
const INSTALL_PATH = "/usr/local/bin" const INSTALL_PATH = "/usr/local/bin"
@ -115,7 +116,7 @@ func downloadAndInstallAsset(body GithubRelease, name string) error {
} }
func InstallRestic() error { func InstallRestic() error {
installed := internal.CheckIfCommandIsCallable("restic") installed := utils.CheckIfCommandIsCallable("restic")
if installed { if installed {
colors.Body.Println("restic already installed") colors.Body.Println("restic already installed")
return nil return nil
@ -129,7 +130,7 @@ func InstallRestic() error {
} }
func upgradeRestic() error { func upgradeRestic() error {
_, _, err := internal.ExecuteCommand(internal.ExecuteOptions{ _, _, err := utils.ExecuteCommand(utils.ExecuteOptions{
Command: flags.RESTIC_BIN, Command: flags.RESTIC_BIN,
}, "self-update") }, "self-update")
return err return err
@ -147,7 +148,7 @@ func Upgrade(restic bool) error {
} }
// Upgrade self // Upgrade self
current, err := semver.ParseTolerant(internal.VERSION) current, err := semver.ParseTolerant(version.VERSION)
if err != nil { if err != nil {
return err return err
} }

View File

@ -11,23 +11,20 @@ import (
"github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/flags" "github.com/cupcakearmy/autorestic/internal/flags"
"github.com/cupcakearmy/autorestic/internal/lock" "github.com/cupcakearmy/autorestic/internal/lock"
"github.com/cupcakearmy/autorestic/internal/options"
"github.com/cupcakearmy/autorestic/internal/utils"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
const VERSION = "1.7.7"
type OptionMap map[string][]interface{}
type Options map[string]OptionMap
type Config struct { type Config struct {
Version string `mapstructure:"version"` Version string `mapstructure:"version"`
Extras interface{} `mapstructure:"extras"` Extras interface{} `mapstructure:"extras"`
Locations map[string]Location `mapstructure:"locations"` Locations map[string]Location `mapstructure:"locations"`
Backends map[string]Backend `mapstructure:"backends"` Backends map[string]Backend `mapstructure:"backends"`
Global Options `mapstructure:"global"` Global options.Options `mapstructure:"global"`
} }
var once sync.Once var once sync.Once
@ -184,7 +181,7 @@ func CheckConfig() error {
if c == nil { if c == nil {
return fmt.Errorf("config could not be loaded/found") 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) return fmt.Errorf(`%s was not found. Install either with "autorestic install" or manually`, flags.RESTIC_BIN)
} }
for name, backend := range c.Backends { for name, backend := range c.Backends {
@ -263,7 +260,7 @@ func AddFlagsToCommand(cmd *cobra.Command, backend bool) {
func (c *Config) SaveConfig() error { func (c *Config) SaveConfig() error {
file := viper.ConfigFileUsed() file := viper.ConfigFileUsed()
if err := CopyFile(file, file+".old"); err != nil { if err := utils.CopyFile(file, file+".old"); err != nil {
return err return err
} }
colors.Secondary.Println("Saved a backup copy of your file next to the original.") 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() 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 { func combineBackendOptions(key string, b Backend) []string {
// Priority: backend > global // Priority: backend > global
var options []string var options []string
gFlags := getOptions(GetConfig().Global, []string{key}) gFlags := GetConfig().Global.GetOptions([]string{key})
bFlags := getOptions(b.Options, []string{"all", key}) bFlags := b.Options.GetOptions([]string{"all", key})
options = append(options, gFlags...) options = append(options, gFlags...)
options = append(options, bFlags...) options = append(options, bFlags...)
return options return options
@ -316,9 +284,9 @@ func combineBackendOptions(key string, b Backend) []string {
func combineAllOptions(key string, l Location, b Backend) []string { func combineAllOptions(key string, l Location, b Backend) []string {
// Priority: location > backend > global // Priority: location > backend > global
var options []string var options []string
gFlags := getOptions(GetConfig().Global, []string{key}) gFlags := GetConfig().Global.GetOptions([]string{key})
bFlags := getOptions(b.Options, []string{"all", key}) bFlags := b.Options.GetOptions([]string{"all", key})
lFlags := getOptions(l.Options, []string{"all", key}) lFlags := l.Options.GetOptions([]string{"all", key})
options = append(options, gFlags...) options = append(options, gFlags...)
options = append(options, bFlags...) options = append(options, bFlags...)
options = append(options, lFlags...) options = append(options, lFlags...)

View File

@ -12,6 +12,8 @@ import (
"github.com/cupcakearmy/autorestic/internal/flags" "github.com/cupcakearmy/autorestic/internal/flags"
"github.com/cupcakearmy/autorestic/internal/lock" "github.com/cupcakearmy/autorestic/internal/lock"
"github.com/cupcakearmy/autorestic/internal/metadata" "github.com/cupcakearmy/autorestic/internal/metadata"
"github.com/cupcakearmy/autorestic/internal/utils"
"github.com/cupcakearmy/autorestic/internal/options"
"github.com/robfig/cron" "github.com/robfig/cron"
) )
@ -49,7 +51,7 @@ type Location struct {
To []string `mapstructure:"to,omitempty"` To []string `mapstructure:"to,omitempty"`
Hooks Hooks `mapstructure:"hooks,omitempty"` Hooks Hooks `mapstructure:"hooks,omitempty"`
Cron string `mapstructure:"cron,omitempty"` Cron string `mapstructure:"cron,omitempty"`
Options Options `mapstructure:"options,omitempty"` Options options.Options `mapstructure:"options,omitempty"`
ForgetOption LocationForgetOption `mapstructure:"forget,omitempty"` ForgetOption LocationForgetOption `mapstructure:"forget,omitempty"`
CopyOption LocationCopy `mapstructure:"copy,omitempty"` CopyOption LocationCopy `mapstructure:"copy,omitempty"`
} }
@ -101,14 +103,14 @@ func (l Location) validate() error {
if _, ok := GetBackend(copyFrom); !ok { if _, ok := GetBackend(copyFrom); !ok {
return fmt.Errorf(`location "%s" has an invalid backend "%s" in copy option`, l.name, copyFrom) 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) return fmt.Errorf(`location "%s" has an invalid copy from "%s"`, l.name, copyFrom)
} }
for _, copyToTarget := range copyTo { for _, copyToTarget := range copyTo {
if _, ok := GetBackend(copyToTarget); !ok { if _, ok := GetBackend(copyToTarget); !ok {
return fmt.Errorf(`location "%s" has an invalid backend "%s" in copy option`, l.name, copyToTarget) 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) 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 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 { if len(commands) == 0 {
return nil return nil
} }
@ -137,7 +139,7 @@ func (l Location) ExecuteHooks(commands []string, options ExecuteOptions) error
colors.Secondary.Println("\nRunning hooks") colors.Secondary.Println("\nRunning hooks")
for _, command := range commands { for _, command := range commands {
colors.Body.Println("> " + command) colors.Body.Println("> " + command)
_, out, err := ExecuteCommand(options, "-c", command) _, out, err := utils.ExecuteCommand(options, "-c", command)
if err != nil { if err != nil {
colors.Error.Println(out) colors.Error.Println(out)
return err return err
@ -176,7 +178,7 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
return errors return errors
} }
cwd, _ := GetPathRelativeToConfig(".") cwd, _ := GetPathRelativeToConfig(".")
options := ExecuteOptions{ options := utils.ExecuteOptions{
Command: "bash", Command: "bash",
Dir: cwd, Dir: cwd,
Envs: map[string]string{ 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", buildTag("cron"))
} }
cmd = append(cmd, "--tag", l.getLocationTags()) cmd = append(cmd, "--tag", l.getLocationTags())
backupOptions := ExecuteOptions{ backupOptions := utils.ExecuteOptions{
Envs: env, Envs: env,
Global: GetConfig().Global,
} }
var code int = 0 var code int = 0
@ -237,9 +240,9 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
} }
cmd = append(cmd, path) cmd = append(cmd, path)
} }
code, out, err = ExecuteResticCommand(backupOptions, cmd...) code, out, err = utils.ExecuteResticCommand(backupOptions, cmd...)
case TypeVolume: case TypeVolume:
ok := CheckIfVolumeExists(l.From[0]) ok := utils.CheckIfVolumeExists(l.From[0])
if !ok { if !ok {
errors = append(errors, fmt.Errorf("volume \"%s\" does not exist", l.From[0])) errors = append(errors, fmt.Errorf("volume \"%s\" does not exist", l.From[0]))
continue continue
@ -277,8 +280,9 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
for k, v := range env2 { for k, v := range env2 {
env[k+"2"] = v env[k+"2"] = v
} }
_, _, err := ExecuteResticCommand(ExecuteOptions{ _, _, err := utils.ExecuteResticCommand(utils.ExecuteOptions{
Envs: env, Envs: env,
Global: GetConfig().Global,
}, "copy", md.SnapshotID) }, "copy", md.SnapshotID)
if err != nil { if err != nil {
@ -335,8 +339,9 @@ func (l Location) Forget(prune bool, dry bool) error {
if err != nil { if err != nil {
return nil return nil
} }
options := ExecuteOptions{ options := utils.ExecuteOptions{
Envs: env, Envs: env,
Global: GetConfig().Global,
} }
cmd := []string{"forget", "--tag", l.getLocationTags()} cmd := []string{"forget", "--tag", l.getLocationTags()}
if prune { if prune {
@ -346,7 +351,7 @@ func (l Location) Forget(prune bool, dry bool) error {
cmd = append(cmd, "--dry-run") cmd = append(cmd, "--dry-run")
} }
cmd = append(cmd, combineAllOptions("forget", l, backend)...) cmd = append(cmd, combineAllOptions("forget", l, backend)...)
_, _, err = ExecuteResticCommand(options, cmd...) _, _, err = utils.ExecuteResticCommand(options, cmd...)
if err != nil { if err != nil {
return err return err
} }

View File

@ -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
}

View File

@ -1,4 +1,4 @@
package internal package utils
import ( import (
"bytes" "bytes"
@ -9,6 +9,7 @@ import (
"github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/flags" "github.com/cupcakearmy/autorestic/internal/flags"
"github.com/cupcakearmy/autorestic/internal/options"
"github.com/fatih/color" "github.com/fatih/color"
) )
@ -26,6 +27,7 @@ type ExecuteOptions struct {
Envs map[string]string Envs map[string]string
Dir string Dir string
Silent bool Silent bool
Global options.Options
} }
type ColoredWriter struct { 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) { func ExecuteResticCommand(options ExecuteOptions, args ...string) (int, string, error) {
options.Command = flags.RESTIC_BIN options.Command = flags.RESTIC_BIN
var c = GetConfig() var optionsAsString = options.Global.GetOptions([]string{"all"})
var optionsAsString = getOptions(c.Global, []string{"all"})
args = append(optionsAsString, args...) args = append(optionsAsString, args...)
return ExecuteCommand(options, args...) return ExecuteCommand(options, args...)
} }

View File

@ -0,0 +1,3 @@
package version
const VERSION = "1.7.7"