github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "io" 9 "path/filepath" 10 "strings" 11 12 "github.com/juju/cmd" 13 "github.com/juju/errors" 14 "github.com/juju/gnuflag" 15 "gopkg.in/juju/names.v2" 16 17 "github.com/juju/juju/api/backups" 18 "github.com/juju/juju/api/base" 19 "github.com/juju/juju/apiserver/params" 20 jujucmd "github.com/juju/juju/cmd" 21 "github.com/juju/juju/cmd/juju/controller" 22 "github.com/juju/juju/cmd/modelcmd" 23 "github.com/juju/juju/environs/bootstrap" 24 ) 25 26 // NewRestoreCommand returns a command used to restore a backup. 27 func NewRestoreCommand() cmd.Command { 28 c := &restoreCommand{} 29 c.getModelStatusAPI = func() (ModelStatusAPI, error) { return c.NewModelManagerAPIClient() } 30 return modelcmd.Wrap(c) 31 } 32 33 // restoreCommand is a subcommand of backups that implement the restore behavior 34 // it is invoked with "juju restore-backup". 35 type restoreCommand struct { 36 CommandBase 37 getModelStatusAPI func() (ModelStatusAPI, error) 38 39 Filename string 40 BackupId string 41 } 42 43 // RestoreAPI is used to invoke various API calls. 44 type RestoreAPI interface { 45 // Close is taken from io.Closer. 46 Close() error 47 48 // Restore is taken from backups.Client. 49 Restore(backupId string, newClient backups.ClientConnection) error 50 51 // RestoreReader is taken from backups.Client. 52 RestoreReader(r io.ReadSeeker, meta *params.BackupsMetadataResult, newClient backups.ClientConnection) error 53 } 54 55 // ModelStatusAPI is used to invoke common.ModelStatus 56 // The interface is used to facilitate testing. 57 // 58 //go:generate mockgen -package backups_test -destination modelstatusapi_mock_test.go github.com/juju/juju/cmd/juju/backups ModelStatusAPI 59 type ModelStatusAPI interface { 60 Close() error 61 ModelStatus(tags ...names.ModelTag) ([]base.ModelStatus, error) 62 } 63 64 var restoreDoc = ` 65 Restores the Juju state database backup that was previously created with 66 "juju create-backup", returning an existing controller to a previous state. 67 68 Note: Only the database will be restored. Juju will not change the existing 69 environment to match the restored database, e.g. no units, relations, nor 70 machines will be added or removed during the restore process. 71 72 Note: Extra care is needed to restore in an HA environment, please see 73 https://docs.jujucharms.com/stable/controllers-backup for more information. 74 75 If the provided state cannot be restored, this command will fail with 76 an explanation. 77 ` 78 79 // Info returns the content for --help. 80 func (c *restoreCommand) Info() *cmd.Info { 81 return jujucmd.Info(&cmd.Info{ 82 Name: "restore-backup", 83 Purpose: "Restore from a backup archive to the existing controller.", 84 Args: "", 85 Doc: strings.TrimSpace(restoreDoc), 86 }) 87 } 88 89 // SetFlags handles known option flags. 90 func (c *restoreCommand) SetFlags(f *gnuflag.FlagSet) { 91 c.CommandBase.SetFlags(f) 92 f.StringVar(&c.Filename, "file", "", "Provide a file to be used as the backup") 93 f.StringVar(&c.BackupId, "id", "", "Provide the name of the backup to be restored") 94 } 95 96 // Init is where the preconditions for this command can be checked. 97 func (c *restoreCommand) Init(args []string) error { 98 if c.Filename == "" && c.BackupId == "" { 99 return errors.Errorf("you must specify either a file or a backup id.") 100 } 101 if c.Filename != "" && c.BackupId != "" { 102 return errors.Errorf("you must specify either a file or a backup id but not both.") 103 } 104 105 if c.Filename != "" { 106 var err error 107 c.Filename, err = filepath.Abs(c.Filename) 108 if err != nil { 109 return errors.Trace(err) 110 } 111 } 112 113 return nil 114 } 115 116 func (c *restoreCommand) modelStatus() (string, []base.ModelStatus, error) { 117 modelUUIDs, err := c.ModelUUIDs([]string{bootstrap.ControllerModelName}) 118 if err != nil { 119 return "", nil, errors.Annotatef(err, "cannot get controller model uuid") 120 } 121 if len(modelUUIDs) != 1 { 122 return "", nil, errors.New("cannot get controller model uuid") 123 } 124 controllerModelUUID := modelUUIDs[0] 125 126 modelClient, err := c.getModelStatusAPI() 127 if err != nil { 128 return "", nil, errors.Annotatef(err, "cannot get model status client") 129 } 130 defer modelClient.Close() 131 132 modelStatus, err := modelClient.ModelStatus(names.NewModelTag(controllerModelUUID)) 133 if err != nil { 134 return "", nil, errors.Annotatef(err, "cannot refresh controller model") 135 } 136 if len(modelStatus) != 1 { 137 return "", nil, errors.New("could not find controller model status") 138 } 139 140 return controllerModelUUID, modelStatus, nil 141 } 142 143 func (c *restoreCommand) newClient() (*backups.Client, error) { 144 client, err := c.NewAPIClient() 145 if err != nil { 146 return nil, errors.Trace(err) 147 } 148 backupsClient, ok := client.(*backups.Client) 149 if !ok { 150 return nil, errors.Errorf("invalid client for backups") 151 } 152 return backupsClient, nil 153 } 154 155 // Run is the entry point for this command. 156 func (c *restoreCommand) Run(ctx *cmd.Context) error { 157 if c.Log != nil { 158 if err := c.Log.Start(ctx); err != nil { 159 return err 160 } 161 } 162 163 // Don't allow restore in an HA environment 164 controllerModelUUID, modelStatus, err := c.modelStatus() 165 if err != nil { 166 return errors.Trace(err) 167 } 168 activeCount, _ := controller.ControllerMachineCounts(controllerModelUUID, modelStatus) 169 if activeCount > 1 { 170 return errors.Errorf("unable to restore backup in HA configuration. For help see https://docs.jujucharms.com/stable/controllers-backup") 171 } 172 173 var archive ArchiveReader 174 var meta *params.BackupsMetadataResult 175 target := c.BackupId 176 if c.Filename != "" { 177 // Read archive specified by the Filename 178 target = c.Filename 179 var err error 180 archive, meta, err = getArchive(c.Filename) 181 if err != nil { 182 return errors.Trace(err) 183 } 184 defer archive.Close() 185 } 186 187 client, err := c.NewAPIClient() 188 if err != nil { 189 return errors.Trace(err) 190 } 191 defer client.Close() 192 193 // We have a backup client, now use the relevant method 194 // to restore the backup. 195 if c.Filename != "" { 196 err = client.RestoreReader(archive, meta, c.newClient) 197 } else { 198 err = client.Restore(c.BackupId, c.newClient) 199 } 200 if err != nil { 201 return errors.Trace(err) 202 } 203 fmt.Fprintf(ctx.Stdout, "restore from %q completed\n", target) 204 return nil 205 }