From 4375a38bbac0795b1e7ea37b9ef2d7f856b2688b Mon Sep 17 00:00:00 2001 From: Romain de Laage Date: Mon, 11 Jul 2022 20:41:30 +0200 Subject: [PATCH] Add restore hooks feature --- cmd/restore.go | 10 ++++- internal/config.go | 14 +++--- internal/location.go | 105 +++++++++++++++++++++++++++++++------------ 3 files changed, 93 insertions(+), 36 deletions(-) diff --git a/cmd/restore.go b/cmd/restore.go index 8faf4a7..2b748d9 100644 --- a/cmd/restore.go +++ b/cmd/restore.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/cupcakearmy/autorestic/internal" + "github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/lock" "github.com/spf13/cobra" ) @@ -42,8 +43,13 @@ var restoreCmd = &cobra.Command{ } } - err = l.Restore(target, from, force, snapshot, optional) - CheckErr(err) + errs := l.Restore(target, from, force, snapshot, optional) + for _, err := range errs { + colors.Error.Printf("%s\n\n", err) + } + if len(errs) > 0 { + CheckErr(fmt.Errorf("%d errors were found", len(errs))) + } }, } diff --git a/internal/config.go b/internal/config.go index 7db81b1..f6cb2f1 100644 --- a/internal/config.go +++ b/internal/config.go @@ -83,7 +83,7 @@ func GetConfig() *Config { exitConfig(nil, "version specified in config file is not an int") } else { // Check for version - if version != 2 { + if version != 3 { exitConfig(nil, "unsupported config version number. please check the docs for migration\nhttps://autorestic.vercel.app/migration/") } } @@ -132,10 +132,14 @@ func (c *Config) Describe() { tmp = "" hooks := map[string][]string{ - "Before": l.Hooks.Before, - "After": l.Hooks.After, - "Failure": l.Hooks.Failure, - "Success": l.Hooks.Success, + "Before backup": l.Hooks.BackupOption.Before, + "After backup": l.Hooks.BackupOption.After, + "Failure backup": l.Hooks.BackupOption.Failure, + "Success backup": l.Hooks.BackupOption.Success, + "Before restore": l.Hooks.RestoreOption.Before, + "After restore": l.Hooks.RestoreOption.After, + "Failure restore": l.Hooks.RestoreOption.Failure, + "Success restore": l.Hooks.RestoreOption.Success, } for hook, commands := range hooks { if len(commands) > 0 { diff --git a/internal/location.go b/internal/location.go index d4c73e7..85db6cf 100644 --- a/internal/location.go +++ b/internal/location.go @@ -33,6 +33,13 @@ const ( ) type Hooks struct { + RestoreOption HooksList `mapstructure:"restore,omitempty"` + BackupOption HooksList `mapstructure:"backup,omitempty"` +} + +type LocationCopy = map[string][]string + +type HooksList struct { Dir string `mapstructure:"dir"` Before HookArray `mapstructure:"before,omitempty"` After HookArray `mapstructure:"after,omitempty"` @@ -40,18 +47,16 @@ type Hooks struct { Failure HookArray `mapstructure:"failure,omitempty"` } -type LocationCopy = map[string][]string - type Location struct { - name string `mapstructure:",omitempty"` - From []string `mapstructure:"from,omitempty"` - Type string `mapstructure:"type,omitempty"` - To []string `mapstructure:"to,omitempty"` - Hooks Hooks `mapstructure:"hooks,omitempty"` - Cron string `mapstructure:"cron,omitempty"` - Options Options `mapstructure:"options,omitempty"` - ForgetOption LocationForgetOption `mapstructure:"forget,omitempty"` - CopyOption LocationCopy `mapstructure:"copy,omitempty"` + name string `mapstructure:",omitempty"` + From []string `mapstructure:"from,omitempty"` + Type string `mapstructure:"type,omitempty"` + To []string `mapstructure:"to,omitempty"` + Hooks Hooks `mapstructure:"hooks,omitempty"` + Cron string `mapstructure:"cron,omitempty"` + Options Options `mapstructure:"options,omitempty"` + ForgetOption LocationForgetOption `mapstructure:"forget,omitempty"` + CopyOption LocationCopy `mapstructure:"copy,omitempty"` } func GetLocation(name string) (Location, bool) { @@ -123,12 +128,12 @@ func (l Location) validate() error { return nil } -func (l Location) ExecuteHooks(commands []string, options ExecuteOptions) error { +func (l Location) ExecuteHooks(commands []string, directory string, options ExecuteOptions) error { if len(commands) == 0 { return nil } - if l.Hooks.Dir != "" { - if dir, err := GetPathRelativeToConfig(l.Hooks.Dir); err != nil { + if directory != "" { + if dir, err := GetPathRelativeToConfig(directory); err != nil { return err } else { options.Dir = dir @@ -190,7 +195,7 @@ func (l Location) Backup(cron bool, specificBackend string) []error { } // Hooks - if err := l.ExecuteHooks(l.Hooks.Before, options); err != nil { + if err := l.ExecuteHooks(l.Hooks.BackupOption.Before, l.Hooks.BackupOption.Dir, options); err != nil { errors = append(errors, err) goto after } @@ -290,7 +295,7 @@ func (l Location) Backup(cron bool, specificBackend string) []error { } // After hooks - if err := l.ExecuteHooks(l.Hooks.After, options); err != nil { + if err := l.ExecuteHooks(l.Hooks.BackupOption.After, l.Hooks.BackupOption.Dir, options); err != nil { errors = append(errors, err) } @@ -298,11 +303,11 @@ after: var commands []string var isSuccess = len(errors) == 0 if isSuccess { - commands = l.Hooks.Success + commands = l.Hooks.BackupOption.Success } else { - commands = l.Hooks.Failure + commands = l.Hooks.BackupOption.Failure } - if err := l.ExecuteHooks(commands, options); err != nil { + if err := l.ExecuteHooks(commands, l.Hooks.BackupOption.Dir, options); err != nil { errors = append(errors, err) } @@ -367,11 +372,35 @@ func buildRestoreCommand(l Location, to string, snapshot string, options []strin return base } -func (l Location) Restore(to, from string, force bool, snapshot string, options []string) error { +func (l Location) Restore(to, from string, force bool, snapshot string, options []string) (errors []error) { + cwd, _ := GetPathRelativeToConfig(".") + hooksOptions := ExecuteOptions{ + Command: "bash", + Dir: cwd, + Envs: map[string]string{ + "AUTORESTIC_LOCATION": l.name, + }, + } + + defer func() { + var commands []string + var isSuccess = len(errors) == 0 + if isSuccess { + commands = l.Hooks.RestoreOption.Success + } else { + commands = l.Hooks.RestoreOption.Failure + } + if err := l.ExecuteHooks(commands, l.Hooks.RestoreOption.Dir, hooksOptions); err != nil { + errors = append(errors, err) + } + + colors.Success.Println("Done") + }() + if from == "" { from = l.To[0] } else if !l.hasBackend(from) { - return fmt.Errorf("invalid backend: \"%s\"", from) + errors = append(errors, fmt.Errorf("invalid backend: \"%s\"", from)) } if snapshot == "" { @@ -382,15 +411,23 @@ func (l Location) Restore(to, from string, force bool, snapshot string, options backend, _ := GetBackend(from) colors.Secondary.Printf("Restoring %s@%s → %s\n", snapshot, backend.name, to) + // Before Hooks for restore + if err := l.ExecuteHooks(l.Hooks.RestoreOption.Before, l.Hooks.RestoreOption.Dir, hooksOptions); err != nil { + errors = append(errors, err) + return + } + t, err := l.getType() if err != nil { - return err + errors = append(errors, err) + return } switch t { case TypeLocal: to, err = filepath.Abs(to) if err != nil { - return err + errors = append(errors, err) + return } // Check if target is empty if !force { @@ -399,14 +436,17 @@ func (l Location) Restore(to, from string, force bool, snapshot string, options if err == nil { files, err := ioutil.ReadDir(to) if err != nil { - return err + errors = append(errors, err) + return } if len(files) > 0 { - return notEmptyError + errors = append(errors, notEmptyError) + return } } else { if !os.IsNotExist(err) { - return err + errors = append(errors, err) + return } } } @@ -415,10 +455,17 @@ func (l Location) Restore(to, from string, force bool, snapshot string, options _, _, err = backend.ExecDocker(l, buildRestoreCommand(l, "/", snapshot, options)) } if err != nil { - return err + errors = append(errors, err) + return } - colors.Success.Println("Done") - return nil + + // After Hooks for restore + if err := l.ExecuteHooks(l.Hooks.RestoreOption.After, l.Hooks.RestoreOption.Dir, hooksOptions); err != nil { + errors = append(errors, err) + return + } + + return } func (l Location) RunCron() error {