Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add restore hooks feature #213

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions cmd/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)))
}
},
}

Expand Down
1 change: 1 addition & 0 deletions docs/markdown/_toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
>
> [0.x → 1.0](/migration/0.x_1.0)
> [1.4 → 1.5](/migration/1.4_1.5)
> [1.7 → 1.8](/migration/1.7_1.8)

[Examples](/examples)
[Docker](/docker)
Expand Down
9 changes: 5 additions & 4 deletions docs/markdown/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ version: 2

extras:
hooks: &foo
before:
- echo "Hello"
after:
- echo "kthxbye"
backup:
before:
- echo "Hello"
after:
- echo "kthxbye"
policies: &bar
keep-daily: 14
keep-weekly: 52
Expand Down
13 changes: 7 additions & 6 deletions docs/markdown/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ autorestic exec -b my-backend -- unlock
extras:
healthchecks: &healthchecks
hooks:
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'
failure:
- 'curl -m 10 --retry 5 -X POST -H "Content-Type: text/plain" --data "Backup failed for location: ${AUTORESTIC_LOCATION}" https://<healthchecks-url>/ping/<uid>/fail'
success:
- 'curl -m 10 --retry 5 -X POST -H "Content-Type: text/plain" --data "Backup successful for location: ${AUTORESTIC_LOCATION}" https://<healthchecks-url>/ping/<uid>'
backup:
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'
failure:
- 'curl -m 10 --retry 5 -X POST -H "Content-Type: text/plain" --data "Backup failed for location: ${AUTORESTIC_LOCATION}" https://<healthchecks-url>/ping/<uid>/fail'
success:
- 'curl -m 10 --retry 5 -X POST -H "Content-Type: text/plain" --data "Backup successful for location: ${AUTORESTIC_LOCATION}" https://<healthchecks-url>/ping/<uid>'

locations:
something:
Expand Down
35 changes: 23 additions & 12 deletions docs/markdown/location/hooks.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# 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:

Expand All @@ -17,16 +17,27 @@ locations:
from: /data
to: my-backend
hooks:
before:
- echo "One"
- echo "Two"
- echo "Three"
after:
- echo "Byte"
failure:
- echo "Something went wrong"
success:
- echo "Well done!"
backup:
before:
- echo "One"
- echo "Two"
- echo "Three"
after:
- echo "Byte"
failure:
- echo "Something went wrong"
success:
- 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
Expand Down
45 changes: 45 additions & 0 deletions docs/markdown/migration/1.7_1.8.md
Original file line number Diff line number Diff line change
@@ -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"
```
1 change: 1 addition & 0 deletions docs/markdown/migration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

- [From 0.x to 1.0](/migration/0.x_1.0)
- [From 1.4 to 1.5](/migration/1.4_1.5)
- [From 1.7 to 1.8](/migration/1.7_1.8)
14 changes: 9 additions & 5 deletions internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/")
}
}
Expand Down Expand Up @@ -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 {
Expand Down
105 changes: 76 additions & 29 deletions internal/location.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,30 @@ 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"`
Success HookArray `mapstructure:"success,omitempty"`
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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -290,19 +295,19 @@ 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)
}

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

Expand Down Expand Up @@ -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 == "" {
Expand All @@ -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 {
Expand All @@ -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
}
}
}
Expand All @@ -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 {
Expand Down