github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/api/cloud/cloud.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package cloud 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/loggo" 9 "gopkg.in/juju/names.v2" 10 11 "github.com/juju/juju/api/base" 12 "github.com/juju/juju/apiserver/common" 13 "github.com/juju/juju/apiserver/params" 14 jujucloud "github.com/juju/juju/cloud" 15 "github.com/juju/juju/permission" 16 ) 17 18 var logger = loggo.GetLogger("juju.api.cloud") 19 20 // Client provides methods that the Juju client command uses to interact 21 // with models stored in the Juju Server. 22 type Client struct { 23 base.ClientFacade 24 facade base.FacadeCaller 25 } 26 27 // NewClient creates a new `Client` based on an existing authenticated API 28 // connection. 29 func NewClient(st base.APICallCloser) *Client { 30 frontend, backend := base.NewClientFacade(st, "Cloud") 31 return &Client{ClientFacade: frontend, facade: backend} 32 } 33 34 // Clouds returns the details of all clouds supported by the controller. 35 func (c *Client) Clouds() (map[names.CloudTag]jujucloud.Cloud, error) { 36 var result params.CloudsResult 37 if err := c.facade.FacadeCall("Clouds", nil, &result); err != nil { 38 return nil, errors.Trace(err) 39 } 40 clouds := make(map[names.CloudTag]jujucloud.Cloud) 41 for tagString, cloud := range result.Clouds { 42 tag, err := names.ParseCloudTag(tagString) 43 if err != nil { 44 return nil, errors.Trace(err) 45 } 46 clouds[tag] = common.CloudFromParams(tag.Id(), cloud) 47 } 48 return clouds, nil 49 } 50 51 // Cloud returns the details of the cloud with the given tag. 52 func (c *Client) Cloud(tag names.CloudTag) (jujucloud.Cloud, error) { 53 var results params.CloudResults 54 args := params.Entities{[]params.Entity{{tag.String()}}} 55 if err := c.facade.FacadeCall("Cloud", args, &results); err != nil { 56 return jujucloud.Cloud{}, errors.Trace(err) 57 } 58 if len(results.Results) != 1 { 59 return jujucloud.Cloud{}, errors.Errorf("expected 1 result, got %d", len(results.Results)) 60 } 61 if results.Results[0].Error != nil { 62 return jujucloud.Cloud{}, results.Results[0].Error 63 } 64 return common.CloudFromParams(tag.Id(), *results.Results[0].Cloud), nil 65 } 66 67 // DefaultCloud returns the tag of the cloud that models will be 68 // created in by default. 69 func (c *Client) DefaultCloud() (names.CloudTag, error) { 70 var result params.StringResult 71 if err := c.facade.FacadeCall("DefaultCloud", nil, &result); err != nil { 72 return names.CloudTag{}, errors.Trace(err) 73 } 74 if result.Error != nil { 75 return names.CloudTag{}, result.Error 76 } 77 cloudTag, err := names.ParseCloudTag(result.Result) 78 if err != nil { 79 return names.CloudTag{}, errors.Trace(err) 80 } 81 return cloudTag, nil 82 } 83 84 // UserCredentials returns the tags for cloud credentials available to a user for 85 // use with a specific cloud. 86 func (c *Client) UserCredentials(user names.UserTag, cloud names.CloudTag) ([]names.CloudCredentialTag, error) { 87 var results params.StringsResults 88 args := params.UserClouds{[]params.UserCloud{ 89 {UserTag: user.String(), CloudTag: cloud.String()}, 90 }} 91 if err := c.facade.FacadeCall("UserCredentials", args, &results); err != nil { 92 return nil, errors.Trace(err) 93 } 94 if len(results.Results) != 1 { 95 return nil, errors.Errorf("expected 1 result, got %d", len(results.Results)) 96 } 97 if results.Results[0].Error != nil { 98 return nil, results.Results[0].Error 99 } 100 tags := make([]names.CloudCredentialTag, len(results.Results[0].Result)) 101 for i, s := range results.Results[0].Result { 102 tag, err := names.ParseCloudCredentialTag(s) 103 if err != nil { 104 return nil, errors.Trace(err) 105 } 106 tags[i] = tag 107 } 108 return tags, nil 109 } 110 111 // UpdateCredentialsCheckModels updates a cloud credential content 112 // stored on the controller. This call validates that the new content works 113 // for all models that are using this credential. 114 func (c *Client) UpdateCredentialsCheckModels(tag names.CloudCredentialTag, credential jujucloud.Credential) ([]params.UpdateCredentialModelResult, error) { 115 in := params.UpdateCredentialArgs{ 116 Credentials: []params.TaggedCredential{{ 117 Tag: tag.String(), 118 Credential: params.CloudCredential{ 119 AuthType: string(credential.AuthType()), 120 Attributes: credential.Attributes(), 121 }, 122 }}, 123 } 124 125 if c.facade.BestAPIVersion() < 3 { 126 var out params.ErrorResults 127 if err := c.facade.FacadeCall("UpdateCredentials", in, &out); err != nil { 128 return nil, errors.Trace(err) 129 } 130 if len(out.Results) != 1 { 131 return nil, errors.Errorf("expected 1 result for when updating credential %q, got %d", tag.Name(), len(out.Results)) 132 } 133 if out.Results[0].Error != nil { 134 return nil, errors.Trace(out.Results[0].Error) 135 } 136 return nil, nil 137 } 138 139 var out params.UpdateCredentialResults 140 if err := c.facade.FacadeCall("UpdateCredentialsCheckModels", in, &out); err != nil { 141 return nil, errors.Trace(err) 142 } 143 if len(out.Results) != 1 { 144 return nil, errors.Errorf("expected 1 result for when updating credential %q, got %d", tag.Name(), len(out.Results)) 145 } 146 if out.Results[0].Error != nil { 147 // Unlike many other places, we want to return something valid here to provide more details. 148 return out.Results[0].Models, errors.Trace(out.Results[0].Error) 149 } 150 return out.Results[0].Models, nil 151 } 152 153 // RevokeCredential revokes/deletes a cloud credential. 154 func (c *Client) RevokeCredential(tag names.CloudCredentialTag) error { 155 var results params.ErrorResults 156 157 if c.facade.BestAPIVersion() < 3 { 158 args := params.Entities{ 159 Entities: []params.Entity{{ 160 Tag: tag.String(), 161 }}, 162 } 163 if err := c.facade.FacadeCall("RevokeCredentials", args, &results); err != nil { 164 return errors.Trace(err) 165 } 166 return results.OneError() 167 } 168 169 args := params.RevokeCredentialArgs{ 170 Credentials: []params.RevokeCredentialArg{ 171 {Tag: tag.String()}, 172 }, 173 } 174 if err := c.facade.FacadeCall("RevokeCredentialsCheckModels", args, &results); err != nil { 175 return errors.Trace(err) 176 } 177 return results.OneError() 178 } 179 180 // Credentials returns a slice of credential values for the specified tags. 181 // Secrets are excluded from the credential attributes. 182 func (c *Client) Credentials(tags ...names.CloudCredentialTag) ([]params.CloudCredentialResult, error) { 183 if len(tags) == 0 { 184 return []params.CloudCredentialResult{}, nil 185 } 186 var results params.CloudCredentialResults 187 args := params.Entities{ 188 Entities: make([]params.Entity, len(tags)), 189 } 190 for i, tag := range tags { 191 args.Entities[i].Tag = tag.String() 192 } 193 if err := c.facade.FacadeCall("Credential", args, &results); err != nil { 194 return nil, errors.Trace(err) 195 } 196 return results.Results, nil 197 } 198 199 // AddCredential adds a credential to the controller with a given tag. 200 // This can be a credential for a cloud that is not the same cloud as the controller's host. 201 func (c *Client) AddCredential(tag string, credential jujucloud.Credential) error { 202 if bestVer := c.BestAPIVersion(); bestVer < 2 { 203 return errors.NotImplementedf("AddCredential() (need v2+, have v%d)", bestVer) 204 } 205 var results params.ErrorResults 206 cloudCredential := params.CloudCredential{ 207 AuthType: string(credential.AuthType()), 208 Attributes: credential.Attributes(), 209 } 210 args := params.TaggedCredentials{ 211 Credentials: []params.TaggedCredential{{ 212 Tag: tag, 213 Credential: cloudCredential, 214 }, 215 }} 216 if err := c.facade.FacadeCall("AddCredentials", args, &results); err != nil { 217 return errors.Trace(err) 218 } 219 return results.OneError() 220 } 221 222 // AddCloud adds a new cloud to current controller. 223 func (c *Client) AddCloud(cloud jujucloud.Cloud) error { 224 if bestVer := c.BestAPIVersion(); bestVer < 2 { 225 return errors.NotImplementedf("AddCloud() (need v2+, have v%d)", bestVer) 226 } 227 args := params.AddCloudArgs{Name: cloud.Name, Cloud: common.CloudToParams(cloud)} 228 err := c.facade.FacadeCall("AddCloud", args, nil) 229 if err != nil { 230 return errors.Trace(err) 231 } 232 return nil 233 } 234 235 // RemoveCloud removes a cloud from the current controller. 236 func (c *Client) RemoveCloud(cloud string) error { 237 if bestVer := c.BestAPIVersion(); bestVer < 2 { 238 return errors.NotImplementedf("RemoveCloud() (need v2+, have v%d)", bestVer) 239 } 240 args := params.Entities{Entities: []params.Entity{{Tag: names.NewCloudTag(cloud).String()}}} 241 var result params.ErrorResults 242 err := c.facade.FacadeCall("RemoveClouds", args, &result) 243 if err != nil { 244 return errors.Trace(err) 245 } 246 return result.OneError() 247 } 248 249 // CredentialContents returns contents of the credential values for the specified 250 // cloud and credential name. Secrets will be included if requested. 251 func (c *Client) CredentialContents(cloud, credential string, withSecrets bool) ([]params.CredentialContentResult, error) { 252 if bestVer := c.BestAPIVersion(); bestVer < 2 { 253 return nil, errors.NotImplementedf("CredentialContents() (need v2+, have v%d)", bestVer) 254 } 255 oneCredential := params.CloudCredentialArg{} 256 if cloud == "" && credential == "" { 257 // this is valid and means we want all. 258 } else if cloud == "" { 259 return nil, errors.New("cloud name must be supplied") 260 } else if credential == "" { 261 return nil, errors.New("credential name must be supplied") 262 } else { 263 oneCredential.CloudName = cloud 264 oneCredential.CredentialName = credential 265 } 266 var out params.CredentialContentResults 267 in := params.CloudCredentialArgs{ 268 IncludeSecrets: withSecrets, 269 } 270 if !oneCredential.IsEmpty() { 271 in.Credentials = []params.CloudCredentialArg{oneCredential} 272 } 273 err := c.facade.FacadeCall("CredentialContents", in, &out) 274 if err != nil { 275 return nil, errors.Trace(err) 276 } 277 if !oneCredential.IsEmpty() && len(out.Results) != 1 { 278 return nil, errors.Errorf("expected 1 result for credential %q on cloud %q, got %d", cloud, credential, len(out.Results)) 279 } 280 return out.Results, nil 281 } 282 283 // GrantCloud grants a user access to a cloud. 284 func (c *Client) GrantCloud(user, access string, clouds ...string) error { 285 if bestVer := c.BestAPIVersion(); bestVer < 3 { 286 return errors.NotImplementedf("GrantCloud() (need v3+, have v%d)", bestVer) 287 } 288 return c.modifyCloudUser(params.GrantCloudAccess, user, access, clouds) 289 } 290 291 // RevokeCloud revokes a user's access to a cloud. 292 func (c *Client) RevokeCloud(user, access string, clouds ...string) error { 293 if bestVer := c.BestAPIVersion(); bestVer < 3 { 294 return errors.NotImplementedf("RevokeCloud() (need v3+, have v%d)", bestVer) 295 } 296 return c.modifyCloudUser(params.RevokeCloudAccess, user, access, clouds) 297 } 298 299 func (c *Client) modifyCloudUser(action params.CloudAction, user, access string, clouds []string) error { 300 var args params.ModifyCloudAccessRequest 301 302 if !names.IsValidUser(user) { 303 return errors.Errorf("invalid username: %q", user) 304 } 305 userTag := names.NewUserTag(user) 306 307 cloudAccess := permission.Access(access) 308 if err := permission.ValidateCloudAccess(cloudAccess); err != nil { 309 return errors.Trace(err) 310 } 311 for _, cloud := range clouds { 312 if !names.IsValidCloud(cloud) { 313 return errors.NotValidf("cloud %q", cloud) 314 } 315 cloudTag := names.NewCloudTag(cloud) 316 args.Changes = append(args.Changes, params.ModifyCloudAccess{ 317 UserTag: userTag.String(), 318 Action: action, 319 Access: access, 320 CloudTag: cloudTag.String(), 321 }) 322 } 323 324 var result params.ErrorResults 325 err := c.facade.FacadeCall("ModifyCloudAccess", args, &result) 326 if err != nil { 327 return errors.Trace(err) 328 } 329 if len(result.Results) != len(args.Changes) { 330 return errors.Errorf("expected %d results, got %d", len(args.Changes), len(result.Results)) 331 } 332 333 for i, r := range result.Results { 334 if r.Error != nil && r.Error.Code == params.CodeAlreadyExists { 335 logger.Warningf("cloud %q is already shared with %q", clouds[i], userTag.Id()) 336 result.Results[i].Error = nil 337 } 338 } 339 return result.Combine() 340 }