1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
4
package controller_test
11
"github.com/juju/errors"
12
jc "github.com/juju/testing/checkers"
13
"github.com/juju/utils"
14
gc "gopkg.in/check.v1"
17
"github.com/juju/juju/api"
18
"github.com/juju/juju/apiserver/params"
19
"github.com/juju/juju/cloud"
20
"github.com/juju/juju/cmd/juju/controller"
21
"github.com/juju/juju/jujuclient"
22
"github.com/juju/juju/jujuclient/jujuclienttesting"
23
_ "github.com/juju/juju/provider/ec2"
24
"github.com/juju/juju/testing"
25
"gopkg.in/juju/names.v2"
28
type addSuite struct {
29
testing.FakeJujuXDGDataHomeSuite
30
fakeAddModelAPI *fakeAddClient
31
fakeCloundAPI *fakeCloudAPI
32
store *jujuclienttesting.MemStore
35
var _ = gc.Suite(&addSuite{})
37
func (s *addSuite) SetUpTest(c *gc.C) {
38
s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
39
s.fakeAddModelAPI = &fakeAddClient{
40
model: params.ModelInfo{
42
UUID: "fake-model-uuid",
43
OwnerTag: "ignored-for-now",
46
s.fakeCloundAPI = &fakeCloudAPI{}
48
// Set up the current controller, and write just enough info
49
// so we don't try to refresh
50
controllerName := "test-master"
51
s.store = jujuclienttesting.NewMemStore()
52
s.store.CurrentControllerName = controllerName
53
s.store.Controllers[controllerName] = jujuclient.ControllerDetails{}
54
s.store.Accounts[controllerName] = jujuclient.AccountDetails{
57
s.store.Credentials["aws"] = cloud.CloudCredential{
58
AuthCredentials: map[string]cloud.Credential{
59
"secrets": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
61
"secret-key": "sekret",
67
type fakeAPIConnection struct {
71
func (*fakeAPIConnection) Close() error {
75
func (s *addSuite) run(c *gc.C, args ...string) (*cmd.Context, error) {
76
command, _ := controller.NewAddModelCommandForTest(&fakeAPIConnection{}, s.fakeAddModelAPI, s.fakeCloundAPI, s.store)
77
return testing.RunCommand(c, command, args...)
80
func (s *addSuite) TestInit(c *gc.C) {
81
modelNameErr := "%q is not a valid name: model names may only contain lowercase letters, digits and hyphens"
82
for i, test := range []struct {
87
values map[string]interface{}
90
err: "model name is required",
92
args: []string{"new-model"},
98
args: []string{"new model"},
99
err: fmt.Sprintf(modelNameErr, "new model"),
101
args: []string{"newModel"},
102
err: fmt.Sprintf(modelNameErr, "newModel"),
105
err: fmt.Sprintf(modelNameErr, "-"),
107
args: []string{"new@model"},
108
err: fmt.Sprintf(modelNameErr, "new@model"),
110
args: []string{"new-model", "--owner", "foo"},
114
args: []string{"new-model", "--owner", "not=valid"},
115
err: `"not=valid" is not a valid user`,
117
args: []string{"new-model", "--config", "key=value", "--config", "key2=value2"},
119
values: map[string]interface{}{"key": "value", "key2": "value2"},
121
args: []string{"new-model", "extra", "args"},
122
err: `unrecognized args: \["extra" "args"\]`,
126
wrappedCommand, command := controller.NewAddModelCommandForTest(nil, nil, nil, s.store)
127
err := testing.InitCommand(wrappedCommand, test.args)
129
c.Assert(err, gc.ErrorMatches, test.err)
133
c.Assert(err, jc.ErrorIsNil)
134
c.Assert(command.Name, gc.Equals, test.name)
135
c.Assert(command.Owner, gc.Equals, test.owner)
136
attrs, err := command.Config.ReadAttrs(nil)
137
c.Assert(err, jc.ErrorIsNil)
138
if len(test.values) == 0 {
139
c.Assert(attrs, gc.HasLen, 0)
141
c.Assert(attrs, jc.DeepEquals, test.values)
146
func (s *addSuite) TestAddExistingName(c *gc.C) {
147
// If there's any model details existing, we just overwrite them. The
148
// controller will error out if the model already exists. Overwriting
149
// means we'll replace any stale details from an previously existing
150
// model with the same name.
151
err := s.store.UpdateModel("test-master", "bob@local/test", jujuclient.ModelDetails{
154
c.Assert(err, jc.ErrorIsNil)
156
_, err = s.run(c, "test")
157
c.Assert(err, jc.ErrorIsNil)
159
details, err := s.store.ModelByName("test-master", "bob@local/test")
160
c.Assert(err, jc.ErrorIsNil)
161
c.Assert(details, jc.DeepEquals, &jujuclient.ModelDetails{"fake-model-uuid"})
164
func (s *addSuite) TestCredentialsPassedThrough(c *gc.C) {
165
c.Skip("TODO(wallyworld) - port to using new credential management")
166
_, err := s.run(c, "test", "--credential", "secrets")
167
c.Assert(err, jc.ErrorIsNil)
169
c.Assert(s.fakeAddModelAPI.cloudCredential, gc.Equals, "secrets")
170
c.Assert(s.fakeAddModelAPI.config["type"], gc.Equals, "ec2")
173
func (s *addSuite) TestComandLineConfigPassedThrough(c *gc.C) {
174
_, err := s.run(c, "test", "--config", "account=magic", "--config", "cloud=special")
175
c.Assert(err, jc.ErrorIsNil)
177
c.Assert(s.fakeAddModelAPI.config["account"], gc.Equals, "magic")
178
c.Assert(s.fakeAddModelAPI.config["cloud"], gc.Equals, "special")
181
func (s *addSuite) TestConfigFileValuesPassedThrough(c *gc.C) {
182
config := map[string]string{
186
bytes, err := yaml.Marshal(config)
187
c.Assert(err, jc.ErrorIsNil)
188
file, err := ioutil.TempFile(c.MkDir(), "")
189
c.Assert(err, jc.ErrorIsNil)
193
_, err = s.run(c, "test", "--config", file.Name())
194
c.Assert(err, jc.ErrorIsNil)
195
c.Assert(s.fakeAddModelAPI.config["account"], gc.Equals, "magic")
196
c.Assert(s.fakeAddModelAPI.config["cloud"], gc.Equals, "9")
199
func (s *addSuite) TestConfigFileWithNestedMaps(c *gc.C) {
200
nestedConfig := map[string]interface{}{
204
config := map[string]interface{}{
206
"nested": nestedConfig,
209
bytes, err := yaml.Marshal(config)
210
c.Assert(err, jc.ErrorIsNil)
211
file, err := ioutil.TempFile(c.MkDir(), "")
212
c.Assert(err, jc.ErrorIsNil)
216
_, err = s.run(c, "test", "--config", file.Name())
217
c.Assert(err, jc.ErrorIsNil)
218
c.Assert(s.fakeAddModelAPI.config["foo"], gc.Equals, "bar")
219
c.Assert(s.fakeAddModelAPI.config["nested"], jc.DeepEquals, nestedConfig)
222
func (s *addSuite) TestConfigFileFailsToConform(c *gc.C) {
223
nestedConfig := map[int]interface{}{
226
config := map[string]interface{}{
228
"nested": nestedConfig,
230
bytes, err := yaml.Marshal(config)
231
c.Assert(err, jc.ErrorIsNil)
232
file, err := ioutil.TempFile(c.MkDir(), "")
233
c.Assert(err, jc.ErrorIsNil)
237
_, err = s.run(c, "test", "--config", file.Name())
238
c.Assert(err, gc.ErrorMatches, `unable to parse config: map keyed with non-string value`)
241
func (s *addSuite) TestConfigFileFormatError(c *gc.C) {
242
file, err := ioutil.TempFile(c.MkDir(), "")
243
c.Assert(err, jc.ErrorIsNil)
244
file.Write(([]byte)("not: valid: yaml"))
247
_, err = s.run(c, "test", "--config", file.Name())
248
c.Assert(err, gc.ErrorMatches, `unable to parse config: yaml: .*`)
251
func (s *addSuite) TestConfigFileDoesntExist(c *gc.C) {
252
_, err := s.run(c, "test", "--config", "missing-file")
253
errMsg := ".*" + utils.NoSuchFileErrRegexp
254
c.Assert(err, gc.ErrorMatches, errMsg)
257
func (s *addSuite) TestConfigValuePrecedence(c *gc.C) {
258
config := map[string]string{
262
bytes, err := yaml.Marshal(config)
263
c.Assert(err, jc.ErrorIsNil)
264
file, err := ioutil.TempFile(c.MkDir(), "")
265
c.Assert(err, jc.ErrorIsNil)
269
_, err = s.run(c, "test", "--config", file.Name(), "--config", "account=magic", "--config", "cloud=special")
270
c.Assert(err, jc.ErrorIsNil)
271
c.Assert(s.fakeAddModelAPI.config["account"], gc.Equals, "magic")
272
c.Assert(s.fakeAddModelAPI.config["cloud"], gc.Equals, "special")
275
func (s *addSuite) TestAddErrorRemoveConfigstoreInfo(c *gc.C) {
276
s.fakeAddModelAPI.err = errors.New("bah humbug")
278
_, err := s.run(c, "test")
279
c.Assert(err, gc.ErrorMatches, "bah humbug")
281
_, err = s.store.ModelByName("test-master", "bob@local/test")
282
c.Assert(err, jc.Satisfies, errors.IsNotFound)
285
func (s *addSuite) TestAddStoresValues(c *gc.C) {
286
_, err := s.run(c, "test")
287
c.Assert(err, jc.ErrorIsNil)
289
model, err := s.store.ModelByName("test-master", "bob@local/test")
290
c.Assert(err, jc.ErrorIsNil)
291
c.Assert(model, jc.DeepEquals, &jujuclient.ModelDetails{"fake-model-uuid"})
294
func (s *addSuite) TestNoEnvCacheOtherUser(c *gc.C) {
295
_, err := s.run(c, "test", "--owner", "zeus")
296
c.Assert(err, jc.ErrorIsNil)
298
// Creating a model for another user does not update the model cache.
299
_, err = s.store.ModelByName("test-master", "bob@local/test")
300
c.Assert(err, jc.Satisfies, errors.IsNotFound)
303
// fakeAddClient is used to mock out the behavior of the real
305
type fakeAddClient struct {
308
cloudCredential string
309
config map[string]interface{}
311
model params.ModelInfo
314
var _ controller.AddModelAPI = (*fakeAddClient)(nil)
316
func (*fakeAddClient) Close() error {
320
func (f *fakeAddClient) CreateModel(name, owner, cloudRegion, cloudCredential string, config map[string]interface{}) (params.ModelInfo, error) {
322
return params.ModelInfo{}, f.err
325
f.cloudCredential = cloudCredential
326
f.cloudRegion = cloudRegion
331
// TODO(wallyworld) - improve this stub and add test asserts
332
type fakeCloudAPI struct {
336
func (c *fakeCloudAPI) Credentials(names.UserTag, names.CloudTag) (map[string]cloud.Credential, error) {
337
return map[string]cloud.Credential{
338
"default": cloud.NewEmptyCredential(),
342
func (c *fakeCloudAPI) UpdateCredentials(names.UserTag, names.CloudTag, map[string]cloud.Credential) error {