github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/controller/addmodel_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package controller_test 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "strings" 10 11 "github.com/juju/cmd" 12 "github.com/juju/cmd/cmdtesting" 13 "github.com/juju/errors" 14 gitjujutesting "github.com/juju/testing" 15 jc "github.com/juju/testing/checkers" 16 "github.com/juju/utils" 17 "github.com/juju/version" 18 gc "gopkg.in/check.v1" 19 "gopkg.in/juju/names.v2" 20 "gopkg.in/yaml.v2" 21 22 "github.com/juju/juju/api" 23 "github.com/juju/juju/api/base" 24 "github.com/juju/juju/apiserver/params" 25 "github.com/juju/juju/cloud" 26 "github.com/juju/juju/cmd/juju/controller" 27 "github.com/juju/juju/core/model" 28 "github.com/juju/juju/environs" 29 "github.com/juju/juju/feature" 30 "github.com/juju/juju/jujuclient" 31 _ "github.com/juju/juju/provider/ec2" 32 "github.com/juju/juju/testing" 33 ) 34 35 type AddModelSuite struct { 36 testing.FakeJujuXDGDataHomeSuite 37 fakeAddModelAPI *fakeAddClient 38 fakeCloudAPI *fakeCloudAPI 39 fakeProvider *fakeProvider 40 fakeProviderRegistry *fakeProviderRegistry 41 store *jujuclient.MemStore 42 } 43 44 var _ = gc.Suite(&AddModelSuite{}) 45 46 func (s *AddModelSuite) SetUpTest(c *gc.C) { 47 s.FakeJujuXDGDataHomeSuite.SetUpTest(c) 48 49 agentVersion, err := version.Parse("2.55.5") 50 c.Assert(err, jc.ErrorIsNil) 51 s.fakeAddModelAPI = &fakeAddClient{ 52 model: base.ModelInfo{ 53 Name: "test", 54 Type: model.IAAS, 55 UUID: "fake-model-uuid", 56 Owner: "ignored-for-now", 57 AgentVersion: &agentVersion, 58 }, 59 } 60 s.fakeCloudAPI = &fakeCloudAPI{ 61 authTypes: []cloud.AuthType{ 62 cloud.EmptyAuthType, 63 cloud.AccessKeyAuthType, 64 }, 65 credentials: []names.CloudCredentialTag{ 66 names.NewCloudCredentialTag("cloud/admin/default"), 67 names.NewCloudCredentialTag("aws/other/secrets"), 68 }, 69 } 70 s.fakeProvider = &fakeProvider{ 71 detected: cloud.NewEmptyCloudCredential(), 72 } 73 s.fakeProviderRegistry = &fakeProviderRegistry{ 74 provider: s.fakeProvider, 75 } 76 77 // Set up the current controller, and write just enough info 78 // so we don't try to refresh 79 controllerName := "test-master" 80 s.store = jujuclient.NewMemStore() 81 s.store.CurrentControllerName = controllerName 82 s.store.Controllers[controllerName] = jujuclient.ControllerDetails{} 83 s.store.Accounts[controllerName] = jujuclient.AccountDetails{ 84 User: "bob", 85 } 86 s.store.Credentials["aws"] = cloud.CloudCredential{ 87 AuthCredentials: map[string]cloud.Credential{ 88 "secrets": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{ 89 "access-key": "key", 90 "secret-key": "sekret", 91 }), 92 }, 93 } 94 } 95 96 type fakeAPIConnection struct { 97 api.Connection 98 } 99 100 func (*fakeAPIConnection) Close() error { 101 return nil 102 } 103 104 func (s *AddModelSuite) run(c *gc.C, args ...string) (*cmd.Context, error) { 105 command, _ := controller.NewAddModelCommandForTest( 106 &fakeAPIConnection{}, 107 s.fakeAddModelAPI, 108 s.fakeCloudAPI, 109 s.store, 110 s.fakeProviderRegistry, 111 ) 112 return cmdtesting.RunCommand(c, command, args...) 113 } 114 115 func (s *AddModelSuite) TestInit(c *gc.C) { 116 modelNameErr := "%q is not a valid name: model names may only contain lowercase letters, digits and hyphens" 117 for i, test := range []struct { 118 args []string 119 err string 120 name string 121 owner string 122 cloudRegion string 123 values map[string]interface{} 124 }{ 125 { 126 err: "model name is required", 127 }, { 128 args: []string{"new-model"}, 129 name: "new-model", 130 }, { 131 args: []string{"n"}, 132 name: "n", 133 }, { 134 args: []string{"new model"}, 135 err: fmt.Sprintf(modelNameErr, "new model"), 136 }, { 137 args: []string{"newModel"}, 138 err: fmt.Sprintf(modelNameErr, "newModel"), 139 }, { 140 args: []string{"-"}, 141 err: fmt.Sprintf(modelNameErr, "-"), 142 }, { 143 args: []string{"new@model"}, 144 err: fmt.Sprintf(modelNameErr, "new@model"), 145 }, { 146 args: []string{"new-model", "--owner", "foo"}, 147 name: "new-model", 148 owner: "foo", 149 }, { 150 args: []string{"new-model", "--owner", "not=valid"}, 151 err: `"not=valid" is not a valid user`, 152 }, { 153 args: []string{"new-model", "--config", "key=value", "--config", "key2=value2"}, 154 name: "new-model", 155 values: map[string]interface{}{"key": "value", "key2": "value2"}, 156 }, { 157 args: []string{"new-model", "cloud/region"}, 158 name: "new-model", 159 cloudRegion: "cloud/region", 160 }, { 161 args: []string{"new-model", "cloud/region", "extra", "args"}, 162 err: `unrecognized args: \["extra" "args"\]`, 163 }, 164 } { 165 c.Logf("test %d", i) 166 wrappedCommand, command := controller.NewAddModelCommandForTest(nil, nil, nil, s.store, nil) 167 err := cmdtesting.InitCommand(wrappedCommand, test.args) 168 if test.err != "" { 169 c.Assert(err, gc.ErrorMatches, test.err) 170 continue 171 } 172 173 c.Assert(err, jc.ErrorIsNil) 174 c.Assert(command.Name, gc.Equals, test.name) 175 c.Assert(command.Owner, gc.Equals, test.owner) 176 c.Assert(command.CloudRegion, gc.Equals, test.cloudRegion) 177 attrs, err := command.Config.ReadAttrs(nil) 178 c.Assert(err, jc.ErrorIsNil) 179 if len(test.values) == 0 { 180 c.Assert(attrs, gc.HasLen, 0) 181 } else { 182 c.Assert(attrs, jc.DeepEquals, test.values) 183 } 184 } 185 } 186 187 func (s *AddModelSuite) TestAddExistingName(c *gc.C) { 188 s.SetFeatureFlags(feature.Generations) 189 // If there's any model details existing, we just overwrite them. The 190 // controller will error out if the model already exists. Overwriting 191 // means we'll replace any stale details from an previously existing 192 // model with the same name. 193 err := s.store.UpdateModel("test-master", "bob/test", jujuclient.ModelDetails{ 194 ModelUUID: "stale-uuid", 195 ModelType: model.IAAS, 196 }) 197 c.Assert(err, jc.ErrorIsNil) 198 199 _, err = s.run(c, "test") 200 c.Assert(err, jc.ErrorIsNil) 201 202 details, err := s.store.ModelByName("test-master", "bob/test") 203 c.Assert(err, jc.ErrorIsNil) 204 c.Assert(details, jc.DeepEquals, &jujuclient.ModelDetails{ 205 ModelUUID: "fake-model-uuid", 206 ModelType: model.IAAS, 207 ModelGeneration: model.GenerationCurrent, 208 }) 209 } 210 211 func (s *AddModelSuite) TestAddModelUnauthorizedMentionsJujuGrant(c *gc.C) { 212 s.fakeAddModelAPI.err = ¶ms.Error{ 213 Message: "permission denied", 214 Code: params.CodeUnauthorized, 215 } 216 ctx, _ := s.run(c, "test") 217 errString := strings.Replace(cmdtesting.Stderr(ctx), "\n", " ", -1) 218 c.Assert(errString, gc.Matches, `.*juju grant.*`) 219 } 220 221 func (s *AddModelSuite) TestCredentialsPassedThrough(c *gc.C) { 222 _, err := s.run(c, "test", "--credential", "secrets") 223 c.Assert(err, jc.ErrorIsNil) 224 225 c.Assert(s.fakeAddModelAPI.cloudCredential, gc.Equals, names.NewCloudCredentialTag("aws/bob/secrets")) 226 } 227 228 func (s *AddModelSuite) TestCredentialsOtherUserPassedThrough(c *gc.C) { 229 _, err := s.run(c, "test", "--credential", "other/secrets") 230 c.Assert(err, jc.ErrorIsNil) 231 232 c.Assert(s.fakeAddModelAPI.cloudCredential, gc.Equals, names.NewCloudCredentialTag("aws/other/secrets")) 233 } 234 235 func (s *AddModelSuite) TestCredentialsOtherUserPassedThroughWhenCloud(c *gc.C) { 236 _, err := s.run(c, "test", "--credential", "other/secrets", "aws/us-west-1") 237 c.Assert(err, jc.ErrorIsNil) 238 239 c.Assert(s.fakeAddModelAPI.cloudCredential, gc.Equals, names.NewCloudCredentialTag("aws/other/secrets")) 240 } 241 242 func (s *AddModelSuite) TestCredentialsOtherUserCredentialNotFound(c *gc.C) { 243 // Have the API respond with no credentials. 244 s.PatchValue(&s.fakeCloudAPI.credentials, []names.CloudCredentialTag{}) 245 246 _, err := s.run(c, "test", "--credential", "other/secrets") 247 c.Assert(err, gc.ErrorMatches, "credential 'other/secrets' not found") 248 249 // There should be no detection or UpdateCredentials call. 250 s.fakeCloudAPI.CheckCallNames(c, "DefaultCloud", "Cloud", "UserCredentials") 251 } 252 253 func (s *AddModelSuite) TestCredentialsNoDefaultCloud(c *gc.C) { 254 s.fakeCloudAPI.SetErrors(¶ms.Error{Code: params.CodeNotFound}) 255 _, err := s.run(c, "test", "--credential", "secrets") 256 c.Assert(err, gc.ErrorMatches, `there is no default cloud defined, please specify one using: 257 258 juju add-model \[options\] \<model-name\> cloud\[/region\]`) 259 } 260 261 func (s *AddModelSuite) TestCredentialsOneCached(c *gc.C) { 262 // Disable empty auth and clear the local credentials, 263 // forcing a check for credentials in the controller. 264 s.PatchValue(&s.fakeCloudAPI.authTypes, []cloud.AuthType{cloud.AccessKeyAuthType}) 265 delete(s.store.Credentials, "aws") 266 267 // Cache just a single credential in the controller, 268 // so it is selected automatically. 269 credentialTag := names.NewCloudCredentialTag("aws/foo/secrets") 270 s.PatchValue(&s.fakeCloudAPI.credentials, []names.CloudCredentialTag{credentialTag}) 271 272 _, err := s.run(c, "test", "aws/us-west-1") 273 c.Assert(err, jc.ErrorIsNil) 274 275 // The cached credential should be used, along with 276 // the user-specified cloud region. 277 c.Assert(s.fakeAddModelAPI.cloudCredential, gc.Equals, credentialTag) 278 c.Assert(s.fakeAddModelAPI.cloudRegion, gc.Equals, "us-west-1") 279 } 280 281 func (s *AddModelSuite) TestControllerCredentialsDetected(c *gc.C) { 282 // Disable empty auth and clear the local credentials, 283 // forcing a check for credentials in the controller. 284 // There are multiple credentials in the controller, 285 // so none of them will be chosen by default. 286 s.PatchValue(&s.fakeCloudAPI.authTypes, []cloud.AuthType{cloud.AccessKeyAuthType}) 287 288 // Delete all local credentials, so we don't choose 289 // any of them to upload. This will force credential 290 // detection. 291 delete(s.store.Credentials, "aws") 292 293 _, err := s.run(c, "test") 294 c.Assert(err, jc.ErrorIsNil) 295 296 credentialTag := names.NewCloudCredentialTag("aws/bob/default") 297 credential := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{}) 298 credential.Label = "finalized" 299 300 c.Assert(s.fakeAddModelAPI.cloudCredential, gc.Equals, credentialTag) 301 s.fakeCloudAPI.CheckCallNames(c, "DefaultCloud", "Cloud", "UserCredentials") 302 } 303 304 func (s *AddModelSuite) TestControllerCredentialsDetectedAmbiguous(c *gc.C) { 305 // Disable empty auth and clear the local credentials, 306 // forcing a check for credentials in the controller. 307 // There are multiple credentials in the controller, 308 // so none of them will be chosen by default. 309 s.PatchValue(&s.fakeCloudAPI.authTypes, []cloud.AuthType{cloud.AccessKeyAuthType}) 310 311 // Delete all local credentials, so we don't choose 312 // any of them to upload. This will force credential 313 // detection. 314 delete(s.store.Credentials, "aws") 315 316 s.PatchValue(&s.fakeProvider.detected, &cloud.CloudCredential{ 317 AuthCredentials: map[string]cloud.Credential{ 318 "one": {}, 319 "two": {}, 320 }, 321 }) 322 323 _, err := s.run(c, "test") 324 c.Assert(err, gc.ErrorMatches, ` 325 more than one credential detected. Add all detected credentials 326 to the client with: 327 328 juju autoload-credentials 329 330 and then run the add-model command again with the --credential option.`[1:]) 331 } 332 333 func (s *AddModelSuite) TestCloudRegionPassedThrough(c *gc.C) { 334 _, err := s.run(c, "test", "aws/us-west-1") 335 c.Assert(err, jc.ErrorIsNil) 336 337 c.Assert(s.fakeAddModelAPI.cloudName, gc.Equals, "aws") 338 c.Assert(s.fakeAddModelAPI.cloudRegion, gc.Equals, "us-west-1") 339 } 340 341 func (s *AddModelSuite) TestDefaultCloudPassedThrough(c *gc.C) { 342 _, err := s.run(c, "test") 343 c.Assert(err, jc.ErrorIsNil) 344 345 s.fakeCloudAPI.CheckCallNames(c, "DefaultCloud", "Cloud") 346 c.Assert(s.fakeAddModelAPI.cloudName, gc.Equals, "aws") 347 c.Assert(s.fakeAddModelAPI.cloudRegion, gc.Equals, "") 348 } 349 350 func (s *AddModelSuite) TestDefaultCloudRegionPassedThrough(c *gc.C) { 351 _, err := s.run(c, "test", "us-west-1") 352 c.Assert(err, jc.ErrorIsNil) 353 354 s.fakeCloudAPI.CheckCalls(c, []gitjujutesting.StubCall{ 355 {"Cloud", []interface{}{names.NewCloudTag("us-west-1")}}, 356 {"DefaultCloud", nil}, 357 {"Cloud", []interface{}{names.NewCloudTag("aws")}}, 358 }) 359 c.Assert(s.fakeAddModelAPI.cloudName, gc.Equals, "aws") 360 c.Assert(s.fakeAddModelAPI.cloudRegion, gc.Equals, "us-west-1") 361 } 362 363 func (s *AddModelSuite) TestNoDefaultCloudRegion(c *gc.C) { 364 s.fakeCloudAPI.SetErrors( 365 ¶ms.Error{Code: params.CodeNotFound}, // no default region 366 ) 367 _, err := s.run(c, "test", "us-west-1") 368 c.Assert(err, gc.ErrorMatches, ` 369 "us-west-1" is not a cloud supported by this controller, 370 and there is no default cloud. The clouds/regions supported 371 by this controller are: 372 373 Cloud Regions 374 aws us-east-1, us-west-1 375 lxd 376 `[1:]) 377 s.fakeCloudAPI.CheckCalls(c, []gitjujutesting.StubCall{ 378 {"Cloud", []interface{}{names.NewCloudTag("us-west-1")}}, 379 {"DefaultCloud", nil}, 380 {"Clouds", nil}, 381 }) 382 } 383 384 func (s *AddModelSuite) TestCloudUnspecifiedRegionPassedThrough(c *gc.C) { 385 // Use a cloud that doesn't support empty authorization. 386 s.fakeCloudAPI = &fakeCloudAPI{ 387 authTypes: []cloud.AuthType{ 388 cloud.AccessKeyAuthType, 389 }, 390 credentials: []names.CloudCredentialTag{ 391 names.NewCloudCredentialTag("cloud/admin/default"), 392 names.NewCloudCredentialTag("aws/other/secrets"), 393 }, 394 } 395 _, err := s.run(c, "test", "aws") 396 c.Assert(err, jc.ErrorIsNil) 397 398 c.Assert(s.fakeAddModelAPI.cloudName, gc.Equals, "aws") 399 c.Assert(s.fakeAddModelAPI.cloudRegion, gc.Equals, "") 400 } 401 402 func (s *AddModelSuite) TestCloudDefaultRegionUsedIfSet(c *gc.C) { 403 // Overwrite the credentials with a default region. 404 s.store.Credentials["aws"] = cloud.CloudCredential{ 405 DefaultRegion: "us-west-1", 406 AuthCredentials: map[string]cloud.Credential{ 407 "secrets": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{ 408 "access-key": "key", 409 "secret-key": "sekret", 410 }), 411 }, 412 } 413 // Use a cloud that doesn't support empty authorization. 414 s.fakeCloudAPI = &fakeCloudAPI{ 415 authTypes: []cloud.AuthType{ 416 cloud.AccessKeyAuthType, 417 }, 418 credentials: []names.CloudCredentialTag{ 419 names.NewCloudCredentialTag("cloud/admin/default"), 420 names.NewCloudCredentialTag("aws/other/secrets"), 421 }, 422 } 423 _, err := s.run(c, "test", "aws") 424 c.Assert(err, jc.ErrorIsNil) 425 426 c.Assert(s.fakeAddModelAPI.cloudName, gc.Equals, "aws") 427 c.Assert(s.fakeAddModelAPI.cloudRegion, gc.Equals, "us-west-1") 428 } 429 430 func (s *AddModelSuite) TestInvalidCloudOrRegionName(c *gc.C) { 431 _, err := s.run(c, "test", "oro") 432 c.Assert(err, gc.ErrorMatches, ` 433 "oro" is neither a cloud supported by this controller, 434 nor a region in the controller's default cloud "aws". 435 The clouds/regions supported by this controller are: 436 437 Cloud Regions 438 aws us-east-1, us-west-1 439 lxd 440 `[1:]) 441 } 442 443 func (s *AddModelSuite) TestComandLineConfigPassedThrough(c *gc.C) { 444 _, err := s.run(c, "test", "--config", "account=magic", "--config", "cloud=special") 445 c.Assert(err, jc.ErrorIsNil) 446 447 c.Assert(s.fakeAddModelAPI.config["account"], gc.Equals, "magic") 448 c.Assert(s.fakeAddModelAPI.config["cloud"], gc.Equals, "special") 449 } 450 451 func (s *AddModelSuite) TestConfigFileValuesPassedThrough(c *gc.C) { 452 config := map[string]string{ 453 "account": "magic", 454 "cloud": "9", 455 } 456 bytes, err := yaml.Marshal(config) 457 c.Assert(err, jc.ErrorIsNil) 458 file, err := ioutil.TempFile(c.MkDir(), "") 459 c.Assert(err, jc.ErrorIsNil) 460 file.Write(bytes) 461 file.Close() 462 463 _, err = s.run(c, "test", "--config", file.Name()) 464 c.Assert(err, jc.ErrorIsNil) 465 c.Assert(s.fakeAddModelAPI.config["account"], gc.Equals, "magic") 466 c.Assert(s.fakeAddModelAPI.config["cloud"], gc.Equals, "9") 467 } 468 469 func (s *AddModelSuite) TestConfigFileWithNestedMaps(c *gc.C) { 470 nestedConfig := map[string]interface{}{ 471 "account": "magic", 472 "cloud": "9", 473 } 474 config := map[string]interface{}{ 475 "foo": "bar", 476 "nested": nestedConfig, 477 } 478 479 bytes, err := yaml.Marshal(config) 480 c.Assert(err, jc.ErrorIsNil) 481 file, err := ioutil.TempFile(c.MkDir(), "") 482 c.Assert(err, jc.ErrorIsNil) 483 file.Write(bytes) 484 file.Close() 485 486 _, err = s.run(c, "test", "--config", file.Name()) 487 c.Assert(err, jc.ErrorIsNil) 488 c.Assert(s.fakeAddModelAPI.config["foo"], gc.Equals, "bar") 489 c.Assert(s.fakeAddModelAPI.config["nested"], jc.DeepEquals, nestedConfig) 490 } 491 492 func (s *AddModelSuite) TestConfigFileFailsToConform(c *gc.C) { 493 nestedConfig := map[int]interface{}{ 494 9: "9", 495 } 496 config := map[string]interface{}{ 497 "foo": "bar", 498 "nested": nestedConfig, 499 } 500 bytes, err := yaml.Marshal(config) 501 c.Assert(err, jc.ErrorIsNil) 502 file, err := ioutil.TempFile(c.MkDir(), "") 503 c.Assert(err, jc.ErrorIsNil) 504 file.Write(bytes) 505 file.Close() 506 507 _, err = s.run(c, "test", "--config", file.Name()) 508 c.Assert(err, gc.ErrorMatches, `unable to parse config: map keyed with non-string value`) 509 } 510 511 func (s *AddModelSuite) TestConfigFileFormatError(c *gc.C) { 512 file, err := ioutil.TempFile(c.MkDir(), "") 513 c.Assert(err, jc.ErrorIsNil) 514 file.Write(([]byte)("not: valid: yaml")) 515 file.Close() 516 517 _, err = s.run(c, "test", "--config", file.Name()) 518 c.Assert(err, gc.ErrorMatches, `unable to parse config: yaml: .*`) 519 } 520 521 func (s *AddModelSuite) TestConfigFileDoesntExist(c *gc.C) { 522 _, err := s.run(c, "test", "--config", "missing-file") 523 errMsg := ".*" + utils.NoSuchFileErrRegexp 524 c.Assert(err, gc.ErrorMatches, errMsg) 525 } 526 527 func (s *AddModelSuite) TestConfigValuePrecedence(c *gc.C) { 528 config := map[string]string{ 529 "account": "magic", 530 "cloud": "9", 531 } 532 bytes, err := yaml.Marshal(config) 533 c.Assert(err, jc.ErrorIsNil) 534 file, err := ioutil.TempFile(c.MkDir(), "") 535 c.Assert(err, jc.ErrorIsNil) 536 file.Write(bytes) 537 file.Close() 538 539 _, err = s.run(c, "test", "--config", file.Name(), "--config", "account=magic", "--config", "cloud=special") 540 c.Assert(err, jc.ErrorIsNil) 541 c.Assert(s.fakeAddModelAPI.config["account"], gc.Equals, "magic") 542 c.Assert(s.fakeAddModelAPI.config["cloud"], gc.Equals, "special") 543 } 544 545 func (s *AddModelSuite) TestAddErrorRemoveConfigstoreInfo(c *gc.C) { 546 s.fakeAddModelAPI.err = errors.New("bah humbug") 547 548 _, err := s.run(c, "test") 549 c.Assert(err, gc.ErrorMatches, "bah humbug") 550 551 _, err = s.store.ModelByName("test-master", "bob/test") 552 c.Assert(err, jc.Satisfies, errors.IsNotFound) 553 } 554 555 func (s *AddModelSuite) TestAddStoresValues(c *gc.C) { 556 s.SetFeatureFlags(feature.Generations) 557 const controllerName = "test-master" 558 559 _, err := s.run(c, "test") 560 c.Assert(err, jc.ErrorIsNil) 561 562 c.Check(s.store.CurrentControllerName, gc.Equals, controllerName) 563 modelName, err := s.store.CurrentModel(controllerName) 564 c.Assert(err, jc.ErrorIsNil) 565 c.Check(modelName, gc.Equals, "bob/test") 566 567 m, err := s.store.ModelByName(controllerName, modelName) 568 c.Assert(err, jc.ErrorIsNil) 569 c.Assert(m, jc.DeepEquals, &jujuclient.ModelDetails{ 570 ModelUUID: "fake-model-uuid", 571 ModelType: model.IAAS, 572 ModelGeneration: model.GenerationCurrent, 573 }) 574 } 575 576 func (s *AddModelSuite) TestNoSwitch(c *gc.C) { 577 s.SetFeatureFlags(feature.Generations) 578 const controllerName = "test-master" 579 checkNoModelSelected := func() { 580 _, err := s.store.CurrentModel(controllerName) 581 c.Check(err, jc.Satisfies, errors.IsNotFound) 582 } 583 checkNoModelSelected() 584 585 _, err := s.run(c, "test", "--no-switch") 586 c.Assert(err, jc.ErrorIsNil) 587 588 // New model should not be selected by should still exist in the 589 // store. 590 checkNoModelSelected() 591 m, err := s.store.ModelByName(controllerName, "bob/test") 592 c.Assert(err, jc.ErrorIsNil) 593 c.Assert(m, jc.DeepEquals, &jujuclient.ModelDetails{ 594 ModelUUID: "fake-model-uuid", 595 ModelType: model.IAAS, 596 ModelGeneration: model.GenerationCurrent, 597 }) 598 } 599 600 func (s *AddModelSuite) TestNoEnvCacheOtherUser(c *gc.C) { 601 _, err := s.run(c, "test", "--owner", "zeus") 602 c.Assert(err, jc.ErrorIsNil) 603 604 // Creating a model for another user does not update the model cache. 605 _, err = s.store.ModelByName("test-master", "bob/test") 606 c.Assert(err, jc.Satisfies, errors.IsNotFound) 607 } 608 609 // fakeAddClient is used to mock out the behavior of the real 610 // AddModel command. 611 type fakeAddClient struct { 612 owner string 613 cloudName string 614 cloudRegion string 615 cloudCredential names.CloudCredentialTag 616 config map[string]interface{} 617 err error 618 model base.ModelInfo 619 } 620 621 var _ controller.AddModelAPI = (*fakeAddClient)(nil) 622 623 func (*fakeAddClient) Close() error { 624 return nil 625 } 626 627 func (f *fakeAddClient) CreateModel(name, owner, cloudName, cloudRegion string, cloudCredential names.CloudCredentialTag, config map[string]interface{}) (base.ModelInfo, error) { 628 if f.err != nil { 629 return base.ModelInfo{}, f.err 630 } 631 f.owner = owner 632 f.cloudCredential = cloudCredential 633 f.cloudName = cloudName 634 f.cloudRegion = cloudRegion 635 f.config = config 636 return f.model, nil 637 } 638 639 // TODO(wallyworld) - improve this stub and add test asserts 640 type fakeCloudAPI struct { 641 controller.CloudAPI 642 gitjujutesting.Stub 643 authTypes []cloud.AuthType 644 credentials []names.CloudCredentialTag 645 } 646 647 func (c *fakeCloudAPI) DefaultCloud() (names.CloudTag, error) { 648 c.MethodCall(c, "DefaultCloud") 649 return names.NewCloudTag("aws"), c.NextErr() 650 } 651 652 func (c *fakeCloudAPI) Clouds() (map[names.CloudTag]cloud.Cloud, error) { 653 c.MethodCall(c, "Clouds") 654 return map[names.CloudTag]cloud.Cloud{ 655 names.NewCloudTag("aws"): { 656 Name: "aws", 657 AuthTypes: c.authTypes, 658 Regions: []cloud.Region{ 659 {Name: "us-east-1"}, 660 {Name: "us-west-1"}, 661 }, 662 }, 663 names.NewCloudTag("lxd"): {}, 664 }, c.NextErr() 665 } 666 667 func (c *fakeCloudAPI) Cloud(tag names.CloudTag) (cloud.Cloud, error) { 668 c.MethodCall(c, "Cloud", tag) 669 if tag.Id() != "aws" { 670 return cloud.Cloud{}, ¶ms.Error{Code: params.CodeNotFound} 671 } 672 return cloud.Cloud{ 673 Name: "aws", 674 Type: "ec2", 675 AuthTypes: c.authTypes, 676 Regions: []cloud.Region{ 677 {Name: "us-east-1"}, 678 {Name: "us-west-1"}, 679 }, 680 }, c.NextErr() 681 } 682 683 func (c *fakeCloudAPI) UserCredentials(user names.UserTag, cloud names.CloudTag) ([]names.CloudCredentialTag, error) { 684 c.MethodCall(c, "UserCredentials", user, cloud) 685 return c.credentials, c.NextErr() 686 } 687 688 func (c *fakeCloudAPI) AddCredential(tag string, credential cloud.Credential) error { 689 c.MethodCall(c, "AddCredential", tag, credential) 690 return c.NextErr() 691 } 692 693 type fakeProviderRegistry struct { 694 gitjujutesting.Stub 695 environs.ProviderRegistry 696 provider environs.EnvironProvider 697 } 698 699 func (r *fakeProviderRegistry) Provider(providerType string) (environs.EnvironProvider, error) { 700 r.MethodCall(r, "Provider", providerType) 701 return r.provider, r.NextErr() 702 } 703 704 type fakeProvider struct { 705 gitjujutesting.Stub 706 environs.EnvironProvider 707 detected *cloud.CloudCredential 708 } 709 710 func (p *fakeProvider) DetectCredentials() (*cloud.CloudCredential, error) { 711 p.MethodCall(p, "DetectCredentials") 712 return p.detected, p.NextErr() 713 } 714 715 func (p *fakeProvider) FinalizeCredential( 716 ctx environs.FinalizeCredentialContext, 717 args environs.FinalizeCredentialParams, 718 ) (*cloud.Credential, error) { 719 p.MethodCall(p, "FinalizeCredential", ctx, args) 720 out := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{}) 721 out.Label = "finalized" 722 return &out, p.NextErr() 723 } 724 725 func (p *fakeProvider) CredentialSchemas() map[cloud.AuthType]cloud.CredentialSchema { 726 return map[cloud.AuthType]cloud.CredentialSchema{cloud.EmptyAuthType: {}} 727 }