Skip to content

Commit

Permalink
Add restic wrapper library
Browse files Browse the repository at this point in the history
  • Loading branch information
hossainemruz committed Feb 20, 2019
1 parent a23a5ca commit c65aa3a
Show file tree
Hide file tree
Showing 13 changed files with 1,314 additions and 12 deletions.
4 changes: 0 additions & 4 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ import:
version: v1.4.0
- package: github.com/pkg/errors
version: v0.8.0
- package: github.com/prometheus/client_golang/prometheus
version: master
- package: github.com/prometheus/client_golang
version: v0.9.2
- package: github.com/russross/blackfriday
version: v1.5.2
- package: github.com/spf13/afero
Expand Down
65 changes: 65 additions & 0 deletions pkg/restic/backup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package restic

import "time"

func (w *ResticWrapper) RunBackup(backupOption *BackupOptions) (*BackupOutput, error) {
// Start clock to measure total session duration
startTime := time.Now()

// Initialize restic repository if it does not exist
_, err := w.InitRepositoryIfAbsent()
if err != nil {
return nil, err
}

backupOutput := &BackupOutput{}

// Backup all target directories
for _, dir := range backupOption.BackupDirs {
out, err := w.Backup(dir, nil)
if err != nil {
return nil, err
}
// Extract information from the output of backup command
err = backupOutput.ExtractBackupInfo(out, dir)
if err != nil {
return nil, err
}
}

// Check repository integrity
out, err := w.Check()
if err != nil {
return nil, err
}
// Extract information from output of "check" command
backupOutput.ExtractCheckInfo(out)

// Cleanup old snapshot according to retention policy
out, err = w.Cleanup(backupOption.Cleanup)
if err != nil {
return nil, err
}
// Extract information from output of cleanup command
err = backupOutput.ExtractCleanupInfo(out)
if err != nil {
return nil, err
}

// Read repository statics after cleanup
out, err = w.Stats()
if err != nil {
return nil, err
}
// Extract information from output of "stats" command
err = backupOutput.ExtractStatsInfo(out)
if err != nil {
return nil, err
}

// Backup complete. Read current time and calculate total session duration.
endTime := time.Now()
backupOutput.SessionDuration = endTime.Sub(startTime).String()

return backupOutput, nil
}
178 changes: 178 additions & 0 deletions pkg/restic/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package restic

import (
"path/filepath"
"strings"
"time"

"github.com/appscode/go/log"
"github.com/pkg/errors"
)

const (
Exe = "/bin/restic"
)

type Snapshot struct {
ID string `json:"id"`
Time time.Time `json:"time"`
Tree string `json:"tree"`
Paths []string `json:"paths"`
Hostname string `json:"hostname"`
Username string `json:"username"`
UID int `json:"uid"`
Gid int `json:"gid"`
Tags []string `json:"tags"`
}

func (w *ResticWrapper) ListSnapshots(snapshotIDs []string) ([]Snapshot, error) {
result := make([]Snapshot, 0)
args := w.appendCacheDirFlag([]interface{}{"snapshots", "--json", "--quiet", "--no-lock"})
args = w.appendCaCertFlag(args)
for _, id := range snapshotIDs {
args = append(args, id)
}

err := w.sh.Command(Exe, args...).UnmarshalJSON(&result)
return result, err
}

func (w *ResticWrapper) DeleteSnapshots(snapshotIDs []string) ([]byte, error) {
args := w.appendCacheDirFlag([]interface{}{"forget", "--quiet", "--prune"})
args = w.appendCaCertFlag(args)
for _, id := range snapshotIDs {
args = append(args, id)
}

return w.run(Exe, args)
}

func (w *ResticWrapper) InitRepositoryIfAbsent() ([]byte, error) {
log.Infoln("Ensuring restic repository in the backend")
args := w.appendCacheDirFlag([]interface{}{"snapshots", "--json"})
args = w.appendCaCertFlag(args)
if _, err := w.run(Exe, args); err != nil {
args = w.appendCacheDirFlag([]interface{}{"init"})
args = w.appendCaCertFlag(args)

return w.run(Exe, args)
}
return nil, nil
}

func (w *ResticWrapper) Backup(path string, tags []string) ([]byte, error) {
log.Infoln("Backing up target data")
args := []interface{}{"backup", path}
if w.config.Hostname != "" {
args = append(args, "--host")
args = append(args, w.config.Hostname)
}
// add tags if any
for _, tag := range tags {
args = append(args, "--tag")
args = append(args, tag)
}
args = w.appendCacheDirFlag(args)
args = w.appendCaCertFlag(args)

return w.run(Exe, args)
}

