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 = &params.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(&params.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  		&params.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{}, &params.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  }