Skip to content

Commit dbff05f

Browse files
Dipta Dastamalsaha
authored andcommitted
Backup from stdin and dump to stdout (#694)
1 parent 08ff2fa commit dbff05f

File tree

5 files changed

+302
-37
lines changed

5 files changed

+302
-37
lines changed

pkg/restic/backup.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,28 @@ func (w *ResticWrapper) RunBackup(backupOption BackupOptions) (*BackupOutput, er
2222
},
2323
}
2424

25-
// Backup all target directories
26-
for _, dir := range backupOption.BackupDirs {
27-
out, err := w.backup(dir, backupOption.Host, nil)
25+
if backupOption.StdinPipeCommand.Name != "" { // Backup from stdin
26+
out, err := w.backupFromStdin(backupOption)
2827
if err != nil {
2928
return nil, err
3029
}
3130
// Extract information from the output of backup command
32-
err = backupOutput.extractBackupInfo(out, dir, backupOption.Host)
31+
err = backupOutput.extractBackupInfo(out, backupOption.StdinFileName, backupOption.Host)
3332
if err != nil {
3433
return nil, err
3534
}
35+
} else { // Backup all target directories
36+
for _, dir := range backupOption.BackupDirs {
37+
out, err := w.backup(dir, backupOption.Host, nil)
38+
if err != nil {
39+
return nil, err
40+
}
41+
// Extract information from the output of backup command
42+
err = backupOutput.extractBackupInfo(out, dir, backupOption.Host)
43+
if err != nil {
44+
return nil, err
45+
}
46+
}
3647
}
3748

3849
// Check repository integrity

pkg/restic/commands.go

Lines changed: 114 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
package restic
22

33
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"os"
48
"path/filepath"
59
"strconv"
610
"strings"
711
"time"
812

913
"github.com/appscode/go/log"
1014
"github.com/appscode/stash/apis/stash/v1alpha1"
11-
"github.com/pkg/errors"
15+
"github.com/armon/circbuf"
1216
)
1317

1418
const (
15-
Exe = "/bin/restic_0.9.4"
19+
ResticCMD = "/bin/restic_0.9.4"
1620
)
1721

1822
type Snapshot struct {
@@ -34,8 +38,11 @@ func (w *ResticWrapper) listSnapshots(snapshotIDs []string) ([]Snapshot, error)
3438
for _, id := range snapshotIDs {
3539
args = append(args, id)
3640
}
37-
38-
err := w.sh.Command(Exe, args...).UnmarshalJSON(&result)
41+
out, err := w.run(Command{Name: ResticCMD, Args: args})
42+
if err != nil {
43+
return nil, err
44+
}
45+
err = json.Unmarshal(out, &result)
3946
return result, err
4047
}
4148

@@ -46,18 +53,18 @@ func (w *ResticWrapper) deleteSnapshots(snapshotIDs []string) ([]byte, error) {
4653
args = append(args, id)
4754
}
4855

49-
return w.run(Exe, args)
56+
return w.run(Command{Name: ResticCMD, Args: args})
5057
}
5158