func (w *ResticWrapper) Cleanup(cleanupOptions CleanupOptions) ([]byte, error) {
log.Infoln("Cleaning old snapshots according to retention policy")

args := []interface{}{"forget"}

policy := cleanupOptions.RetentionPolicyName
if !strings.HasPrefix(policy, "--") {
policy = "--" + policy
}

args = append(args, policy)
args = append(args, cleanupOptions.RetentionValue)

if cleanupOptions.Prune {
args = append(args, "--prune")
}

if cleanupOptions.DryRun {
args = append(args, "--dry-run")
}

if len(args) > 1 {
args = w.appendCacheDirFlag(args)
args = w.appendCaCertFlag(args)

return w.run(Exe, args)
}
return nil, nil
}

func (w *ResticWrapper) Restore(path, host, snapshotID string) ([]byte, error) {
log.Infoln("Restoring backed up data")
args := []interface{}{"restore"}
if snapshotID != "" {
args = append(args, snapshotID)
} else {
args = append(args, "latest")
}
args = append(args, "--path")
args = append(args, path) // source-path specified in restic fileGroup
args = append(args, "--host")
args = append(args, host)

// Remove last part from the path.
// https://github.com/appscode/stash/issues/392
args = append(args, "--target")
args = append(args, filepath.Dir(path))

args = w.appendCacheDirFlag(args)
args = w.appendCaCertFlag(args)

return w.run(Exe, args)
}

func (w *ResticWrapper) Check() ([]byte, error) {
log.Infoln("Checking integrity of repository")
args := w.appendCacheDirFlag([]interface{}{"check"})
args = w.appendCaCertFlag(args)

return w.run(Exe, args)
}

func (w *ResticWrapper) Stats() ([]byte, error) {
log.Infoln("Reading repository status")
args := w.appendCacheDirFlag([]interface{}{"stats"})
args = append(args, "--mode=raw-data", "--quiet")
args = w.appendCaCertFlag(args)

return w.run(Exe, args)
}

func (w *ResticWrapper) appendCacheDirFlag(args []interface{}) []interface{} {
if w.config.EnableCache {
cacheDir := filepath.Join(w.config.ScratchDir, "restic-cache")
return append(args, "--cache-dir", cacheDir)
}
return append(args, "--no-cache")
}

func (w *ResticWrapper) appendCaCertFlag(args []interface{}) []interface{} {
if w.config.CacertFile != "" {
return append(args, "--cacert", w.config.CacertFile)
}
return args
}

func (w *ResticWrapper) run(cmd string, args []interface{}) ([]byte, error) {
out, err := w.sh.Command(cmd, args...).Output()
if err != nil {
log.Errorf("Error running command '%s %s' output:\n%s", cmd, args, string(out))
parts := strings.Split(strings.TrimSuffix(string(out), "\n"), "\n")
if len(parts) > 1 {
parts = parts[len(parts)-1:]
return nil, errors.New(parts[0])
}
}
return out, err
}
63 changes: 63 additions & 0 deletions pkg/restic/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package restic

import (
v1beta1_api "github.com/appscode/stash/apis/stash/v1beta1"
shell "github.com/codeskyblue/go-sh"
)

type ResticWrapper struct {
sh *shell.Session
config SetupOptions
}

type BackupOptions struct {
BackupDirs []string
Cleanup CleanupOptions
}

type RestoreOptions struct {
Rules []v1beta1_api.Rule
}

type SetupOptions struct {
Provider string
Bucket string
Endpoint string
Path string
SecretDir string
CacertFile string
ScratchDir string
EnableCache bool
Hostname string
}

type CleanupOptions struct {
RetentionPolicyName string
RetentionValue string
Prune bool
DryRun bool
}

type RetentionPolicy struct {
Policy string
Value string
Prune bool
DryRun bool
}

type MetricsOptions struct {
Enabled bool
PushgatewayURL string
MetricFileDir string
Labels []string
}

func NewResticWrapper(options SetupOptions) *ResticWrapper {
wrapper := &ResticWrapper{
sh: shell.NewSession(),
config: options,
}
wrapper.sh.SetDir(wrapper.config.ScratchDir)
wrapper.sh.ShowCMD = true
return wrapper
}
Loading

0 comments on commit c65aa3a

Please sign in to comment.