github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"regexp"
    12  	"strings"
    13  
    14  	"github.com/golang/mock/gomock"
    15  	"github.com/juju/cmd"
    16  	"github.com/juju/cmd/cmdtesting"
    17  	"github.com/juju/errors"
    18  	jc "github.com/juju/testing/checkers"
    19  	gc "gopkg.in/check.v1"
    20  
    21  	jujucloud "github.com/juju/juju/cloud"
    22  	"github.com/juju/juju/cmd/juju/cloud"
    23  	"github.com/juju/juju/environs"
    24  	environsTesting "github.com/juju/juju/environs/testing"
    25  	"github.com/juju/juju/jujuclient"
    26  	_ "github.com/juju/juju/provider/all"
    27  	"github.com/juju/juju/testing"
    28  )
    29  
    30  type addCredentialSuite struct {
    31  	testing.BaseSuite
    32  
    33  	store           *jujuclient.MemStore
    34  	schema          map[jujucloud.AuthType]jujucloud.CredentialSchema
    35  	authTypes       []jujucloud.AuthType
    36  	cloudByNameFunc func(string) (*jujucloud.Cloud, error)
    37  }
    38  
    39  var _ = gc.Suite(&addCredentialSuite{
    40  	store: jujuclient.NewMemStore(),
    41  })
    42  
    43  func (s *addCredentialSuite) SetUpSuite(c *gc.C) {
    44  	s.BaseSuite.SetUpSuite(c)
    45  	unreg := environs.RegisterProvider("mock-addcredential-provider", &mockProvider{credSchemas: &s.schema})
    46  	s.AddCleanup(func(_ *gc.C) {
    47  		unreg()
    48  	})
    49  	s.cloudByNameFunc = func(cloud string) (*jujucloud.Cloud, error) {
    50  		if cloud != "somecloud" && cloud != "anothercloud" {
    51  			return nil, errors.NotFoundf("cloud %v", cloud)
    52  		}
    53  		return &jujucloud.Cloud{
    54  			Type:             "mock-addcredential-provider",
    55  			AuthTypes:        s.authTypes,
    56  			Endpoint:         "cloud-endpoint",
    57  			IdentityEndpoint: "cloud-identity-endpoint",
    58  		}, nil
    59  	}
    60  }
    61  
    62  func (s *addCredentialSuite) SetUpTest(c *gc.C) {
    63  	s.BaseSuite.SetUpTest(c)
    64  	s.store.Credentials = make(map[string]jujucloud.CloudCredential)
    65  }
    66  
    67  func (s *addCredentialSuite) run(c *gc.C, stdin io.Reader, args ...string) (*cmd.Context, error) {
    68  	addCmd := cloud.NewAddCredentialCommandForTest(s.store, s.cloudByNameFunc)
    69  	err := cmdtesting.InitCommand(addCmd, args)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	ctx := cmdtesting.Context(c)
    74  	ctx.Stdin = stdin
    75  	return ctx, addCmd.Run(ctx)
    76  }
    77  
    78  func (s *addCredentialSuite) TestBadArgs(c *gc.C) {
    79  	_, err := s.run(c, nil)
    80  	c.Assert(err, gc.ErrorMatches, `Usage: juju add-credential <cloud-name> \[-f <credentials.yaml>\]`)
    81  	_, err = s.run(c, nil, "somecloud", "-f", "credential.yaml", "extra")
    82  	c.Assert(err, gc.ErrorMatches, `unrecognized args: \["extra"\]`)
    83  }
    84  
    85  func (s *addCredentialSuite) TestBadCloudName(c *gc.C) {
    86  	_, err := s.run(c, nil, "badcloud")
    87  	c.Assert(err, gc.ErrorMatches, "cloud badcloud not valid")
    88  }
    89  
    90  func (s *addCredentialSuite) TestAddFromFileBadFilename(c *gc.C) {
    91  	_, err := s.run(c, nil, "somecloud", "-f", "somefile.yaml")
    92  	c.Assert(err, gc.ErrorMatches, ".*open somefile.yaml: .*")
    93  }
    94  
    95  func (s *addCredentialSuite) TestNoCredentialsRequired(c *gc.C) {
    96  	s.authTypes = nil
    97  	_, err := s.run(c, nil, "somecloud")
    98  	c.Assert(err, gc.ErrorMatches, `cloud "somecloud" does not require credentials`)
    99  }
   100  
   101  func (s *addCredentialSuite) createTestCredentialData(c *gc.C) string {
   102  	return s.createTestCredentialDataWithAuthType(c, "access-key")
   103  }
   104  
   105  func (s *addCredentialSuite) createTestCredentialDataWithAuthType(c *gc.C, authType string) string {
   106  	dir := c.MkDir()
   107  	credsFile := filepath.Join(dir, "cred.yaml")
   108  	data := fmt.Sprintf(`
   109  credentials:
   110    somecloud:
   111      me:
   112        auth-type: %v
   113        access-key: <key>
   114        secret-key: <secret>
   115  `[1:], authType)
   116  	err := ioutil.WriteFile(credsFile, []byte(data), 0600)
   117  	c.Assert(err, jc.ErrorIsNil)
   118  	return credsFile
   119  }
   120  
   121  func (s *addCredentialSuite) TestAddFromFileNoCredentialsFound(c *gc.C) {
   122  	sourceFile := s.createTestCredentialData(c)
   123  	_, err := s.run(c, nil, "anothercloud", "-f", sourceFile)
   124  	c.Assert(err, gc.ErrorMatches, `no credentials for cloud anothercloud exist in file.*`)
   125  }
   126  
   127  func (s *addCredentialSuite) TestAddFromFileExisting(c *gc.C) {
   128  	s.store.Credentials = map[string]jujucloud.CloudCredential{
   129  		"somecloud": {
   130  			AuthCredentials: map[string]jujucloud.Credential{"cred": {}},
   131  		},
   132  	}
   133  	sourceFile := s.createTestCredentialData(c)
   134  	_, err := s.run(c, nil, "somecloud", "-f", sourceFile)
   135  	c.Assert(err, gc.ErrorMatches, `local credentials for cloud "somecloud" already exist; use --replace to overwrite / merge`)
   136  }
   137  
   138  func (s *addCredentialSuite) TestAddFromFileExistingReplace(c *gc.C) {
   139  	s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType, jujucloud.AccessKeyAuthType}
   140  	s.store.Credentials = map[string]jujucloud.CloudCredential{
   141  		"somecloud": {
   142  			AuthCredentials: map[string]jujucloud.Credential{
   143  				"cred": jujucloud.NewCredential(jujucloud.UserPassAuthType, nil)},
   144  		},
   145  	}
   146  	sourceFile := s.createTestCredentialData(c)
   147  	_, err := s.run(c, nil, "somecloud", "-f", sourceFile, "--replace")
   148  	c.Assert(err, jc.ErrorIsNil)
   149  	c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{
   150  		"somecloud": {
   151  			AuthCredentials: map[string]jujucloud.Credential{
   152  				"cred": jujucloud.NewCredential(jujucloud.UserPassAuthType, nil),
   153  				"me": jujucloud.NewCredential(jujucloud.AccessKeyAuthType, map[string]string{
   154  					"access-key": "<key>",
   155  					"secret-key": "<secret>",
   156  				})},
   157  		},
   158  	})
   159  }
   160  
   161  func (s *addCredentialSuite) TestAddNewFromFile(c *gc.C) {
   162  	s.authTypes = []jujucloud.AuthType{jujucloud.AccessKeyAuthType}
   163  	sourceFile := s.createTestCredentialData(c)
   164  	_, err := s.run(c, nil, "somecloud", "-f", sourceFile)
   165  	c.Assert(err, jc.ErrorIsNil)
   166  	c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{
   167  		"somecloud": {
   168  			AuthCredentials: map[string]jujucloud.Credential{
   169  				"me": jujucloud.NewCredential(jujucloud.AccessKeyAuthType, map[string]string{
   170  					"access-key": "<key>",
   171  					"secret-key": "<secret>",
   172  				})},
   173  		},
   174  	})
   175  }
   176  
   177  func (s *addCredentialSuite) TestAddInvalidAuth(c *gc.C) {
   178  	s.authTypes = []jujucloud.AuthType{jujucloud.AccessKeyAuthType}
   179  	sourceFile := s.createTestCredentialDataWithAuthType(c, "invalid auth")
   180  	_, err := s.run(c, nil, "somecloud", "-f", sourceFile)
   181  	c.Assert(err, gc.ErrorMatches,
   182  		regexp.QuoteMeta(`credential "me" contains invalid auth type "invalid auth", valid auth types for cloud "somecloud" are [access-key]`))
   183  }
   184  
   185  func (s *addCredentialSuite) TestAddCloudUnsupportedAuth(c *gc.C) {
   186  	s.authTypes = []jujucloud.AuthType{jujucloud.AccessKeyAuthType}
   187  	sourceFile := s.createTestCredentialDataWithAuthType(c, fmt.Sprintf("%v", jujucloud.JSONFileAuthType))
   188  	_, err := s.run(c, nil, "somecloud", "-f", sourceFile)
   189  	c.Assert(err, gc.ErrorMatches,
   190  		regexp.QuoteMeta(`credential "me" contains invalid auth type "jsonfile", valid auth types for cloud "somecloud" are [access-key]`))
   191  }
   192  
   193  func (s *addCredentialSuite) assertAddUserpassCredential(c *gc.C, input string, expected *jujucloud.Credential, msg string) {
   194  	s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{
   195  		jujucloud.UserPassAuthType: {
   196  			{
   197  				"username", jujucloud.CredentialAttr{Optional: false},
   198  			}, {
   199  				"password", jujucloud.CredentialAttr{Hidden: true},
   200  			},
   201  		},
   202  	}
   203  	stdin := strings.NewReader(input)
   204  	ctx, err := s.run(c, stdin, "somecloud")
   205  	c.Assert(err, jc.ErrorIsNil)
   206  	var cred jujucloud.Credential
   207  	if expected == nil {
   208  		cred = jujucloud.NewCredential(jujucloud.UserPassAuthType, map[string]string{
   209  			"username": "user",
   210  			"password": "password",
   211  		})
   212  	} else {
   213  		cred = *expected
   214  	}
   215  	c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{
   216  		"somecloud": {
   217  			AuthCredentials: map[string]jujucloud.Credential{
   218  				"fred": cred,
   219  			},
   220  		},
   221  	})
   222  	c.Assert(cmdtesting.Stdout(ctx), gc.Equals, msg)
   223  }
   224  
   225  func (s *addCredentialSuite) TestAddCredentialSingleAuthType(c *gc.C) {
   226  	s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType}
   227  	expected := `
   228  Enter credential name: 
   229  Using auth-type "userpass".
   230  
   231  Enter username: 
   232  Enter password: 
   233  Credential "fred" added locally for cloud "somecloud".
   234  
   235  `[1:]
   236  	s.assertAddUserpassCredential(c, "fred\nuser\npassword\n", nil, expected)
   237  }
   238  
   239  func (s *addCredentialSuite) TestAddCredentialRetryOnMissingMandatoryAttribute(c *gc.C) {
   240  	s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType}
   241  	expected := `
   242  Enter credential name: 
   243  Using auth-type "userpass".
   244  
   245  Enter username: 
   246  Enter username: 
   247  Enter password: 
   248  Credential "fred" added locally for cloud "somecloud".
   249  
   250  `[1:]
   251  	s.assertAddUserpassCredential(c, "fred\n\nuser\npassword\n", nil, expected)
   252  }
   253  
   254  func (s *addCredentialSuite) TestAddCredentialMultipleAuthType(c *gc.C) {
   255  	s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType, jujucloud.AccessKeyAuthType}
   256  	expected := `
   257  Enter credential name: 
   258  Auth Types
   259    userpass
   260    access-key
   261  
   262  Select auth type [userpass]: 
   263  Enter username: 
   264  Enter password: 
   265  Credential "fred" added locally for cloud "somecloud".
   266  
   267  `[1:]
   268  	s.assertAddUserpassCredential(c, "fred\nuserpass\nuser\npassword\n", nil, expected)
   269  }
   270  
   271  func (s *addCredentialSuite) TestAddCredentialInteractive(c *gc.C) {
   272  	s.authTypes = []jujucloud.AuthType{"interactive"}
   273  	s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{
   274  		"interactive": {{"username", jujucloud.CredentialAttr{}}},
   275  	}
   276  
   277  	stdin := strings.NewReader("bobscreds\nbob\n")
   278  	ctx, err := s.run(c, stdin, "somecloud")
   279  	c.Assert(err, jc.ErrorIsNil)
   280  
   281  	// there's an extra line return after Using auth-type because the rest get a
   282  	// second line return from the user hitting return when they enter a value
   283  	// (which is not shown here), but that one does not.
   284  	c.Assert(cmdtesting.Stdout(ctx), gc.Equals, `
   285  Enter credential name: 
   286  Using auth-type "interactive".
   287  
   288  Enter username: 
   289  Credential "bobscreds" added locally for cloud "somecloud".
   290  
   291  `[1:])
   292  
   293  	// FinalizeCredential should have generated a userpass credential
   294  	// based on the input from the interactive credential.
   295  	c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{
   296  		"somecloud": {
   297  			AuthCredentials: map[string]jujucloud.Credential{
   298  				"bobscreds": jujucloud.NewCredential(jujucloud.UserPassAuthType, map[string]string{
   299  					"username":             "bob",
   300  					"password":             "cloud-endpoint",
   301  					"application-password": "cloud-identity-endpoint",
   302  				}),
   303  			},
   304  		},
   305  	})
   306  }
   307  
   308  func (s *addCredentialSuite) TestAddCredentialCredSchemaInteractive(c *gc.C) {
   309  	s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType}
   310  	s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{
   311  		"interactive": {{"username", jujucloud.CredentialAttr{}}},
   312  		jujucloud.UserPassAuthType: {
   313  			{
   314  				"username", jujucloud.CredentialAttr{Optional: false},
   315  			}, {
   316  				"password", jujucloud.CredentialAttr{Hidden: true},
   317  			},
   318  		},
   319  	}
   320  
   321  	stdin := strings.NewReader("bobscreds\n\nbob\n")
   322  	ctx, err := s.run(c, stdin, "somecloud")
   323  	c.Assert(err, jc.ErrorIsNil)
   324  
   325  	// there's an extra line return after Using auth-type because the rest get a
   326  	// second line return from the user hitting return when they enter a value
   327  	// (which is not shown here), but that one does not.
   328  	c.Assert(cmdtesting.Stdout(ctx), gc.Equals, `
   329  Enter credential name: 
   330  Auth Types
   331    userpass
   332    interactive
   333  
   334  Select auth type [interactive]: 
   335  Enter username: 
   336  Credential "bobscreds" added locally for cloud "somecloud".
   337  
   338  `[1:])
   339  
   340  	// FinalizeCredential should have generated a userpass credential
   341  	// based on the input from the interactive credential.
   342  	c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{
   343  		"somecloud": {
   344  			AuthCredentials: map[string]jujucloud.Credential{
   345  				"bobscreds": jujucloud.NewCredential(jujucloud.UserPassAuthType, map[string]string{
   346  					"username":             "bob",
   347  					"password":             "cloud-endpoint",
   348  					"application-password": "cloud-identity-endpoint",
   349  				}),
   350  			},
   351  		},
   352  	})
   353  }
   354  
   355  func (s *addCredentialSuite) TestAddCredentialReplace(c *gc.C) {
   356  	s.store.Credentials = map[string]jujucloud.CloudCredential{
   357  		"somecloud": {
   358  			AuthCredentials: map[string]jujucloud.Credential{
   359  				"fred": jujucloud.NewCredential(jujucloud.UserPassAuthType, nil)},
   360  		},
   361  	}
   362  	s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType}
   363  	expected := `
   364  Enter credential name: 
   365  A credential "fred" already exists locally on this client.
   366  Replace local credential? (y/N): 
   367  Using auth-type "userpass".
   368  
   369  Enter username: 
   370  Enter password: 
   371  Credential "fred" updated locally for cloud "somecloud".
   372  
   373  `[1:]
   374  	s.assertAddUserpassCredential(c, "fred\ny\nuser\npassword\n", nil, expected)
   375  }
   376  
   377  func (s *addCredentialSuite) TestAddCredentialReplaceDecline(c *gc.C) {
   378  	cred := jujucloud.NewCredential(jujucloud.UserPassAuthType, nil)
   379  	s.store.Credentials = map[string]jujucloud.CloudCredential{
   380  		"somecloud": {
   381  			AuthCredentials: map[string]jujucloud.Credential{
   382  				"fred": cred},
   383  		},
   384  	}
   385  	s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType}
   386  	expected := `
   387  Enter credential name: 
   388  A credential "fred" already exists locally on this client.
   389  Replace local credential? (y/N): 
   390  `[1:]
   391  	s.assertAddUserpassCredential(c, "fred\nn\n", &cred, expected)
   392  }
   393  
   394  func (s *addCredentialSuite) assertAddFileCredential(c *gc.C, input, fileKey string) {
   395  	dir := c.MkDir()
   396  	filename := filepath.Join(dir, "jsonfile")
   397  	err := ioutil.WriteFile(filename, []byte{}, 0600)
   398  	c.Assert(err, jc.ErrorIsNil)
   399  
   400  	stdin := strings.NewReader(fmt.Sprintf(input, filename))
   401  	addCmd := cloud.NewAddCredentialCommandForTest(s.store, s.cloudByNameFunc)
   402  	err = cmdtesting.InitCommand(addCmd, []string{"somecloud"})
   403  	c.Assert(err, jc.ErrorIsNil)
   404  	ctx := cmdtesting.ContextForDir(c, dir)
   405  	ctx.Stdin = stdin
   406  	err = addCmd.Run(ctx)
   407  	c.Assert(err, jc.ErrorIsNil)
   408  
   409  	c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{
   410  		"somecloud": {
   411  			AuthCredentials: map[string]jujucloud.Credential{
   412  				"fred": jujucloud.NewCredential(s.authTypes[0], map[string]string{
   413  					fileKey: filename,
   414  				}),
   415  			},
   416  		},
   417  	})
   418  }
   419  
   420  func (s *addCredentialSuite) TestAddJsonFileCredential(c *gc.C) {
   421  	s.authTypes = []jujucloud.AuthType{jujucloud.JSONFileAuthType}
   422  	s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{
   423  		jujucloud.JSONFileAuthType: {
   424  			{
   425  				"file",
   426  				jujucloud.CredentialAttr{
   427  					Optional: false,
   428  					FilePath: true,
   429  				},
   430  			},
   431  		},
   432  	}
   433  	// Input includes invalid file info.
   434  	s.assertAddFileCredential(c, "fred\nbadfile\n.\n%s\n", "file")
   435  }
   436  
   437  func (s *addCredentialSuite) TestAddCredentialWithFileAttr(c *gc.C) {
   438  	s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType}
   439  	s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{
   440  		jujucloud.UserPassAuthType: {
   441  			{
   442  				"key",
   443  				jujucloud.CredentialAttr{
   444  					FileAttr: "key-file",
   445  				},
   446  			},
   447  		},
   448  	}
   449  	// Input includes invalid file info.
   450  	s.assertAddFileCredential(c, "fred\nbadfile\n.\n%s\n", "key-file")
   451  }
   452  
   453  func (s *addCredentialSuite) assertAddCredentialWithOptions(c *gc.C, input string) {
   454  	s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType}
   455  	s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{
   456  		jujucloud.UserPassAuthType: {
   457  			{
   458  				"username", jujucloud.CredentialAttr{Optional: false},
   459  			}, {
   460  				"algorithm", jujucloud.CredentialAttr{Options: []interface{}{"optionA", "optionB"}},
   461  			},
   462  		},
   463  	}
   464  	// Input includes a bad option
   465  	stdin := strings.NewReader(input)
   466  	_, err := s.run(c, stdin, "somecloud")
   467  	c.Assert(err, jc.ErrorIsNil)
   468  	c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{
   469  		"somecloud": {
   470  			AuthCredentials: map[string]jujucloud.Credential{
   471  				"fred": jujucloud.NewCredential(jujucloud.UserPassAuthType, map[string]string{
   472  					"username":  "user",
   473  					"algorithm": "optionA",
   474  				}),
   475  			},
   476  		},
   477  	})
   478  }
   479  
   480  func (s *addCredentialSuite) TestAddCredentialWithOptions(c *gc.C) {
   481  	s.assertAddCredentialWithOptions(c, "fred\nuser\nbadoption\noptionA\n")
   482  }
   483  
   484  func (s *addCredentialSuite) TestAddCredentialWithOptionsAutofill(c *gc.C) {
   485  	s.assertAddCredentialWithOptions(c, "fred\nuser\n\n")
   486  }
   487  
   488  func (s *addCredentialSuite) TestAddMAASCredential(c *gc.C) {
   489  	s.authTypes = []jujucloud.AuthType{jujucloud.OAuth1AuthType}
   490  	s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{
   491  		jujucloud.OAuth1AuthType: {
   492  			{
   493  				"maas-oauth", jujucloud.CredentialAttr{},
   494  			},
   495  		},
   496  	}
   497  	stdin := strings.NewReader("fred\nauth:token\n")
   498  	_, err := s.run(c, stdin, "somecloud")
   499  	c.Assert(err, jc.ErrorIsNil)
   500  	c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{
   501  		"somecloud": {
   502  			AuthCredentials: map[string]jujucloud.Credential{
   503  				"fred": jujucloud.NewCredential(jujucloud.OAuth1AuthType, map[string]string{
   504  					"maas-oauth": "auth:token",
   505  				}),
   506  			},
   507  		},
   508  	})
   509  }
   510  
   511  func (s *addCredentialSuite) TestAddGCEFileCredentials(c *gc.C) {
   512  	s.authTypes = []jujucloud.AuthType{jujucloud.JSONFileAuthType}
   513  	s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{
   514  		jujucloud.JSONFileAuthType: {
   515  			{
   516  				"file",
   517  				jujucloud.CredentialAttr{
   518  					Description: "path to the credential file",
   519  					Optional:    false,
   520  					FilePath:    true,
   521  				},
   522  			},
   523  		},
   524  	}
   525  	sourceFile := s.createTestCredentialDataWithAuthType(c, fmt.Sprintf("%v", jujucloud.JSONFileAuthType))
   526  	stdin := strings.NewReader(fmt.Sprintf("blah\n%s\n", sourceFile))
   527  	ctx, err := s.run(c, stdin, "somecloud")
   528  	c.Assert(err, jc.ErrorIsNil)
   529  	expected := `
   530  Enter credential name: 
   531  Using auth-type "jsonfile".
   532  
   533  Enter path to the credential file: 
   534  Credential "blah" added locally for cloud "somecloud".
   535  
   536  `[1:]
   537  	c.Assert(cmdtesting.Stdout(ctx), gc.Equals, expected)
   538  }
   539  
   540  func (s *addCredentialSuite) TestShouldFinalizeCredentialWithEnvironProvider(c *gc.C) {
   541  	ctrl := gomock.NewController(c)
   542  	defer ctrl.Finish()
   543  
   544  	provider := environsTesting.NewMockEnvironProvider(ctrl)
   545  	cred := jujucloud.Credential{}
   546  	got := cloud.ShouldFinalizeCredential(provider, cred)
   547  	c.Assert(got, jc.IsFalse)
   548  }
   549  
   550  func (s *addCredentialSuite) TestShouldFinalizeCredentialSuccess(c *gc.C) {
   551  	ctrl := gomock.NewController(c)
   552  	defer ctrl.Finish()
   553  
   554  	provider := struct {
   555  		environs.EnvironProvider
   556  		*environsTesting.MockRequestFinalizeCredential
   557  	}{
   558  		EnvironProvider:               environsTesting.NewMockEnvironProvider(ctrl),
   559  		MockRequestFinalizeCredential: environsTesting.NewMockRequestFinalizeCredential(ctrl),
   560  	}
   561  
   562  	cred := jujucloud.Credential{}
   563  	provider.MockRequestFinalizeCredential.EXPECT().ShouldFinalizeCredential(cred).Return(true)
   564  
   565  	got := cloud.ShouldFinalizeCredential(provider, cred)
   566  	c.Assert(got, jc.IsTrue)
   567  }
   568  
   569  func (s *addCredentialSuite) TestShouldFinalizeCredentialFailure(c *gc.C) {
   570  	ctrl := gomock.NewController(c)
   571  	defer ctrl.Finish()
   572  
   573  	provider := struct {
   574  		environs.EnvironProvider
   575  		*environsTesting.MockRequestFinalizeCredential
   576  	}{
   577  		EnvironProvider:               environsTesting.NewMockEnvironProvider(ctrl),
   578  		MockRequestFinalizeCredential: environsTesting.NewMockRequestFinalizeCredential(ctrl),
   579  	}
   580  
   581  	cred := jujucloud.Credential{}
   582  	provider.MockRequestFinalizeCredential.EXPECT().ShouldFinalizeCredential(cred).Return(false)
   583  
   584  	got := cloud.ShouldFinalizeCredential(provider, cred)
   585  	c.Assert(got, jc.IsFalse)
   586  }