5259
func (w *ResticWrapper) initRepositoryIfAbsent() ([]byte, error) {
5360
log.Infoln("Ensuring restic repository in the backend")
5461
args := w.appendCacheDirFlag([]interface{}{"snapshots", "--json"})
5562
args = w.appendCaCertFlag(args)
56-
if _, err := w.run(Exe, args); err != nil {
63+
if _, err := w.run(Command{Name: ResticCMD, Args: args}); err != nil {
5764
args = w.appendCacheDirFlag([]interface{}{"init"})
5865
args = w.appendCaCertFlag(args)
5966

60-
return w.run(Exe, args)
67+
return w.run(Command{Name: ResticCMD, Args: args})
6168
}
6269
return nil, nil
6370
}
@@ -77,7 +84,32 @@ func (w *ResticWrapper) backup(path, host string, tags []string) ([]byte, error)
7784
args = w.appendCacheDirFlag(args)
7885
args = w.appendCaCertFlag(args)
7986

80-
return w.run(Exe, args)
87+
return w.run(Command{Name: ResticCMD, Args: args})
88+
}
89+
90+
func (w *ResticWrapper) backupFromStdin(options BackupOptions) ([]byte, error) {
91+
log.Infoln("Backing up stdin data")
92+
93+
// first add StdinPipeCommand, then add restic command
94+
var commands []Command
95+
if options.StdinPipeCommand.Name != "" {
96+
commands = append(commands, options.StdinPipeCommand)
97+
}
98+
99+
args := []interface{}{"backup", "--stdin"}
100+
if options.StdinFileName != "" {
101+
args = append(args, "--stdin-filename")
102+
args = append(args, options.StdinFileName)
103+
}
104+
if options.Host != "" {
105+
args = append(args, "--host")
106+
args = append(args, options.Host)
107+
}
108+
args = w.appendCacheDirFlag(args)
109+
args = w.appendCaCertFlag(args)
110+
111+
commands = append(commands, Command{Name: ResticCMD, Args: args})
112+
return w.run(commands...)
81113
}
82114

83115
func (w *ResticWrapper) cleanup(retentionPolicy v1alpha1.RetentionPolicy) ([]byte, error) {
@@ -124,41 +156,78 @@ func (w *ResticWrapper) cleanup(retentionPolicy v1alpha1.RetentionPolicy) ([]byt
124156
args = w.appendCacheDirFlag(args)
125157
args = w.appendCaCertFlag(args)
126158

127-
return w.run(Exe, args)
159+
return w.run(Command{Name: ResticCMD, Args: args})
128160
}
129161
return nil, nil
130162
}
131163

132164
func (w *ResticWrapper) restore(path, host, snapshotID string) ([]byte, error) {
133165
log.Infoln("Restoring backed up data")
166+
134167
args := []interface{}{"restore"}
135168
if snapshotID != "" {
136169
args = append(args, snapshotID)
137170
} else {
138171
args = append(args, "latest")
139172
}
140-
args = append(args, "--path")
141-
args = append(args, path) // source-path specified in restic fileGroup
142-
args = append(args, "--host")
143-
args = append(args, host)
173+
if path != "" {
174+
args = append(args, "--path")
175+
args = append(args, path) // source-path specified in restic fileGroup
176+
}
177+
if host != "" {
178+
args = append(args, "--host")
179+
args = append(args, host)
180+
}
181+
182+
args = append(args, "--target", "/") // restore in absolute path
183+
args = w.appendCacheDirFlag(args)
184+
args = w.appendCaCertFlag(args)
185+
186+
return w.run(Command{Name: ResticCMD, Args: args})
187+
}
144188

145-
// Remove last part from the path.
146-
// https://github.com/appscode/stash/issues/392
147-
args = append(args, "--target", "/")
148-
// args = append(args, filepath.Dir(path))
189+
func (w *ResticWrapper) dump(dumpOptions DumpOptions) ([]byte, error) {
190+
log.Infoln("Dumping backed up data")
191+
192+
args := []interface{}{"dump"}
193+
if dumpOptions.Snapshot != "" {
194+
args = append(args, dumpOptions.Snapshot)
195+
} else {
196+
args = append(args, "latest")
197+
}
198+
if dumpOptions.FileName != "" {
199+
args = append(args, dumpOptions.FileName)
200+
} else {
201+
args = append(args, "stdin")
202+
}
203+
if dumpOptions.Host != "" {
204+
args = append(args, "--host")
205+
args = append(args, dumpOptions.Host)
206+
}
207+
if dumpOptions.Path != "" {
208+
args = append(args, "--path")
209+
args = append(args, dumpOptions.Path)
210+
}
149211

150212
args = w.appendCacheDirFlag(args)
151213
args = w.appendCaCertFlag(args)
152214

153-
return w.run(Exe, args)
215+
// first add restic command, then add StdoutPipeCommand
216+
commands := []Command{
217+
{Name: ResticCMD, Args: args},
218+
}
219+
if dumpOptions.StdoutPipeCommand.Name != "" {
220+
commands = append(commands, dumpOptions.StdoutPipeCommand)
221+
}
222+
return w.run(commands...)
154223
}
155224

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

