github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/model/setcredential.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for infos. 3 4 package model 5 6 import ( 7 "github.com/juju/cmd" 8 "github.com/juju/errors" 9 "gopkg.in/juju/names.v2" 10 11 "github.com/juju/juju/api/base" 12 cloudapi "github.com/juju/juju/api/cloud" 13 "github.com/juju/juju/api/modelmanager" 14 "github.com/juju/juju/cloud" 15 jujucmd "github.com/juju/juju/cmd" 16 "github.com/juju/juju/cmd/juju/common" 17 "github.com/juju/juju/cmd/modelcmd" 18 ) 19 20 // ModelCredentialAPI defines methods used to replace model credential. 21 type ModelCredentialAPI interface { 22 Close() error 23 ChangeModelCredential(model names.ModelTag, credential names.CloudCredentialTag) error 24 } 25 26 // CloudAPI defines methods used to detemine if cloud credential exists on the controller. 27 type CloudAPI interface { 28 Close() error 29 UserCredentials(names.UserTag, names.CloudTag) ([]names.CloudCredentialTag, error) 30 AddCredential(tag string, credential cloud.Credential) error 31 } 32 33 // modelCredentialCommand allows to change, replace a cloud credential for a model. 34 type modelCredentialCommand struct { 35 modelcmd.ModelCommandBase 36 37 cloud string 38 credential string 39 40 newAPIRootFunc func() (base.APICallCloser, error) 41 newModelCredentialAPIFunc func(base.APICallCloser) ModelCredentialAPI 42 newCloudAPIFunc func(base.APICallCloser) CloudAPI 43 } 44 45 func NewModelCredentialCommand() cmd.Command { 46 command := &modelCredentialCommand{ 47 newModelCredentialAPIFunc: func(root base.APICallCloser) ModelCredentialAPI { 48 return modelmanager.NewClient(root) 49 }, 50 newCloudAPIFunc: func(root base.APICallCloser) CloudAPI { 51 return cloudapi.NewClient(root) 52 }, 53 } 54 command.newAPIRootFunc = func() (base.APICallCloser, error) { 55 return command.NewControllerAPIRoot() 56 } 57 return modelcmd.Wrap(command) 58 } 59 60 // Info implements Command.Info. 61 func (c *modelCredentialCommand) Info() *cmd.Info { 62 return jujucmd.Info(&cmd.Info{ 63 Name: "set-credential", 64 Args: "<cloud name> <credential name>", 65 Purpose: "Relates a remote credential to a model.", 66 Doc: modelCredentialDoc, 67 }) 68 } 69 70 // Init implements Command.Init. 71 func (c *modelCredentialCommand) Init(args []string) error { 72 if len(args) != 2 { 73 return errors.Errorf("Usage: juju set-credential [options] <cloud name> <credential name>") 74 } 75 if !names.IsValidCloud(args[0]) { 76 return errors.NotValidf("cloud name %q", args[0]) 77 } 78 if !names.IsValidCloudCredentialName(args[1]) { 79 return errors.NotValidf("cloud credential name %q", args[1]) 80 } 81 c.cloud = args[0] 82 c.credential = args[1] 83 return nil 84 } 85 86 // Run implements Command.Run. 87 func (c *modelCredentialCommand) Run(ctx *cmd.Context) error { 88 fail := func(e error) error { 89 ctx.Infof("Failed to change model credential: %v", e) 90 return e 91 } 92 93 root, err := c.newAPIRootFunc() 94 if err != nil { 95 return fail(errors.Annotate(err, "opening API connection")) 96 } 97 defer root.Close() 98 99 accountDetails, err := c.CurrentAccountDetails() 100 if err != nil { 101 return fail(errors.Annotate(err, "getting current account")) 102 } 103 userTag := names.NewUserTag(accountDetails.User) 104 cloudTag := names.NewCloudTag(c.cloud) 105 credentialTag, err := common.ResolveCloudCredentialTag(userTag, cloudTag, c.credential) 106 if err != nil { 107 return fail(errors.Annotate(err, "resolving credential")) 108 } 109 110 cloudClient := c.newCloudAPIFunc(root) 111 defer cloudClient.Close() 112 113 remote := false 114 remoteCredentials, err := cloudClient.UserCredentials(userTag, cloudTag) 115 if err != nil { 116 // This is ok - we can proceed with local ones anyway. 117 ctx.Infof("Could not determine if there are remote credentials for the user: %v", err) 118 } else { 119 for _, credTag := range remoteCredentials { 120 if credTag == credentialTag { 121 remote = true 122 ctx.Infof("Found credential remotely, on the controller. Not looking locally...") 123 break 124 } 125 } 126 } 127 128 // Credential does not exist remotely. Upload it. 129 if !remote { 130 ctx.Infof("Did not find credential remotely. Looking locally...") 131 credential, err := c.findCredentialLocally(ctx) 132 if err != nil { 133 return fail((err)) 134 } 135 ctx.Infof("Uploading local credential to the controller.") 136 err = cloudClient.AddCredential(credentialTag.String(), *credential) 137 if err != nil { 138 return fail(err) 139 } 140 } 141 142 modelName, modelDetails, err := c.ModelDetails() 143 if err != nil { 144 return fail(errors.Trace(err)) 145 } 146 modelTag := names.NewModelTag(modelDetails.ModelUUID) 147 148 modelClient := c.newModelCredentialAPIFunc(root) 149 defer modelClient.Close() 150 151 err = modelClient.ChangeModelCredential(modelTag, credentialTag) 152 if err != nil { 153 return fail(errors.Trace(err)) 154 } 155 ctx.Infof("Changed cloud credential on model %q to %q.", modelName, c.credential) 156 return nil 157 } 158 159 func (c *modelCredentialCommand) findCredentialLocally(ctx *cmd.Context) (*cloud.Credential, error) { 160 foundcloud, err := common.CloudByName(c.cloud) 161 if err != nil { 162 return nil, err 163 } 164 getCredentialsParams := modelcmd.GetCredentialsParams{ 165 Cloud: *foundcloud, 166 CredentialName: c.credential, 167 } 168 credential, _, _, err := modelcmd.GetCredentials(ctx, c.ClientStore(), getCredentialsParams) 169 if err != nil { 170 return nil, err 171 } 172 return credential, nil 173 } 174 175 const modelCredentialDoc = ` 176 This command relates a credential cached on a controller to a specific model. 177 It does not change/update the contents of an existing active credential. See 178 command ` + "`update-credential`" + ` for that. 179 180 The credential specified may exist locally (on the client), remotely (on the 181 controller), or both. The command will error out if the credential is stored 182 neither remotely nor locally. 183 184 When remote, the credential will be related to the specified model. 185 186 When local and not remote, the credential will first be uploaded to the 187 controller and then related. 188 189 This command does not affect an existing relation between the specified 190 credential and another model. If the credential is already related to a model 191 this operation will result in that credential being related to two models. 192 193 Use the ` + "`show-credential`" + ` command to see how remote credentials are related 194 to models. 195 196 Examples: 197 198 For cloud 'aws', relate remote credential 'bob' to model 'trinity': 199 200 juju set-credential -m trinity aws bob 201 202 See also: 203 credentials 204 show-credential 205 update-credential 206 `