support for volume locations

This commit is contained in:
cupcakearmy 2021-04-15 21:55:35 +02:00
parent da6d9c53aa
commit 6449b8999f
No known key found for this signature in database
GPG Key ID: D28129AE5654D9D9
3 changed files with 136 additions and 45 deletions

View File

@ -50,7 +50,7 @@ func init() {
restoreCmd.Flags().BoolP("force", "f", false, "Force, target folder will be overwritten") restoreCmd.Flags().BoolP("force", "f", false, "Force, target folder will be overwritten")
restoreCmd.Flags().String("from", "", "Which backend to use") restoreCmd.Flags().String("from", "", "Which backend to use")
restoreCmd.Flags().String("to", "", "Where to restore the data") restoreCmd.Flags().String("to", "", "Where to restore the data")
restoreCmd.MarkFlagRequired("to") // restoreCmd.MarkFlagRequired("to")
restoreCmd.Flags().StringP("location", "l", "", "Location to be restored") restoreCmd.Flags().StringP("location", "l", "", "Location to be restored")
restoreCmd.MarkFlagRequired("location") restoreCmd.MarkFlagRequired("location")
} }

View File

@ -4,6 +4,7 @@ import (
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/colors"
@ -114,3 +115,39 @@ func (b Backend) Exec(args []string) error {
} }
return err return err
} }
func (b Backend) ExecDocker(l Location, args []string) error {
env, err := b.getEnv()
if err != nil {
return err
}
volume := l.getVolumeName()
path, _ := l.getPath()
options := ExecuteOptions{
Command: "docker",
Envs: env,
}
docker := []string{
"run", "--rm",
"--entrypoint", "ash",
"--workdir", path,
"--volume", volume + ":" + path,
}
if hostname, err := os.Hostname(); err == nil {
docker = append(docker, "--hostname", hostname)
}
if b.Type == "local" {
actual := env["RESTIC_REPOSITORY"]
docker = append(docker, "--volume", actual+":"+"/repo")
env["RESTIC_REPOSITORY"] = "/repo"
}
for key, value := range env {
docker = append(docker, "--env", key+"="+value)
}
docker = append(docker, "restic/restic", "-c", "restic "+strings.Join(args, " "))
out, err := ExecuteCommand(options, docker...)
if VERBOSE {
colors.Faint.Println(out)
}
return err
}

View File

