Compare commits

...

3 Commits

Author SHA1 Message Date
rdelaage
f7a20a1210
Merge f43cc32ac3 into 3b57602fe8 2023-08-12 23:36:25 +02:00
Romain de Laage
f43cc32ac3
Update docs to explain restore hooks feature and add a migration guide 2022-09-14 13:08:30 +02:00
Romain de Laage
4375a38bba
Add restore hooks feature 2022-09-14 11:18:26 +02:00
9 changed files with 175 additions and 58 deletions

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/cupcakearmy/autorestic/internal" "github.com/cupcakearmy/autorestic/internal"
"github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/lock" "github.com/cupcakearmy/autorestic/internal/lock"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -42,8 +43,13 @@ var restoreCmd = &cobra.Command{
} }
} }
err = l.Restore(target, from, force, snapshot, optional) errs := l.Restore(target, from, force, snapshot, optional)
CheckErr(err) for _, err := range errs {
colors.Error.Printf("%s\n\n", err)
}
if len(errs) > 0 {
CheckErr(fmt.Errorf("%d errors were found", len(errs)))
}
}, },
} }

View File

@ -45,6 +45,7 @@
> >
> [0.x → 1.0](/migration/0.x_1.0) > [0.x → 1.0](/migration/0.x_1.0)
> [1.4 → 1.5](/migration/1.4_1.5) > [1.4 → 1.5](/migration/1.4_1.5)
> [1.7 → 1.8](/migration/1.7_1.8)
[Examples](/examples) [Examples](/examples)
[Docker](/docker) [Docker](/docker)

View File

@ -55,6 +55,7 @@ version: 2
extras: extras:
hooks: &foo hooks: &foo
backup:
before: before:
- echo "Hello" - echo "Hello"
after: after:

View File

@ -22,6 +22,7 @@ autorestic exec -b my-backend -- unlock
extras: extras:
healthchecks: &healthchecks healthchecks: &healthchecks
hooks: hooks:
backup:
before: before:
- 'curl -m 10 --retry 5 -X POST -H "Content-Type: text/plain" --data "Starting backup for location: ${AUTORESTIC_LOCATION}" https://<healthchecks-url>/ping/<uid>/start' - 'curl -m 10 --retry 5 -X POST -H "Content-Type: text/plain" --data "Starting backup for location: ${AUTORESTIC_LOCATION}" https://<healthchecks-url>/ping/<uid>/start'
failure: failure:

View File

@ -1,8 +1,8 @@
# Hooks # Hooks
If you want to perform some commands before and/or after a backup, you can use hooks. If you want to perform some commands before and/or after a backup or restore, you can use hooks.
They consist of a list of commands that will be executed in the same directory as the target `from`. They consist of a list of commands that will be executed in the same directory as the config file or in the `dir` directory if configured.
The following hooks groups are supported, none are required: The following hooks groups are supported, none are required:
@ -17,6 +17,7 @@ locations:
from: /data from: /data
to: my-backend to: my-backend
hooks: hooks:
backup:
before: before:
- echo "One" - echo "One"
- echo "Two" - echo "Two"
@ -27,6 +28,16 @@ locations:
- echo "Something went wrong" - echo "Something went wrong"
success: success:
- echo "Well done!" - echo "Well done!"
restore:
dir: /var/www/html
before:
- echo "Let's restore this backup!"
after:
- echo "Finished to restore"
failure:
- echo "A problem has been encountered :("
success:
- echo "Successfully restored!"
``` ```
## Flowchart ## Flowchart

View File

@ -0,0 +1,45 @@
# Migration from `1.7` to `1.8`
## Config files
- The config version have been changed
- You can now configure restore hooks
See detailed instructions below.
## Config Version
The version field of the config file has been changed from `2` to `3`.
## Hooks
Since `1.8` both backup and restore hooks are possible.
For this reason, backup hooks have been moved one layer deeper, you have to move them in a `backup` object.
Before:
```yaml
locations:
l1:
# ...
from: /foo/bar
hooks:
before:
- pwd
```
After:
```yaml
locations:
l1:
# ...
from: /foo/bar
hooks:
backup:
before:
- pwd
restore:
after:
- echo "My super restore hook"
```

View File

@ -2,3 +2,4 @@
- [From 0.x to 1.0](/migration/0.x_1.0) - [From 0.x to 1.0](/migration/0.x_1.0)
- [From 1.4 to 1.5](/migration/1.4_1.5) - [From 1.4 to 1.5](/migration/1.4_1.5)
- [From 1.7 to 1.8](/migration/1.7_1.8)

