github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  	"github.com/juju/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  }