github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/backups/create.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package backups 5 6 import ( 7 "io" 8 "os" 9 "time" 10 11 "github.com/juju/cmd" 12 "github.com/juju/errors" 13 "github.com/juju/gnuflag" 14 15 "github.com/juju/juju/apiserver/params" 16 jujucmd "github.com/juju/juju/cmd" 17 "github.com/juju/juju/cmd/modelcmd" 18 "github.com/juju/juju/state/backups" 19 ) 20 21 const ( 22 notset = backups.FilenamePrefix + "<date>-<time>.tar.gz" 23 downloadWarning = "downloading backup archives is recommended; " + 24 "backups stored remotely are not guaranteed to be available." 25 ) 26 27 const createDoc = ` 28 This command requests that Juju creates a backup of its state and prints the 29 backup's unique ID. You may provide a note to associate with the backup. 30 31 By default, the backup archive and associated metadata are downloaded 32 without keeping a copy remotely on the controller. 33 34 Use --no-download to avoid getting a local copy of the backup downloaded 35 at the end of the backup process. 36 37 Use --keep-copy option to store a copy of backup remotely on the controller. 38 39 Use --verbose to see extra information about backup. 40 41 To access remote backups stored on the controller, see 'juju download-backup'. 42 43 Examples: 44 juju create-backup 45 juju create-backup --no-download 46 juju create-backup --no-download --keep-copy=false // ignores --keep-copy 47 juju create-backup --keep-copy 48 juju create-backup --verbose 49 50 See also: 51 backups 52 download-backup 53 ` 54 55 // NewCreateCommand returns a command used to create backups. 56 func NewCreateCommand() cmd.Command { 57 return modelcmd.Wrap(&createCommand{}) 58 } 59 60 // createCommand is the sub-command for creating a new backup. 61 type createCommand struct { 62 CommandBase 63 // NoDownload means the backups archive should not be downloaded. 64 NoDownload bool 65 // Filename is where the backup should be downloaded. 66 Filename string 67 // Notes is the custom message to associated with the new backup. 68 Notes string 69 // KeepCopy means the backup archive should be stored in the controller db. 70 KeepCopy bool 71 fs *gnuflag.FlagSet 72 } 73 74 // Info implements Command.Info. 75 func (c *createCommand) Info() *cmd.Info { 76 return jujucmd.Info(&cmd.Info{ 77 Name: "create-backup", 78 Args: "[<notes>]", 79 Purpose: "Create a backup.", 80 Doc: createDoc, 81 }) 82 } 83 84 // SetFlags implements Command.SetFlags. 85 func (c *createCommand) SetFlags(f *gnuflag.FlagSet) { 86 c.CommandBase.SetFlags(f) 87 f.BoolVar(&c.NoDownload, "no-download", false, "Do not download the archive, implies keep-copy") 88 f.BoolVar(&c.KeepCopy, "keep-copy", false, "Keep a copy of the archive on the controller") 89 f.StringVar(&c.Filename, "filename", notset, "Download to this file") 90 c.fs = f 91 } 92 93 // Init implements Command.Init. 94 func (c *createCommand) Init(args []string) error { 95 // If user specifies that a download is not desired (i.e. no-download == true), 96 // and they have EXPLICITLY not wanted to store a remote backup file copy 97 // (i.e keep-copy == false), then there is no point for us to proceed as 98 // all the backup will not be stored anywhere. 99 if c.NoDownload { 100 keepCopySet := false 101 c.fs.Visit(func(flag *gnuflag.Flag) { 102 if flag.Name == "keep-copy" { 103 keepCopySet = true 104 } 105 }) 106 if keepCopySet && !c.KeepCopy { 107 return errors.Errorf("--no-download cannot be set when --keep-copy is not: the backup will not be created") 108 } 109 } 110 notes, err := cmd.ZeroOrOneArgs(args) 111 if err != nil { 112 return err 113 } 114 c.Notes = notes 115 116 if c.Filename != notset && c.NoDownload { 117 return errors.Errorf("cannot mix --no-download and --filename") 118 } 119 120 if c.Filename == "" { 121 return errors.Errorf("missing filename") 122 } 123 return nil 124 } 125 126 // Run implements Command.Run. 127 func (c *createCommand) Run(ctx *cmd.Context) error { 128 if c.Log != nil { 129 if err := c.Log.Start(ctx); err != nil { 130 return err 131 } 132 } 133 134 client, apiVersion, err := c.NewGetAPI() 135 if err != nil { 136 return errors.Trace(err) 137 } 138 defer client.Close() 139 140 if apiVersion < 2 { 141 if c.KeepCopy { 142 return errors.New("--keep-copy is not supported by this controller") 143 } 144 // for API v1, keepCopy is the default and only choice, so set it here 145 c.KeepCopy = true 146 } 147 148 if c.NoDownload { 149 ctx.Warningf(downloadWarning) 150 c.KeepCopy = true 151 } 152 153 metadataResult, copyFrom, err := c.create(client, apiVersion) 154 if err != nil { 155 return errors.Trace(err) 156 } 157 158 // TODO: (hml) 2018-04-25 159 // fix to dump the metadata when --verbose used 160 if c.Log != nil && !c.Log.Quiet { 161 c.dumpMetadata(ctx, metadataResult) 162 } 163 164 if c.KeepCopy { 165 ctx.Infof("Remote backup stored on the controller as %v.", metadataResult.ID) 166 } else { 167 ctx.Infof("Remote backup was not created.") 168 } 169 170 // Handle download. 171 if !c.NoDownload { 172 filename := c.decideFilename(ctx, c.Filename, metadataResult.Started) 173 if err := c.download(ctx, client, copyFrom, filename); err != nil { 174 return errors.Trace(err) 175 } 176 } 177 178 return nil 179 } 180 181 func (c *createCommand) decideFilename(ctx *cmd.Context, filename string, timestamp time.Time) string { 182 if filename != notset { 183 return filename 184 } 185 // Downloading but no filename given, so generate one. 186 return timestamp.Format(backups.FilenameTemplate) 187 } 188 189 func (c *createCommand) download(ctx *cmd.Context, client APIClient, copyFrom string, archiveFilename string) error { 190 resultArchive, err := client.Download(copyFrom) 191 if err != nil { 192 return errors.Trace(err) 193 } 194 defer resultArchive.Close() 195 196 archive, err := os.Create(archiveFilename) 197 if err != nil { 198 return errors.Annotatef(err, "while creating local archive file %v", archiveFilename) 199 } 200 defer archive.Close() 201 202 _, err = io.Copy(archive, resultArchive) 203 if err != nil { 204 return errors.Annotatef(err, "while copying to local archive file %v", archiveFilename) 205 } 206 ctx.Infof("Downloaded to %v.", archiveFilename) 207 return nil 208 } 209 210 func (c *createCommand) create(client APIClient, apiVersion int) (*params.BackupsMetadataResult, string, error) { 211 result, err := client.Create(c.Notes, c.KeepCopy, c.NoDownload) 212 if err != nil { 213 return nil, "", errors.Trace(err) 214 } 215 copyFrom := result.ID 216 217 if apiVersion >= 2 { 218 copyFrom = result.Filename 219 } 220 221 return result, copyFrom, err 222 }