View File

@ -83,7 +83,7 @@ func GetConfig() *Config {
exitConfig(nil, "version specified in config file is not an int") exitConfig(nil, "version specified in config file is not an int")
} else { } else {
// Check for version // 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/") 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 = "" tmp = ""
hooks := map[string][]string{ hooks := map[string][]string{
"Before": l.Hooks.Before, "Before backup": l.Hooks.BackupOption.Before,
"After": l.Hooks.After, "After backup": l.Hooks.BackupOption.After,
"Failure": l.Hooks.Failure, "Failure backup": l.Hooks.BackupOption.Failure,
"Success": l.Hooks.Success, "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 { for hook, commands := range hooks {
if len(commands) > 0 { if len(commands) > 0 {

View File

@ -33,6 +33,13 @@ const (
) )
type Hooks struct { 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"` Dir string `mapstructure:"dir"`
Before HookArray `mapstructure:"before,omitempty"` Before HookArray `mapstructure:"before,omitempty"`
After HookArray `mapstructure:"after,omitempty"` After HookArray `mapstructure:"after,omitempty"`
@ -40,8 +47,6 @@ type Hooks struct {
Failure HookArray `mapstructure:"failure,omitempty"` Failure HookArray `mapstructure:"failure,omitempty"`
} }
type LocationCopy = map[string][]string
type Location struct { type Location struct {
name string `mapstructure:",omitempty"` name string `mapstructure:",omitempty"`
From []string `mapstructure:"from,omitempty"` From []string `mapstructure:"from,omitempty"`
@ -123,12 +128,12 @@ func (l Location) validate() error {
return nil 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 { if len(commands) == 0 {
return nil return nil
} }
if l.Hooks.Dir != "" { if directory != "" {
if dir, err := GetPathRelativeToConfig(l.Hooks.Dir); err != nil { if dir, err := GetPathRelativeToConfig(directory); err != nil {
return err return err
} else { } else {
options.Dir = dir options.Dir = dir
@ -190,7 +195,7 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
} }
// Hooks // 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) errors = append(errors, err)
goto after goto after
} }
@ -290,7 +295,7 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
} }
// After hooks // 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) errors = append(errors, err)
} }
@ -298,11 +303,11 @@ after:
var commands []string var commands []string
var isSuccess = len(errors) == 0 var isSuccess = len(errors) == 0
if isSuccess { if isSuccess {
commands = l.Hooks.Success commands = l.Hooks.BackupOption.Success
} else { } 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) errors = append(errors, err)
} }
@ -370,11 +375,35 @@ func buildRestoreCommand(l Location, to string, snapshot string, options []strin
return base 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 == "" { if from == "" {
from = l.To[0] from = l.To[0]
} else if !l.hasBackend(from) { } else if !l.hasBackend(from) {
return fmt.Errorf("invalid backend: \"%s\"", from) errors = append(errors, fmt.Errorf("invalid backend: \"%s\"", from))
} }
if snapshot == "" { if snapshot == "" {
@ -385,15 +414,23 @@ func (l Location) Restore(to, from string, force bool, snapshot string, options
backend, _ := GetBackend(from) backend, _ := GetBackend(from)
colors.Secondary.Printf("Restoring %s@%s → %s\n", snapshot, backend.name, to) 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() t, err := l.getType()
if err != nil { if err != nil {
return err errors = append(errors, err)
return
} }
switch t { switch t {
case TypeLocal: case TypeLocal:
to, err = filepath.Abs(to) to, err = filepath.Abs(to)
if err != nil { if err != nil {
return err errors = append(errors, err)
return
} }
// Check if target is empty // Check if target is empty
if !force { if !force {
@ -402,14 +439,17 @@ func (l Location) Restore(to, from string, force bool, snapshot string, options
if err == nil { if err == nil {
files, err := ioutil.ReadDir(to) files, err := ioutil.ReadDir(to)
if err != nil { if err != nil {
return err errors = append(errors, err)
return
} }
if len(files) > 0 { if len(files) > 0 {
return notEmptyError errors = append(errors, notEmptyError)
return
} }
} else { } else {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
return err errors = append(errors, err)
return
} }
} }
} }
@ -418,10 +458,17 @@ func (l Location) Restore(to, from string, force bool, snapshot string, options
_, _, err = backend.ExecDocker(l, buildRestoreCommand(l, "/", snapshot, options)) _, _, err = backend.ExecDocker(l, buildRestoreCommand(l, "/", snapshot, options))
} }
if err != nil { 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 { func (l Location) RunCron() error {