github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/cloud/add_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 "bytes" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "os" 12 "path" 13 "regexp" 14 "strings" 15 16 "github.com/juju/cmd" 17 "github.com/juju/cmd/cmdtesting" 18 "github.com/juju/errors" 19 "github.com/juju/loggo" 20 jujutesting "github.com/juju/testing" 21 jc "github.com/juju/testing/checkers" 22 gc "gopkg.in/check.v1" 23 "gopkg.in/yaml.v2" 24 25 jujucloud "github.com/juju/juju/cloud" 26 "github.com/juju/juju/cmd/juju/cloud" 27 "github.com/juju/juju/environs" 28 "github.com/juju/juju/jujuclient" 29 "github.com/juju/juju/testing" 30 ) 31 32 type addSuite struct { 33 jujutesting.IsolationSuite 34 } 35 36 var _ = gc.Suite(&addSuite{}) 37 38 func newFakeCloudMetadataStore() *fakeCloudMetadataStore { 39 var logger loggo.Logger 40 return &fakeCloudMetadataStore{CallMocker: jujutesting.NewCallMocker(logger)} 41 } 42 43 type fakeCloudMetadataStore struct { 44 *jujutesting.CallMocker 45 } 46 47 func (f *fakeCloudMetadataStore) ParseCloudMetadataFile(path string) (map[string]jujucloud.Cloud, error) { 48 results := f.MethodCall(f, "ParseCloudMetadataFile", path) 49 return results[0].(map[string]jujucloud.Cloud), jujutesting.TypeAssertError(results[1]) 50 } 51 52 func (f *fakeCloudMetadataStore) PublicCloudMetadata(searchPaths ...string) (result map[string]jujucloud.Cloud, fallbackUsed bool, _ error) { 53 results := f.MethodCall(f, "PublicCloudMetadata", searchPaths) 54 return results[0].(map[string]jujucloud.Cloud), results[1].(bool), jujutesting.TypeAssertError(results[2]) 55 } 56 57 func (f *fakeCloudMetadataStore) PersonalCloudMetadata() (map[string]jujucloud.Cloud, error) { 58 results := f.MethodCall(f, "PersonalCloudMetadata") 59 return results[0].(map[string]jujucloud.Cloud), jujutesting.TypeAssertError(results[1]) 60 } 61 62 func (f *fakeCloudMetadataStore) WritePersonalCloudMetadata(cloudsMap map[string]jujucloud.Cloud) error { 63 results := f.MethodCall(f, "WritePersonalCloudMetadata", cloudsMap) 64 return jujutesting.TypeAssertError(results[0]) 65 } 66 67 func (f *fakeCloudMetadataStore) ParseOneCloud(data []byte) (jujucloud.Cloud, error) { 68 results := f.MethodCall(f, "ParseOneCloud", data) 69 if len(results) != 2 { 70 fmt.Printf("ParseOneCloud()\n(%s)\n", string(data)) 71 return jujucloud.Cloud{}, errors.New("ParseOneCloud failed, not enough results") 72 } 73 return results[0].(jujucloud.Cloud), jujutesting.TypeAssertError(results[1]) 74 } 75 76 func (s *addSuite) TestAddBadArgs(c *gc.C) { 77 _, err := cmdtesting.RunCommand(c, cloud.NewAddCloudCommand(nil), "cloud", "cloud.yaml", "extra") 78 c.Assert(err, gc.ErrorMatches, `unrecognized args: \["extra"\]`) 79 } 80 81 var ( 82 homeStackYamlFile = ` 83 clouds: 84 homestack: 85 type: openstack 86 auth-types: [access-key] 87 endpoint: "http://homestack" 88 regions: 89 london: 90 endpoint: "http://london/1.0"` 91 92 homestackCloud = jujucloud.Cloud{ 93 Name: "homestack", 94 Type: "openstack", 95 AuthTypes: []jujucloud.AuthType{"userpass", "access-key"}, 96 Endpoint: "http://homestack", 97 Regions: []jujucloud.Region{ 98 { 99 Name: "london", 100 Endpoint: "http://london/1.0", 101 }, 102 }, 103 } 104 105 localhostYamlFile = ` 106 clouds: 107 localhost: 108 type: lxd` 109 110 awsYamlFile = ` 111 clouds: 112 aws: 113 type: ec2 114 auth-types: [access-key] 115 regions: 116 us-east-1: 117 endpoint: "https://us-east-1.aws.amazon.com/v1.2/"` 118 119 garageMaasYamlFile = ` 120 clouds: 121 garage-maas: 122 type: maas 123 auth-types: [oauth1] 124 endpoint: "http://garagemaas"` 125 126 garageMAASCloud = jujucloud.Cloud{ 127 Name: "garage-maas", 128 Type: "maas", 129 AuthTypes: []jujucloud.AuthType{"oauth1"}, 130 Endpoint: "http://garagemaas", 131 } 132 133 manualCloud = jujucloud.Cloud{ 134 Name: "manual", 135 Type: "manual", 136 AuthTypes: []jujucloud.AuthType{"manual"}, 137 Endpoint: "192.168.1.6", 138 } 139 ) 140 141 func homestackMetadata() map[string]jujucloud.Cloud { 142 return map[string]jujucloud.Cloud{"homestack": homestackCloud} 143 } 144 145 func (*addSuite) TestAddBadFilename(c *gc.C) { 146 fake := newFakeCloudMetadataStore() 147 badFileErr := errors.New("") 148 fake.Call("ParseCloudMetadataFile", "somefile.yaml").Returns(map[string]jujucloud.Cloud{}, badFileErr) 149 150 addCmd := cloud.NewAddCloudCommand(fake) 151 _, err := cmdtesting.RunCommand(c, addCmd, "cloud", "somefile.yaml") 152 c.Check(errors.Cause(err), gc.Equals, badFileErr) 153 } 154 155 func (*addSuite) TestAddBadCloudName(c *gc.C) { 156 fake := newFakeCloudMetadataStore() 157 fake.Call("ParseCloudMetadataFile", "testFile").Returns(map[string]jujucloud.Cloud{}, nil) 158 159 addCmd := cloud.NewAddCloudCommand(fake) 160 _, err := cmdtesting.RunCommand(c, addCmd, "cloud", "testFile") 161 c.Assert(err, gc.ErrorMatches, `cloud "cloud" not found in file .*`) 162 } 163 164 func (*addSuite) TestAddInvalidCloudName(c *gc.C) { 165 fake := newFakeCloudMetadataStore() 166 fake.Call("ParseCloudMetadataFile", "testFile").Returns(map[string]jujucloud.Cloud{}, nil) 167 168 addCmd := cloud.NewAddCloudCommand(fake) 169 _, err := cmdtesting.RunCommand(c, addCmd, "bad^cloud", "testFile") 170 c.Assert(err, gc.ErrorMatches, `cloud name "bad\^cloud" not valid`) 171 } 172 173 func (*addSuite) TestAddExisting(c *gc.C) { 174 fake := newFakeCloudMetadataStore() 175 176 cloudFile := prepareTestCloudYaml(c, homeStackYamlFile) 177 defer cloudFile.Close() 178 defer os.Remove(cloudFile.Name()) 179 180 mockCloud, err := jujucloud.ParseCloudMetadataFile(cloudFile.Name()) 181 c.Assert(err, jc.ErrorIsNil) 182 183 fake.Call("ParseCloudMetadataFile", cloudFile.Name()).Returns(mockCloud, nil) 184 fake.Call("PublicCloudMetadata", []string(nil)).Returns(map[string]jujucloud.Cloud{}, false, nil) 185 fake.Call("PersonalCloudMetadata").Returns(mockCloud, nil) 186 187 _, err = cmdtesting.RunCommand(c, cloud.NewAddCloudCommand(fake), "homestack", cloudFile.Name()) 188 c.Assert(err, gc.ErrorMatches, `"homestack" already exists; use --replace to replace this existing cloud`) 189 } 190 191 func (*addSuite) TestAddExistingReplace(c *gc.C) { 192 fake := newFakeCloudMetadataStore() 193 194 cloudFile := prepareTestCloudYaml(c, homeStackYamlFile) 195 defer cloudFile.Close() 196 defer os.Remove(cloudFile.Name()) 197 198 mockCloud, err := jujucloud.ParseCloudMetadataFile(cloudFile.Name()) 199 c.Assert(err, jc.ErrorIsNil) 200 201 fake.Call("ParseCloudMetadataFile", cloudFile.Name()).Returns(mockCloud, nil) 202 fake.Call("PersonalCloudMetadata").Returns(mockCloud, nil) 203 numCallsToWrite := fake.Call("WritePersonalCloudMetadata", mockCloud).Returns(nil) 204 205 _, err = cmdtesting.RunCommand(c, cloud.NewAddCloudCommand(fake), "homestack", cloudFile.Name(), "--replace") 206 c.Assert(err, jc.ErrorIsNil) 207 208 c.Check(numCallsToWrite(), gc.Equals, 1) 209 } 210 211 func (*addSuite) TestAddExistingPublic(c *gc.C) { 212 cloudFile := prepareTestCloudYaml(c, awsYamlFile) 213 defer cloudFile.Close() 214 defer os.Remove(cloudFile.Name()) 215 216 mockCloud, err := jujucloud.ParseCloudMetadataFile(cloudFile.Name()) 217 c.Assert(err, jc.ErrorIsNil) 218 219 fake := newFakeCloudMetadataStore() 220 fake.Call("ParseCloudMetadataFile", cloudFile.Name()).Returns(mockCloud, nil) 221 fake.Call("PublicCloudMetadata", []string(nil)).Returns(mockCloud, false, nil) 222 fake.Call("PersonalCloudMetadata").Returns(map[string]jujucloud.Cloud{}, nil) 223 224 _, err = cmdtesting.RunCommand(c, cloud.NewAddCloudCommand(fake), "aws", cloudFile.Name()) 225 c.Assert(err, gc.ErrorMatches, `"aws" is the name of a public cloud; use --replace to override this definition`) 226 } 227 228 func (*addSuite) TestAddExistingBuiltin(c *gc.C) { 229 cloudFile := prepareTestCloudYaml(c, localhostYamlFile) 230 defer cloudFile.Close() 231 defer os.Remove(cloudFile.Name()) 232 233 mockCloud, err := jujucloud.ParseCloudMetadataFile(cloudFile.Name()) 234 c.Assert(err, jc.ErrorIsNil) 235 236 fake := newFakeCloudMetadataStore() 237 fake.Call("ParseCloudMetadataFile", cloudFile.Name()).Returns(mockCloud, nil) 238 fake.Call("PublicCloudMetadata", []string(nil)).Returns(map[string]jujucloud.Cloud{}, false, nil) 239 fake.Call("PersonalCloudMetadata").Returns(map[string]jujucloud.Cloud{}, nil) 240 241 _, err = cmdtesting.RunCommand(c, cloud.NewAddCloudCommand(fake), "localhost", cloudFile.Name()) 242 c.Assert(err, gc.ErrorMatches, `"localhost" is the name of a built-in cloud; use --replace to override this definition`) 243 } 244 245 func (*addSuite) TestAddExistingPublicReplace(c *gc.C) { 246 cloudFile := prepareTestCloudYaml(c, awsYamlFile) 247 defer cloudFile.Close() 248 defer os.Remove(cloudFile.Name()) 249 250 mockCloud, err := jujucloud.ParseCloudMetadataFile(cloudFile.Name()) 251 c.Assert(err, jc.ErrorIsNil) 252 253 fake := newFakeCloudMetadataStore() 254 fake.Call("ParseCloudMetadataFile", cloudFile.Name()).Returns(mockCloud, nil) 255 fake.Call("PublicCloudMetadata", []string(nil)).Returns(mockCloud, false, nil) 256 fake.Call("PersonalCloudMetadata").Returns(map[string]jujucloud.Cloud{}, nil) 257 writeCall := fake.Call("WritePersonalCloudMetadata", mockCloud).Returns(nil) 258 259 _, err = cmdtesting.RunCommand(c, cloud.NewAddCloudCommand(fake), "aws", cloudFile.Name(), "--replace") 260 c.Assert(err, jc.ErrorIsNil) 261 262 c.Check(writeCall(), gc.Equals, 1) 263 } 264 265 func (*addSuite) TestAddNew(c *gc.C) { 266 cloudFile := prepareTestCloudYaml(c, garageMaasYamlFile) 267 defer cloudFile.Close() 268 defer os.Remove(cloudFile.Name()) 269 270 mockCloud, err := jujucloud.ParseCloudMetadataFile(cloudFile.Name()) 271 c.Assert(err, jc.ErrorIsNil) 272 273 fake := newFakeCloudMetadataStore() 274 fake.Call("ParseCloudMetadataFile", cloudFile.Name()).Returns(mockCloud, nil) 275 fake.Call("PublicCloudMetadata", []string(nil)).Returns(map[string]jujucloud.Cloud{}, false, nil) 276 fake.Call("PersonalCloudMetadata").Returns(map[string]jujucloud.Cloud{}, nil) 277 numCallsToWrite := fake.Call("WritePersonalCloudMetadata", mockCloud).Returns(nil) 278 279 _, err = cmdtesting.RunCommand(c, cloud.NewAddCloudCommand(fake), "garage-maas", cloudFile.Name()) 280 c.Assert(err, jc.ErrorIsNil) 281 c.Check(numCallsToWrite(), gc.Equals, 1) 282 } 283 284 func (*addSuite) TestAddNewInvalidAuthType(c *gc.C) { 285 fake := newFakeCloudMetadataStore() 286 fakeCloudYamlFile := ` 287 clouds: 288 fakecloud: 289 type: maas 290 auth-types: [oauth1, user-pass] 291 endpoint: "http://garagemaas"` 292 293 cloudFile := prepareTestCloudYaml(c, fakeCloudYamlFile) 294 defer cloudFile.Close() 295 defer os.Remove(cloudFile.Name()) 296 297 mockCloud, err := jujucloud.ParseCloudMetadataFile(cloudFile.Name()) 298 c.Assert(err, jc.ErrorIsNil) 299 300 fake.Call("ParseCloudMetadataFile", cloudFile.Name()).Returns(mockCloud, nil) 301 302 _, err = cmdtesting.RunCommand(c, cloud.NewAddCloudCommand(fake), "fakecloud", cloudFile.Name()) 303 c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`auth type "user-pass" not supported`)) 304 } 305 306 type fakeAddCloudAPI struct { 307 jujutesting.Stub 308 } 309 310 func (api *fakeAddCloudAPI) Close() error { 311 api.AddCall("Close", nil) 312 return nil 313 } 314 315 func (api *fakeAddCloudAPI) AddCloud(cloud jujucloud.Cloud) error { 316 api.AddCall("AddCloud", cloud) 317 return nil 318 } 319 320 func (api *fakeAddCloudAPI) AddCredential(tag string, credential jujucloud.Credential) error { 321 api.AddCall("AddCredential", tag, credential) 322 return nil 323 } 324 325 func (s *addSuite) setupControllerCloudScenario(c *gc.C) ( 326 string, *cloud.AddCloudCommand, *jujuclient.MemStore, *fakeAddCloudAPI, jujucloud.Credential, 327 ) { 328 cloudfile := prepareTestCloudYaml(c, garageMaasYamlFile) 329 s.AddCleanup(func(_ *gc.C) { 330 defer cloudfile.Close() 331 defer os.Remove(cloudfile.Name()) 332 }) 333 334 mockCloud, err := jujucloud.ParseCloudMetadataFile(cloudfile.Name()) 335 c.Assert(err, jc.ErrorIsNil) 336 337 fake := newFakeCloudMetadataStore() 338 fake.Call("ParseCloudMetadataFile", cloudfile.Name()).Returns(mockCloud, nil) 339 fake.Call("PublicCloudMetadata", []string(nil)).Returns(map[string]jujucloud.Cloud{}, false, nil) 340 fake.Call("PersonalCloudMetadata").Returns(map[string]jujucloud.Cloud{}, nil) 341 fake.Call("WritePersonalCloudMetadata", mockCloud).Returns(nil) 342 343 store := jujuclient.NewMemStore() 344 store.Controllers["mycontroller"] = jujuclient.ControllerDetails{} 345 store.Accounts["mycontroller"] = jujuclient.AccountDetails{User: "fred"} 346 cred := jujucloud.NewCredential(jujucloud.OAuth1AuthType, map[string]string{ 347 "maas-oauth": "auth:token", 348 }) 349 store.Credentials["garage-maas"] = jujucloud.CloudCredential{ 350 AuthCredentials: map[string]jujucloud.Credential{"default": cred}, 351 } 352 353 api := &fakeAddCloudAPI{} 354 cmd := cloud.NewAddCloudCommandForTest(fake, store, func() (cloud.AddCloudAPI, error) { 355 return api, nil 356 }) 357 return cloudfile.Name(), cmd, store, api, cred 358 } 359 360 func (s *addSuite) TestAddToController(c *gc.C) { 361 cloudFileName, cmd, _, api, cred := s.setupControllerCloudScenario(c) 362 _, err := cmdtesting.RunCommand( 363 c, cmd, "garage-maas", cloudFileName, "-c", "mycontroller") 364 c.Assert(err, jc.ErrorIsNil) 365 api.CheckCallNames(c, "AddCloud", "AddCredential") 366 api.CheckCall(c, 0, "AddCloud", jujucloud.Cloud{ 367 Name: "garage-maas", 368 Type: "maas", 369 Description: "Metal As A Service", 370 AuthTypes: jujucloud.AuthTypes{"oauth1"}, 371 Endpoint: "http://garagemaas", 372 }) 373 api.CheckCall(c, 1, "AddCredential", "cloudcred-garage-maas_fred_default", cred) 374 } 375 376 func (s *addSuite) TestAddToControllerBadController(c *gc.C) { 377 cloudFileName, cmd, store, _, _ := s.setupControllerCloudScenario(c) 378 store.Credentials = nil 379 380 _, err := cmdtesting.RunCommand( 381 c, cmd, "garage-maas", cloudFileName, "-c", "badcontroller") 382 c.Assert(err, gc.ErrorMatches, `controller badcontroller not found`) 383 } 384 385 func (s *addSuite) TestAddToControllerMissingCredential(c *gc.C) { 386 cloudFileName, cmd, store, _, _ := s.setupControllerCloudScenario(c) 387 store.Credentials = nil 388 389 _, err := cmdtesting.RunCommand( 390 c, cmd, "garage-maas", cloudFileName, "-c", "mycontroller") 391 c.Assert(err, gc.ErrorMatches, `loading credentials: credentials for cloud garage-maas not found`) 392 } 393 394 func (s *addSuite) TestAddToControllerAmbiguousCredential(c *gc.C) { 395 cloudFileName, cmd, store, _, cred := s.setupControllerCloudScenario(c) 396 store.Credentials["garage-maas"].AuthCredentials["another"] = cred 397 398 _, err := cmdtesting.RunCommand( 399 c, cmd, "garage-maas", cloudFileName, "-c", "mycontroller") 400 errMsg := strings.Replace(err.Error(), "\n", "", -1) 401 c.Assert(errMsg, gc.Matches, `.*more than one credential is available.*`) 402 } 403 404 func (*addSuite) TestInteractive(c *gc.C) { 405 command := cloud.NewAddCloudCommand(nil) 406 err := cmdtesting.InitCommand(command, nil) 407 c.Assert(err, jc.ErrorIsNil) 408 409 out := &bytes.Buffer{} 410 411 ctx := &cmd.Context{ 412 Dir: c.MkDir(), 413 Stdout: out, 414 Stderr: ioutil.Discard, 415 Stdin: &bytes.Buffer{}, 416 } 417 err = command.Run(ctx) 418 c.Check(errors.Cause(err), gc.Equals, io.EOF) 419 420 c.Assert(out.String(), gc.Equals, ""+ 421 "Cloud Types\n"+ 422 " lxd\n"+ 423 " maas\n"+ 424 " manual\n"+ 425 " openstack\n"+ 426 " vsphere\n"+ 427 "\n"+ 428 "Select cloud type: \n", 429 ) 430 } 431 432 func (*addSuite) TestInteractiveMaas(c *gc.C) { 433 fake := newFakeCloudMetadataStore() 434 fake.Call("PublicCloudMetadata", []string(nil)).Returns(map[string]jujucloud.Cloud{}, false, nil) 435 fake.Call("PersonalCloudMetadata").Returns(map[string]jujucloud.Cloud{}, nil) 436 const expectedYAMLarg = "" + 437 "auth-types:\n" + 438 "- oauth1\n" + 439 "endpoint: http://mymaas\n" 440 fake.Call("ParseOneCloud", []byte(expectedYAMLarg)).Returns(garageMAASCloud, nil) 441 m1Cloud := garageMAASCloud 442 m1Cloud.Name = "m1" 443 m1Metadata := map[string]jujucloud.Cloud{"m1": m1Cloud} 444 numCallsToWrite := fake.Call("WritePersonalCloudMetadata", m1Metadata).Returns(nil) 445 446 command := cloud.NewAddCloudCommandForTest(fake, jujuclient.NewMemStore(), nil) 447 err := cmdtesting.InitCommand(command, nil) 448 c.Assert(err, jc.ErrorIsNil) 449 450 ctx := &cmd.Context{ 451 Stdout: ioutil.Discard, 452 Stderr: ioutil.Discard, 453 Stdin: strings.NewReader("" + 454 /* Select cloud type: */ "maas\n" + 455 /* Enter a name for the cloud: */ "m1\n" + 456 /* Enter the controller's hostname or IP address: */ "http://mymaas\n", 457 ), 458 } 459 460 err = command.Run(ctx) 461 c.Assert(err, jc.ErrorIsNil) 462 463 c.Check(numCallsToWrite(), gc.Equals, 1) 464 } 465 466 func (*addSuite) TestInteractiveManual(c *gc.C) { 467 manCloud := manualCloud 468 manCloud.Name = "man" 469 fake := newFakeCloudMetadataStore() 470 fake.Call("PublicCloudMetadata", []string(nil)).Returns(map[string]jujucloud.Cloud{}, false, nil) 471 fake.Call("PersonalCloudMetadata").Returns(map[string]jujucloud.Cloud{}, nil) 472 fake.Call("ParseOneCloud", []byte("endpoint: 192.168.1.6\n")).Returns(manCloud, nil) 473 manMetadata := map[string]jujucloud.Cloud{"man": manCloud} 474 numCallsToWrite := fake.Call("WritePersonalCloudMetadata", manMetadata).Returns(nil) 475 476 command := cloud.NewAddCloudCommandForTest(fake, jujuclient.NewMemStore(), nil) 477 err := cmdtesting.InitCommand(command, nil) 478 c.Assert(err, jc.ErrorIsNil) 479 480 ctx := &cmd.Context{ 481 Stdout: ioutil.Discard, 482 Stderr: ioutil.Discard, 483 Stdin: strings.NewReader("" + 484 /* Select cloud type: */ "manual\n" + 485 /* Enter a name for the cloud: */ "man\n" + 486 /* Enter the controller's hostname or IP address: */ "192.168.1.6\n", 487 ), 488 } 489 490 err = command.Run(ctx) 491 c.Check(err, jc.ErrorIsNil) 492 493 c.Check(numCallsToWrite(), gc.Equals, 1) 494 } 495 496 func (*addSuite) TestInteractiveVSphere(c *gc.C) { 497 fake := newFakeCloudMetadataStore() 498 fake.Call("PublicCloudMetadata", []string(nil)).Returns(map[string]jujucloud.Cloud{}, false, nil) 499 fake.Call("PersonalCloudMetadata").Returns(map[string]jujucloud.Cloud{}, nil) 500 vsphereCloud := jujucloud.Cloud{ 501 Name: "mvs", 502 Type: "vsphere", 503 AuthTypes: []jujucloud.AuthType{"userpass", "access-key"}, 504 Endpoint: "192.168.1.6", 505 Regions: []jujucloud.Region{ 506 { 507 Name: "foo", 508 Endpoint: "192.168.1.6", 509 }, 510 { 511 Name: "bar", 512 Endpoint: "192.168.1.6", 513 }, 514 }, 515 } 516 const expectedYAMLarg = "" + 517 "auth-types:\n" + 518 "- userpass\n" + 519 "endpoint: 192.168.1.6\n" + 520 "regions:\n" + 521 " bar: {}\n" + 522 " foo: {}\n" 523 fake.Call("ParseOneCloud", []byte(expectedYAMLarg)).Returns(vsphereCloud, nil) 524 vsphereMetadata := map[string]jujucloud.Cloud{"mvs": vsphereCloud} 525 numCallsToWrite := fake.Call("WritePersonalCloudMetadata", vsphereMetadata).Returns(nil) 526 527 command := cloud.NewAddCloudCommandForTest(fake, jujuclient.NewMemStore(), nil) 528 err := cmdtesting.InitCommand(command, nil) 529 c.Assert(err, jc.ErrorIsNil) 530 531 var stdout bytes.Buffer 532 ctx := &cmd.Context{ 533 Stdout: &stdout, 534 Stderr: ioutil.Discard, 535 Stdin: strings.NewReader("" + 536 /* Select cloud type: */ "vsphere\n" + 537 /* Enter a name for the cloud: */ "mvs\n" + 538 /* Enter the vCenter address or URL: */ "192.168.1.6\n" + 539 /* Enter datacenter name: */ "foo\n" + 540 /* Enter another datacenter? (y/N): */ "y\n" + 541 /* Enter datacenter name: */ "bar\n" + 542 /* Enter another datacenter? (y/N): */ "n\n", 543 ), 544 } 545 546 err = command.Run(ctx) 547 c.Check(err, jc.ErrorIsNil) 548 549 c.Check(numCallsToWrite(), gc.Equals, 1) 550 c.Check(stdout.String(), gc.Matches, "(.|\n)*"+` 551 Select cloud type: 552 Enter a name for your vsphere cloud: 553 Enter the vCenter address or URL: 554 Enter datacenter name: 555 Enter another datacenter\? \(y/N\): 556 Enter datacenter name: 557 Enter another datacenter\? \(y/N\): 558 `[1:]+"(.|\n)*") 559 } 560 561 func (*addSuite) TestInteractiveExistingNameOverride(c *gc.C) { 562 manualCloud := manualCloud 563 manualCloud.Name = "homestack" 564 565 fake := newFakeCloudMetadataStore() 566 fake.Call("PublicCloudMetadata", []string(nil)).Returns(map[string]jujucloud.Cloud{}, false, nil) 567 fake.Call("PersonalCloudMetadata").Returns(homestackMetadata(), nil) 568 manMetadata := map[string]jujucloud.Cloud{"homestack": manualCloud} 569 fake.Call("ParseOneCloud", []byte("endpoint: 192.168.1.6\n")).Returns(manualCloud, nil) 570 numCallsToWrite := fake.Call("WritePersonalCloudMetadata", manMetadata).Returns(nil) 571 572 command := cloud.NewAddCloudCommandForTest(fake, jujuclient.NewMemStore(), nil) 573 err := cmdtesting.InitCommand(command, nil) 574 c.Assert(err, jc.ErrorIsNil) 575 576 ctx := &cmd.Context{ 577 Stdout: ioutil.Discard, 578 Stderr: ioutil.Discard, 579 Stdin: strings.NewReader("" + 580 /* Select cloud type: */ "manual\n" + 581 /* Enter a name for the cloud: */ "homestack\n" + 582 /* Do you want to replace that definition? */ "y\n" + 583 /* Enter the controller's hostname or IP address: */ "192.168.1.6\n", 584 ), 585 } 586 587 err = command.Run(ctx) 588 c.Check(err, jc.ErrorIsNil) 589 590 c.Check(numCallsToWrite(), gc.Equals, 1) 591 } 592 593 func (*addSuite) TestInteractiveExistingNameNoOverride(c *gc.C) { 594 fake := newFakeCloudMetadataStore() 595 fake.Call("PublicCloudMetadata", []string(nil)).Returns(map[string]jujucloud.Cloud{}, false, nil) 596 fake.Call("PersonalCloudMetadata").Returns(homestackMetadata(), nil) 597 homestack2Cloud := jujucloud.Cloud{ 598 Name: "homestack2", 599 Type: "manual", 600 Endpoint: "192.168.1.6", 601 } 602 fake.Call("ParseOneCloud", []byte("endpoint: 192.168.1.6\n")).Returns(homestack2Cloud, nil) 603 compoundCloudMetadata := map[string]jujucloud.Cloud{ 604 "homestack": homestackCloud, 605 "homestack2": homestack2Cloud, 606 } 607 numCallsToWrite := fake.Call("WritePersonalCloudMetadata", compoundCloudMetadata).Returns(nil) 608 609 command := cloud.NewAddCloudCommandForTest(fake, jujuclient.NewMemStore(), nil) 610 err := cmdtesting.InitCommand(command, nil) 611 c.Assert(err, jc.ErrorIsNil) 612 613 var out bytes.Buffer 614 ctx := &cmd.Context{ 615 Stdout: &out, 616 Stderr: ioutil.Discard, 617 Stdin: strings.NewReader("" + 618 /* Select cloud type: */ "manual\n" + 619 /* Enter a name for the cloud: */ "homestack" + "\n" + 620 /* Do you want to replace that definition? (y/N): */ "n\n" + 621 /* Enter a name for the cloud: */ "homestack2" + "\n" + 622 /* Enter the controller's hostname or IP address: */ "192.168.1.6" + "\n", 623 ), 624 } 625 626 err = command.Run(ctx) 627 c.Log(out.String()) 628 c.Assert(err, jc.ErrorIsNil) 629 630 c.Check(numCallsToWrite(), gc.Equals, 1) 631 } 632 633 func (*addSuite) TestInteractiveAddCloud_PromptForNameIsCorrect(c *gc.C) { 634 var out bytes.Buffer 635 ctx := &cmd.Context{ 636 Stdout: &out, 637 Stderr: ioutil.Discard, 638 Stdin: strings.NewReader("" + 639 /* Select cloud type: */ "manual\n", 640 ), 641 } 642 643 fake := newFakeCloudMetadataStore() 644 fake.Call("PublicCloudMetadata", []string(nil)).Returns(map[string]jujucloud.Cloud{}, false, nil) 645 fake.Call("PersonalCloudMetadata").Returns(homestackMetadata(), nil) 646 647 command := cloud.NewAddCloudCommand(fake) 648 // Running the command will return an error because we only give 649 // enough input to get to the prompt we care about checking. This 650 // test ignores this error. 651 err := command.Run(ctx) 652 c.Assert(errors.Cause(err), gc.Equals, io.EOF) 653 654 c.Check(out.String(), gc.Matches, "(?s).+Enter a name for your manual cloud: .*") 655 } 656 657 func (*addSuite) TestSpecifyingjujucloudThroughFlag_CorrectlySetsMemberVar(c *gc.C) { 658 command := cloud.NewAddCloudCommand(nil) 659 runCmd := func() { 660 cmdtesting.RunCommand(c, command, "garage-maas", "-f", "fake.yaml") 661 } 662 c.Assert(runCmd, gc.PanicMatches, "runtime error: invalid memory address or nil pointer dereference") 663 //c.Check(command.jujucloud, gc.Equals, "fake.yaml") 664 } 665 666 func (*addSuite) TestSpecifyingjujucloudThroughFlagAndArgument_Errors(c *gc.C) { 667 command := cloud.NewAddCloudCommand(nil) 668 _, err := cmdtesting.RunCommand(c, command, "garage-maas", "-f", "fake.yaml", "foo.yaml") 669 c.Check(err, gc.ErrorMatches, "cannot specify cloud file with option and argument") 670 } 671 672 func (*addSuite) TestValidateGoodCloudFile(c *gc.C) { 673 data := ` 674 clouds: 675 foundations: 676 type: maas 677 auth-types: [oauth1] 678 endpoint: "http://10.245.31.100/MAAS"` 679 680 cloudFile := prepareTestCloudYaml(c, data) 681 defer cloudFile.Close() 682 defer os.Remove(cloudFile.Name()) 683 684 var logWriter loggo.TestWriter 685 writerName := "add_cloud_tests_writer" 686 c.Assert(loggo.RegisterWriter(writerName, &logWriter), jc.ErrorIsNil) 687 defer func() { 688 loggo.RemoveWriter(writerName) 689 logWriter.Clear() 690 }() 691 692 mockCloud, err := jujucloud.ParseCloudMetadataFile(cloudFile.Name()) 693 c.Assert(err, jc.ErrorIsNil) 694 695 fake := newFakeCloudMetadataStore() 696 fake.Call("ParseCloudMetadataFile", cloudFile.Name()).Returns(mockCloud, nil) 697 fake.Call("PersonalCloudMetadata").Returns(map[string]jujucloud.Cloud{}, nil) 698 fake.Call("PublicCloudMetadata", []string(nil)).Returns(map[string]jujucloud.Cloud{}, false, nil) 699 fake.Call("WritePersonalCloudMetadata", mockCloud).Returns(nil) 700 701 command := cloud.NewAddCloudCommand(fake) 702 703 _, err = cmdtesting.RunCommand(c, command, "foundations", cloudFile.Name()) 704 c.Check(err, jc.ErrorIsNil) 705 706 c.Check(logWriter.Log(), jc.LogMatches, []jc.SimpleMessage{}) 707 } 708 709 func (*addSuite) TestValidateBadjujucloud(c *gc.C) { 710 data := ` 711 clouds: 712 foundations: 713 type: maas 714 auth-typs: [oauth1] 715 endpoint: "http://10.245.31.100/MAAS"` 716 717 cloudFile := prepareTestCloudYaml(c, data) 718 defer cloudFile.Close() 719 defer os.Remove(cloudFile.Name()) 720 721 mockCloud, err := jujucloud.ParseCloudMetadataFile(cloudFile.Name()) 722 c.Assert(err, jc.ErrorIsNil) 723 724 fake := newFakeCloudMetadataStore() 725 fake.Call("ParseCloudMetadataFile", cloudFile.Name()).Returns(mockCloud, nil) 726 fake.Call("PersonalCloudMetadata").Returns(map[string]jujucloud.Cloud{}, nil) 727 fake.Call("PublicCloudMetadata", []string(nil)).Returns(map[string]jujucloud.Cloud{}, false, nil) 728 fake.Call("WritePersonalCloudMetadata", mockCloud).Returns(nil) 729 730 command := cloud.NewAddCloudCommand(fake) 731 732 var logWriter loggo.TestWriter 733 writerName := "add_cloud_tests_writer" 734 c.Assert(loggo.RegisterWriter(writerName, &logWriter), jc.ErrorIsNil) 735 defer func() { 736 loggo.RemoveWriter(writerName) 737 logWriter.Clear() 738 }() 739 740 _, err = cmdtesting.RunCommand(c, command, "foundations", cloudFile.Name()) 741 c.Check(err, jc.ErrorIsNil) 742 743 c.Check(logWriter.Log(), jc.LogMatches, []jc.SimpleMessage{ 744 { 745 Level: loggo.WARNING, 746 Message: `property "auth-typs" is invalid. Perhaps you mean "auth-types".`, 747 }, 748 }) 749 } 750 751 func prepareTestCloudYaml(c *gc.C, data string) *os.File { 752 jujucloud, err := ioutil.TempFile("", "jujucloud") 753 c.Assert(err, jc.ErrorIsNil) 754 755 err = ioutil.WriteFile(jujucloud.Name(), []byte(data), 0644) 756 if err != nil { 757 jujucloud.Close() 758 os.Remove(jujucloud.Name()) 759 c.Fatal(err.Error()) 760 } 761 762 return jujucloud 763 } 764 765 func (s *addSuite) TestInvalidCredentialMessage(c *gc.C) { 766 fake := newFakeCloudMetadataStore() 767 fake.Call("PublicCloudMetadata", []string(nil)).Returns(map[string]jujucloud.Cloud{}, false, nil) 768 fake.Call("PersonalCloudMetadata").Returns(map[string]jujucloud.Cloud{}, nil) 769 const expectedYAMLarg = "" + 770 "auth-types:\n" + 771 "- oauth1\n" + 772 "endpoint: http://mymaas\n" 773 fake.Call("ParseOneCloud", []byte(expectedYAMLarg)).Returns(garageMAASCloud, nil) 774 m1Cloud := garageMAASCloud 775 m1Cloud.Name = "m1" 776 m1Metadata := map[string]jujucloud.Cloud{"m1": m1Cloud} 777 fake.Call("WritePersonalCloudMetadata", m1Metadata).Returns(nil) 778 779 command := cloud.NewAddCloudCommandForTest(fake, jujuclient.NewMemStore(), nil) 780 command.Ping = func(environs.EnvironProvider, string) error { 781 return command.CloudCallCtx.InvalidateCredential("running test") 782 } 783 784 ctx := cmdtesting.Context(c) 785 ctx.Stdin = strings.NewReader("" + 786 /* Select cloud type: */ "maas\n" + 787 /* Enter a name for the cloud: */ "m1\n" + 788 /* Enter the controller's hostname or IP address: */ "http://mymaas\n", 789 ) 790 791 err := command.Run(ctx) 792 c.Assert(err, jc.ErrorIsNil) 793 c.Assert(cmdtesting.Stderr(ctx), jc.Contains, "Cloud credential is not accepted by cloud provider: running test") 794 } 795 796 func (*addSuite) TestInteractiveOpenstackNoCloudCert(c *gc.C) { 797 myOpenstack := jujucloud.Cloud{ 798 Name: "os1", 799 Type: "openstack", 800 AuthTypes: []jujucloud.AuthType{"userpass", "access-key"}, 801 Endpoint: "http://myopenstack", 802 Regions: []jujucloud.Region{ 803 { 804 Name: "regionone", 805 Endpoint: "http://boston/1.0", 806 }, 807 }, 808 } 809 810 var expectedYAMLarg = "" + 811 "auth-types:\n" + 812 "- userpass\n" + 813 "- access-key\n" + 814 "certfilename: \"\"\n" + 815 "endpoint: http://myopenstack\n" + 816 "regions:\n" + 817 " regionone:\n" + 818 " endpoint: http://boston/1.0\n" 819 820 var input = "" + 821 /* Select cloud type: */ "openstack\n" + 822 /* Enter a name for your openstack cloud: */ "os1\n" + 823 /* Enter the API endpoint url for the cloud []: */ "http://myopenstack\n" + 824 /* Enter ta path to the CA certificate for your cloud if one is required to access it. (optional) [none] */ "\n" + 825 /* Select one or more auth types separated by commas: */ "userpass,access-key\n" + 826 /* Enter region name: */ "regionone\n" + 827 /* Enter the API endpoint url for the region [use cloud api url]: */ "http://boston/1.0\n" + 828 /* Enter another region? (Y/n): */ "n\n" 829 830 testInteractiveOpenstack(c, myOpenstack, expectedYAMLarg, input, "", "") 831 } 832 833 // Note: The first %s is filled with a string containing a newline 834 var expectedCloudYAMLarg = ` 835 auth-types: 836 - userpass 837 - access-key 838 %scertfilename: %s 839 endpoint: http://myopenstack 840 regions: 841 regionone: 842 endpoint: "" 843 `[1:] 844 845 func (*addSuite) TestInteractiveOpenstackCloudCertFail(c *gc.C) { 846 fakeCertDir := c.MkDir() 847 fakeCertFilename := path.Join(fakeCertDir, "cloudcert.crt") 848 849 invalidCertFilename := path.Join(fakeCertDir, "invalid.crt") 850 ioutil.WriteFile(invalidCertFilename, []byte("testing certification validation"), 0666) 851 852 input := fmt.Sprintf(""+ 853 /* Select cloud type: */ "openstack\n"+ 854 /* Enter a name for your openstack cloud: */ "os1\n"+ 855 /* Enter the API endpoint url for the cloud []: */ "http://myopenstack\n"+ 856 /* Enter a path to the CA certificate for your cloud if one is required to access it. (optional) [none] */ "%s\n"+ 857 /* Enter a path to the CA certificate for your cloud if one is required to access it. (optional) [none] */ "%s\n"+ 858 /* Select one or more auth types separated by commas: */ "userpass,access-key\n"+ 859 /* Enter region name: */ "regionone\n"+ 860 /* Enter the API endpoint url for the region [use cloud api url]: */ "\n"+ 861 /* Enter another region? (Y/n): */ "n\n", invalidCertFilename, fakeCertFilename) 862 863 testInteractiveOpenstackCloudCert(c, fakeCertFilename, input, 864 fmt.Sprintf("Successfully read CA Certificate from %s\n", fakeCertFilename), 865 fmt.Sprintf("Can't validate CA Certificate %s: no certificates found", invalidCertFilename)) 866 } 867 868 func (*addSuite) TestInteractiveOpenstackCloudCertReadFailRetry(c *gc.C) { 869 var invalidCertFilename = "/tmp/no-such-file" 870 fakeCertDir := c.MkDir() 871 fakeCertFilename := path.Join(fakeCertDir, "cloudcert.crt") 872 873 input := fmt.Sprintf(""+ 874 /* Select cloud type: */ "openstack\n"+ 875 /* Enter a name for your openstack cloud: */ "os1\n"+ 876 /* Enter the API endpoint url for the cloud []: */ "http://myopenstack\n"+ 877 /* Enter a path to the CA certificate for your cloud if one is required to access it. (optional) [none] */ "%s\n"+ 878 /* Enter a path to the CA certificate for your cloud if one is required to access it. (optional) [none] */ "%s\n"+ 879 /* Select one or more auth types separated by commas: */ "userpass,access-key\n"+ 880 /* Enter region name: */ "regionone\n"+ 881 /* Enter the API endpoint url for the region [use cloud api url]: */ "\n"+ 882 /* Enter another region? (Y/n): */ "n\n", invalidCertFilename, fakeCertFilename) 883 884 testInteractiveOpenstackCloudCert(c, 885 fakeCertFilename, 886 input, 887 fmt.Sprintf("Successfully read CA Certificate from %s\n", fakeCertFilename), 888 fmt.Sprintf("Can't validate CA Certificate file: open %s:", invalidCertFilename), 889 ) 890 } 891 892 func (*addSuite) TestInteractiveOpenstackCloudCert(c *gc.C) { 893 fakeCertFilename := path.Join(c.MkDir(), "cloudcert.crt") 894 895 input := fmt.Sprintf(""+ 896 /* Select cloud type: */ "openstack\n"+ 897 /* Enter a name for your openstack cloud: */ "os1\n"+ 898 /* Enter the API endpoint url for the cloud []: */ "http://myopenstack\n"+ 899 /* Enter a path to the CA certificate for your cloud if one is required to access it. (optional) [none] */ "%s\n"+ 900 /* Select one or more auth types separated by commas: */ "userpass,access-key\n"+ 901 /* Enter region name: */ "regionone\n"+ 902 /* Enter the API endpoint url for the region [use cloud api url]: */ "\n"+ 903 /* Enter another region? (Y/n): */ "n\n", fakeCertFilename) 904 905 testInteractiveOpenstackCloudCert(c, fakeCertFilename, input, 906 fmt.Sprintf("Successfully read CA Certificate from %s\n", fakeCertFilename), "") 907 } 908 909 type addOpenStackSuite struct { 910 jujutesting.IsolationSuite 911 } 912 913 var _ = gc.Suite(&addOpenStackSuite{}) 914 915 func (s *addOpenStackSuite) TearDownTest(c *gc.C) { 916 s.IsolationSuite.TearDownTest(c) 917 os.Unsetenv("OS_CACERT") 918 os.Unsetenv("OS_AUTH_URL") 919 } 920 921 func (*addOpenStackSuite) TestInteractiveOpenstackCloudCertEnvVar(c *gc.C) { 922 fakeCertFilename := path.Join(c.MkDir(), "cloudcert.crt") 923 924 input := "" + 925 /* Select cloud type: */ "openstack\n" + 926 /* Enter a name for your openstack cloud: */ "os1\n" + 927 /* Enter the API endpoint url for the cloud [$OS_AUTH_URL]: */ "\n" + 928 /* Enter a path to the CA certificate for your cloud if one is required to access it. (optional) [$OS_CACERT] */ "\n" + 929 /* Select one or more auth types separated by commas: */ "userpass,access-key\n" + 930 /* Enter region name: */ "regionone\n" + 931 /* Enter the API endpoint url for the region [use cloud api url]: */ "\n" + 932 /* Enter another region? (Y/n): */ "n\n" 933 934 os.Setenv("OS_CACERT", fakeCertFilename) 935 os.Setenv("OS_AUTH_URL", "http://myopenstack") 936 937 testInteractiveOpenstackCloudCert(c, fakeCertFilename, input, 938 fmt.Sprintf("Successfully read CA Certificate from %s\n", fakeCertFilename), "") 939 } 940 941 func testInteractiveOpenstackCloudCert(c *gc.C, fakeCertFilename, input, addStdErrMsg, stdOutMsg string) { 942 fakeCert := testing.CACert 943 ioutil.WriteFile(fakeCertFilename, []byte(fakeCert), 0666) 944 945 myOpenstack := jujucloud.Cloud{ 946 Name: "os1", 947 Type: "openstack", 948 AuthTypes: []jujucloud.AuthType{"userpass", "access-key"}, 949 Endpoint: "http://myopenstack", 950 Regions: []jujucloud.Region{ 951 { 952 Name: "regionone", 953 Endpoint: "http://myopenstack", 954 }, 955 }, 956 CACertificates: []string{fakeCert}, 957 } 958 959 fakeCertMap := map[string]interface{}{ 960 "ca-certificates": []string{fakeCert}, 961 } 962 fakeCertYaml, err := yaml.Marshal(fakeCertMap) 963 c.Assert(err, gc.IsNil) 964 965 expectedYAMLarg := fmt.Sprintf(expectedCloudYAMLarg, fakeCertYaml, fakeCertFilename) 966 967 testInteractiveOpenstack(c, myOpenstack, expectedYAMLarg, input, addStdErrMsg, stdOutMsg) 968 } 969 970 func testInteractiveOpenstack(c *gc.C, myOpenstack jujucloud.Cloud, expectedYAMLarg, input, addStdErrMsg, stdOutMsg string) { 971 fake := newFakeCloudMetadataStore() 972 fake.Call("PublicCloudMetadata", []string(nil)).Returns(map[string]jujucloud.Cloud{}, false, nil) 973 fake.Call("PersonalCloudMetadata").Returns(map[string]jujucloud.Cloud{}, nil) 974 975 fake.Call("ParseOneCloud", []byte(expectedYAMLarg)).Returns(myOpenstack, nil) 976 m1Metadata := map[string]jujucloud.Cloud{"os1": myOpenstack} 977 numCallsToWrite := fake.Call("WritePersonalCloudMetadata", m1Metadata).Returns(nil) 978 979 command := cloud.NewAddCloudCommandForTest(fake, jujuclient.NewMemStore(), nil) 980 err := cmdtesting.InitCommand(command, nil) 981 c.Assert(err, jc.ErrorIsNil) 982 983 ctx := cmdtesting.Context(c) 984 ctx.Stdin = strings.NewReader(input) 985 986 err = command.Run(ctx) 987 988 if err != nil { 989 fmt.Printf("expectedYAML\n(%s)\n", expectedYAMLarg) 990 } 991 992 c.Check(err, jc.ErrorIsNil) 993 var output = addStdErrMsg + 994 "Cloud \"os1\" successfully added\n" + 995 "\n" + 996 "You will need to add credentials for this cloud (`juju add-credential os1`)\n" + 997 "before creating a controller (`juju bootstrap os1`).\n" 998 c.Assert(cmdtesting.Stderr(ctx), jc.Contains, output) 999 c.Assert(cmdtesting.Stdout(ctx), jc.Contains, stdOutMsg) 1000 1001 c.Check(numCallsToWrite(), gc.Equals, 1) 1002 }