-
Notifications
You must be signed in to change notification settings - Fork 24
/
backup_download.go
161 lines (140 loc) · 4.16 KB
/
backup_download.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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package db
import (
"context"
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/briandowns/spinner"
"github.com/cheggaaa/pb/v3"
"gopkg.in/errgo.v1"
"github.com/Scalingo/cli/config"
"github.com/Scalingo/go-scalingo/v7"
"github.com/Scalingo/go-scalingo/v7/debug"
httpclient "github.com/Scalingo/go-scalingo/v7/http"
)
type DownloadBackupOpts struct {
Output string
Silent bool
}
func DownloadBackup(ctx context.Context, app, addon, backupID string, opts DownloadBackupOpts) error {
// Output management (manage -s and -o - flags)
var fileWriter io.Writer
var logWriter io.Writer
writeToStdout := false
if opts.Output == "-" {
logWriter = os.Stderr
fileWriter = os.Stdout
writeToStdout = true
} else {
logWriter = os.Stdout
}
if opts.Silent {
logWriter = io.Discard
}
client, err := config.ScalingoClient(ctx)
if err != nil {
return errgo.Notef(err, "fail to get Scalingo client to download a backup")
}
if backupID == "" {
backups, err := client.BackupList(ctx, app, addon)
if err != nil {
return errgo.Notef(err, "fail to get the most recent backup")
}
if len(backups) == 0 {
return errgo.New("this addon has no backup")
}
backupID, err = getLastSuccessfulBackup(backups)
if err != nil {
return errgo.Notef(err, "fail to get a successful backup")
}
fmt.Fprintln(logWriter, "-----> Selected the most recent successful backup")
}
// Start a spinner when loading metadatas
spinner := spinner.New(spinner.CharSets[11], 100*time.Millisecond)
spinner.Suffix = " Preparing download"
spinner.Writer = logWriter
spinner.Start()
// Get backup metadatas
backup, err := client.BackupShow(ctx, app, addon, backupID)
if err != nil {
return errgo.Notef(err, "fail to get backup")
}
// Generate the filename and file writer
filepath := ""
if !writeToStdout { // No need to generate the filename nor the file writer if we're outputing to stdout
filepath = fmt.Sprintf("%s.tar.gz", backup.Name) // Default filename
if opts.Output != "" { // If the Output flag was defined
if isDir(opts.Output) { // If it's a directory use the default filename in this directory
filepath = fmt.Sprintf("%s/%s.tar.gz", opts.Output, backup.Name)
} else { // If the output is not a directory use it as the filename
filepath = opts.Output
}
}
// Open the output file
f, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
return errgo.Notef(err, "fail to open file")
}
fileWriter = f // Set the output file as the fileWriter
}
// Get the pre-signed download URL
downloadURL, err := client.BackupDownloadURL(ctx, app, addon, backupID)
if err != nil {
return errgo.Notef(err, "fail to get backup download URL")
}
debug.Println("Temporary URL to download backup is: ", downloadURL)
// Start the download
resp, err := http.Get(downloadURL)
if err != nil {
return errgo.Notef(err, "fail to start download")
}
defer resp.Body.Close()
// Stop the spinner
spinner.Stop()
if resp.StatusCode != http.StatusOK {
return httpclient.NewRequestFailedError(resp, &httpclient.APIRequest{
URL: downloadURL,
Method: "GET",
})
}
// Start the progress bar
bar := pb.New64(int64(backup.Size)).
Set(pb.Bytes, true).
SetWriter(logWriter)
bar.Start()
reader := bar.NewProxyReader(resp.Body) // Did I tell you this library is awesome?
_, err = io.Copy(fileWriter, reader)
if writeToStdout { // If we were writing the file to Stdout do not print the filepath at the end
bar.Finish()
} else {
// If we were writing the backup to a file, write the file path
bar.Finish()
fmt.Fprintf(logWriter, "===> %s\n", filepath)
}
if err != nil {
return errgo.Notef(err, "fail to download file")
}
return nil
}
// isDir returns true if it's a valid path to a directory, false otherwise
func isDir(path string) bool {
a, err := os.Open(path)
if err != nil {
return false
}
s, err := a.Stat()
if err != nil {
return false
}
return s.IsDir()
}
func getLastSuccessfulBackup(backups []scalingo.Backup) (string, error) {
for _, backup := range backups {
if backup.Status == scalingo.BackupStatusDone {
return backup.ID, nil
}
}
return "", errgo.New("can't find any successful backup")
}