forked from JamesClonk/backman
-
Notifications
You must be signed in to change notification settings - Fork 20
/
backup.go
109 lines (90 loc) · 3.13 KB
/
backup.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package redis
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"github.com/cloudfoundry-community/go-cfenv"
"github.com/swisscom/backman/log"
"github.com/swisscom/backman/s3"
"github.com/swisscom/backman/service/util"
"github.com/swisscom/backman/state"
)
var redisMutex = &sync.Mutex{}
func Backup(ctx context.Context, s3 *s3.Client, service util.Service, binding *cfenv.Service, filename string) error {
state.BackupQueue(service)
// lock global redis mutex, only 1 backup of this service-type is allowed to run in parallel
// to avoid issues with memory and disk space consumption
redisMutex.Lock()
defer redisMutex.Unlock()
state.BackupStart(service)
credentials := GetCredentials(binding)
// tmp file
_ = os.Mkdir("tmp", os.ModePerm)
localFilename := filepath.Join("tmp", strings.TrimSuffix(filename, ".gz"))
localFilenameGzipped := filepath.Join("tmp", filename)
// prepare redis dump command
var command []string
command = append(command, "redis-cli")
command = append(command, "-h")
command = append(command, credentials.Hostname)
command = append(command, "-p")
command = append(command, credentials.Port)
command = append(command, "-a")
command = append(command, credentials.Password)
command = append(command, "--rdb")
command = append(command, localFilename)
log.Debugf("executing redis backup command: %v", strings.Join(command, " "))
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
// capture and read stderr in case an error occurs
var errBuf bytes.Buffer
cmd.Stderr = &errBuf
if err := cmd.Start(); err != nil {
log.Errorf("could not run redis dump: %v", err)
state.BackupFailure(service)
return fmt.Errorf("redis dump: %v", err)
}
if err := cmd.Wait(); err != nil {
state.BackupFailure(service)
// check for timeout error
if ctx.Err() == context.DeadlineExceeded {
return fmt.Errorf("redis dump: timeout: %v", ctx.Err())
}
log.Errorln(strings.TrimRight(errBuf.String(), "\r\n"))
return fmt.Errorf("redis dump: %v", err)
}
// gzip file
log.Debugf("gzipping redis dump [%s]", localFilename)
cmd = exec.CommandContext(ctx, "gzip", localFilename)
if err := cmd.Run(); err != nil {
log.Errorf("could not gzip redis dump [%s]: %v", localFilename, err)
state.BackupFailure(service)
return fmt.Errorf("redis dump: %v", err)
}
// upload file
uploadCtx, uploadCancel := context.WithCancel(context.Background()) // allows upload to be cancelable, in case backup times out
defer uploadCancel()
uploadfile, err := os.Open(localFilenameGzipped)
if err != nil {
state.BackupFailure(service)
log.Errorf("could not open local redis dump [%s]: %v", localFilenameGzipped, err)
return fmt.Errorf("redis dump: %v", err)
}
defer uploadfile.Close()
objectPath := fmt.Sprintf("%s/%s/%s", service.Label, service.Name, filename)
err = s3.UploadWithContext(uploadCtx, objectPath, uploadfile, -1)
if err != nil {
state.BackupFailure(service)
log.Errorf("could not upload service backup [%s] to S3: %v", service.Name, err)
}
// delete local file again
defer os.Remove(localFilenameGzipped)
if err == nil {
state.BackupSuccess(service)
}
return err
}