github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 }, nil 50 } 51 } 52 53 func (s *addCredentialSuite) SetUpTest(c *gc.C) { 54 s.BaseSuite.SetUpTest(c) 55 s.store.Credentials = make(map[string]jujucloud.CloudCredential) 56 } 57 58 func (s *addCredentialSuite) run(c *gc.C, stdin io.Reader, args ...string) (*cmd.Context, error) { 59 addCmd := cloud.NewAddCredentialCommandForTest(s.store, s.cloudByNameFunc) 60 err := testing.InitCommand(addCmd, args) 61 if err != nil { 62 return nil, err 63 } 64 ctx := testing.Context(c) 65 ctx.Stdin = stdin 66 return ctx, addCmd.Run(ctx) 67 } 68 69 func (s *addCredentialSuite) TestBadArgs(c *gc.C) { 70 _, err := s.run(c, nil) 71 c.Assert(err, gc.ErrorMatches, `Usage: juju add-credential <cloud-name> \[-f <credentials.yaml>\]`) 72 _, err = s.run(c, nil, "somecloud", "-f", "credential.yaml", "extra") 73 c.Assert(err, gc.ErrorMatches, `unrecognized args: \["extra"\]`) 74 } 75 76 func (s *addCredentialSuite) TestBadCloudName(c *gc.C) { 77 _, err := s.run(c, nil, "badcloud") 78 c.Assert(err, gc.ErrorMatches, "cloud badcloud not valid") 79 } 80 81 func (s *addCredentialSuite) TestAddFromFileBadFilename(c *gc.C) { 82 _, err := s.run(c, nil, "somecloud", "-f", "somefile.yaml") 83 c.Assert(err, gc.ErrorMatches, ".*open somefile.yaml: .*") 84 } 85 86 func (s *addCredentialSuite) TestNoCredentialsRequired(c *gc.C) { 87 _, err := s.run(c, nil, "manual") 88 c.Assert(err, gc.ErrorMatches, `cloud "manual" does not require credentials`) 89 } 90 91 func (s *addCredentialSuite) createTestCredentialData(c *gc.C) string { 92 dir := c.MkDir() 93 credsFile := filepath.Join(dir, "cred.yaml") 94 data := ` 95 credentials: 96 somecloud: 97 me: 98 auth-type: access-key 99 access-key: <key> 100 secret-key: <secret> 101 `[1:] 102 err := ioutil.WriteFile(credsFile, []byte(data), 0600) 103 c.Assert(err, jc.ErrorIsNil) 104 return credsFile 105 } 106 107 func (s *addCredentialSuite) TestAddFromFileNoCredentialsFound(c *gc.C) { 108 sourceFile := s.createTestCredentialData(c) 109 _, err := s.run(c, nil, "anothercloud", "-f", sourceFile) 110 c.Assert(err, gc.ErrorMatches, `no credentials for cloud anothercloud exist in file.*`) 111 } 112 113 func (s *addCredentialSuite) TestAddFromFileExisting(c *gc.C) { 114 s.store.Credentials = map[string]jujucloud.CloudCredential{ 115 "somecloud": { 116 AuthCredentials: map[string]jujucloud.Credential{"cred": {}}, 117 }, 118 } 119 sourceFile := s.createTestCredentialData(c) 120 _, err := s.run(c, nil, "somecloud", "-f", sourceFile) 121 c.Assert(err, gc.ErrorMatches, `credentials for cloud somecloud already exist; use --replace to overwrite / merge`) 122 } 123 124 func (s *addCredentialSuite) TestAddFromFileExistingReplace(c *gc.C) { 125 s.store.Credentials = map[string]jujucloud.CloudCredential{ 126 "somecloud": { 127 AuthCredentials: map[string]jujucloud.Credential{ 128 "cred": jujucloud.NewCredential(jujucloud.UserPassAuthType, nil)}, 129 }, 130 } 131 sourceFile := s.createTestCredentialData(c) 132 _, err := s.run(c, nil, "somecloud", "-f", sourceFile, "--replace") 133 c.Assert(err, jc.ErrorIsNil) 134 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 135 "somecloud": { 136 AuthCredentials: map[string]jujucloud.Credential{ 137 "cred": jujucloud.NewCredential(jujucloud.UserPassAuthType, nil), 138 "me": jujucloud.NewCredential(jujucloud.AccessKeyAuthType, map[string]string{ 139 "access-key": "<key>", 140 "secret-key": "<secret>", 141 })}, 142 }, 143 }) 144 } 145 146 func (s *addCredentialSuite) TestAddNewFromFile(c *gc.C) { 147 sourceFile := s.createTestCredentialData(c) 148 _, err := s.run(c, nil, "somecloud", "-f", sourceFile) 149 c.Assert(err, jc.ErrorIsNil) 150 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 151 "somecloud": { 152 AuthCredentials: map[string]jujucloud.Credential{ 153 "me": jujucloud.NewCredential(jujucloud.AccessKeyAuthType, map[string]string{ 154 "access-key": "<key>", 155 "secret-key": "<secret>", 156 })}, 157 }, 158 }) 159 } 160 161 // TODO(wallyworld) - these tests should also validate that the prompts and messages are as expected. 162 163 func (s *addCredentialSuite) assertAddUserpassCredential(c *gc.C, input string, expected *jujucloud.Credential) { 164 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 165 jujucloud.UserPassAuthType: { 166 { 167 "username", jujucloud.CredentialAttr{Optional: false}, 168 }, { 169 "password", jujucloud.CredentialAttr{Hidden: true}, 170 }, 171 }, 172 } 173 stdin := strings.NewReader(input) 174 _, err := s.run(c, stdin, "somecloud") 175 c.Assert(err, jc.ErrorIsNil) 176 var cred jujucloud.Credential 177 if expected == nil { 178 cred = jujucloud.NewCredential(jujucloud.UserPassAuthType, map[string]string{ 179 "username": "user", 180 "password": "password", 181 }) 182 } else { 183 cred = *expected 184 } 185 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 186 "somecloud": { 187 AuthCredentials: map[string]jujucloud.Credential{ 188 "fred": cred, 189 }, 190 }, 191 }) 192 } 193 194 func (s *addCredentialSuite) TestAddCredentialSingleAuthType(c *gc.C) { 195 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 196 s.assertAddUserpassCredential(c, "fred\nuser\npassword\n", nil) 197 } 198 199 func (s *addCredentialSuite) TestAddCredentialRetryOnMissingMandatoryAttribute(c *gc.C) { 200 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 201 s.assertAddUserpassCredential(c, "fred\n\nuser\npassword\n", nil) 202 } 203 204 func (s *addCredentialSuite) TestAddCredentialMultipleAuthType(c *gc.C) { 205 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType, jujucloud.AccessKeyAuthType} 206 s.assertAddUserpassCredential(c, "fred\nuserpass\nuser\npassword\n", nil) 207 } 208 209 func (s *addCredentialSuite) TestAddCredentialReplace(c *gc.C) { 210 s.store.Credentials = map[string]jujucloud.CloudCredential{ 211 "somecloud": { 212 AuthCredentials: map[string]jujucloud.Credential{ 213 "fred": jujucloud.NewCredential(jujucloud.UserPassAuthType, nil)}, 214 }, 215 } 216 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 217 s.assertAddUserpassCredential(c, "fred\ny\nuser\npassword\n", nil) 218 } 219 220 func (s *addCredentialSuite) TestAddCredentialReplaceDecline(c *gc.C) { 221 cred := jujucloud.NewCredential(jujucloud.UserPassAuthType, nil) 222 s.store.Credentials = map[string]jujucloud.CloudCredential{ 223 "somecloud": { 224 AuthCredentials: map[string]jujucloud.Credential{ 225 "fred": cred}, 226 }, 227 } 228 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 229 s.assertAddUserpassCredential(c, "fred\nn\n", &cred) 230 } 231 232 func (s *addCredentialSuite) assertAddFileCredential(c *gc.C, input, fileKey string) { 233 dir := c.MkDir() 234 filename := filepath.Join(dir, "jsonfile") 235 err := ioutil.WriteFile(filename, []byte{}, 0600) 236 c.Assert(err, jc.ErrorIsNil) 237 238 stdin := strings.NewReader(fmt.Sprintf(input, filename)) 239 addCmd := cloud.NewAddCredentialCommandForTest(s.store, s.cloudByNameFunc) 240 err = testing.InitCommand(addCmd, []string{"somecloud"}) 241 c.Assert(err, jc.ErrorIsNil) 242 ctx := testing.ContextForDir(c, dir) 243 ctx.Stdin = stdin 244 err = addCmd.Run(ctx) 245 c.Assert(err, jc.ErrorIsNil) 246 247 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 248 "somecloud": { 249 AuthCredentials: map[string]jujucloud.Credential{ 250 "fred": jujucloud.NewCredential(s.authTypes[0], map[string]string{ 251 fileKey: filename, 252 }), 253 }, 254 }, 255 }) 256 } 257 258 func (s *addCredentialSuite) TestAddJsonFileCredential(c *gc.C) { 259 s.authTypes = []jujucloud.AuthType{jujucloud.JSONFileAuthType} 260 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 261 jujucloud.JSONFileAuthType: { 262 { 263 "file", 264 jujucloud.CredentialAttr{ 265 Optional: false, 266 FilePath: true, 267 }, 268 }, 269 }, 270 } 271 // Input includes invalid file info. 272 s.assertAddFileCredential(c, "fred\nbadfile\n.\n%s\n", "file") 273 } 274 275 func (s *addCredentialSuite) TestAddCredentialWithFileAttr(c *gc.C) { 276 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 277 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 278 jujucloud.UserPassAuthType: { 279 { 280 "key", 281 jujucloud.CredentialAttr{ 282 FileAttr: "key-file", 283 }, 284 }, 285 }, 286 } 287 // Input includes invalid file info. 288 s.assertAddFileCredential(c, "fred\nbadfile\n.\n%s\n", "key-file") 289 } 290 291 func (s *addCredentialSuite) assertAddCredentialWithOptions(c *gc.C, input string) { 292 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 293 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 294 jujucloud.UserPassAuthType: { 295 { 296 "username", jujucloud.CredentialAttr{Optional: false}, 297 }, { 298 "algorithm", jujucloud.CredentialAttr{Options: []interface{}{"optionA", "optionB"}}, 299 }, 300 }, 301 } 302 // Input includes a bad option 303 stdin := strings.NewReader(input) 304 _, err := s.run(c, stdin, "somecloud") 305 c.Assert(err, jc.ErrorIsNil) 306 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 307 "somecloud": { 308 AuthCredentials: map[string]jujucloud.Credential{ 309 "fred": jujucloud.NewCredential(jujucloud.UserPassAuthType, map[string]string{ 310 "username": "user", 311 "algorithm": "optionA", 312 }), 313 }, 314 }, 315 }) 316 } 317 318 func (s *addCredentialSuite) TestAddCredentialWithOptions(c *gc.C) { 319 s.assertAddCredentialWithOptions(c, "fred\nuser\nbadoption\noptionA\n") 320 } 321 322 func (s *addCredentialSuite) TestAddCredentialWithOptionsAutofill(c *gc.C) { 323 s.assertAddCredentialWithOptions(c, "fred\nuser\n\n") 324 } 325 326 func (s *addCredentialSuite) TestAddMAASCredential(c *gc.C) { 327 s.authTypes = []jujucloud.AuthType{jujucloud.OAuth1AuthType} 328 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 329 jujucloud.OAuth1AuthType: { 330 { 331 "maas-oauth", jujucloud.CredentialAttr{}, 332 }, 333 }, 334 } 335 stdin := strings.NewReader("fred\nauth:token\n") 336 _, err := s.run(c, stdin, "maas") 337 c.Assert(err, jc.ErrorIsNil) 338 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 339 "maas": { 340 AuthCredentials: map[string]jujucloud.Credential{ 341 "fred": jujucloud.NewCredential(jujucloud.OAuth1AuthType, map[string]string{ 342 "maas-oauth": "auth:token", 343 }), 344 }, 345 }, 346 }) 347 }