github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "fmt" 8 "io" 9 "os" 10 "time" 11 12 "github.com/juju/cmd" 13 "github.com/juju/errors" 14 "launchpad.net/gnuflag" 15 16 "github.com/juju/juju/cmd/modelcmd" 17 "github.com/juju/juju/state/backups" 18 ) 19 20 const ( 21 notset = backups.FilenamePrefix + "<date>-<time>.tar.gz" 22 downloadWarning = "WARNING: downloading backup archives is recommended; " + 23 "backups stored remotely are not guaranteed to be available" 24 ) 25 26 const createDoc = ` 27 create-backup requests that juju create a backup of its state and print the 28 backup's unique ID. You may provide a note to associate with the backup. 29 30 The backup archive and associated metadata are stored remotely by juju. 31 32 The --download option may be used without the --filename option. In 33 that case, the backup archive will be stored in the current working 34 directory with a name matching juju-backup-<date>-<time>.tar.gz. 35 36 WARNING: Remotely stored backups will be lost when the model is 37 destroyed. Furthermore, the remotely backup is not guaranteed to be 38 available. 39 40 Therefore, you should use the --download or --filename options, or use: 41 42 juju download-backups 43 44 to get a local copy of the backup archive. 45 This local copy can then be used to restore an model even if that 46 model was already destroyed or is otherwise unavailable. 47 ` 48 49 // NewCreateCommand returns a command used to create backups. 50 func NewCreateCommand() cmd.Command { 51 return modelcmd.Wrap(&createCommand{}) 52 } 53 54 // createCommand is the sub-command for creating a new backup. 55 type createCommand struct { 56 CommandBase 57 // NoDownload means the backups archive should not be downloaded. 58 NoDownload bool 59 // Filename is where the backup should be downloaded. 60 Filename string 61 // Notes is the custom message to associated with the new backup. 62 Notes string 63 } 64 65 // Info implements Command.Info. 66 func (c *createCommand) Info() *cmd.Info { 67 return &cmd.Info{ 68 Name: "create-backup", 69 Args: "[<notes>]", 70 Purpose: "create a backup", 71 Doc: createDoc, 72 } 73 } 74 75 // SetFlags implements Command.SetFlags. 76 func (c *createCommand) SetFlags(f *gnuflag.FlagSet) { 77 c.CommandBase.SetFlags(f) 78 f.BoolVar(&c.NoDownload, "no-download", false, "do not download the archive") 79 f.StringVar(&c.Filename, "filename", notset, "download to this file") 80 } 81 82 // Init implements Command.Init. 83 func (c *createCommand) Init(args []string) error { 84 notes, err := cmd.ZeroOrOneArgs(args) 85 if err != nil { 86 return err 87 } 88 c.Notes = notes 89 90 if c.Filename != notset && c.NoDownload { 91 return errors.Errorf("cannot mix --no-download and --filename") 92 } 93 if c.Filename == "" { 94 return errors.Errorf("missing filename") 95 } 96 97 return nil 98 } 99 100 // Run implements Command.Run. 101 func (c *createCommand) Run(ctx *cmd.Context) error { 102 if c.Log != nil { 103 if err := c.Log.Start(ctx); err != nil { 104 return err 105 } 106 } 107 client, err := c.NewAPIClient() 108 if err != nil { 109 return errors.Trace(err) 110 } 111 defer client.Close() 112 113 result, err := client.Create(c.Notes) 114 if err != nil { 115 return errors.Trace(err) 116 } 117 118 if c.Log != nil && !c.Log.Quiet { 119 if c.NoDownload { 120 fmt.Fprintln(ctx.Stderr, downloadWarning) 121 } 122 c.dumpMetadata(ctx, result) 123 } 124 125 fmt.Fprintln(ctx.Stdout, result.ID) 126 127 // Handle download. 128 filename := c.decideFilename(ctx, c.Filename, result.Started) 129 if filename != "" { 130 if err := c.download(ctx, result.ID, filename); err != nil { 131 return errors.Trace(err) 132 } 133 } 134 135 return nil 136 } 137 138 func (c *createCommand) decideFilename(ctx *cmd.Context, filename string, timestamp time.Time) string { 139 if filename != notset { 140 return filename 141 } 142 if c.NoDownload { 143 return "" 144 } 145 146 // Downloading but no filename given, so generate one. 147 return timestamp.Format(backups.FilenameTemplate) 148 } 149 150 func (c *createCommand) download(ctx *cmd.Context, id string, filename string) error { 151 fmt.Fprintln(ctx.Stdout, "downloading to "+filename) 152 153 // TODO(ericsnow) lp-1399722 This needs further investigation: 154 // There is at least anecdotal evidence that we cannot use an API 155 // client for more than a single request. So we use a new client 156 // for download. 157 client, err := c.NewAPIClient() 158 if err != nil { 159 return errors.Trace(err) 160 } 161 defer client.Close() 162 163 archive, err := client.Download(id) 164 if err != nil { 165 return errors.Trace(err) 166 } 167 defer archive.Close() 168 169 outfile, err := os.Create(filename) 170 if err != nil { 171 return errors.Trace(err) 172 } 173 defer outfile.Close() 174 175 _, err = io.Copy(outfile, archive) 176 return errors.Trace(err) 177 }