github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/api/client/cloud/cloud_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package cloud_test 5 6 import ( 7 "fmt" 8 "reflect" 9 "sort" 10 11 "github.com/juju/errors" 12 "github.com/juju/names/v5" 13 jc "github.com/juju/testing/checkers" 14 "github.com/kr/pretty" 15 "go.uber.org/mock/gomock" 16 gc "gopkg.in/check.v1" 17 18 basemocks "github.com/juju/juju/api/base/mocks" 19 cloudapi "github.com/juju/juju/api/client/cloud" 20 apiservererrors "github.com/juju/juju/apiserver/errors" 21 "github.com/juju/juju/cloud" 22 "github.com/juju/juju/rpc/params" 23 coretesting "github.com/juju/juju/testing" 24 ) 25 26 type cloudSuite struct { 27 } 28 29 var _ = gc.Suite(&cloudSuite{}) 30 31 func (s *cloudSuite) SetUpTest(c *gc.C) { 32 } 33 34 func (s *cloudSuite) TestCloud(c *gc.C) { 35 ctrl := gomock.NewController(c) 36 defer ctrl.Finish() 37 38 args := params.Entities{ 39 Entities: []params.Entity{{Tag: "cloud-foo"}}, 40 } 41 res := new(params.CloudResults) 42 results := params.CloudResults{ 43 Results: []params.CloudResult{{ 44 Cloud: ¶ms.Cloud{ 45 Type: "dummy", 46 AuthTypes: []string{"empty", "userpass"}, 47 Regions: []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}}, 48 }}, 49 }, 50 } 51 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 52 mockFacadeCaller.EXPECT().FacadeCall("Cloud", args, res).SetArg(2, results).Return(nil) 53 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 54 55 result, err := client.Cloud(names.NewCloudTag("foo")) 56 c.Assert(err, jc.ErrorIsNil) 57 c.Assert(result, jc.DeepEquals, cloud.Cloud{ 58 Name: "foo", 59 Type: "dummy", 60 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType}, 61 Regions: []cloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 62 }) 63 } 64 65 func (s *cloudSuite) TestCloudInfo(c *gc.C) { 66 ctrl := gomock.NewController(c) 67 defer ctrl.Finish() 68 69 args := params.Entities{ 70 Entities: []params.Entity{ 71 {Tag: "cloud-foo"}, {Tag: "cloud-bar"}, 72 }, 73 } 74 res := new(params.CloudInfoResults) 75 results := params.CloudInfoResults{ 76 Results: []params.CloudInfoResult{{ 77 Result: ¶ms.CloudInfo{ 78 CloudDetails: params.CloudDetails{ 79 Type: "dummy", 80 AuthTypes: []string{"empty", "userpass"}, 81 Regions: []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}}, 82 }, 83 Users: []params.CloudUserInfo{{ 84 UserName: "fred", 85 DisplayName: "Fred", 86 Access: "admin", 87 }, { 88 UserName: "bob", 89 DisplayName: "Bob", 90 Access: "add-model", 91 }}, 92 }, 93 }, { 94 Result: ¶ms.CloudInfo{ 95 CloudDetails: params.CloudDetails{ 96 Type: "dummy", 97 AuthTypes: []string{"empty", "userpass"}, 98 Regions: []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}}, 99 }, 100 Users: []params.CloudUserInfo{{ 101 UserName: "mary", 102 DisplayName: "Mary", 103 Access: "admin", 104 }}, 105 }, 106 }}, 107 } 108 109 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 110 mockFacadeCaller.EXPECT().FacadeCall("CloudInfo", args, res).SetArg(2, results).Return(nil) 111 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 112 113 result, err := client.CloudInfo([]names.CloudTag{ 114 names.NewCloudTag("foo"), 115 names.NewCloudTag("bar"), 116 }) 117 c.Assert(err, jc.ErrorIsNil) 118 c.Assert(result, jc.DeepEquals, []cloudapi.CloudInfo{{ 119 Cloud: cloud.Cloud{ 120 Name: "foo", 121 Type: "dummy", 122 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType}, 123 Regions: []cloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 124 }, 125 Users: map[string]cloudapi.CloudUserInfo{ 126 "bob": { 127 DisplayName: "Bob", 128 Access: "add-model", 129 }, 130 "fred": { 131 DisplayName: "Fred", 132 Access: "admin", 133 }, 134 }, 135 }, { 136 Cloud: cloud.Cloud{ 137 Name: "bar", 138 Type: "dummy", 139 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType}, 140 Regions: []cloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 141 }, 142 Users: map[string]cloudapi.CloudUserInfo{ 143 "mary": { 144 DisplayName: "Mary", 145 Access: "admin", 146 }, 147 }, 148 }}) 149 } 150 151 func (s *cloudSuite) TestClouds(c *gc.C) { 152 ctrl := gomock.NewController(c) 153 defer ctrl.Finish() 154 155 res := new(params.CloudsResult) 156 results := params.CloudsResult{ 157 Clouds: map[string]params.Cloud{ 158 "cloud-foo": { 159 Type: "bar", 160 }, 161 "cloud-baz": { 162 Type: "qux", 163 AuthTypes: []string{"empty", "userpass"}, 164 Regions: []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}}, 165 }, 166 }} 167 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 168 mockFacadeCaller.EXPECT().FacadeCall("Clouds", nil, res).SetArg(2, results).Return(nil) 169 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 170 171 clouds, err := client.Clouds() 172 c.Assert(err, jc.ErrorIsNil) 173 c.Assert(clouds, jc.DeepEquals, map[names.CloudTag]cloud.Cloud{ 174 names.NewCloudTag("foo"): { 175 Name: "foo", 176 Type: "bar", 177 }, 178 names.NewCloudTag("baz"): { 179 Name: "baz", 180 Type: "qux", 181 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType}, 182 Regions: []cloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 183 }, 184 }) 185 } 186 187 func (s *cloudSuite) TestUserCredentials(c *gc.C) { 188 ctrl := gomock.NewController(c) 189 defer ctrl.Finish() 190 191 args := params.UserClouds{UserClouds: []params.UserCloud{{ 192 UserTag: "user-bob", 193 CloudTag: "cloud-foo", 194 }}} 195 res := new(params.StringsResults) 196 results := params.StringsResults{ 197 Results: []params.StringsResult{{ 198 Result: []string{ 199 "cloudcred-foo_bob_one", 200 "cloudcred-foo_bob_two", 201 }, 202 }}, 203 } 204 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 205 mockFacadeCaller.EXPECT().FacadeCall("UserCredentials", args, res).SetArg(2, results).Return(nil) 206 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 207 208 result, err := client.UserCredentials(names.NewUserTag("bob"), names.NewCloudTag("foo")) 209 c.Assert(err, jc.ErrorIsNil) 210 c.Assert(result, jc.SameContents, []names.CloudCredentialTag{ 211 names.NewCloudCredentialTag("foo/bob/one"), 212 names.NewCloudCredentialTag("foo/bob/two"), 213 }) 214 } 215 216 func (s *cloudSuite) TestUpdateCredential(c *gc.C) { 217 ctrl := gomock.NewController(c) 218 defer ctrl.Finish() 219 220 args := params.UpdateCredentialArgs{ 221 Credentials: []params.TaggedCredential{{ 222 Tag: "cloudcred-foo_bob_bar", 223 Credential: params.CloudCredential{ 224 AuthType: "userpass", 225 Attributes: map[string]string{ 226 "username": "admin", 227 "password": "adm1n", 228 }, 229 }, 230 }}} 231 res := new(params.UpdateCredentialResults) 232 results := params.UpdateCredentialResults{ 233 Results: []params.UpdateCredentialResult{{}}, 234 } 235 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 236 mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil) 237 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 238 239 result, err := client.UpdateCredentialsCheckModels(testCredentialTag, testCredential) 240 c.Assert(err, jc.ErrorIsNil) 241 c.Assert(result, gc.IsNil) 242 } 243 244 func (s *cloudSuite) TestUpdateCredentialError(c *gc.C) { 245 ctrl := gomock.NewController(c) 246 defer ctrl.Finish() 247 248 args := params.UpdateCredentialArgs{ 249 Credentials: []params.TaggedCredential{{ 250 Tag: "cloudcred-foo_bob_bar", 251 Credential: params.CloudCredential{ 252 AuthType: "userpass", 253 Attributes: map[string]string{ 254 "username": "admin", 255 "password": "adm1n", 256 }, 257 }, 258 }}} 259 res := new(params.UpdateCredentialResults) 260 results := params.UpdateCredentialResults{ 261 Results: []params.UpdateCredentialResult{ 262 { 263 CredentialTag: "cloudcred-foo_bob_bar", 264 Error: apiservererrors.ServerError(errors.New("validation failure")), 265 }, 266 }, 267 } 268 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 269 mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil) 270 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 271 272 errs, err := client.UpdateCredentialsCheckModels(testCredentialTag, testCredential) 273 c.Assert(err, gc.ErrorMatches, "validation failure") 274 c.Assert(errs, gc.IsNil) 275 } 276 277 func (s *cloudSuite) TestUpdateCredentialManyResults(c *gc.C) { 278 ctrl := gomock.NewController(c) 279 defer ctrl.Finish() 280 281 args := params.UpdateCredentialArgs{ 282 Credentials: []params.TaggedCredential{{ 283 Tag: "cloudcred-foo_bob_bar", 284 Credential: params.CloudCredential{ 285 AuthType: "userpass", 286 Attributes: map[string]string{ 287 "username": "admin", 288 "password": "adm1n", 289 }, 290 }, 291 }}} 292 res := new(params.UpdateCredentialResults) 293 results := params.UpdateCredentialResults{ 294 Results: []params.UpdateCredentialResult{ 295 {}, 296 {}, 297 }} 298 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 299 mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil) 300 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 301 302 result, err := client.UpdateCredentialsCheckModels(testCredentialTag, testCredential) 303 c.Assert(err, gc.ErrorMatches, `expected 1 result got 2 when updating credentials`) 304 c.Assert(result, gc.IsNil) 305 } 306 307 func (s *cloudSuite) TestUpdateCredentialModelErrors(c *gc.C) { 308 ctrl := gomock.NewController(c) 309 defer ctrl.Finish() 310 311 args := params.UpdateCredentialArgs{ 312 Credentials: []params.TaggedCredential{{ 313 Tag: "cloudcred-foo_bob_bar", 314 Credential: params.CloudCredential{ 315 AuthType: "userpass", 316 Attributes: map[string]string{ 317 "username": "admin", 318 "password": "adm1n", 319 }, 320 }, 321 }}} 322 res := new(params.UpdateCredentialResults) 323 results := params.UpdateCredentialResults{ 324 Results: []params.UpdateCredentialResult{ 325 { 326 CredentialTag: testCredentialTag.String(), 327 Models: []params.UpdateCredentialModelResult{ 328 { 329 ModelUUID: coretesting.ModelTag.Id(), 330 ModelName: "test-model", 331 Errors: []params.ErrorResult{ 332 {apiservererrors.ServerError(errors.New("validation failure one"))}, 333 {apiservererrors.ServerError(errors.New("validation failure two"))}, 334 }, 335 }, 336 }, 337 }, 338 }} 339 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 340 mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil) 341 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 342 343 errs, err := client.UpdateCredentialsCheckModels(testCredentialTag, testCredential) 344 c.Assert(err, jc.ErrorIsNil) 345 c.Assert(errs, gc.DeepEquals, []params.UpdateCredentialModelResult{ 346 { 347 ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 348 ModelName: "test-model", 349 Errors: []params.ErrorResult{ 350 {Error: ¶ms.Error{Message: "validation failure one", Code: ""}}, 351 {Error: ¶ms.Error{Message: "validation failure two", Code: ""}}, 352 }, 353 }, 354 }) 355 } 356 357 var ( 358 testCredentialTag = names.NewCloudCredentialTag("foo/bob/bar") 359 testCredential = cloud.NewCredential(cloud.UserPassAuthType, map[string]string{ 360 "username": "admin", 361 "password": "adm1n", 362 }) 363 ) 364 365 func (s *cloudSuite) TestRevokeCredential(c *gc.C) { 366 ctrl := gomock.NewController(c) 367 defer ctrl.Finish() 368 369 args := params.RevokeCredentialArgs{ 370 Credentials: []params.RevokeCredentialArg{ 371 {Tag: "cloudcred-foo_bob_bar", Force: true}, 372 }, 373 } 374 res := new(params.ErrorResults) 375 results := params.ErrorResults{ 376 Results: []params.ErrorResult{{}}, 377 } 378 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 379 mockFacadeCaller.EXPECT().FacadeCall("RevokeCredentialsCheckModels", args, res).SetArg(2, results).Return(nil) 380 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 381 382 tag := names.NewCloudCredentialTag("foo/bob/bar") 383 err := client.RevokeCredential(tag, true) 384 c.Assert(err, jc.ErrorIsNil) 385 } 386 387 func (s *cloudSuite) TestCredentials(c *gc.C) { 388 ctrl := gomock.NewController(c) 389 defer ctrl.Finish() 390 391 args := params.Entities{Entities: []params.Entity{{ 392 Tag: "cloudcred-foo_bob_bar", 393 }}} 394 res := new(params.CloudCredentialResults) 395 results := params.CloudCredentialResults{ 396 Results: []params.CloudCredentialResult{ 397 { 398 Result: ¶ms.CloudCredential{ 399 AuthType: "userpass", 400 Attributes: map[string]string{"username": "fred"}, 401 Redacted: []string{"password"}, 402 }, 403 }, { 404 Result: ¶ms.CloudCredential{ 405 AuthType: "userpass", 406 Attributes: map[string]string{"username": "mary"}, 407 Redacted: []string{"password"}, 408 }, 409 }, 410 }, 411 } 412 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 413 mockFacadeCaller.EXPECT().FacadeCall("Credential", args, res).SetArg(2, results).Return(nil) 414 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 415 416 tag := names.NewCloudCredentialTag("foo/bob/bar") 417 result, err := client.Credentials(tag) 418 c.Assert(err, jc.ErrorIsNil) 419 c.Assert(result, jc.DeepEquals, []params.CloudCredentialResult{ 420 { 421 Result: ¶ms.CloudCredential{ 422 AuthType: "userpass", 423 Attributes: map[string]string{"username": "fred"}, 424 Redacted: []string{"password"}, 425 }, 426 }, { 427 Result: ¶ms.CloudCredential{ 428 AuthType: "userpass", 429 Attributes: map[string]string{"username": "mary"}, 430 Redacted: []string{"password"}, 431 }, 432 }, 433 }) 434 } 435 436 var testCloud = cloud.Cloud{ 437 Name: "foo", 438 Type: "dummy", 439 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType}, 440 Regions: []cloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 441 } 442 443 func (s *cloudSuite) TestAddCloudForce(c *gc.C) { 444 ctrl := gomock.NewController(c) 445 defer ctrl.Finish() 446 447 force := true 448 args := params.AddCloudArgs{ 449 Name: "foo", 450 Cloud: cloudapi.CloudToParams(testCloud), 451 Force: &force, 452 } 453 454 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 455 mockFacadeCaller.EXPECT().FacadeCall("AddCloud", args, nil).Return(nil) 456 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 457 458 err := client.AddCloud(testCloud, force) 459 c.Assert(err, jc.ErrorIsNil) 460 } 461 462 func (s *cloudSuite) TestCredentialContentsArgumentCheck(c *gc.C) { 463 ctrl := gomock.NewController(c) 464 defer ctrl.Finish() 465 466 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 467 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 468 469 // Check supplying cloud name without credential name is invalid. 470 result, err := client.CredentialContents("cloud", "", true) 471 c.Assert(result, gc.IsNil) 472 c.Assert(err, gc.ErrorMatches, "credential name must be supplied") 473 474 // Check supplying credential name without cloud name is invalid. 475 result, err = client.CredentialContents("", "credential", true) 476 c.Assert(result, gc.IsNil) 477 c.Assert(err, gc.ErrorMatches, "cloud name must be supplied") 478 } 479 480 func (s *cloudSuite) TestCredentialContentsAll(c *gc.C) { 481 ctrl := gomock.NewController(c) 482 defer ctrl.Finish() 483 484 expectedResults := []params.CredentialContentResult{ 485 { 486 Result: ¶ms.ControllerCredentialInfo{ 487 Content: params.CredentialContent{ 488 Cloud: "cloud-name", 489 Name: "credential-name", 490 AuthType: "userpass", 491 Attributes: map[string]string{ 492 "username": "fred", 493 "password": "sekret"}, 494 }, 495 Models: []params.ModelAccess{ 496 {Model: "abcmodel", Access: "admin"}, 497 {Model: "xyzmodel", Access: "read"}, 498 {Model: "no-access-model"}, // no access 499 }, 500 }, 501 }, { 502 Error: apiservererrors.ServerError(errors.New("boom")), 503 }, 504 } 505 506 args := params.CloudCredentialArgs{ 507 IncludeSecrets: true, 508 } 509 res := new(params.CredentialContentResults) 510 results := params.CredentialContentResults{ 511 Results: expectedResults, 512 } 513 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 514 mockFacadeCaller.EXPECT().FacadeCall("CredentialContents", args, res).SetArg(2, results).Return(nil) 515 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 516 517 ress, err := client.CredentialContents("", "", true) 518 c.Assert(err, jc.ErrorIsNil) 519 c.Assert(ress, jc.DeepEquals, expectedResults) 520 } 521 522 func (s *cloudSuite) TestCredentialContentsOne(c *gc.C) { 523 ctrl := gomock.NewController(c) 524 defer ctrl.Finish() 525 526 args := params.CloudCredentialArgs{ 527 IncludeSecrets: true, 528 Credentials: []params.CloudCredentialArg{ 529 {CloudName: "cloud-name", CredentialName: "credential-name"}, 530 }, 531 } 532 res := new(params.CredentialContentResults) 533 ress := params.CredentialContentResults{ 534 Results: []params.CredentialContentResult{ 535 {}, 536 }, 537 } 538 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 539 mockFacadeCaller.EXPECT().FacadeCall("CredentialContents", args, res).SetArg(2, ress).Return(nil) 540 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 541 542 results, err := client.CredentialContents("cloud-name", "credential-name", true) 543 c.Assert(err, jc.ErrorIsNil) 544 c.Assert(results, gc.HasLen, 1) 545 } 546 547 func (s *cloudSuite) TestCredentialContentsGotMoreThanBargainedFor(c *gc.C) { 548 ctrl := gomock.NewController(c) 549 defer ctrl.Finish() 550 551 args := params.CloudCredentialArgs{ 552 IncludeSecrets: true, 553 Credentials: []params.CloudCredentialArg{ 554 {CloudName: "cloud-name", CredentialName: "credential-name"}, 555 }, 556 } 557 res := new(params.CredentialContentResults) 558 ress := params.CredentialContentResults{ 559 Results: []params.CredentialContentResult{ 560 {}, 561 {}, 562 }, 563 } 564 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 565 mockFacadeCaller.EXPECT().FacadeCall("CredentialContents", args, res).SetArg(2, ress).Return(nil) 566 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 567 568 results, err := client.CredentialContents("cloud-name", "credential-name", true) 569 c.Assert(results, gc.IsNil) 570 c.Assert(err, gc.ErrorMatches, "expected 1 result for credential \"cloud-name\" on cloud \"credential-name\", got 2") 571 } 572 573 func (s *cloudSuite) TestCredentialContentsServerError(c *gc.C) { 574 ctrl := gomock.NewController(c) 575 defer ctrl.Finish() 576 577 args := params.CloudCredentialArgs{ 578 IncludeSecrets: true, 579 } 580 res := new(params.CredentialContentResults) 581 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 582 mockFacadeCaller.EXPECT().FacadeCall("CredentialContents", args, res).Return(errors.New("boom")) 583 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 584 585 results, err := client.CredentialContents("", "", true) 586 c.Assert(results, gc.IsNil) 587 c.Assert(err, gc.ErrorMatches, "boom") 588 } 589 590 func (s *cloudSuite) TestRemoveCloud(c *gc.C) { 591 ctrl := gomock.NewController(c) 592 defer ctrl.Finish() 593 594 args := params.Entities{ 595 Entities: []params.Entity{{Tag: "cloud-foo"}}, 596 } 597 res := new(params.ErrorResults) 598 ress := params.ErrorResults{ 599 Results: []params.ErrorResult{{ 600 Error: ¶ms.Error{Message: "FAIL"}, 601 }}, 602 } 603 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 604 mockFacadeCaller.EXPECT().FacadeCall("RemoveClouds", args, res).SetArg(2, ress).Return(nil) 605 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 606 607 err := client.RemoveCloud("foo") 608 c.Assert(err, gc.ErrorMatches, "FAIL") 609 } 610 611 func (s *cloudSuite) TestRemoveCloudErrorMapping(c *gc.C) { 612 ctrl := gomock.NewController(c) 613 defer ctrl.Finish() 614 615 args := params.Entities{ 616 Entities: []params.Entity{{Tag: "cloud-foo"}}, 617 } 618 res := new(params.ErrorResults) 619 ress := params.ErrorResults{ 620 Results: []params.ErrorResult{{ 621 Error: ¶ms.Error{ 622 Code: params.CodeNotFound, 623 Message: `cloud "cloud-foo" not found`, 624 }}, 625 }, 626 } 627 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 628 mockFacadeCaller.EXPECT().FacadeCall("RemoveClouds", args, res).SetArg(2, ress).Return(nil) 629 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 630 631 err := client.RemoveCloud("foo") 632 c.Assert(err, jc.Satisfies, errors.IsNotFound, gc.Commentf("expected client to be map server error into a NotFound error")) 633 } 634 635 func (s *cloudSuite) TestGrantCloud(c *gc.C) { 636 ctrl := gomock.NewController(c) 637 defer ctrl.Finish() 638 639 args := params.ModifyCloudAccessRequest{ 640 Changes: []params.ModifyCloudAccess{ 641 {UserTag: "user-fred", CloudTag: "cloud-fluffy", Action: "grant", Access: "admin"}, 642 }, 643 } 644 res := new(params.ErrorResults) 645 ress := params.ErrorResults{ 646 Results: []params.ErrorResult{{ 647 Error: ¶ms.Error{Message: "FAIL"}}, 648 }, 649 } 650 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 651 mockFacadeCaller.EXPECT().FacadeCall("ModifyCloudAccess", args, res).SetArg(2, ress).Return(nil) 652 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 653 654 err := client.GrantCloud("fred", "admin", "fluffy") 655 c.Assert(err, gc.ErrorMatches, "FAIL") 656 } 657 658 func (s *cloudSuite) TestRevokeCloud(c *gc.C) { 659 ctrl := gomock.NewController(c) 660 defer ctrl.Finish() 661 662 args := params.ModifyCloudAccessRequest{ 663 Changes: []params.ModifyCloudAccess{ 664 {UserTag: "user-fred", CloudTag: "cloud-fluffy", Action: "revoke", Access: "admin"}, 665 }, 666 } 667 res := new(params.ErrorResults) 668 ress := params.ErrorResults{ 669 Results: []params.ErrorResult{{ 670 Error: ¶ms.Error{Message: "FAIL"}}, 671 }, 672 } 673 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 674 mockFacadeCaller.EXPECT().FacadeCall("ModifyCloudAccess", args, res).SetArg(2, ress).Return(nil) 675 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 676 677 err := client.RevokeCloud("fred", "admin", "fluffy") 678 c.Assert(err, gc.ErrorMatches, "FAIL") 679 } 680 681 func createCredentials(n int) map[string]cloud.Credential { 682 result := map[string]cloud.Credential{} 683 for i := 0; i < n; i++ { 684 result[names.NewCloudCredentialTag(fmt.Sprintf("foo/bob/bar%d", i)).String()] = testCredential 685 } 686 return result 687 } 688 689 func (s *cloudSuite) TestUpdateCloud(c *gc.C) { 690 ctrl := gomock.NewController(c) 691 defer ctrl.Finish() 692 693 updatedCloud := cloud.Cloud{ 694 Name: "foo", 695 Type: "dummy", 696 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType}, 697 Regions: []cloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 698 } 699 700 args := params.UpdateCloudArgs{Clouds: []params.AddCloudArgs{{ 701 Name: "foo", 702 Cloud: cloudapi.CloudToParams(updatedCloud), 703 }}} 704 res := new(params.ErrorResults) 705 results := params.ErrorResults{ 706 Results: []params.ErrorResult{{}}, 707 } 708 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 709 mockFacadeCaller.EXPECT().FacadeCall("UpdateCloud", args, res).SetArg(2, results).Return(nil) 710 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 711 712 err := client.UpdateCloud(updatedCloud) 713 714 c.Assert(err, jc.ErrorIsNil) 715 } 716 717 func (s *cloudSuite) TestUpdateCloudsCredentials(c *gc.C) { 718 ctrl := gomock.NewController(c) 719 defer ctrl.Finish() 720 721 args := params.UpdateCredentialArgs{ 722 Force: true, 723 Credentials: []params.TaggedCredential{{ 724 Tag: "cloudcred-foo_bob_bar0", 725 Credential: params.CloudCredential{ 726 AuthType: "userpass", 727 Attributes: map[string]string{ 728 "username": "admin", 729 "password": "adm1n", 730 }, 731 }, 732 }}} 733 res := new(params.UpdateCredentialResults) 734 results := params.UpdateCredentialResults{ 735 Results: []params.UpdateCredentialResult{{}}, 736 } 737 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 738 mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil) 739 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 740 741 result, err := client.UpdateCloudsCredentials(createCredentials(1), true) 742 c.Assert(err, jc.ErrorIsNil) 743 c.Assert(result, gc.DeepEquals, []params.UpdateCredentialResult{{}}) 744 } 745 746 func (s *cloudSuite) TestUpdateCloudsCredentialsError(c *gc.C) { 747 ctrl := gomock.NewController(c) 748 defer ctrl.Finish() 749 750 args := params.UpdateCredentialArgs{ 751 Force: false, 752 Credentials: []params.TaggedCredential{{ 753 Tag: "cloudcred-foo_bob_bar0", 754 Credential: params.CloudCredential{ 755 AuthType: "userpass", 756 Attributes: map[string]string{ 757 "username": "admin", 758 "password": "adm1n", 759 }, 760 }, 761 }}} 762 res := new(params.UpdateCredentialResults) 763 results := params.UpdateCredentialResults{ 764 Results: []params.UpdateCredentialResult{ 765 {CredentialTag: "cloudcred-foo_bob_bar0", 766 Error: apiservererrors.ServerError(errors.New("validation failure")), 767 }, 768 }, 769 } 770 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 771 mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil) 772 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 773 774 errs, err := client.UpdateCloudsCredentials(createCredentials(1), false) 775 c.Assert(err, jc.ErrorIsNil) 776 c.Assert(errs, gc.DeepEquals, []params.UpdateCredentialResult{ 777 {CredentialTag: "cloudcred-foo_bob_bar0", Error: apiservererrors.ServerError(errors.New("validation failure"))}, 778 }) 779 } 780 781 func (s *cloudSuite) TestUpdateCloudsCredentialsManyResults(c *gc.C) { 782 ctrl := gomock.NewController(c) 783 defer ctrl.Finish() 784 785 args := params.UpdateCredentialArgs{ 786 Force: false, 787 Credentials: []params.TaggedCredential{{ 788 Tag: "cloudcred-foo_bob_bar0", 789 Credential: params.CloudCredential{ 790 AuthType: "userpass", 791 Attributes: map[string]string{ 792 "username": "admin", 793 "password": "adm1n", 794 }, 795 }, 796 }}} 797 res := new(params.UpdateCredentialResults) 798 results := params.UpdateCredentialResults{ 799 Results: []params.UpdateCredentialResult{ 800 {}, 801 {}, 802 }} 803 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 804 mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil) 805 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 806 807 result, err := client.UpdateCloudsCredentials(createCredentials(1), false) 808 c.Assert(err, gc.ErrorMatches, `expected 1 result got 2 when updating credentials`) 809 c.Assert(result, gc.IsNil) 810 } 811 812 func (s *cloudSuite) TestUpdateCloudsCredentialsManyMatchingResults(c *gc.C) { 813 ctrl := gomock.NewController(c) 814 defer ctrl.Finish() 815 816 args := params.UpdateCredentialArgs{ 817 Force: false, 818 } 819 count := 2 820 for tag, credential := range createCredentials(count) { 821 args.Credentials = append(args.Credentials, params.TaggedCredential{ 822 Tag: tag, 823 Credential: params.CloudCredential{ 824 AuthType: string(credential.AuthType()), 825 Attributes: credential.Attributes(), 826 }, 827 }) 828 } 829 830 res := new(params.UpdateCredentialResults) 831 results := params.UpdateCredentialResults{ 832 Results: []params.UpdateCredentialResult{ 833 {}, 834 {}, 835 }} 836 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 837 mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", cloudCredentialMatcher{args}, res).SetArg(2, results).Return(nil) 838 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 839 840 result, err := client.UpdateCloudsCredentials(createCredentials(count), false) 841 c.Assert(err, jc.ErrorIsNil) 842 c.Assert(result, gc.HasLen, count) 843 } 844 845 func (s *cloudSuite) TestUpdateCloudsCredentialsModelErrors(c *gc.C) { 846 ctrl := gomock.NewController(c) 847 defer ctrl.Finish() 848 849 args := params.UpdateCredentialArgs{ 850 Force: false, 851 Credentials: []params.TaggedCredential{{ 852 Tag: "cloudcred-foo_bob_bar0", 853 Credential: params.CloudCredential{ 854 AuthType: "userpass", 855 Attributes: map[string]string{ 856 "username": "admin", 857 "password": "adm1n", 858 }, 859 }, 860 }}} 861 res := new(params.UpdateCredentialResults) 862 results := params.UpdateCredentialResults{ 863 Results: []params.UpdateCredentialResult{ 864 { 865 CredentialTag: testCredentialTag.String(), 866 Models: []params.UpdateCredentialModelResult{ 867 { 868 ModelUUID: coretesting.ModelTag.Id(), 869 ModelName: "test-model", 870 Errors: []params.ErrorResult{ 871 {apiservererrors.ServerError(errors.New("validation failure one"))}, 872 {apiservererrors.ServerError(errors.New("validation failure two"))}, 873 }, 874 }, 875 }, 876 }, 877 }} 878 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 879 mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil) 880 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 881 882 errs, err := client.UpdateCloudsCredentials(createCredentials(1), false) 883 c.Assert(err, jc.ErrorIsNil) 884 c.Assert(errs, gc.DeepEquals, []params.UpdateCredentialResult{ 885 {CredentialTag: "cloudcred-foo_bob_bar", 886 Models: []params.UpdateCredentialModelResult{ 887 {ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 888 ModelName: "test-model", 889 Errors: []params.ErrorResult{ 890 {Error: apiservererrors.ServerError(errors.New("validation failure one"))}, 891 {Error: apiservererrors.ServerError(errors.New("validation failure two"))}, 892 }, 893 }, 894 }, 895 }, 896 }) 897 } 898 899 func (s *cloudSuite) TestAddCloudsCredentials(c *gc.C) { 900 ctrl := gomock.NewController(c) 901 defer ctrl.Finish() 902 903 args := params.UpdateCredentialArgs{ 904 Credentials: []params.TaggedCredential{{ 905 Tag: "cloudcred-foo_bob_bar0", 906 Credential: params.CloudCredential{ 907 AuthType: "userpass", 908 Attributes: map[string]string{ 909 "username": "admin", 910 "password": "adm1n", 911 }, 912 }, 913 }}} 914 res := new(params.UpdateCredentialResults) 915 results := params.UpdateCredentialResults{ 916 Results: []params.UpdateCredentialResult{{}}, 917 } 918 mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl) 919 mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil) 920 client := cloudapi.NewClientFromCaller(mockFacadeCaller) 921 922 result, err := client.AddCloudsCredentials(createCredentials(1)) 923 c.Assert(err, jc.ErrorIsNil) 924 c.Assert(result, gc.DeepEquals, []params.UpdateCredentialResult{{}}) 925 } 926 927 type cloudCredentialMatcher struct { 928 arg params.UpdateCredentialArgs 929 } 930 931 func (c cloudCredentialMatcher) Matches(x interface{}) bool { 932 cred, ok := x.(params.UpdateCredentialArgs) 933 if !ok { 934 return false 935 } 936 if len(cred.Credentials) != len(c.arg.Credentials) { 937 return false 938 } 939 // sort both input and expected slices the same way to avoid ordering discrepancies when ranging. 940 sort.Slice(cred.Credentials, func(i, j int) bool { return cred.Credentials[i].Tag < cred.Credentials[j].Tag }) 941 sort.Slice(c.arg.Credentials, func(i, j int) bool { return c.arg.Credentials[i].Tag < c.arg.Credentials[j].Tag }) 942 for idx, taggedCred := range cred.Credentials { 943 if taggedCred.Tag != c.arg.Credentials[idx].Tag { 944 return false 945 } 946 if !reflect.DeepEqual(taggedCred.Credential, c.arg.Credentials[idx].Credential) { 947 return false 948 } 949 } 950 951 if cred.Force != c.arg.Force { 952 return false 953 } 954 return true 955 } 956 957 func (c cloudCredentialMatcher) String() string { 958 return pretty.Sprint(c.arg) 959 }