161-
return w.run(Exe, args)
230+
return w.run(Command{Name: ResticCMD, Args: args})
162231
}
163232

164233
func (w *ResticWrapper) stats() ([]byte, error) {
@@ -167,7 +236,7 @@ func (w *ResticWrapper) stats() ([]byte, error) {
167236
args = append(args, "--mode=raw-data", "--quiet")
168237
args = w.appendCaCertFlag(args)
169238

170-
return w.run(Exe, args)
239+
return w.run(Command{Name: ResticCMD, Args: args})
171240
}
172241

173242
func (w *ResticWrapper) appendCacheDirFlag(args []interface{}) []interface{} {
@@ -185,15 +254,30 @@ func (w *ResticWrapper) appendCaCertFlag(args []interface{}) []interface{} {
185254
return args
186255
}
187256

188-
func (w *ResticWrapper) run(cmd string, args []interface{}) ([]byte, error) {
189-
out, err := w.sh.Command(cmd, args...).Output()
257+
func (w *ResticWrapper) run(commands ...Command) ([]byte, error) {
258+
// write std errors into os.Stderr and buffer
259+
errBuff, err := circbuf.NewBuffer(256)
190260
if err != nil {
191-
log.Errorf("Error running command '%s %s' output:\n%s", cmd, args, string(out))
192-
parts := strings.Split(strings.TrimSuffix(string(out), "\n"), "\n")
193-
if len(parts) > 1 {
194-
parts = parts[len(parts)-1:]
195-
return nil, errors.New(parts[0])
196-
}
197-
}
198-
return out, err
261+
return nil, err
262+
}
263+
w.sh.Stderr = io.MultiWriter(os.Stderr, errBuff)
264+
265+
for _, cmd := range commands {
266+
w.sh.Command(cmd.Name, cmd.Args...)
267+
}
268+
out, err := w.sh.Output()
269+
if err != nil {
270+
return nil, formatError(err, errBuff.String())
271+
}
272+
log.Infoln("sh-output:", string(out))
273+
return out, nil
274+
}
275+
276+
// return last line of std error as error reason
277+
func formatError(err error, stdErr string) error {
278+
parts := strings.Split(strings.TrimSuffix(stdErr, "\n"), "\n")
279+
if len(parts) > 1 {
280+
return fmt.Errorf("%s, reason: %s", err, parts[len(parts)-1:][0])
281+
}
282+
return err
199283
}

pkg/restic/config.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@ type ResticWrapper struct {
1515
config SetupOptions
1616
}
1717

18+
type Command struct {
19+
Name string
20+
Args []interface{}
21+
}
22+
23+
// if StdinPipeCommand is specified, BackupDirs will not be used
1824
type BackupOptions struct {
19-
Host string
20-
BackupDirs []string
21-
RetentionPolicy v1alpha1.RetentionPolicy
25+
Host string
26+
BackupDirs []string
27+
StdinPipeCommand Command
28+
StdinFileName string // default "stdin"
29+
RetentionPolicy v1alpha1.RetentionPolicy
2230
}
2331

2432
type RestoreOptions struct {
@@ -28,6 +36,14 @@ type RestoreOptions struct {
2836
Snapshots []string // when Snapshots are specified SourceHost and RestoreDirs will not be used
2937
}
3038

39+
type DumpOptions struct {
40+
Host string
41+
Snapshot string // default "latest"
42+
Path string
43+
FileName string // default "stdin"
44+
StdoutPipeCommand Command
45+
}
46+
3147
type SetupOptions struct {
3248
Provider string
3349
Bucket string

0 commit comments

Comments
 (0)