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  }