Commit fb207eba authored by ale's avatar ale

Implement a version check for restic

It must be at least version 0.9 in order to support the expected
restore semantics.
parent 3ece81fd
......@@ -8,12 +8,17 @@ import (
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/hashicorp/go-version"
)
type resticRepository struct {
bin string
uri string
passwordFile string
shell *Shell
......@@ -21,18 +26,45 @@ type resticRepository struct {
excludeFiles []string
}
func (r *resticRepository) resticOptions() string {
opts := []string{"-r", r.uri}
func (r *resticRepository) resticCmd() string {
args := []string{r.bin, "-r", r.uri}
if r.passwordFile != "" {
opts = append(opts, "--password-file", r.passwordFile)
args = append(args, "--password-file", r.passwordFile)
}
for _, x := range r.excludes {
opts = append(opts, "--exclude", x)
args = append(args, "--exclude", x)
}
for _, x := range r.excludeFiles {
opts = append(opts, "--exclude-file", x)
args = append(args, "--exclude-file", x)
}
return strings.Join(opts, " ")
return strings.Join(args, " ")
}
// We need to check that we're running at least restic 0.9, or the
// restore functionality won't work as expected.
var (
resticVersionRx = regexp.MustCompile(`^restic ([0-9.]+)`)
resticMinGoodVersion = "0.9"
)
func checkResticVersion(bin string) error {
output, err := exec.Command(bin, "version").Output() // #nosec
if err != nil {
return err
}
m := resticVersionRx.FindStringSubmatch(string(output))
if len(m) < 2 {
return errors.New("could not parse restic version")
}
v, err := version.NewVersion(m[1])
if err != nil {
return err
}
minV, _ := version.NewVersion(resticMinGoodVersion) // nolint
if v.LessThan(minV) {
return fmt.Errorf("restic should be at least version %s (is %s)", minV, v)
}
return nil
}
// newResticRepository returns a restic repository.
......@@ -48,6 +80,13 @@ func newResticRepository(params map[string]interface{}, shell *Shell) (Repositor
ex, _ := params["exclude"].([]string)
exf, _ := params["exclude_files"].([]string)
bin, _ := params["restic_binary"].(string)
if bin == "" {
bin = "restic"
}
if err := checkResticVersion(bin); err != nil {
return nil, err
}
tmpf, err := ioutil.TempFile("", "restic-pw-")
if err != nil {
......@@ -63,6 +102,7 @@ func newResticRepository(params map[string]interface{}, shell *Shell) (Repositor
}
return &resticRepository{
bin: bin,
uri: uri,
passwordFile: tmpf.Name(),
excludes: ex,
......@@ -77,23 +117,23 @@ func (r *resticRepository) Close() error {
func (r *resticRepository) Init(ctx context.Context) error {
return r.shell.Run(ctx, fmt.Sprintf(
"restic %s init --quiet",
r.resticOptions(),
"%s init --quiet",
r.resticCmd(),
))
}
func (r *resticRepository) Prepare(ctx context.Context, backup Backup) error {
return r.shell.Run(ctx, fmt.Sprintf(
"restic %s forget --host %s --keep-last 10 --prune",
r.resticOptions(),
"%s forget --host %s --keep-last 10 --prune",
r.resticCmd(),
backup.Host,
))
}
func (r *resticRepository) Backup(ctx context.Context, backup Backup, ds Dataset, sourcePath string) error {
cmd := fmt.Sprintf(
"restic %s backup --cleanup-cache --exclude-caches --one-file-system --tag %s --tag backup_id=%s",
r.resticOptions(),
"%s backup --cleanup-cache --exclude-caches --one-file-system --tag %s --tag backup_id=%s",
r.resticCmd(),
ds.Name,
backup.ID,
)
......@@ -108,8 +148,8 @@ func (r *resticRepository) Backup(ctx context.Context, backup Backup, ds Dataset
func (r *resticRepository) getSnapshotID(ctx context.Context, backup Backup, ds Dataset) (string, error) {
data, err := r.shell.Output(ctx, fmt.Sprintf(
"restic %s snapshots --json --tag backup_id=%s --tag %s",
r.resticOptions(),
"%s snapshots --json --tag backup_id=%s --tag %s",
r.resticCmd(),
backup.ID,
ds.Name,
))
......@@ -132,8 +172,8 @@ func (r *resticRepository) Restore(ctx context.Context, backup Backup, ds Datase
return err
}
cmd := fmt.Sprintf(
"restic %s restore %s",
r.resticOptions(),
"%s restore %s",
r.resticCmd(),
snap,
)
for _, atom := range ds.Atoms {
......@@ -150,8 +190,8 @@ func (r *resticRepository) BackupStream(ctx context.Context, backup Backup, ds D
name = fmt.Sprintf("%s/%s", ds.Name, ds.Atoms[0].Name)
}
return r.shell.Run(ctx, fmt.Sprintf(
"restic %s backup --stdin --stdin-name %s",
r.resticOptions(),
"%s backup --stdin --stdin-name %s",
r.resticCmd(),
name,
))
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment