forked from juju/juju
/
create.go
178 lines (147 loc) · 4.64 KB
/
create.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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package backups
import (
"fmt"
"io"
"time"
"github.com/juju/cmd/v3"
"github.com/juju/errors"
"github.com/juju/gnuflag"
jujucmd "github.com/juju/juju/cmd"
"github.com/juju/juju/cmd/modelcmd"
"github.com/juju/juju/rpc/params"
"github.com/juju/juju/state/backups"
)
const (
notset = backups.FilenamePrefix + "<date>-<time>.tar.gz"
downloadWarning = "--no-download flag is DEPRECATED."
)
const createDoc = `
This command requests that Juju creates a backup of its state.
You may provide a note to associate with the backup.
By default, the backup archive and associated metadata are downloaded.
Use --no-download to avoid getting a local copy of the backup downloaded
at the end of the backup process. In this case it is recommended that the
model config attribute "backup-dir" be set to point to a path where the
backup archives should be stored long term. This could be a remotely mounted
filesystem; the same path must exist on each controller if using HA.
Use --verbose to see extra information about backup.
To access remote backups stored on the controller, see 'juju download-backup'.
Examples:
juju create-backup
juju create-backup --no-download
See also:
download-backup
`
// NewCreateCommand returns a command used to create backups.
func NewCreateCommand() cmd.Command {
return modelcmd.Wrap(&createCommand{})
}
// createCommand is the sub-command for creating a new backup.
type createCommand struct {
CommandBase
// NoDownload means the backups archive should not be downloaded.
NoDownload bool
// Filename is where the backup should be downloaded.
Filename string
// Notes is the custom message to associated with the new backup.
Notes string
}
// Info implements Command.Info.
func (c *createCommand) Info() *cmd.Info {
return jujucmd.Info(&cmd.Info{
Name: "create-backup",
Args: "[<notes>]",
Purpose: "Create a backup.",
Doc: createDoc,
})
}
// SetFlags implements Command.SetFlags.
func (c *createCommand) SetFlags(f *gnuflag.FlagSet) {
c.CommandBase.SetFlags(f)
f.BoolVar(&c.NoDownload, "no-download", false, "Do not download the archive. DEPRECATED.")
f.StringVar(&c.Filename, "filename", notset, "Download to this file")
c.fs = f
}
// Init implements Command.Init.
func (c *createCommand) Init(args []string) error {
if err := c.CommandBase.Init(args); err != nil {
return err
}
notes, err := cmd.ZeroOrOneArgs(args)
if err != nil {
return err
}
c.Notes = notes
if c.Filename != notset && c.NoDownload {
return errors.Errorf("cannot mix --no-download and --filename")
}
if c.Filename == "" {
return errors.Errorf("missing filename")
}
return nil
}
// Run implements Command.Run.
func (c *createCommand) Run(ctx *cmd.Context) error {
if err := c.validateIaasController(c.Info().Name); err != nil {
return errors.Trace(err)
}
client, err := c.NewGetAPI()
if err != nil {
return errors.Trace(err)
}
defer client.Close()
if c.NoDownload {
ctx.Warningf(downloadWarning)
}
metadataResult, copyFrom, err := c.create(client)
if err != nil {
return errors.Trace(err)
}
if !c.quiet {
fmt.Fprintln(ctx.Stdout, c.metadata(metadataResult))
}
if c.NoDownload {
ctx.Infof("Remote backup stored on the controller as %v", metadataResult.Filename)
} else {
filename := c.decideFilename(ctx, c.Filename, metadataResult.Started)
if err := c.download(ctx, client, copyFrom, filename); err != nil {
return errors.Trace(err)
}
}
return nil
}
func (c *createCommand) decideFilename(ctx *cmd.Context, filename string, timestamp time.Time) string {
if filename != notset {
return filename
}
// Downloading but no filename given, so generate one.
return timestamp.Format(backups.FilenameTemplate)
}
func (c *createCommand) download(ctx *cmd.Context, client APIClient, copyFrom string, archiveFilename string) error {
resultArchive, err := client.Download(copyFrom)
if err != nil {
return errors.Trace(err)
}
defer resultArchive.Close()
archive, err := c.Filesystem().Create(archiveFilename)
if err != nil {
return errors.Annotatef(err, "while creating local archive file %v", archiveFilename)
}
defer archive.Close()
_, err = io.Copy(archive, resultArchive)
if err != nil {
return errors.Annotatef(err, "while copying to local archive file %v", archiveFilename)
}
ctx.Infof("Downloaded to %v", archiveFilename)
return nil
}
func (c *createCommand) create(client APIClient) (*params.BackupsMetadataResult, string, error) {
result, err := client.Create(c.Notes, c.NoDownload)
if err != nil {
return nil, "", errors.Trace(err)
}
copyFrom := result.Filename
return result, copyFrom, err
}