github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/cloud/addcredential_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package cloud_test 5 6 import ( 7 "fmt" 8 "io" 9 "io/ioutil" 10 "path/filepath" 11 "strings" 12 13 "github.com/juju/cmd" 14 "github.com/juju/errors" 15 jc "github.com/juju/testing/checkers" 16 gc "gopkg.in/check.v1" 17 18 jujucloud "github.com/juju/juju/cloud" 19 "github.com/juju/juju/cmd/juju/cloud" 20 "github.com/juju/juju/environs" 21 "github.com/juju/juju/jujuclient/jujuclienttesting" 22 _ "github.com/juju/juju/provider/all" 23 "github.com/juju/juju/testing" 24 ) 25 26 type addCredentialSuite struct { 27 testing.BaseSuite 28 29 store *jujuclienttesting.MemStore 30 schema map[jujucloud.AuthType]jujucloud.CredentialSchema 31 authTypes []jujucloud.AuthType 32 cloudByNameFunc func(string) (*jujucloud.Cloud, error) 33 } 34 35 var _ = gc.Suite(&addCredentialSuite{ 36 store: jujuclienttesting.NewMemStore(), 37 }) 38 39 func (s *addCredentialSuite) SetUpSuite(c *gc.C) { 40 s.BaseSuite.SetUpSuite(c) 41 environs.RegisterProvider("mock-addcredential-provider", &mockProvider{credSchemas: &s.schema}) 42 s.cloudByNameFunc = func(cloud string) (*jujucloud.Cloud, error) { 43 if cloud != "somecloud" && cloud != "anothercloud" { 44 return nil, errors.NotFoundf("cloud %v", cloud) 45 } 46 return &jujucloud.Cloud{ 47 Type: "mock-addcredential-provider", 48 AuthTypes: s.authTypes, 49 Endpoint: "cloud-endpoint", 50 IdentityEndpoint: "cloud-identity-endpoint", 51 }, nil 52 } 53 } 54 55 func (s *addCredentialSuite) SetUpTest(c *gc.C) { 56 s.BaseSuite.SetUpTest(c) 57 s.store.Credentials = make(map[string]jujucloud.CloudCredential) 58 } 59 60 func (s *addCredentialSuite) run(c *gc.C, stdin io.Reader, args ...string) (*cmd.Context, error) { 61 addCmd := cloud.NewAddCredentialCommandForTest(s.store, s.cloudByNameFunc) 62 err := testing.InitCommand(addCmd, args) 63 if err != nil { 64 return nil, err 65 } 66 ctx := testing.Context(c) 67 ctx.Stdin = stdin 68 return ctx, addCmd.Run(ctx) 69 } 70 71 func (s *addCredentialSuite) TestBadArgs(c *gc.C) { 72 _, err := s.run(c, nil) 73 c.Assert(err, gc.ErrorMatches, `Usage: juju add-credential <cloud-name> \[-f <credentials.yaml>\]`) 74 _, err = s.run(c, nil, "somecloud", "-f", "credential.yaml", "extra") 75 c.Assert(err, gc.ErrorMatches, `unrecognized args: \["extra"\]`) 76 } 77 78 func (s *addCredentialSuite) TestBadCloudName(c *gc.C) { 79 _, err := s.run(c, nil, "badcloud") 80 c.Assert(err, gc.ErrorMatches, "cloud badcloud not valid") 81 } 82 83 func (s *addCredentialSuite) TestAddFromFileBadFilename(c *gc.C) { 84 _, err := s.run(c, nil, "somecloud", "-f", "somefile.yaml") 85 c.Assert(err, gc.ErrorMatches, ".*open somefile.yaml: .*") 86 } 87 88 func (s *addCredentialSuite) TestNoCredentialsRequired(c *gc.C) { 89 s.authTypes = nil 90 _, err := s.run(c, nil, "somecloud") 91 c.Assert(err, gc.ErrorMatches, `cloud "somecloud" does not require credentials`) 92 } 93 94 func (s *addCredentialSuite) createTestCredentialData(c *gc.C) string { 95 dir := c.MkDir() 96 credsFile := filepath.Join(dir, "cred.yaml") 97 data := ` 98 credentials: 99 somecloud: 100 me: 101 auth-type: access-key 102 access-key: <key> 103 secret-key: <secret> 104 `[1:] 105 err := ioutil.WriteFile(credsFile, []byte(data), 0600) 106 c.Assert(err, jc.ErrorIsNil) 107 return credsFile 108 } 109 110 func (s *addCredentialSuite) TestAddFromFileNoCredentialsFound(c *gc.C) { 111 sourceFile := s.createTestCredentialData(c) 112 _, err := s.run(c, nil, "anothercloud", "-f", sourceFile) 113 c.Assert(err, gc.ErrorMatches, `no credentials for cloud anothercloud exist in file.*`) 114 } 115 116 func (s *addCredentialSuite) TestAddFromFileExisting(c *gc.C) { 117 s.store.Credentials = map[string]jujucloud.CloudCredential{ 118 "somecloud": { 119 AuthCredentials: map[string]jujucloud.Credential{"cred": {}}, 120 }, 121 } 122 sourceFile := s.createTestCredentialData(c) 123 _, err := s.run(c, nil, "somecloud", "-f", sourceFile) 124 c.Assert(err, gc.ErrorMatches, `credentials for cloud somecloud already exist; use --replace to overwrite / merge`) 125 } 126 127 func (s *addCredentialSuite) TestAddFromFileExistingReplace(c *gc.C) { 128 s.store.Credentials = map[string]jujucloud.CloudCredential{ 129 "somecloud": { 130 AuthCredentials: map[string]jujucloud.Credential{ 131 "cred": jujucloud.NewCredential(jujucloud.UserPassAuthType, nil)}, 132 }, 133 } 134 sourceFile := s.createTestCredentialData(c) 135 _, err := s.run(c, nil, "somecloud", "-f", sourceFile, "--replace") 136 c.Assert(err, jc.ErrorIsNil) 137 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 138 "somecloud": { 139 AuthCredentials: map[string]jujucloud.Credential{ 140 "cred": jujucloud.NewCredential(jujucloud.UserPassAuthType, nil), 141 "me": jujucloud.NewCredential(jujucloud.AccessKeyAuthType, map[string]string{ 142 "access-key": "<key>", 143 "secret-key": "<secret>", 144 })}, 145 }, 146 }) 147 } 148 149 func (s *addCredentialSuite) TestAddNewFromFile(c *gc.C) { 150 sourceFile := s.createTestCredentialData(c) 151 _, err := s.run(c, nil, "somecloud", "-f", sourceFile) 152 c.Assert(err, jc.ErrorIsNil) 153 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 154 "somecloud": { 155 AuthCredentials: map[string]jujucloud.Credential{ 156 "me": jujucloud.NewCredential(jujucloud.AccessKeyAuthType, map[string]string{ 157 "access-key": "<key>", 158 "secret-key": "<secret>", 159 })}, 160 }, 161 }) 162 } 163 164 // TODO(wallyworld) - these tests should also validate that the prompts and messages are as expected. 165 166 func (s *addCredentialSuite) assertAddUserpassCredential(c *gc.C, input string, expected *jujucloud.Credential) { 167 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 168 jujucloud.UserPassAuthType: { 169 { 170 "username", jujucloud.CredentialAttr{Optional: false}, 171 }, { 172 "password", jujucloud.CredentialAttr{Hidden: true}, 173 }, 174 }, 175 } 176 stdin := strings.NewReader(input) 177 _, err := s.run(c, stdin, "somecloud") 178 c.Assert(err, jc.ErrorIsNil) 179 var cred jujucloud.Credential 180 if expected == nil { 181 cred = jujucloud.NewCredential(jujucloud.UserPassAuthType, map[string]string{ 182 "username": "user", 183 "password": "password", 184 }) 185 } else { 186 cred = *expected 187 } 188 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 189 "somecloud": { 190 AuthCredentials: map[string]jujucloud.Credential{ 191 "fred": cred, 192 }, 193 }, 194 }) 195 } 196 197 func (s *addCredentialSuite) TestAddCredentialSingleAuthType(c *gc.C) { 198 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 199 s.assertAddUserpassCredential(c, "fred\nuser\npassword\n", nil) 200 } 201 202 func (s *addCredentialSuite) TestAddCredentialRetryOnMissingMandatoryAttribute(c *gc.C) { 203 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 204 s.assertAddUserpassCredential(c, "fred\n\nuser\npassword\n", nil) 205 } 206 207 func (s *addCredentialSuite) TestAddCredentialMultipleAuthType(c *gc.C) { 208 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType, jujucloud.AccessKeyAuthType} 209 s.assertAddUserpassCredential(c, "fred\nuserpass\nuser\npassword\n", nil) 210 } 211 212 func (s *addCredentialSuite) TestAddCredentialInteractive(c *gc.C) { 213 s.authTypes = []jujucloud.AuthType{"interactive"} 214 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 215 "interactive": {{"username", jujucloud.CredentialAttr{}}}, 216 } 217 218 stdin := strings.NewReader("bobscreds\nbob\n") 219 ctx, err := s.run(c, stdin, "somecloud") 220 c.Assert(err, jc.ErrorIsNil) 221 222 c.Assert(testing.Stderr(ctx), gc.Equals, ` 223 Enter credential name: Using auth-type "interactive". 224 Enter username: generating userpass credential 225 `[1:]) 226 227 // FinalizeCredential should have generated a userpass credential 228 // based on the input from the interactive credential. 229 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 230 "somecloud": { 231 AuthCredentials: map[string]jujucloud.Credential{ 232 "bobscreds": jujucloud.NewCredential(jujucloud.UserPassAuthType, map[string]string{ 233 "username": "bob", 234 "password": "cloud-endpoint", 235 "application-password": "cloud-identity-endpoint", 236 }), 237 }, 238 }, 239 }) 240 } 241 242 func (s *addCredentialSuite) TestAddCredentialReplace(c *gc.C) { 243 s.store.Credentials = map[string]jujucloud.CloudCredential{ 244 "somecloud": { 245 AuthCredentials: map[string]jujucloud.Credential{ 246 "fred": jujucloud.NewCredential(jujucloud.UserPassAuthType, nil)}, 247 }, 248 } 249 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 250 s.assertAddUserpassCredential(c, "fred\ny\nuser\npassword\n", nil) 251 } 252 253 func (s *addCredentialSuite) TestAddCredentialReplaceDecline(c *gc.C) { 254 cred := jujucloud.NewCredential(jujucloud.UserPassAuthType, nil) 255 s.store.Credentials = map[string]jujucloud.CloudCredential{ 256 "somecloud": { 257 AuthCredentials: map[string]jujucloud.Credential{ 258 "fred": cred}, 259 }, 260 } 261 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 262 s.assertAddUserpassCredential(c, "fred\nn\n", &cred) 263 } 264 265 func (s *addCredentialSuite) assertAddFileCredential(c *gc.C, input, fileKey string) { 266 dir := c.MkDir() 267 filename := filepath.Join(dir, "jsonfile") 268 err := ioutil.WriteFile(filename, []byte{}, 0600) 269 c.Assert(err, jc.ErrorIsNil) 270 271 stdin := strings.NewReader(fmt.Sprintf(input, filename)) 272 addCmd := cloud.NewAddCredentialCommandForTest(s.store, s.cloudByNameFunc) 273 err = testing.InitCommand(addCmd, []string{"somecloud"}) 274 c.Assert(err, jc.ErrorIsNil) 275 ctx := testing.ContextForDir(c, dir) 276 ctx.Stdin = stdin 277 err = addCmd.Run(ctx) 278 c.Assert(err, jc.ErrorIsNil) 279 280 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 281 "somecloud": { 282 AuthCredentials: map[string]jujucloud.Credential{ 283 "fred": jujucloud.NewCredential(s.authTypes[0], map[string]string{ 284 fileKey: filename, 285 }), 286 }, 287 }, 288 }) 289 } 290 291 func (s *addCredentialSuite) TestAddJsonFileCredential(c *gc.C) { 292 s.authTypes = []jujucloud.AuthType{jujucloud.JSONFileAuthType} 293 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 294 jujucloud.JSONFileAuthType: { 295 { 296 "file", 297 jujucloud.CredentialAttr{ 298 Optional: false, 299 FilePath: true, 300 }, 301 }, 302 }, 303 } 304 // Input includes invalid file info. 305 s.assertAddFileCredential(c, "fred\nbadfile\n.\n%s\n", "file") 306 } 307 308 func (s *addCredentialSuite) TestAddCredentialWithFileAttr(c *gc.C) { 309 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 310 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 311 jujucloud.UserPassAuthType: { 312 { 313 "key", 314 jujucloud.CredentialAttr{ 315 FileAttr: "key-file", 316 }, 317 }, 318 }, 319 } 320 // Input includes invalid file info. 321 s.assertAddFileCredential(c, "fred\nbadfile\n.\n%s\n", "key-file") 322 } 323 324 func (s *addCredentialSuite) assertAddCredentialWithOptions(c *gc.C, input string) { 325 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 326 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 327 jujucloud.UserPassAuthType: { 328 { 329 "username", jujucloud.CredentialAttr{Optional: false}, 330 }, { 331 "algorithm", jujucloud.CredentialAttr{Options: []interface{}{"optionA", "optionB"}}, 332 }, 333 }, 334 } 335 // Input includes a bad option 336 stdin := strings.NewReader(input) 337 _, err := s.run(c, stdin, "somecloud") 338 c.Assert(err, jc.ErrorIsNil) 339 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 340 "somecloud": { 341 AuthCredentials: map[string]jujucloud.Credential{ 342 "fred": jujucloud.NewCredential(jujucloud.UserPassAuthType, map[string]string{ 343 "username": "user", 344 "algorithm": "optionA", 345 }), 346 }, 347 }, 348 }) 349 } 350 351 func (s *addCredentialSuite) TestAddCredentialWithOptions(c *gc.C) { 352 s.assertAddCredentialWithOptions(c, "fred\nuser\nbadoption\noptionA\n") 353 } 354 355 func (s *addCredentialSuite) TestAddCredentialWithOptionsAutofill(c *gc.C) { 356 s.assertAddCredentialWithOptions(c, "fred\nuser\n\n") 357 } 358 359 func (s *addCredentialSuite) TestAddMAASCredential(c *gc.C) { 360 s.authTypes = []jujucloud.AuthType{jujucloud.OAuth1AuthType} 361 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 362 jujucloud.OAuth1AuthType: { 363 { 364 "maas-oauth", jujucloud.CredentialAttr{}, 365 }, 366 }, 367 } 368 stdin := strings.NewReader("fred\nauth:token\n") 369 _, err := s.run(c, stdin, "somecloud") 370 c.Assert(err, jc.ErrorIsNil) 371 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 372 "somecloud": { 373 AuthCredentials: map[string]jujucloud.Credential{ 374 "fred": jujucloud.NewCredential(jujucloud.OAuth1AuthType, map[string]string{ 375 "maas-oauth": "auth:token", 376 }), 377 }, 378 }, 379 }) 380 }