github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cmd/juju/backups/restore.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package backups 5 6 import ( 7 "fmt" 8 "path/filepath" 9 "strings" 10 11 "github.com/juju/cmd" 12 "github.com/juju/errors" 13 "launchpad.net/gnuflag" 14 15 "github.com/juju/juju/api/backups" 16 "github.com/juju/juju/apiserver/params" 17 "github.com/juju/juju/cmd/envcmd" 18 "github.com/juju/juju/constraints" 19 "github.com/juju/juju/environs" 20 "github.com/juju/juju/environs/bootstrap" 21 "github.com/juju/juju/environs/configstore" 22 ) 23 24 // RestoreCommand is a subcommand of backups that implement the restore behaior 25 // it is invoked with "juju backups restore". 26 type RestoreCommand struct { 27 CommandBase 28 constraints constraints.Value 29 filename string 30 backupId string 31 bootstrap bool 32 } 33 34 var restoreDoc = ` 35 Restores a backup that was previously created with "juju backup" and 36 "juju backups create". 37 38 This command creates a new state server and arranges for it to replace 39 the previous state server for an environment. It does *not* restore 40 an existing server to a previous state, but instead creates a new server 41 with equivalent state. As part of restore, all known instances are 42 configured to treat the new state server as their master. 43 44 The given constraints will be used to choose the new instance. 45 46 If the provided state cannot be restored, this command will fail with 47 an appropriate message. For instance, if the existing bootstrap 48 instance is already running then the command will fail with a message 49 to that effect. 50 ` 51 52 // Info returns the content for --help. 53 func (c *RestoreCommand) Info() *cmd.Info { 54 return &cmd.Info{ 55 Name: "restore", 56 Purpose: "restore from a backup archive to a new state server", 57 Args: "", 58 Doc: strings.TrimSpace(restoreDoc), 59 } 60 } 61 62 // SetFlags handles known option flags. 63 func (c *RestoreCommand) SetFlags(f *gnuflag.FlagSet) { 64 f.Var(constraints.ConstraintsValue{Target: &c.constraints}, 65 "constraints", "set environment constraints") 66 67 f.BoolVar(&c.bootstrap, "b", false, "bootstrap a new state machine") 68 f.StringVar(&c.filename, "file", "", "provide a file to be used as the backup.") 69 f.StringVar(&c.backupId, "id", "", "provide the name of the backup to be restored.") 70 } 71 72 // Init is where the preconditions for this commands can be checked. 73 func (c *RestoreCommand) Init(args []string) error { 74 if c.filename == "" && c.backupId == "" { 75 return errors.Errorf("you must specify either a file or a backup id.") 76 } 77 if c.filename != "" && c.backupId != "" { 78 return errors.Errorf("you must specify either a file or a backup id but not both.") 79 } 80 if c.backupId != "" && c.bootstrap { 81 return errors.Errorf("it is not possible to rebootstrap and restore from an id.") 82 } 83 var err error 84 if c.filename != "" { 85 c.filename, err = filepath.Abs(c.filename) 86 if err != nil { 87 return errors.Trace(err) 88 } 89 } 90 return nil 91 } 92 93 const restoreAPIIncompatibility = "server version not compatible for " + 94 "restore with client version" 95 96 // runRestore will implement the actual calls to the different Client parts 97 // of restore. 98 func (c *RestoreCommand) runRestore(ctx *cmd.Context) error { 99 client, closer, err := c.newClient() 100 if err != nil { 101 return errors.Trace(err) 102 } 103 defer closer() 104 var target string 105 var rErr error 106 if c.filename != "" { 107 target = c.filename 108 archive, meta, err := getArchive(c.filename) 109 if err != nil { 110 return errors.Trace(err) 111 } 112 defer archive.Close() 113 114 rErr = client.RestoreReader(archive, meta, c.newClient) 115 } else { 116 target = c.backupId 117 rErr = client.Restore(c.backupId, c.newClient) 118 } 119 if params.IsCodeNotImplemented(rErr) { 120 return errors.Errorf(restoreAPIIncompatibility) 121 } 122 if rErr != nil { 123 return errors.Trace(rErr) 124 } 125 126 fmt.Fprintf(ctx.Stdout, "restore from %q completed\n", target) 127 return nil 128 } 129 130 // rebootstrap will bootstrap a new server in safe-mode (not killing any other agent) 131 // if there is no current server available to restore to. 132 func (c *RestoreCommand) rebootstrap(ctx *cmd.Context) error { 133 store, err := configstore.Default() 134 if err != nil { 135 return errors.Trace(err) 136 } 137 cfg, err := c.Config(store, nil) 138 if err != nil { 139 return errors.Trace(err) 140 } 141 // Turn on safe mode so that the newly bootstrapped instance 142 // will not destroy all the instances it does not know about. 143 cfg, err = cfg.Apply(map[string]interface{}{ 144 "provisioner-safe-mode": true, 145 }) 146 if err != nil { 147 return errors.Annotatef(err, "cannot enable provisioner-safe-mode") 148 } 149 env, err := environs.New(cfg) 150 if err != nil { 151 return errors.Trace(err) 152 } 153 instanceIds, err := env.StateServerInstances() 154 if err != nil { 155 return errors.Annotatef(err, "cannot determine state server instances") 156 } 157 if len(instanceIds) == 0 { 158 return errors.Errorf("no instances found; perhaps the environment was not bootstrapped") 159 } 160 inst, err := env.Instances(instanceIds) 161 if err == nil { 162 return errors.Errorf("old bootstrap instance %q still seems to exist; will not replace", inst) 163 } 164 if err != environs.ErrNoInstances { 165 return errors.Annotatef(err, "cannot detect whether old instance is still running") 166 } 167 168 cons := c.constraints 169 args := bootstrap.BootstrapParams{Constraints: cons} 170 if err := bootstrap.Bootstrap(envcmd.BootstrapContext(ctx), env, args); err != nil { 171 return errors.Annotatef(err, "cannot bootstrap new instance") 172 } 173 return nil 174 } 175 176 func (c *RestoreCommand) newClient() (*backups.Client, func() error, error) { 177 client, err := c.NewAPIClient() 178 if err != nil { 179 return nil, nil, errors.Trace(err) 180 } 181 backupsClient, ok := client.(*backups.Client) 182 if !ok { 183 return nil, nil, errors.Errorf("invalid client for backups") 184 } 185 return backupsClient, client.Close, nil 186 } 187 188 // Run is the entry point for this command. 189 func (c *RestoreCommand) Run(ctx *cmd.Context) error { 190 if c.bootstrap { 191 if err := c.rebootstrap(ctx); err != nil { 192 return errors.Trace(err) 193 } 194 } 195 return c.runRestore(ctx) 196 }