@ -5,6 +5,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"time" "time"
"github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/colors"
@ -12,6 +13,14 @@ import (
"github.com/robfig/cron" "github.com/robfig/cron"
) )
type LocationType string
const (
TypeLocal LocationType = "local"
TypeVolume LocationType = "volume"
VolumePrefix string = "volume:"
)
type HookArray = []string type HookArray = []string
type Hooks struct { type Hooks struct {
@ -90,19 +99,50 @@ func ExecuteHooks(commands []string, options ExecuteOptions) error {
return nil return nil
} }
func (l Location) getType() LocationType {
if strings.HasPrefix(l.From, VolumePrefix) {
return TypeVolume
}
return TypeLocal
}
func (l Location) getVolumeName() string {
return strings.TrimPrefix(l.From, VolumePrefix)
}
func (l Location) getPath() (string, error) {
t := l.getType()
switch t {
case TypeLocal:
if path, err := GetPathRelativeToConfig(l.From); err != nil {
return "", err
} else {
return path, nil
}
case TypeVolume:
return "/volume/" + l.Name + "/" + l.getVolumeName(), nil
}
return "", fmt.Errorf("Could not get path for location \"%s\"", l.Name)
}
func (l Location) Backup() error { func (l Location) Backup() error {
colors.PrimaryPrint(" Backing up location \"%s\" ", l.Name) colors.PrimaryPrint(" Backing up location \"%s\" ", l.Name)
from, err := GetPathRelativeToConfig(l.From) t := l.getType()
if err != nil {
return err
}
options := ExecuteOptions{ options := ExecuteOptions{
Command: "bash", Command: "bash",
Dir: from,
} }
if t == TypeLocal {
dir, _ := GetPathRelativeToConfig(l.From)
options.Dir = dir
}
// Hooks
if err := ExecuteHooks(l.Hooks.Before, options); err != nil { if err := ExecuteHooks(l.Hooks.Before, options); err != nil {
return nil return err
} }
// Backup
for _, to := range l.To { for _, to := range l.To {
backend, _ := GetBackend(to) backend, _ := GetBackend(to)
colors.Secondary.Printf("Backend: %s\n", backend.Name) colors.Secondary.Printf("Backend: %s\n", backend.Name)
@ -110,37 +150,49 @@ func (l Location) Backup() error {
if err != nil { if err != nil {
return nil return nil
} }
options := ExecuteOptions{
Command: "restic",
Dir: from,
Envs: env,
}
flags := l.getOptions("backup") flags := l.getOptions("backup")
cmd := []string{"backup"} cmd := []string{"backup"}
cmd = append(cmd, flags...) cmd = append(cmd, flags...)
cmd = append(cmd, ".") cmd = append(cmd, ".")
out, err := ExecuteResticCommand(options, cmd...) backupOptions := ExecuteOptions{
if VERBOSE { Dir: options.Dir,
colors.Faint.Println(out) Envs: env,
} }
var out string
switch t {
case TypeLocal:
out, err = ExecuteResticCommand(backupOptions, cmd...)
if VERBOSE {
colors.Faint.Println(out)
}
case TypeVolume:
err = backend.ExecDocker(l, cmd)
}
if err != nil { if err != nil {
return err return err
} }
} }
// After hooks
if err := ExecuteHooks(l.Hooks.After, options); err != nil { if err := ExecuteHooks(l.Hooks.After, options); err != nil {
return nil return err
} }
colors.Success.Println("Done") colors.Success.Println("Done")
return err return nil
} }
func (l Location) Forget(prune bool, dry bool) error { func (l Location) Forget(prune bool, dry bool) error {
colors.PrimaryPrint("Forgetting for location \"%s\"", l.Name) colors.PrimaryPrint("Forgetting for location \"%s\"", l.Name)
from, err := GetPathRelativeToConfig(l.From) path, err := l.getPath()
if err != nil { if err != nil {
return err return err
} }
for _, to := range l.To { for _, to := range l.To {
backend, _ := GetBackend(to) backend, _ := GetBackend(to)
colors.Secondary.Printf("For backend \"%s\"\n", backend.Name) colors.Secondary.Printf("For backend \"%s\"\n", backend.Name)
@ -149,12 +201,10 @@ func (l Location) Forget(prune bool, dry bool) error {
return nil return nil
} }
options := ExecuteOptions{ options := ExecuteOptions{
Command: "bash", Envs: env,
Envs: env,
Dir: from,
} }
flags := l.getOptions("forget") flags := l.getOptions("forget")
cmd := []string{"forget", "--path", options.Dir} cmd := []string{"forget", "--path", path}
if prune { if prune {
cmd = append(cmd, "--prune") cmd = append(cmd, "--prune")
} }
@ -195,34 +245,38 @@ func (l Location) Restore(to, from string, force bool) error {
return err return err
} }
colors.PrimaryPrint("Restoring location \"%s\"", l.Name) colors.PrimaryPrint("Restoring location \"%s\"", l.Name)
colors.Secondary.Println("Restoring lastest snapshot")
colors.Body.Printf("%s → %s.\n", from, to)
// Check if target is empty
if !force {
notEmptyError := fmt.Errorf("target %s is not empty", to)
_, err = os.Stat(to)
if err == nil {
files, err := ioutil.ReadDir(to)
if err != nil {
return err
}
if len(files) > 0 {
return notEmptyError
}
} else {
if !os.IsNotExist(err) {
return err
}
}
}
backend, _ := GetBackend(from) backend, _ := GetBackend(from)
resolved, err := GetPathRelativeToConfig(l.From) path, err := l.getPath()
if err != nil { if err != nil {
return nil return nil
} }
err = backend.Exec([]string{"restore", "--target", to, "--path", resolved, "latest"}) colors.Secondary.Println("Restoring lastest snapshot")
colors.Body.Printf("%s → %s.\n", from, path)
switch l.getType() {
case TypeLocal:
// Check if target is empty
if !force {
notEmptyError := fmt.Errorf("target %s is not empty", to)
_, err = os.Stat(to)
if err == nil {
files, err := ioutil.ReadDir(to)
if err != nil {
return err
}
if len(files) > 0 {
return notEmptyError
}
} else {
if !os.IsNotExist(err) {
return err
}
}
}
err = backend.Exec([]string{"restore", "--target", to, "--path", path, "latest"})
case TypeVolume:
err = backend.ExecDocker(l, []string{"restore", "--target", ".", "--path", path, "latest"})
}
if err != nil { if err != nil {
return err return err
} }