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  }