github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/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 "regexp" 9 "sort" 10 11 "github.com/juju/errors" 12 "github.com/juju/names/v5" 13 gitjujutesting "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 "go.uber.org/mock/gomock" 16 gc "gopkg.in/check.v1" 17 18 "github.com/juju/juju/apiserver/common/credentialcommon" 19 apiservererrors "github.com/juju/juju/apiserver/errors" 20 "github.com/juju/juju/apiserver/facades/client/cloud" 21 "github.com/juju/juju/apiserver/facades/client/cloud/mocks" 22 apiservertesting "github.com/juju/juju/apiserver/testing" 23 k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" 24 jujucloud "github.com/juju/juju/cloud" 25 "github.com/juju/juju/core/permission" 26 "github.com/juju/juju/environs/context" 27 _ "github.com/juju/juju/provider/dummy" 28 "github.com/juju/juju/rpc/params" 29 "github.com/juju/juju/state" 30 statetesting "github.com/juju/juju/state/testing" 31 coretesting "github.com/juju/juju/testing" 32 ) 33 34 type cloudSuite struct { 35 gitjujutesting.LoggingCleanupSuite 36 backend *mocks.MockBackend 37 ctrlBackend *mocks.MockBackend 38 pool *mocks.MockModelPoolBackend 39 api *cloud.CloudAPI 40 authorizer *apiservertesting.FakeAuthorizer 41 } 42 43 func (s *cloudSuite) setup(c *gc.C, userTag names.UserTag) *gomock.Controller { 44 ctrl := gomock.NewController(c) 45 46 s.backend = mocks.NewMockBackend(ctrl) 47 s.backend.EXPECT().ControllerTag().Return(coretesting.ControllerTag).AnyTimes() 48 49 s.pool = mocks.NewMockModelPoolBackend(ctrl) 50 s.authorizer = &apiservertesting.FakeAuthorizer{ 51 Tag: userTag, 52 } 53 54 s.ctrlBackend = mocks.NewMockBackend(ctrl) 55 s.ctrlBackend.EXPECT().ControllerTag().Return(coretesting.ControllerTag).AnyTimes() 56 57 api, err := cloud.NewCloudAPI(s.backend, s.ctrlBackend, s.pool, s.authorizer) 58 c.Assert(err, jc.ErrorIsNil) 59 s.api = api 60 return ctrl 61 } 62 63 var _ = gc.Suite(&cloudSuite{}) 64 65 func newModelBackend(c *gc.C, aCloud jujucloud.Cloud, uuid string) *mockModelBackend { 66 return &mockModelBackend{ 67 uuid: uuid, 68 } 69 } 70 71 func (s *cloudSuite) TestCloud(c *gc.C) { 72 defer s.setup(c, names.NewUserTag("admin")).Finish() 73 74 backend := s.backend.EXPECT() 75 backend.Cloud("my-cloud").Return(jujucloud.Cloud{ 76 Name: "dummy", 77 Type: "dummy", 78 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 79 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 80 }, nil) 81 82 results, err := s.api.Cloud(params.Entities{ 83 Entities: []params.Entity{{Tag: "cloud-my-cloud"}, {Tag: "machine-0"}}, 84 }) 85 c.Assert(err, jc.ErrorIsNil) 86 c.Assert(results.Results, gc.HasLen, 2) 87 c.Assert(results.Results[0].Error, gc.IsNil) 88 c.Assert(results.Results[0].Cloud, jc.DeepEquals, ¶ms.Cloud{ 89 Type: "dummy", 90 AuthTypes: []string{"empty", "userpass"}, 91 Regions: []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}}, 92 }) 93 c.Assert(results.Results[1].Error, jc.DeepEquals, ¶ms.Error{ 94 Message: `"machine-0" is not a valid cloud tag`, 95 }) 96 } 97 98 func (s *cloudSuite) TestCloudNotFound(c *gc.C) { 99 defer s.setup(c, names.NewUserTag("admin")).Finish() 100 101 backend := s.backend.EXPECT() 102 backend.Cloud("no-dice").Return(jujucloud.Cloud{}, errors.NotFoundf("cloud \"no-dice\"")) 103 104 results, err := s.api.Cloud(params.Entities{ 105 Entities: []params.Entity{{Tag: "cloud-no-dice"}}, 106 }) 107 c.Assert(err, jc.ErrorIsNil) 108 c.Assert(results.Results, gc.HasLen, 1) 109 c.Assert(results.Results[0].Error, gc.ErrorMatches, "cloud \"no-dice\" not found") 110 } 111 112 func (s *cloudSuite) TestClouds(c *gc.C) { 113 bruce := names.NewUserTag("bruce") 114 defer s.setup(c, bruce).Finish() 115 116 ctrlBackend := s.ctrlBackend.EXPECT() 117 118 ctrlBackend.GetCloudAccess("my-cloud", 119 bruce).Return(permission.AddModelAccess, nil) 120 ctrlBackend.GetCloudAccess("your-cloud", 121 bruce).Return(permission.NoAccess, nil) 122 123 backend := s.backend.EXPECT() 124 backend.Clouds().Return(map[names.CloudTag]jujucloud.Cloud{ 125 names.NewCloudTag("my-cloud"): { 126 Name: "dummy", 127 Type: "dummy", 128 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 129 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 130 }, 131 names.NewCloudTag("your-cloud"): { 132 Name: "dummy", 133 Type: "dummy", 134 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 135 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 136 }, 137 }, nil) 138 139 result, err := s.api.Clouds() 140 c.Assert(err, jc.ErrorIsNil) 141 c.Assert(result.Clouds, jc.DeepEquals, map[string]params.Cloud{ 142 "cloud-my-cloud": { 143 Type: "dummy", 144 AuthTypes: []string{"empty", "userpass"}, 145 Regions: []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}}, 146 }, 147 }) 148 } 149 150 func (s *cloudSuite) TestCloudInfoAdmin(c *gc.C) { 151 ctrl := s.setup(c, names.NewUserTag("admin")) 152 defer ctrl.Finish() 153 154 ctrlBackend := s.ctrlBackend.EXPECT() 155 userPerm := map[string]permission.Access{"fred": permission.AddModelAccess, 156 "mary": permission.AdminAccess} 157 ctrlBackend.GetCloudUsers("my-cloud").Return(userPerm, 158 nil) 159 160 backend := s.backend.EXPECT() 161 backend.Cloud("my-cloud").Return(jujucloud.Cloud{ 162 Name: "dummy", 163 Type: "dummy", 164 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 165 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 166 }, nil) 167 168 mary := mocks.NewMockUser(ctrl) 169 fred := mocks.NewMockUser(ctrl) 170 mary.EXPECT().DisplayName().Return("display-mary") 171 fred.EXPECT().DisplayName().Return("display-fred") 172 173 maryTag := names.NewUserTag("mary") 174 backend.User(maryTag).Return(mary, nil) 175 fredTag := names.NewUserTag("fred") 176 backend.User(fredTag).Return(fred, nil) 177 178 result, err := s.api.CloudInfo(params.Entities{Entities: []params.Entity{{ 179 Tag: "cloud-my-cloud", 180 }, { 181 Tag: "machine-0", 182 }}}) 183 c.Assert(err, jc.ErrorIsNil) 184 // Make sure that the slice is sorted in a predictable manor 185 sort.Slice(result.Results[0].Result.Users, func(i, j int) bool { 186 return result.Results[0].Result.Users[i].UserName < result.Results[0].Result.Users[j].UserName 187 }) 188 c.Assert(result.Results, jc.DeepEquals, []params.CloudInfoResult{ 189 { 190 Result: ¶ms.CloudInfo{ 191 CloudDetails: params.CloudDetails{ 192 Type: "dummy", 193 AuthTypes: []string{"empty", "userpass"}, 194 Regions: []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}}, 195 }, 196 Users: []params.CloudUserInfo{ 197 {UserName: "fred", DisplayName: "display-fred", Access: "add-model"}, 198 {UserName: "mary", DisplayName: "display-mary", Access: "admin"}, 199 }, 200 }, 201 }, { 202 Error: ¶ms.Error{Message: `"machine-0" is not a valid cloud tag`}, 203 }, 204 }) 205 } 206 207 func (s *cloudSuite) TestCloudInfoNonAdmin(c *gc.C) { 208 fredTag := names.NewUserTag("fred") 209 ctrl := s.setup(c, fredTag) 210 defer ctrl.Finish() 211 212 fred := mocks.NewMockUser(ctrl) 213 fred.EXPECT().DisplayName().Return("display-fred") 214 215 ctrlBackend := s.ctrlBackend.EXPECT() 216 ctrlBackend.GetCloudAccess("my-cloud", 217 fredTag).Return(permission.AddModelAccess, nil) 218 userPerm := map[string]permission.Access{"fred": permission.AddModelAccess, 219 "mary": permission.AdminAccess} 220 ctrlBackend.GetCloudUsers("my-cloud").Return(userPerm, 221 nil) 222 223 backend := s.backend.EXPECT() 224 backend.User(fredTag).Return(fred, nil) 225 backend.Cloud("my-cloud").Return(jujucloud.Cloud{ 226 Name: "dummy", 227 Type: "dummy", 228 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 229 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 230 }, nil) 231 232 result, err := s.api.CloudInfo(params.Entities{Entities: []params.Entity{{ 233 Tag: "cloud-my-cloud", 234 }, { 235 Tag: "machine-0", 236 }}}) 237 c.Assert(err, jc.ErrorIsNil) 238 c.Assert(result.Results, gc.HasLen, 2) 239 c.Assert(result.Results, jc.DeepEquals, []params.CloudInfoResult{ 240 { 241 Result: ¶ms.CloudInfo{ 242 CloudDetails: params.CloudDetails{ 243 Type: "dummy", 244 AuthTypes: []string{"empty", "userpass"}, 245 Regions: []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}}, 246 }, 247 Users: []params.CloudUserInfo{ 248 {UserName: "fred", DisplayName: "display-fred", Access: "add-model"}, 249 }, 250 }, 251 }, { 252 Error: ¶ms.Error{Message: `"machine-0" is not a valid cloud tag`}, 253 }, 254 }) 255 } 256 257 func (s *cloudSuite) TestAddCloud(c *gc.C) { 258 adminTag := names.NewUserTag("admin") 259 defer s.setup(c, adminTag).Finish() 260 261 newCloud := jujucloud.Cloud{ 262 Name: "newcloudname", 263 Type: "maas", 264 Endpoint: "fake-endpoint", 265 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 266 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "nether-endpoint"}}, 267 } 268 269 cloud := jujucloud.Cloud{ 270 Name: "newcloudname", 271 Type: "maas", 272 } 273 274 backend := s.backend.EXPECT() 275 backend.ControllerInfo().Return(&state.ControllerInfo{CloudName: "newcloudname"}, nil) 276 backend.Cloud("newcloudname").Return(cloud, nil) 277 backend.AddCloud(newCloud, adminTag.Name()).Return(nil) 278 paramsCloud := params.AddCloudArgs{ 279 Name: "newcloudname", 280 Cloud: params.Cloud{ 281 Type: "maas", 282 AuthTypes: []string{"empty", "userpass"}, 283 Endpoint: "fake-endpoint", 284 Regions: []params.CloudRegion{{Name: "nether", Endpoint: "nether-endpoint"}}, 285 }} 286 287 err := s.api.AddCloud(paramsCloud) 288 c.Assert(err, jc.ErrorIsNil) 289 } 290 291 func createAddCloudParam(cloudType string) params.AddCloudArgs { 292 if cloudType == "" { 293 cloudType = "fake" 294 } 295 return params.AddCloudArgs{ 296 Name: "newcloudname", 297 Cloud: params.Cloud{ 298 Type: cloudType, 299 AuthTypes: []string{"empty", "userpass"}, 300 Endpoint: "fake-endpoint", 301 Regions: []params.CloudRegion{{Name: "nether", Endpoint: "nether-endpoint"}}, 302 }, 303 } 304 } 305 306 func (s *cloudSuite) TestAddCloudNotWhitelisted(c *gc.C) { 307 adminTag := names.NewUserTag("admin") 308 defer s.setup(c, adminTag).Finish() 309 310 cloud := jujucloud.Cloud{ 311 Name: "dummy", 312 Type: "dummy", 313 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 314 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 315 } 316 317 backend := s.backend.EXPECT() 318 backend.ControllerInfo().Return(&state.ControllerInfo{CloudName: "dummy"}, nil) 319 backend.Cloud("dummy").Return(cloud, nil) 320 321 err := s.api.AddCloud(createAddCloudParam("")) 322 c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(` 323 controller cloud type "dummy" is not whitelisted, current whitelist: 324 - controller cloud type "kubernetes" supports [lxd maas openstack] 325 - controller cloud type "lxd" supports [lxd maas openstack] 326 - controller cloud type "maas" supports [maas openstack] 327 - controller cloud type "openstack" supports [openstack]`[1:])) 328 } 329 330 func (s *cloudSuite) TestAddCloudNotWhitelistedButForceAdded(c *gc.C) { 331 adminTag := names.NewUserTag("admin") 332 defer s.setup(c, adminTag).Finish() 333 334 newCloud := jujucloud.Cloud{ 335 Name: "newcloudname", 336 Type: "fake", 337 Endpoint: "fake-endpoint", 338 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 339 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "nether-endpoint"}}, 340 } 341 342 cloud := jujucloud.Cloud{ 343 Name: "newcloudname", 344 Type: "maas", 345 } 346 347 backend := s.backend.EXPECT() 348 backend.ControllerInfo().Return(&state.ControllerInfo{CloudName: "newcloudname"}, nil) 349 backend.Cloud("newcloudname").Return(cloud, nil) 350 backend.AddCloud(newCloud, adminTag.Name()).Return(nil) 351 352 force := true 353 addCloudArg := createAddCloudParam("") 354 addCloudArg.Force = &force 355 err := s.api.AddCloud(addCloudArg) 356 c.Assert(err, jc.ErrorIsNil) 357 } 358 359 func (s *cloudSuite) TestAddCloudControllerInfoErr(c *gc.C) { 360 adminTag := names.NewUserTag("admin") 361 defer s.setup(c, adminTag).Finish() 362 363 backend := s.backend.EXPECT() 364 backend.ControllerInfo().Return(nil, errors.New("kaboom")) 365 366 err := s.api.AddCloud(createAddCloudParam("")) 367 c.Assert(err, gc.ErrorMatches, "kaboom") 368 } 369 370 func (s *cloudSuite) TestAddCloudControllerCloudErr(c *gc.C) { 371 adminTag := names.NewUserTag("admin") 372 defer s.setup(c, adminTag).Finish() 373 374 backend := s.backend.EXPECT() 375 backend.ControllerInfo().Return(&state.ControllerInfo{CloudName: "kaboom"}, nil) 376 backend.Cloud("kaboom").Return(jujucloud.Cloud{}, errors.New("kaboom")) 377 378 err := s.api.AddCloud(createAddCloudParam("")) 379 c.Assert(err, gc.ErrorMatches, "kaboom") 380 } 381 382 func (s *cloudSuite) TestAddCloudK8sForceIrrelevant(c *gc.C) { 383 adminTag := names.NewUserTag("admin") 384 defer s.setup(c, adminTag).Finish() 385 386 cloud := jujucloud.Cloud{ 387 Name: "newcloudname", 388 Type: string(k8sconstants.CAASProviderType), 389 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 390 Endpoint: "fake-endpoint", 391 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "nether-endpoint"}}, 392 } 393 394 backend := s.backend.EXPECT() 395 backend.AddCloud(cloud, adminTag.Name()).Return(nil).Times(2) 396 397 addCloudArg := createAddCloudParam(string(k8sconstants.CAASProviderType)) 398 399 add := func() { 400 err := s.api.AddCloud(addCloudArg) 401 c.Assert(err, jc.ErrorIsNil) 402 } 403 add() 404 force := true 405 addCloudArg.Force = &force 406 add() 407 } 408 409 func (s *cloudSuite) TestAddCloudNoRegion(c *gc.C) { 410 adminTag := names.NewUserTag("admin") 411 defer s.setup(c, adminTag).Finish() 412 413 newCloud := jujucloud.Cloud{ 414 Name: "newcloudname", 415 Type: "maas", 416 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 417 Endpoint: "fake-endpoint", 418 Regions: []jujucloud.Region{{ 419 Name: "default", 420 }}, 421 } 422 423 cloud := jujucloud.Cloud{ 424 Name: "newcloudname", 425 Type: "maas", 426 } 427 428 backend := s.backend.EXPECT() 429 backend.ControllerInfo().Return(&state.ControllerInfo{CloudName: "newcloudname"}, nil) 430 backend.Cloud("newcloudname").Return(cloud, nil) 431 backend.AddCloud(newCloud, adminTag.Name()).Return(nil) 432 paramsCloud := params.AddCloudArgs{ 433 Name: "newcloudname", 434 Cloud: params.Cloud{ 435 Type: "maas", 436 AuthTypes: []string{"empty", "userpass"}, 437 Endpoint: "fake-endpoint", 438 }} 439 err := s.api.AddCloud(paramsCloud) 440 c.Assert(err, jc.ErrorIsNil) 441 442 } 443 444 func (s *cloudSuite) TestAddCloudNoAdminPerms(c *gc.C) { 445 frankTag := names.NewUserTag("frank") 446 defer s.setup(c, frankTag).Finish() 447 448 paramsCloud := params.AddCloudArgs{ 449 Name: "newcloudname", 450 Cloud: params.Cloud{ 451 Type: "fake", 452 AuthTypes: []string{"empty", "userpass"}, 453 Endpoint: "fake-endpoint", 454 Regions: []params.CloudRegion{{Name: "nether", Endpoint: "nether-endpoint"}}, 455 }} 456 err := s.api.AddCloud(paramsCloud) 457 c.Assert(err, gc.ErrorMatches, "permission denied") 458 } 459 460 func (s *cloudSuite) TestUpdateCloud(c *gc.C) { 461 adminTag := names.NewUserTag("admin") 462 defer s.setup(c, adminTag).Finish() 463 464 dummyCloud := jujucloud.Cloud{ 465 Name: "dummy", 466 Type: "dummy", 467 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 468 Regions: []jujucloud.Region{{Name: "nether-updated", Endpoint: "endpoint-updated"}}, 469 } 470 471 backend := s.backend.EXPECT() 472 backend.UpdateCloud(dummyCloud).Return(nil) 473 474 updatedCloud := jujucloud.Cloud{ 475 Name: "dummy", 476 Type: "dummy", 477 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 478 Regions: []jujucloud.Region{{Name: "nether-updated", Endpoint: "endpoint-updated"}}, 479 } 480 results, err := s.api.UpdateCloud(params.UpdateCloudArgs{ 481 Clouds: []params.AddCloudArgs{{ 482 Name: "dummy", 483 Cloud: cloud.CloudToParams(updatedCloud), 484 }}, 485 }) 486 c.Assert(err, jc.ErrorIsNil) 487 488 c.Assert(results.Results, gc.HasLen, 1) 489 c.Assert(results.Results[0].Error, gc.IsNil) 490 } 491 492 func (s *cloudSuite) TestUpdateCloudNonAdminPerm(c *gc.C) { 493 frankTag := names.NewUserTag("frank") 494 defer s.setup(c, frankTag).Finish() 495 496 updatedCloud := jujucloud.Cloud{ 497 Name: "dummy", 498 Type: "dummy", 499 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 500 Regions: []jujucloud.Region{{Name: "nether-updated", Endpoint: "endpoint-updated"}}, 501 } 502 results, err := s.api.UpdateCloud(params.UpdateCloudArgs{ 503 Clouds: []params.AddCloudArgs{{ 504 Name: "dummy", 505 Cloud: cloud.CloudToParams(updatedCloud), 506 }}, 507 }) 508 c.Assert(err, gc.ErrorMatches, "permission denied") 509 c.Assert(results.Results, gc.HasLen, 1) 510 c.Assert(results.Results[0].Error, gc.IsNil) 511 } 512 513 func (s *cloudSuite) TestUpdateNonExistentCloud(c *gc.C) { 514 adminTag := names.NewUserTag("admin") 515 defer s.setup(c, adminTag).Finish() 516 517 dummyCloud := jujucloud.Cloud{ 518 Name: "nope", 519 Type: "dummy", 520 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 521 Regions: []jujucloud.Region{{Name: "nether-updated", Endpoint: "endpoint-updated"}}, 522 } 523 524 backend := s.backend.EXPECT() 525 backend.UpdateCloud(dummyCloud).Return(errors.New("cloud \"nope\" not found")) 526 527 updatedCloud := jujucloud.Cloud{ 528 Name: "nope", 529 Type: "dummy", 530 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 531 Regions: []jujucloud.Region{{Name: "nether-updated", Endpoint: "endpoint-updated"}}, 532 } 533 534 results, err := s.api.UpdateCloud(params.UpdateCloudArgs{ 535 Clouds: []params.AddCloudArgs{{ 536 Name: "nope", 537 Cloud: cloud.CloudToParams(updatedCloud), 538 }}, 539 }) 540 c.Assert(err, jc.ErrorIsNil) 541 c.Assert(results.Results, gc.HasLen, 1) 542 c.Assert(results.Results[0].Error, gc.ErrorMatches, fmt.Sprintf("cloud %q not found", updatedCloud.Name)) 543 } 544 545 func (s *cloudSuite) TestListCloudInfo(c *gc.C) { 546 fredTag := names.NewUserTag("admin") 547 defer s.setup(c, fredTag).Finish() 548 549 cloudInfo := []state.CloudInfo{ 550 { 551 Cloud: jujucloud.Cloud{ 552 Name: "dummy", 553 Type: "dummy", 554 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 555 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 556 }, 557 Access: permission.AddModelAccess, 558 }, 559 } 560 ctrlBackend := s.ctrlBackend.EXPECT() 561 ctrlBackend.CloudsForUser(fredTag, true).Return(cloudInfo, nil) 562 563 result, err := s.api.ListCloudInfo(params.ListCloudsRequest{ 564 UserTag: "user-admin", 565 All: true, 566 }) 567 c.Assert(err, jc.ErrorIsNil) 568 c.Assert(result.Results, jc.DeepEquals, []params.ListCloudInfoResult{ 569 { 570 Result: ¶ms.ListCloudInfo{ 571 CloudDetails: params.CloudDetails{ 572 Type: "dummy", 573 AuthTypes: []string{"empty", "userpass"}, 574 Regions: []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}}, 575 }, 576 Access: "add-model", 577 }, 578 }, 579 }) 580 } 581 582 func (s *cloudSuite) TestUserCredentials(c *gc.C) { 583 bruceTag := names.NewUserTag("bruce") 584 defer s.setup(c, bruceTag).Finish() 585 586 credentialOne, tagOne := cloudCredentialTag(credParams{name: "one", owner: "bruce", cloudName: "meep", permission: jujucloud.EmptyAuthType, 587 attrs: map[string]string{}}, c) 588 credentialTwo, tagTwo := cloudCredentialTag(credParams{name: "two", owner: "bruce", cloudName: "meep", permission: jujucloud.UserPassAuthType, 589 attrs: map[string]string{ 590 "username": "admin", 591 "password": "adm1n", 592 }}, c) 593 594 creds := map[string]state.Credential{ 595 tagOne.Id(): credentialOne, 596 tagTwo.Id(): credentialTwo, 597 } 598 599 backend := s.backend.EXPECT() 600 backend.CloudCredentials(bruceTag, "meep").Return(creds, nil) 601 602 results, err := s.api.UserCredentials(params.UserClouds{UserClouds: []params.UserCloud{{ 603 UserTag: "machine-0", 604 CloudTag: "cloud-meep", 605 }, { 606 UserTag: "user-admin", 607 CloudTag: "cloud-meep", 608 }, { 609 UserTag: "user-bruce", 610 CloudTag: "cloud-meep", 611 }}}) 612 c.Assert(err, jc.ErrorIsNil) 613 c.Assert(results.Results, gc.HasLen, 3) 614 c.Assert(results.Results[0].Error, jc.DeepEquals, ¶ms.Error{ 615 Message: `"machine-0" is not a valid user tag`, 616 }) 617 c.Assert(results.Results[1].Error, jc.DeepEquals, ¶ms.Error{ 618 Message: "permission denied", Code: params.CodeUnauthorized, 619 }) 620 c.Assert(results.Results[2].Error, gc.IsNil) 621 c.Assert(results.Results[2].Result, jc.SameContents, []string{ 622 "cloudcred-meep_bruce_one", 623 "cloudcred-meep_bruce_two", 624 }) 625 } 626 627 func (s *cloudSuite) TestUserCredentialsAdminAccess(c *gc.C) { 628 adminTag := names.NewUserTag("admin") 629 defer s.setup(c, adminTag).Finish() 630 631 julia := names.NewUserTag("julia") 632 backend := s.backend.EXPECT() 633 backend.CloudCredentials(julia, "meep").Return(map[string]state.Credential{}, nil) 634 635 results, err := s.api.UserCredentials(params.UserClouds{UserClouds: []params.UserCloud{{ 636 UserTag: "user-julia", 637 CloudTag: "cloud-meep", 638 }}}) 639 c.Assert(err, jc.ErrorIsNil) 640 c.Assert(results.Results, gc.HasLen, 1) 641 // admin can access others' credentials 642 c.Assert(results.Results[0].Error, gc.IsNil) 643 } 644 645 func (s *cloudSuite) TestUpdateCredentials(c *gc.C) { 646 bruceTag := names.NewUserTag("bruce") 647 defer s.setup(c, bruceTag).Finish() 648 649 _, tagOne := cloudCredentialTag(credParams{name: "three", owner: "bruce", cloudName: "meep", permission: jujucloud.EmptyAuthType, 650 attrs: map[string]string{}}, c) 651 _, tagTwo := cloudCredentialTag(credParams{name: "three", owner: "bruce", cloudName: "badcloud", permission: jujucloud.EmptyAuthType, 652 attrs: map[string]string{}}, c) 653 654 backend := s.backend.EXPECT() 655 backend.CredentialModels(tagOne).Return(nil, nil) 656 backend.UpdateCloudCredential(tagTwo, jujucloud.NewCredential( 657 jujucloud.OAuth1AuthType, 658 map[string]string{"token": "foo:bar:baz"}, 659 )).Return(errors.New("cannot update credential \"three\": controller does not manage cloud \"badcloud\"")) 660 backend.CredentialModels(tagTwo).Return(nil, nil) 661 backend.UpdateCloudCredential(tagOne, jujucloud.NewCredential( 662 jujucloud.OAuth1AuthType, 663 map[string]string{"token": "foo:bar:baz"}, 664 )).Return(nil) 665 666 results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{ 667 Force: false, 668 Credentials: []params.TaggedCredential{{ 669 Tag: "machine-0", 670 }, { 671 Tag: "cloudcred-meep_admin_whatever", 672 }, { 673 Tag: "cloudcred-meep_bruce_three", 674 Credential: params.CloudCredential{ 675 AuthType: "oauth1", 676 Attributes: map[string]string{"token": "foo:bar:baz"}, 677 }, 678 }, { 679 Tag: "cloudcred-badcloud_bruce_three", 680 Credential: params.CloudCredential{ 681 AuthType: "oauth1", 682 Attributes: map[string]string{"token": "foo:bar:baz"}, 683 }, 684 }}}) 685 c.Assert(err, jc.ErrorIsNil) 686 c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{ 687 Results: []params.UpdateCredentialResult{ 688 { 689 CredentialTag: "machine-0", 690 Error: ¶ms.Error{Message: `"machine-0" is not a valid cloudcred tag`}, 691 }, 692 { 693 CredentialTag: "cloudcred-meep_admin_whatever", 694 Error: ¶ms.Error{Message: "permission denied", Code: params.CodeUnauthorized}, 695 }, 696 {CredentialTag: "cloudcred-meep_bruce_three"}, 697 { 698 CredentialTag: "cloudcred-badcloud_bruce_three", 699 Error: ¶ms.Error{Message: `cannot update credential "three": controller does not manage cloud "badcloud"`}, 700 }, 701 }, 702 }) 703 } 704 705 func (s *cloudSuite) TestUpdateCredentialsAdminAccess(c *gc.C) { 706 adminTag := names.NewUserTag("admin") 707 defer s.setup(c, adminTag).Finish() 708 709 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 710 attrs: map[string]string{}}, c) 711 712 backend := s.backend.EXPECT() 713 backend.CredentialModels(tag).Return(nil, nil) 714 backend.UpdateCloudCredential(names.NewCloudCredentialTag("meep/julia/three"), 715 jujucloud.Credential{}).Return(nil) 716 717 results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{ 718 Force: false, 719 Credentials: []params.TaggedCredential{{ 720 Tag: "cloudcred-meep_julia_three", 721 Credential: params.CloudCredential{}, 722 }}}) 723 c.Assert(err, jc.ErrorIsNil) 724 c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{ 725 Results: []params.UpdateCredentialResult{{CredentialTag: "cloudcred-meep_julia_three"}}}) 726 } 727 728 func (s *cloudSuite) TestUpdateCredentialsNoModelsFound(c *gc.C) { 729 adminTag := names.NewUserTag("admin") 730 defer s.setup(c, adminTag).Finish() 731 732 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 733 attrs: map[string]string{}}, c) 734 735 backend := s.backend.EXPECT() 736 backend.CredentialModels(tag).Return(nil, errors.NotFoundf("how about it")) 737 backend.UpdateCloudCredential(names.NewCloudCredentialTag("meep/julia/three"), 738 jujucloud.Credential{}).Return(nil) 739 740 results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{ 741 Force: false, 742 Credentials: []params.TaggedCredential{{ 743 Tag: "cloudcred-meep_julia_three", 744 Credential: params.CloudCredential{}, 745 }}}) 746 c.Assert(err, jc.ErrorIsNil) 747 c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{ 748 Results: []params.UpdateCredentialResult{{CredentialTag: "cloudcred-meep_julia_three"}}}) 749 } 750 751 func (s *cloudSuite) TestUpdateCredentialsModelsError(c *gc.C) { 752 adminTag := names.NewUserTag("admin") 753 defer s.setup(c, adminTag).Finish() 754 755 _, tag := cloudCredentialTag(credParams{"three", "julia", "meep", jujucloud.EmptyAuthType, 756 map[string]string{}}, c) 757 backend := s.backend.EXPECT() 758 backend.CredentialModels(tag).Return(nil, errors.New("cannot get models")) 759 760 results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{ 761 Force: false, 762 Credentials: []params.TaggedCredential{{ 763 Tag: "cloudcred-meep_julia_three", 764 Credential: params.CloudCredential{}, 765 }}}) 766 c.Assert(err, jc.ErrorIsNil) 767 c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{ 768 Results: []params.UpdateCredentialResult{ 769 { 770 CredentialTag: "cloudcred-meep_julia_three", 771 Error: ¶ms.Error{Message: "cannot get models"}, 772 }, 773 }}) 774 } 775 776 func (s *cloudSuite) TestUpdateCredentialsModelsErrorForce(c *gc.C) { 777 adminTag := names.NewUserTag("admin") 778 defer s.setup(c, adminTag).Finish() 779 780 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 781 attrs: map[string]string{}}, c) 782 783 backend := s.backend.EXPECT() 784 backend.CredentialModels(tag).Return(nil, errors.New("cannot get models")) 785 backend.UpdateCloudCredential(tag, 786 jujucloud.Credential{}).Return(nil) 787 788 results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{ 789 Force: true, 790 Credentials: []params.TaggedCredential{{ 791 Tag: "cloudcred-meep_julia_three", 792 Credential: params.CloudCredential{}, 793 }}}) 794 c.Assert(err, jc.ErrorIsNil) 795 c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{ 796 Results: []params.UpdateCredentialResult{ 797 { 798 CredentialTag: "cloudcred-meep_julia_three", 799 }, 800 }}) 801 } 802 803 func (s *cloudSuite) TestUpdateCredentialsOneModelSuccess(c *gc.C) { 804 adminTag := names.NewUserTag("admin") 805 defer s.setup(c, adminTag).Finish() 806 807 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 808 attrs: map[string]string{}}, c) 809 810 s.PatchValue(cloud.ValidateNewCredentialForModelFunc, 811 func( 812 _ credentialcommon.PersistentBackend, _ context.ProviderCallContext, 813 _ names.CloudCredentialTag, _ *jujucloud.Credential, _ bool, _ bool, 814 ) (params.ErrorResults, error) { 815 return params.ErrorResults{}, nil 816 }) 817 818 aCloud := jujucloud.Cloud{ 819 Name: "dummy", 820 Type: "dummy", 821 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 822 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 823 } 824 825 backend := s.backend.EXPECT() 826 backend.CredentialModels(tag).Return(map[string]string{ 827 coretesting.ModelTag.Id(): "testModel1", 828 }, nil) 829 backend.UpdateCloudCredential(tag, jujucloud.Credential{}).Return(nil) 830 831 pool := s.pool.EXPECT() 832 pool.GetModelCallContext(coretesting.ModelTag.Id()).Return(newModelBackend(c, aCloud, coretesting.ModelTag.Id()), 833 context.NewEmptyCloudCallContext(), nil) 834 835 results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{ 836 Force: false, 837 Credentials: []params.TaggedCredential{{ 838 Tag: "cloudcred-meep_julia_three", 839 Credential: params.CloudCredential{}, 840 }}}) 841 c.Assert(err, jc.ErrorIsNil) 842 c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{ 843 Results: []params.UpdateCredentialResult{{ 844 CredentialTag: "cloudcred-meep_julia_three", 845 Models: []params.UpdateCredentialModelResult{ 846 { 847 ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 848 ModelName: "testModel1", 849 }, 850 }, 851 }}, 852 }) 853 } 854 855 func (s *cloudSuite) TestUpdateCredentialsModelGetError(c *gc.C) { 856 adminTag := names.NewUserTag("admin") 857 defer s.setup(c, adminTag).Finish() 858 859 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 860 attrs: map[string]string{}}, c) 861 862 backend := s.backend.EXPECT() 863 backend.CredentialModels(tag).Return(map[string]string{ 864 coretesting.ModelTag.Id(): "testModel1", 865 }, nil) 866 867 pool := s.pool.EXPECT() 868 pool.GetModelCallContext(coretesting.ModelTag.Id()).Return(nil, nil, errors.New("cannot get a model")) 869 870 results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{ 871 Force: false, 872 Credentials: []params.TaggedCredential{{ 873 Tag: "cloudcred-meep_julia_three", 874 Credential: params.CloudCredential{}, 875 }}}) 876 c.Assert(err, jc.ErrorIsNil) 877 c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{ 878 Results: []params.UpdateCredentialResult{{ 879 CredentialTag: "cloudcred-meep_julia_three", 880 Models: []params.UpdateCredentialModelResult{ 881 { 882 ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 883 ModelName: "testModel1", 884 Errors: []params.ErrorResult{{Error: ¶ms.Error{Message: "cannot get a model", Code: ""}}}, 885 }, 886 }, 887 }}, 888 }) 889 } 890 891 func (s *cloudSuite) TestUpdateCredentialsModelGetErrorForce(c *gc.C) { 892 adminTag := names.NewUserTag("admin") 893 defer s.setup(c, adminTag) 894 895 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 896 attrs: map[string]string{}}, c) 897 898 backend := s.backend.EXPECT() 899 backend.CredentialModels(tag).Return(map[string]string{ 900 coretesting.ModelTag.Id(): "testModel1", 901 }, nil) 902 backend.UpdateCloudCredential(tag, jujucloud.Credential{}).Return(nil) 903 904 pool := s.pool.EXPECT() 905 pool.GetModelCallContext(coretesting.ModelTag.Id()).Return(nil, nil, errors.New("cannot get a model")) 906 907 results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{ 908 Force: true, 909 Credentials: []params.TaggedCredential{{ 910 Tag: "cloudcred-meep_julia_three", 911 Credential: params.CloudCredential{}, 912 }}}) 913 c.Assert(err, jc.ErrorIsNil) 914 c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{ 915 Results: []params.UpdateCredentialResult{{ 916 CredentialTag: "cloudcred-meep_julia_three", 917 Models: []params.UpdateCredentialModelResult{ 918 { 919 ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 920 ModelName: "testModel1", 921 Errors: []params.ErrorResult{{Error: ¶ms.Error{Message: "cannot get a model", Code: ""}}}, 922 }, 923 }, 924 }}, 925 }) 926 } 927 928 func (s *cloudSuite) TestUpdateCredentialsModelFailedValidation(c *gc.C) { 929 adminTag := names.NewUserTag("admin") 930 defer s.setup(c, adminTag) 931 932 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 933 attrs: map[string]string{}}, c) 934 935 backend := s.backend.EXPECT() 936 backend.CredentialModels(tag).Return(map[string]string{ 937 coretesting.ModelTag.Id(): "testModel1", 938 }, nil) 939 940 results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{ 941 Force: false, 942 Credentials: []params.TaggedCredential{{ 943 Tag: "cloudcred-meep_julia_three", 944 Credential: params.CloudCredential{}, 945 }}}) 946 c.Assert(err, jc.ErrorIsNil) 947 c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{ 948 Results: []params.UpdateCredentialResult{{ 949 CredentialTag: "cloudcred-meep_julia_three", 950 Models: []params.UpdateCredentialModelResult{ 951 { 952 ModelUUID: coretesting.ModelTag.Id(), 953 ModelName: "testModel1", 954 Errors: []params.ErrorResult{{Error: ¶ms.Error{Message: "not valid for model", Code: ""}}}, 955 }, 956 }, 957 }}, 958 }) 959 } 960 961 func (s *cloudSuite) TestUpdateCredentialsModelFailedValidationForce(c *gc.C) { 962 adminTag := names.NewUserTag("admin") 963 defer s.setup(c, adminTag).Finish() 964 965 s.PatchValue(cloud.ValidateNewCredentialForModelFunc, 966 func(backend credentialcommon.PersistentBackend, _ context.ProviderCallContext, 967 _ names.CloudCredentialTag, _ *jujucloud.Credential, _ bool, _ bool, 968 ) (params.ErrorResults, error) { 969 return params.ErrorResults{Results: []params.ErrorResult{{Error: ¶ms.Error{Message: "not valid for model"}}}}, nil 970 }) 971 972 aCloud := jujucloud.Cloud{ 973 Name: "dummy", 974 Type: "dummy", 975 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 976 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 977 } 978 979 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 980 attrs: map[string]string{}}, c) 981 982 backend := s.backend.EXPECT() 983 backend.CredentialModels(tag).Return(map[string]string{ 984 coretesting.ModelTag.Id(): "testModel1", 985 }, nil) 986 backend.UpdateCloudCredential(tag, jujucloud.Credential{}).Return(nil) 987 988 pool := s.pool.EXPECT() 989 pool.GetModelCallContext(gomock.Any()).DoAndReturn(func(modelUUID string) ( 990 credentialcommon.PersistentBackend, context.ProviderCallContext, error, 991 ) { 992 return newModelBackend(c, aCloud, 993 modelUUID), context.NewEmptyCloudCallContext(), nil 994 }).MinTimes(1) 995 996 results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{ 997 Force: true, 998 Credentials: []params.TaggedCredential{{ 999 Tag: "cloudcred-meep_julia_three", 1000 Credential: params.CloudCredential{}, 1001 }}}) 1002 c.Assert(err, jc.ErrorIsNil) 1003 c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{ 1004 Results: []params.UpdateCredentialResult{{ 1005 CredentialTag: "cloudcred-meep_julia_three", 1006 Models: []params.UpdateCredentialModelResult{ 1007 { 1008 ModelUUID: coretesting.ModelTag.Id(), 1009 ModelName: "testModel1", 1010 Errors: []params.ErrorResult{{Error: ¶ms.Error{Message: "not valid for model", Code: ""}}}, 1011 }, 1012 }, 1013 }}, 1014 }) 1015 } 1016 1017 func (s *cloudSuite) TestUpdateCredentialsSomeModelsFailedValidation(c *gc.C) { 1018 adminTag := names.NewUserTag("admin") 1019 defer s.setup(c, adminTag).Finish() 1020 1021 aCloud := jujucloud.Cloud{ 1022 Name: "dummy", 1023 Type: "dummy", 1024 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 1025 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 1026 } 1027 1028 s.PatchValue(cloud.ValidateNewCredentialForModelFunc, 1029 func(backend credentialcommon.PersistentBackend, _ context.ProviderCallContext, 1030 _ names.CloudCredentialTag, _ *jujucloud.Credential, _ bool, _ bool, 1031 ) (params.ErrorResults, error) { 1032 if backend.(*mockModelBackend).uuid == "deadbeef-0bad-400d-8000-4b1d0d06f00d" { 1033 return params.ErrorResults{Results: []params.ErrorResult{{Error: ¶ms.Error{Message: "not valid for model"}}}}, nil 1034 } 1035 return params.ErrorResults{Results: []params.ErrorResult{}}, nil 1036 }) 1037 1038 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1039 attrs: map[string]string{}}, c) 1040 1041 backend := s.backend.EXPECT() 1042 backend.CredentialModels(tag).Return(map[string]string{ 1043 coretesting.ModelTag.Id(): "testModel1", 1044 "deadbeef-2f18-4fd2-967d-db9663db7bea": "testModel2", 1045 }, nil) 1046 1047 pool := s.pool.EXPECT() 1048 pool.GetModelCallContext(coretesting.ModelTag.Id()).Return(newModelBackend(c, aCloud, 1049 coretesting.ModelTag.Id()), context.NewEmptyCloudCallContext(), nil) 1050 pool.GetModelCallContext("deadbeef-2f18-4fd2-967d-db9663db7bea").Return(newModelBackend(c, aCloud, 1051 "deadbeef-2f18-4fd2-967d-db9663db7bea"), context.NewEmptyCloudCallContext(), nil) 1052 1053 results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{ 1054 Force: false, 1055 Credentials: []params.TaggedCredential{{ 1056 Tag: "cloudcred-meep_julia_three", 1057 Credential: params.CloudCredential{}, 1058 }}, 1059 }) 1060 c.Assert(err, jc.ErrorIsNil) 1061 c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{ 1062 Results: []params.UpdateCredentialResult{{ 1063 CredentialTag: "cloudcred-meep_julia_three", 1064 Models: []params.UpdateCredentialModelResult{{ 1065 ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 1066 ModelName: "testModel1", 1067 Errors: []params.ErrorResult{{ 1068 Error: ¶ms.Error{Message: "not valid for model", Code: ""}, 1069 }}, 1070 }, { 1071 ModelUUID: "deadbeef-2f18-4fd2-967d-db9663db7bea", 1072 ModelName: "testModel2", 1073 }}, 1074 }}, 1075 }) 1076 } 1077 1078 func (s *cloudSuite) TestUpdateCredentialsSomeModelsFailedValidationForce(c *gc.C) { 1079 adminTag := names.NewUserTag("admin") 1080 defer s.setup(c, adminTag).Finish() 1081 1082 aCloud := jujucloud.Cloud{ 1083 Name: "dummy", 1084 Type: "dummy", 1085 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 1086 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 1087 } 1088 1089 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1090 attrs: map[string]string{}}, c) 1091 1092 s.PatchValue(cloud.ValidateNewCredentialForModelFunc, 1093 func( 1094 backend credentialcommon.PersistentBackend, _ context.ProviderCallContext, 1095 _ names.CloudCredentialTag, _ *jujucloud.Credential, _ bool, _ bool, 1096 ) (params.ErrorResults, error) { 1097 if backend.(*mockModelBackend).uuid == "deadbeef-0bad-400d-8000-4b1d0d06f00d" { 1098 return params.ErrorResults{Results: []params.ErrorResult{{Error: ¶ms.Error{Message: "not valid for model"}}}}, nil 1099 } 1100 return params.ErrorResults{Results: []params.ErrorResult{}}, nil 1101 }) 1102 1103 backend := s.backend.EXPECT() 1104 backend.CredentialModels(tag).Return(map[string]string{ 1105 coretesting.ModelTag.Id(): "testModel1", 1106 "deadbeef-2f18-4fd2-967d-db9663db7bea": "testModel2", 1107 }, nil) 1108 backend.UpdateCloudCredential(tag, jujucloud.Credential{}).Return(nil) 1109 1110 pool := s.pool.EXPECT() 1111 pool.GetModelCallContext(coretesting.ModelTag.Id()).Return(newModelBackend(c, aCloud, coretesting.ModelTag.Id()), 1112 context.NewEmptyCloudCallContext(), nil) 1113 pool.GetModelCallContext("deadbeef-2f18-4fd2-967d-db9663db7bea").Return(newModelBackend(c, aCloud, 1114 "deadbeef-2f18-4fd2-967d-db9663db7bea"), nil, nil) 1115 1116 results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{ 1117 Force: true, 1118 Credentials: []params.TaggedCredential{{ 1119 Tag: "cloudcred-meep_julia_three", 1120 Credential: params.CloudCredential{}, 1121 }}}) 1122 c.Assert(err, jc.ErrorIsNil) 1123 c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{ 1124 Results: []params.UpdateCredentialResult{ 1125 { 1126 CredentialTag: "cloudcred-meep_julia_three", 1127 Models: []params.UpdateCredentialModelResult{ 1128 { 1129 ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 1130 ModelName: "testModel1", 1131 Errors: []params.ErrorResult{{Error: ¶ms.Error{Message: "not valid for model", 1132 Code: ""}}}, 1133 }, 1134 { 1135 ModelUUID: "deadbeef-2f18-4fd2-967d-db9663db7bea", 1136 ModelName: "testModel2", 1137 }, 1138 }, 1139 }, 1140 }, 1141 }) 1142 } 1143 1144 func (s *cloudSuite) TestUpdateCredentialsAllModelsFailedValidation(c *gc.C) { 1145 adminTag := names.NewUserTag("admin") 1146 defer s.setup(c, adminTag).Finish() 1147 1148 s.PatchValue(cloud.ValidateNewCredentialForModelFunc, 1149 func(_ credentialcommon.PersistentBackend, _ context.ProviderCallContext, 1150 _ names.CloudCredentialTag, _ *jujucloud.Credential, _ bool, _ bool, 1151 ) (params.ErrorResults, error) { 1152 return params.ErrorResults{Results: []params.ErrorResult{{Error: ¶ms.Error{Message: "not valid for model"}}}}, nil 1153 }) 1154 1155 aCloud := jujucloud.Cloud{ 1156 Name: "dummy", 1157 Type: "dummy", 1158 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 1159 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 1160 } 1161 1162 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1163 attrs: map[string]string{}}, c) 1164 1165 backend := s.backend.EXPECT() 1166 backend.CredentialModels(tag).Return(map[string]string{ 1167 coretesting.ModelTag.Id(): "testModel1", 1168 "deadbeef-2f18-4fd2-967d-db9663db7bea": "testModel2", 1169 }, nil) 1170 1171 pool := s.pool.EXPECT() 1172 pool.GetModelCallContext(gomock.Any()).Return(newModelBackend(c, aCloud, coretesting.ModelTag.Id()), 1173 context.NewEmptyCloudCallContext(), nil).Times(2) 1174 1175 results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{ 1176 Force: false, 1177 Credentials: []params.TaggedCredential{{ 1178 Tag: "cloudcred-meep_julia_three", 1179 Credential: params.CloudCredential{}, 1180 }}}) 1181 c.Assert(err, jc.ErrorIsNil) 1182 c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{ 1183 Results: []params.UpdateCredentialResult{{ 1184 CredentialTag: "cloudcred-meep_julia_three", 1185 Models: []params.UpdateCredentialModelResult{ 1186 { 1187 ModelUUID: coretesting.ModelTag.Id(), 1188 ModelName: "testModel1", 1189 Errors: []params.ErrorResult{{Error: ¶ms.Error{Message: "not valid for model"}}}, 1190 }, 1191 { 1192 ModelUUID: "deadbeef-2f18-4fd2-967d-db9663db7bea", 1193 ModelName: "testModel2", 1194 Errors: []params.ErrorResult{{Error: ¶ms.Error{Message: "not valid for model"}}}, 1195 }, 1196 }, 1197 }}}, 1198 ) 1199 } 1200 1201 func (s *cloudSuite) TestUpdateCredentialsAllModelsFailedValidationForce(c *gc.C) { 1202 adminTag := names.NewUserTag("admin") 1203 defer s.setup(c, adminTag).Finish() 1204 1205 s.PatchValue(cloud.ValidateNewCredentialForModelFunc, 1206 func(_ credentialcommon.PersistentBackend, _ context.ProviderCallContext, 1207 _ names.CloudCredentialTag, _ *jujucloud.Credential, migrating bool, _ bool) (params.ErrorResults, 1208 error) { 1209 return params.ErrorResults{Results: []params.ErrorResult{{Error: ¶ms.Error{Message: "not valid for model"}}}}, nil 1210 }) 1211 1212 aCloud := jujucloud.Cloud{ 1213 Name: "dummy", 1214 Type: "dummy", 1215 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 1216 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 1217 } 1218 1219 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1220 attrs: map[string]string{}}, c) 1221 1222 backend := s.backend.EXPECT() 1223 backend.CredentialModels(tag).Return(map[string]string{ 1224 coretesting.ModelTag.Id(): "testModel1", 1225 "deadbeef-2f18-4fd2-967d-db9663db7bea": "testModel2", 1226 }, nil) 1227 backend.UpdateCloudCredential(tag, jujucloud.Credential{}).Return(nil) 1228 1229 pool := s.pool.EXPECT() 1230 pool.GetModelCallContext(gomock.Any()).Return(newModelBackend(c, aCloud, coretesting.ModelTag.Id()), 1231 context.NewEmptyCloudCallContext(), nil) 1232 pool.GetModelCallContext(gomock.Any()).Return(newModelBackend(c, aCloud, "deadbeef-2f18-4fd2-967d-db9663db7bea"), 1233 context.NewEmptyCloudCallContext(), nil) 1234 1235 results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{ 1236 Force: true, 1237 Credentials: []params.TaggedCredential{{ 1238 Tag: "cloudcred-meep_julia_three", 1239 Credential: params.CloudCredential{}, 1240 }}, 1241 }) 1242 c.Assert(err, jc.ErrorIsNil) 1243 c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{ 1244 Results: []params.UpdateCredentialResult{{ 1245 CredentialTag: "cloudcred-meep_julia_three", 1246 Models: []params.UpdateCredentialModelResult{ 1247 { 1248 ModelUUID: coretesting.ModelTag.Id(), 1249 ModelName: "testModel1", 1250 Errors: []params.ErrorResult{{Error: ¶ms.Error{Message: "not valid for model"}}}, 1251 }, 1252 { 1253 ModelUUID: "deadbeef-2f18-4fd2-967d-db9663db7bea", 1254 ModelName: "testModel2", 1255 Errors: []params.ErrorResult{{Error: ¶ms.Error{Message: "not valid for model"}}}, 1256 }, 1257 }, 1258 }}}, 1259 ) 1260 } 1261 1262 func (s *cloudSuite) TestRevokeCredentials(c *gc.C) { 1263 bruceTag := names.NewUserTag("bruce") 1264 defer s.setup(c, bruceTag).Finish() 1265 1266 _, tag := cloudCredentialTag(credParams{name: "three", owner: "bruce", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1267 attrs: map[string]string{}}, c) 1268 1269 backend := s.backend.EXPECT() 1270 backend.CredentialModels(tag).Return(nil, nil) 1271 backend.RemoveCloudCredential(tag).Return(nil) 1272 backend.RemoveModelsCredential(tag).Return(nil) 1273 1274 results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{ 1275 Credentials: []params.RevokeCredentialArg{ 1276 {Tag: "machine-0"}, 1277 {Tag: "cloudcred-meep_admin_whatever"}, 1278 {Tag: "cloudcred-meep_bruce_three"}, 1279 }, 1280 }) 1281 c.Assert(err, jc.ErrorIsNil) 1282 c.Assert(results.Results, gc.HasLen, 3) 1283 c.Assert(results.Results[0].Error, jc.DeepEquals, ¶ms.Error{ 1284 Message: `"machine-0" is not a valid cloudcred tag`, 1285 }) 1286 c.Assert(results.Results[1].Error, jc.DeepEquals, ¶ms.Error{ 1287 Message: "permission denied", Code: params.CodeUnauthorized, 1288 }) 1289 c.Assert(results.Results[2].Error, gc.IsNil) 1290 } 1291 1292 func (s *cloudSuite) TestRevokeCredentialsAdminAccess(c *gc.C) { 1293 adminTag := names.NewUserTag("admin") 1294 defer s.setup(c, adminTag).Finish() 1295 1296 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1297 attrs: map[string]string{}}, c) 1298 1299 backend := s.backend.EXPECT() 1300 backend.CredentialModels(tag).Return(nil, nil) 1301 backend.RemoveCloudCredential(tag).Return(nil) 1302 backend.RemoveModelsCredential(tag).Return(nil) 1303 1304 results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{ 1305 Credentials: []params.RevokeCredentialArg{ 1306 {Tag: "cloudcred-meep_julia_three"}, 1307 }, 1308 }) 1309 c.Assert(err, jc.ErrorIsNil) 1310 c.Assert(results.Results, gc.HasLen, 1) 1311 // admin can revoke others' credentials 1312 c.Assert(results.Results[0].Error, gc.IsNil) 1313 } 1314 1315 func (s *cloudSuite) TestRevokeCredentialsCantGetModels(c *gc.C) { 1316 adminTag := names.NewUserTag("admin") 1317 defer s.setup(c, adminTag).Finish() 1318 1319 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1320 attrs: map[string]string{}}, c) 1321 1322 backend := s.backend.EXPECT() 1323 backend.CredentialModels(tag).Return(nil, errors.New("no niet nope")) 1324 1325 results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{Credentials: []params.RevokeCredentialArg{ 1326 {Tag: "cloudcred-meep_julia_three"}, 1327 }}) 1328 c.Assert(err, jc.ErrorIsNil) 1329 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 1330 Results: []params.ErrorResult{ 1331 {Error: apiservererrors.ServerError(errors.New("no niet nope"))}, 1332 }, 1333 }) 1334 c.Assert(c.GetTestLog(), jc.Contains, "") 1335 } 1336 1337 func (s *cloudSuite) TestRevokeCredentialsForceCantGetModels(c *gc.C) { 1338 adminTag := names.NewUserTag("admin") 1339 defer s.setup(c, adminTag).Finish() 1340 1341 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1342 attrs: map[string]string{}}, c) 1343 1344 backend := s.backend.EXPECT() 1345 backend.CredentialModels(tag).Return(nil, errors.New("no niet nope")) 1346 backend.RemoveCloudCredential(tag).Return(nil) 1347 backend.RemoveModelsCredential(tag).Return(nil) 1348 1349 results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{Credentials: []params.RevokeCredentialArg{ 1350 {Tag: "cloudcred-meep_julia_three", Force: true}, 1351 }}) 1352 c.Assert(err, jc.ErrorIsNil) 1353 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 1354 Results: []params.ErrorResult{ 1355 {}, // no error: credential deleted 1356 }, 1357 }) 1358 c.Assert(c.GetTestLog(), jc.Contains, 1359 " WARNING juju.apiserver.cloud could not get models that use credential cloudcred-meep_julia_three: no niet nope") 1360 } 1361 1362 func (s *cloudSuite) TestRevokeCredentialsHasModel(c *gc.C) { 1363 adminTag := names.NewUserTag("admin") 1364 defer s.setup(c, adminTag).Finish() 1365 1366 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1367 attrs: map[string]string{}}, c) 1368 1369 backend := s.backend.EXPECT() 1370 backend.CredentialModels(tag).Return(map[string]string{ 1371 coretesting.ModelTag.Id(): "modelName", 1372 }, nil) 1373 1374 results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{Credentials: []params.RevokeCredentialArg{ 1375 {Tag: "cloudcred-meep_julia_three"}, 1376 }}) 1377 c.Assert(err, jc.ErrorIsNil) 1378 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 1379 Results: []params.ErrorResult{ 1380 {Error: apiservererrors.ServerError(errors.New("cannot revoke credential cloudcred-meep_julia_three: it is still used by 1 model"))}, 1381 }, 1382 }) 1383 c.Assert(c.GetTestLog(), jc.Contains, 1384 " WARNING juju.apiserver.cloud credential cloudcred-meep_julia_three cannot be deleted as it is used by model deadbeef-0bad-400d-8000-4b1d0d06f00d") 1385 } 1386 1387 func (s *cloudSuite) TestRevokeCredentialsHasModels(c *gc.C) { 1388 adminTag := names.NewUserTag("admin") 1389 defer s.setup(c, adminTag).Finish() 1390 1391 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1392 attrs: map[string]string{}}, c) 1393 1394 backend := s.backend.EXPECT() 1395 backend.CredentialModels(tag).Return(map[string]string{ 1396 coretesting.ModelTag.Id(): "modelName", 1397 "deadbeef-1bad-511d-8000-4b1d0d06f00d": "anotherModelName", 1398 }, nil) 1399 1400 results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{Credentials: []params.RevokeCredentialArg{ 1401 {Tag: "cloudcred-meep_julia_three"}, 1402 }}) 1403 c.Assert(err, jc.ErrorIsNil) 1404 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 1405 Results: []params.ErrorResult{ 1406 {Error: apiservererrors.ServerError(errors.New("cannot revoke credential cloudcred-meep_julia_three: it is still used by 2 models"))}, 1407 }, 1408 }) 1409 c.Assert(c.GetTestLog(), jc.Contains, 1410 ` WARNING juju.apiserver.cloud credential cloudcred-meep_julia_three cannot be deleted as it is used by models: 1411 - deadbeef-0bad-400d-8000-4b1d0d06f00d 1412 - deadbeef-1bad-511d-8000-4b1d0d06f00d`) 1413 } 1414 1415 func (s *cloudSuite) TestRevokeCredentialsForceHasModel(c *gc.C) { 1416 adminTag := names.NewUserTag("admin") 1417 defer s.setup(c, adminTag).Finish() 1418 1419 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1420 attrs: map[string]string{}}, c) 1421 1422 backend := s.backend.EXPECT() 1423 backend.CredentialModels(tag).Return(map[string]string{ 1424 coretesting.ModelTag.Id(): "modelName", 1425 }, nil) 1426 backend.RemoveCloudCredential(tag).Return(nil) 1427 backend.RemoveModelsCredential(tag).Return(nil) 1428 1429 results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{Credentials: []params.RevokeCredentialArg{ 1430 {Tag: "cloudcred-meep_julia_three", Force: true}, 1431 }}) 1432 c.Assert(err, jc.ErrorIsNil) 1433 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 1434 Results: []params.ErrorResult{ 1435 {}, 1436 }, 1437 }) 1438 c.Assert(c.GetTestLog(), jc.Contains, 1439 ` WARNING juju.apiserver.cloud credential cloudcred-meep_julia_three will be deleted but it is used by model deadbeef-0bad-400d-8000-4b1d0d06f00d`) 1440 1441 } 1442 1443 func (s *cloudSuite) TestRevokeCredentialsForceMany(c *gc.C) { 1444 adminTag := names.NewUserTag("admin") 1445 defer s.setup(c, adminTag).Finish() 1446 1447 _, tagOne := cloudCredentialTag(credParams{name: "three", owner: "bruce", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1448 attrs: map[string]string{}}, c) 1449 _, tagTwo := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1450 attrs: map[string]string{}}, c) 1451 1452 backend := s.backend.EXPECT() 1453 backend.CredentialModels(tagOne).Return(map[string]string{ 1454 coretesting.ModelTag.Id(): "modelName", 1455 }, nil) 1456 backend.CredentialModels(tagTwo).Return(map[string]string{ 1457 coretesting.ModelTag.Id(): "modelName", 1458 }, nil) 1459 backend.RemoveCloudCredential(gomock.Any()).Return(nil) 1460 backend.RemoveModelsCredential(gomock.Any()).Return(nil) 1461 1462 results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{Credentials: []params.RevokeCredentialArg{ 1463 {Tag: "cloudcred-meep_julia_three", Force: true}, 1464 {Tag: "cloudcred-meep_bruce_three"}, 1465 }}) 1466 c.Assert(err, jc.ErrorIsNil) 1467 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 1468 Results: []params.ErrorResult{ 1469 {}, 1470 {Error: apiservererrors.ServerError(errors.New("cannot revoke credential cloudcred-meep_bruce_three: it is still used by 1 model"))}, 1471 }, 1472 }) 1473 c.Assert(c.GetTestLog(), jc.Contains, 1474 ` WARNING juju.apiserver.cloud credential cloudcred-meep_julia_three will be deleted but it is used by model deadbeef-0bad-400d-8000-4b1d0d06f00d`) 1475 c.Assert(c.GetTestLog(), jc.Contains, 1476 ` WARNING juju.apiserver.cloud credential cloudcred-meep_bruce_three cannot be deleted as it is used by model deadbeef-0bad-400d-8000-4b1d0d06f00d`) 1477 } 1478 1479 func (s *cloudSuite) TestRevokeCredentialsClearModelCredentialsError(c *gc.C) { 1480 adminTag := names.NewUserTag("admin") 1481 defer s.setup(c, adminTag).Finish() 1482 1483 _, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1484 attrs: map[string]string{}}, c) 1485 1486 backend := s.backend.EXPECT() 1487 backend.CredentialModels(tag).Return(map[string]string{ 1488 coretesting.ModelTag.Id(): "modelName", 1489 }, nil) 1490 backend.RemoveCloudCredential(tag).Return(nil) 1491 backend.RemoveModelsCredential(tag).Return(errors.New("kaboom")) 1492 1493 results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{Credentials: []params.RevokeCredentialArg{ 1494 {Tag: "cloudcred-meep_julia_three", Force: true}, 1495 }}) 1496 c.Assert(err, jc.ErrorIsNil) 1497 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 1498 Results: []params.ErrorResult{ 1499 {Error: apiservererrors.ServerError(errors.New("kaboom"))}, 1500 }, 1501 }) 1502 c.Assert(c.GetTestLog(), jc.Contains, 1503 " WARNING juju.apiserver.cloud credential cloudcred-meep_julia_three will be deleted but it is used by model deadbeef-0bad-400d-8000-4b1d0d06f00d") 1504 } 1505 1506 func (s *cloudSuite) TestCredential(c *gc.C) { 1507 bruceTag := names.NewUserTag("bruce") 1508 defer s.setup(c, bruceTag).Finish() 1509 1510 credentialOne, tagOne := cloudCredentialTag(credParams{name: "foo", owner: "admin", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1511 attrs: map[string]string{}}, c) 1512 credentialTwo, tagTwo := cloudCredentialTag(credParams{name: "two", owner: "bruce", cloudName: "meep", permission: jujucloud.UserPassAuthType, 1513 attrs: map[string]string{ 1514 "username": "admin", 1515 "password": "adm1n", 1516 }}, c) 1517 1518 creds := map[string]state.Credential{ 1519 tagOne.Id(): credentialOne, 1520 tagTwo.Id(): credentialTwo, 1521 } 1522 1523 cloud := jujucloud.Cloud{ 1524 Name: "meep", 1525 Type: "dummy", 1526 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 1527 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 1528 } 1529 1530 backend := s.backend.EXPECT() 1531 backend.Cloud("meep").Return(cloud, nil) 1532 backend.CloudCredentials(names.NewUserTag("bruce"), "meep").Return(creds, nil) 1533 1534 results, err := s.api.Credential(params.Entities{Entities: []params.Entity{{ 1535 Tag: "machine-0", 1536 }, { 1537 Tag: "cloudcred-meep_admin_foo", 1538 }, { 1539 Tag: "cloudcred-meep_bruce_two", 1540 }}}) 1541 c.Assert(err, jc.ErrorIsNil) 1542 c.Assert(results.Results, gc.HasLen, 3) 1543 c.Assert(results.Results[0].Error, jc.DeepEquals, ¶ms.Error{ 1544 Message: `"machine-0" is not a valid cloudcred tag`, 1545 }) 1546 c.Assert(results.Results[1].Error, jc.DeepEquals, ¶ms.Error{ 1547 Message: "permission denied", Code: params.CodeUnauthorized, 1548 }) 1549 c.Assert(results.Results[2].Error, gc.IsNil) 1550 c.Assert(results.Results[2].Result, jc.DeepEquals, ¶ms.CloudCredential{ 1551 AuthType: "userpass", 1552 Attributes: map[string]string{"username": "admin"}, 1553 Redacted: []string{"password"}, 1554 }) 1555 } 1556 1557 func (s *cloudSuite) TestCredentialAdminAccess(c *gc.C) { 1558 adminTag := names.NewUserTag("admin") 1559 defer s.setup(c, adminTag).Finish() 1560 1561 credential, tag := cloudCredentialTag(credParams{name: "two", owner: "bruce", cloudName: "meep", permission: jujucloud.UserPassAuthType, 1562 attrs: map[string]string{ 1563 "username": "admin", 1564 "password": "adm1n", 1565 }}, c) 1566 1567 creds := map[string]state.Credential{ 1568 tag.Id(): credential, 1569 } 1570 cloud := jujucloud.Cloud{ 1571 Name: "meep", 1572 Type: "dummy", 1573 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 1574 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 1575 } 1576 1577 backend := s.backend.EXPECT() 1578 backend.Cloud("meep").Return(cloud, nil) 1579 backend.CloudCredentials(names.NewUserTag("bruce"), "meep").Return(creds, nil) 1580 1581 results, err := s.api.Credential(params.Entities{Entities: []params.Entity{{ 1582 Tag: "cloudcred-meep_bruce_two", 1583 }}}) 1584 c.Assert(err, jc.ErrorIsNil) 1585 c.Assert(results.Results, gc.HasLen, 1) 1586 // admin can access others' credentials 1587 c.Assert(results.Results[0].Error, gc.IsNil) 1588 } 1589 1590 func (s *cloudSuite) TestModifyCloudAccess(c *gc.C) { 1591 adminTag := names.NewUserTag("admin") 1592 defer s.setup(c, adminTag).Finish() 1593 1594 cloud := jujucloud.Cloud{ 1595 Name: "fluffy", 1596 Type: "fluffy", 1597 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 1598 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 1599 } 1600 1601 backend := s.backend.EXPECT() 1602 backend.Cloud("fluffy").Return(cloud, nil).Times(2) 1603 fred := names.NewUserTag("fred") 1604 mary := names.NewUserTag("mary") 1605 backend.CreateCloudAccess("fluffy", fred, 1606 permission.AddModelAccess).Return(nil) 1607 backend.RemoveCloudAccess("fluffy", mary).Return(nil) 1608 1609 results, err := s.api.ModifyCloudAccess(params.ModifyCloudAccessRequest{ 1610 Changes: []params.ModifyCloudAccess{ 1611 { 1612 Action: params.GrantCloudAccess, 1613 CloudTag: names.NewCloudTag("fluffy").String(), 1614 UserTag: names.NewUserTag("fred").String(), 1615 Access: "add-model", 1616 }, { 1617 Action: params.RevokeCloudAccess, 1618 CloudTag: names.NewCloudTag("fluffy").String(), 1619 UserTag: names.NewUserTag("mary").String(), 1620 Access: "add-model", 1621 }, 1622 }, 1623 }) 1624 c.Assert(err, jc.ErrorIsNil) 1625 c.Assert(results.Results, gc.DeepEquals, []params.ErrorResult{ 1626 {}, {}, 1627 }) 1628 } 1629 1630 func (s *cloudSuite) TestModifyCloudUpdateAccess(c *gc.C) { 1631 adminTag := names.NewUserTag("admin") 1632 defer s.setup(c, adminTag).Finish() 1633 1634 cloud := jujucloud.Cloud{ 1635 Name: "fluffy", 1636 Type: "fluffy", 1637 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 1638 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 1639 } 1640 1641 fredTag := names.NewUserTag("fred") 1642 1643 backend := s.backend.EXPECT() 1644 backend.Cloud("fluffy").Return(cloud, nil) 1645 backend.CreateCloudAccess("fluffy", fredTag, 1646 permission.AdminAccess).Return(errors.AlreadyExistsf("access %s", permission.AdminAccess)) 1647 backend.GetCloudAccess("fluffy", fredTag).Return(permission.AddModelAccess, 1648 nil) 1649 backend.UpdateCloudAccess("fluffy", fredTag, 1650 permission.AdminAccess).Return(nil) 1651 1652 results, err := s.api.ModifyCloudAccess(params.ModifyCloudAccessRequest{ 1653 Changes: []params.ModifyCloudAccess{ 1654 { 1655 Action: params.GrantCloudAccess, 1656 CloudTag: names.NewCloudTag("fluffy").String(), 1657 UserTag: names.NewUserTag("fred").String(), 1658 Access: "admin", 1659 }, 1660 }, 1661 }) 1662 c.Assert(err, jc.ErrorIsNil) 1663 c.Assert(results.Results, gc.DeepEquals, []params.ErrorResult{ 1664 {}, 1665 }) 1666 } 1667 1668 func (s *cloudSuite) TestModifyCloudAlreadyHasAccess(c *gc.C) { 1669 adminTag := names.NewUserTag("admin") 1670 defer s.setup(c, adminTag).Finish() 1671 1672 cloud := jujucloud.Cloud{ 1673 Name: "fluffy", 1674 Type: "fluffy", 1675 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 1676 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 1677 } 1678 1679 fredTag := names.NewUserTag("fred") 1680 1681 backend := s.backend.EXPECT() 1682 backend.Cloud("fluffy").Return(cloud, nil) 1683 backend.CreateCloudAccess("fluffy", fredTag, 1684 permission.AdminAccess).Return(errors.AlreadyExistsf("access %s", permission.AdminAccess)) 1685 backend.GetCloudAccess("fluffy", fredTag).Return(permission.AdminAccess, 1686 nil) 1687 1688 results, err := s.api.ModifyCloudAccess(params.ModifyCloudAccessRequest{ 1689 Changes: []params.ModifyCloudAccess{ 1690 { 1691 Action: params.GrantCloudAccess, 1692 CloudTag: names.NewCloudTag("fluffy").String(), 1693 UserTag: names.NewUserTag("fred").String(), 1694 Access: "admin", 1695 }, 1696 }, 1697 }) 1698 c.Assert(err, jc.ErrorIsNil) 1699 c.Assert(results.Results, gc.DeepEquals, []params.ErrorResult{ 1700 {Error: ¶ms.Error{Message: `could not grant cloud access: user already has "admin" access or greater`}}, 1701 }) 1702 } 1703 1704 func (s *cloudSuite) TestCredentialContentsAllNoSecrets(c *gc.C) { 1705 bruceTag := names.NewUserTag("bruce") 1706 defer s.setup(c, bruceTag).Finish() 1707 1708 credentialOne, tagOne := cloudCredentialTag(credParams{name: "one", owner: "bruce", cloudName: "meep", permission: jujucloud.EmptyAuthType, 1709 attrs: map[string]string{}}, c) 1710 1711 credentialTwo, tagTwo := cloudCredentialTag(credParams{name: "two", owner: "bruce", cloudName: "meep", permission: jujucloud.UserPassAuthType, 1712 attrs: map[string]string{ 1713 "username": "admin", 1714 }}, c) 1715 1716 credentialTwo.Invalid = true 1717 creds := []state.Credential{ 1718 credentialOne, 1719 credentialTwo, 1720 } 1721 cloud := jujucloud.Cloud{ 1722 Name: "dummy", 1723 Type: "dummy", 1724 AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType}, 1725 Regions: []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 1726 } 1727 1728 backend := s.backend.EXPECT() 1729 backend.AllCloudCredentials(bruceTag).Return(creds, nil) 1730 backend.Cloud("meep").Return(cloud, nil) 1731 backend.CredentialModelsAndOwnerAccess(tagOne).Return([]state.CredentialOwnerModelAccess{}, nil) 1732 backend.CredentialModelsAndOwnerAccess(tagTwo).Return([]state.CredentialOwnerModelAccess{}, nil) 1733 1734 results, err := s.api.CredentialContents(params.CloudCredentialArgs{}) 1735 c.Assert(err, jc.ErrorIsNil) 1736 1737 _true := true 1738 _false := false 1739 expected := map[string]params.CredentialContent{ 1740 "one": { 1741 Name: "one", 1742 Cloud: "meep", 1743 AuthType: "empty", 1744 Valid: &_true, 1745 Attributes: map[string]string{}, 1746 }, 1747 "two": { 1748 Name: "two", 1749 Cloud: "meep", 1750 AuthType: "userpass", 1751 Valid: &_false, 1752 Attributes: map[string]string{ 1753 "username": "admin", 1754 }, 1755 }, 1756 } 1757 1758 c.Assert(results.Results, gc.HasLen, len(expected)) 1759 for _, one := range results.Results { 1760 c.Assert(one.Result.Content, gc.DeepEquals, expected[one.Result.Content.Name]) 1761 } 1762 } 1763 1764 func cloudCredentialTag(params credParams, 1765 c *gc.C) (state.Credential, names.CloudCredentialTag) { 1766 cred := statetesting.CloudCredential(params.permission, params.attrs) 1767 cred.Name = params.name 1768 cred.Owner = params.owner 1769 cred.Cloud = params.cloudName 1770 1771 tag, err := cred.CloudCredentialTag() 1772 c.Assert(err, jc.ErrorIsNil) 1773 1774 return cred, tag 1775 } 1776 1777 type credParams struct { 1778 name string 1779 owner string 1780 cloudName string 1781 permission jujucloud.AuthType 1782 attrs map[string]string 1783 } 1784 1785 type mockModelBackend struct { 1786 credentialcommon.PersistentBackend 1787 uuid string 1788 }