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