github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/cloud/credentials_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cloud_test
     5  
     6  import (
     7  	"io/ioutil"
     8  	"path/filepath"
     9  	"regexp"
    10  
    11  	"github.com/juju/errors"
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "gopkg.in/check.v1"
    14  
    15  	"github.com/juju/juju/cloud"
    16  	"github.com/juju/juju/testing"
    17  )
    18  
    19  type credentialsSuite struct {
    20  	testing.FakeJujuXDGDataHomeSuite
    21  }
    22  
    23  var _ = gc.Suite(&credentialsSuite{})
    24  
    25  func (s *credentialsSuite) TestMarshalAccessKey(c *gc.C) {
    26  	creds := map[string]cloud.CloudCredential{
    27  		"aws": {
    28  			DefaultCredential: "default-cred",
    29  			DefaultRegion:     "us-west-2",
    30  			AuthCredentials: map[string]cloud.Credential{
    31  				"peter": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
    32  					"access-key": "key",
    33  					"secret-key": "secret",
    34  				}),
    35  				// TODO(wallyworld) - add anther credential once goyaml.v2 supports inline MapSlice.
    36  				//"paul": &cloud.AccessKeyCredentials{
    37  				//	Key: "paulkey",
    38  				//	Secret: "paulsecret",
    39  				//},
    40  			},
    41  		},
    42  	}
    43  	out, err := cloud.MarshalCredentials(creds)
    44  	c.Assert(err, jc.ErrorIsNil)
    45  	c.Assert(string(out), gc.Equals, `
    46  credentials:
    47    aws:
    48      default-credential: default-cred
    49      default-region: us-west-2
    50      peter:
    51        auth-type: access-key
    52        access-key: key
    53        secret-key: secret
    54  `[1:])
    55  }
    56  
    57  func (s *credentialsSuite) TestMarshalOpenstackAccessKey(c *gc.C) {
    58  	creds := map[string]cloud.CloudCredential{
    59  		"openstack": {
    60  			DefaultCredential: "default-cred",
    61  			DefaultRegion:     "region-a",
    62  			AuthCredentials: map[string]cloud.Credential{
    63  				"peter": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
    64  					"access-key":  "key",
    65  					"secret-key":  "secret",
    66  					"tenant-name": "tenant",
    67  				}),
    68  			},
    69  		},
    70  	}
    71  	out, err := cloud.MarshalCredentials(creds)
    72  	c.Assert(err, jc.ErrorIsNil)
    73  	c.Assert(string(out), gc.Equals, `
    74  credentials:
    75    openstack:
    76      default-credential: default-cred
    77      default-region: region-a
    78      peter:
    79        auth-type: access-key
    80        access-key: key
    81        secret-key: secret
    82        tenant-name: tenant
    83  `[1:])
    84  }
    85  
    86  func (s *credentialsSuite) TestMarshalOpenstackUserPass(c *gc.C) {
    87  	creds := map[string]cloud.CloudCredential{
    88  		"openstack": {
    89  			DefaultCredential: "default-cred",
    90  			DefaultRegion:     "region-a",
    91  			AuthCredentials: map[string]cloud.Credential{
    92  				"peter": cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
    93  					"username":    "user",
    94  					"password":    "secret",
    95  					"tenant-name": "tenant",
    96  				}),
    97  			},
    98  		},
    99  	}
   100  	out, err := cloud.MarshalCredentials(creds)
   101  	c.Assert(err, jc.ErrorIsNil)
   102  	c.Assert(string(out), gc.Equals, `
   103  credentials:
   104    openstack:
   105      default-credential: default-cred
   106      default-region: region-a
   107      peter:
   108        auth-type: userpass
   109        password: secret
   110        tenant-name: tenant
   111        username: user
   112  `[1:])
   113  }
   114  
   115  func (s *credentialsSuite) TestMarshalAzureCredntials(c *gc.C) {
   116  	creds := map[string]cloud.CloudCredential{
   117  		"azure": {
   118  			DefaultCredential: "default-cred",
   119  			DefaultRegion:     "Central US",
   120  			AuthCredentials: map[string]cloud.Credential{
   121  				"peter": cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
   122  					"application-id":       "app-id",
   123  					"application-password": "app-secret",
   124  					"subscription-id":      "subscription-id",
   125  					"tenant-id":            "tenant-id",
   126  				}),
   127  			},
   128  		},
   129  	}
   130  	out, err := cloud.MarshalCredentials(creds)
   131  	c.Assert(err, jc.ErrorIsNil)
   132  	c.Assert(string(out), gc.Equals, `
   133  credentials:
   134    azure:
   135      default-credential: default-cred
   136      default-region: Central US
   137      peter:
   138        auth-type: userpass
   139        application-id: app-id
   140        application-password: app-secret
   141        subscription-id: subscription-id
   142        tenant-id: tenant-id
   143  `[1:])
   144  }
   145  
   146  func (s *credentialsSuite) TestMarshalOAuth1(c *gc.C) {
   147  	creds := map[string]cloud.CloudCredential{
   148  		"maas": {
   149  			DefaultCredential: "default-cred",
   150  			DefaultRegion:     "region-default",
   151  			AuthCredentials: map[string]cloud.Credential{
   152  				"peter": cloud.NewCredential(cloud.OAuth1AuthType, map[string]string{
   153  					"consumer-key":    "consumer-key",
   154  					"consumer-secret": "consumer-secret",
   155  					"access-token":    "access-token",
   156  					"token-secret":    "token-secret",
   157  				}),
   158  			},
   159  		},
   160  	}
   161  	out, err := cloud.MarshalCredentials(creds)
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	c.Assert(string(out), gc.Equals, `
   164  credentials:
   165    maas:
   166      default-credential: default-cred
   167      default-region: region-default
   168      peter:
   169        auth-type: oauth1
   170        access-token: access-token
   171        consumer-key: consumer-key
   172        consumer-secret: consumer-secret
   173        token-secret: token-secret
   174  `[1:])
   175  }
   176  
   177  func (s *credentialsSuite) TestMarshalOAuth2(c *gc.C) {
   178  	creds := map[string]cloud.CloudCredential{
   179  		"google": {
   180  			DefaultCredential: "default-cred",
   181  			DefaultRegion:     "West US",
   182  			AuthCredentials: map[string]cloud.Credential{
   183  				"peter": cloud.NewCredential(cloud.OAuth2AuthType, map[string]string{
   184  					"client-id":    "client-id",
   185  					"client-email": "client-email",
   186  					"private-key":  "secret",
   187  				}),
   188  			},
   189  		},
   190  	}
   191  	out, err := cloud.MarshalCredentials(creds)
   192  	c.Assert(err, jc.ErrorIsNil)
   193  	c.Assert(string(out), gc.Equals, `
   194  credentials:
   195    google:
   196      default-credential: default-cred
   197      default-region: West US
   198      peter:
   199        auth-type: oauth2
   200        client-email: client-email
   201        client-id: client-id
   202        private-key: secret
   203  `[1:])
   204  }
   205  
   206  func (s *credentialsSuite) TestParseCredentials(c *gc.C) {
   207  	s.testParseCredentials(c, []byte(`
   208  credentials:
   209    aws:
   210      default-credential: peter
   211      default-region: us-east-2
   212      peter:
   213        auth-type: access-key
   214        access-key: key
   215        secret-key: secret
   216    aws-china:
   217      default-credential: zhu8jie
   218      zhu8jie:
   219        auth-type: access-key
   220        access-key: key
   221        secret-key: secret
   222      sun5kong:
   223        auth-type: access-key
   224        access-key: quay
   225        secret-key: sekrit
   226    aws-gov:
   227      default-region: us-gov-west-1
   228      supersekrit:
   229        auth-type: access-key
   230        access-key: super
   231        secret-key: sekrit
   232  `[1:]), map[string]cloud.CloudCredential{
   233  		"aws": cloud.CloudCredential{
   234  			DefaultCredential: "peter",
   235  			DefaultRegion:     "us-east-2",
   236  			AuthCredentials: map[string]cloud.Credential{
   237  				"peter": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
   238  					"access-key": "key",
   239  					"secret-key": "secret",
   240  				}),
   241  			},
   242  		},
   243  		"aws-china": cloud.CloudCredential{
   244  			DefaultCredential: "zhu8jie",
   245  			AuthCredentials: map[string]cloud.Credential{
   246  				"zhu8jie": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
   247  					"access-key": "key",
   248  					"secret-key": "secret",
   249  				}),
   250  				"sun5kong": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
   251  					"access-key": "quay",
   252  					"secret-key": "sekrit",
   253  				}),
   254  			},
   255  		},
   256  		"aws-gov": cloud.CloudCredential{
   257  			DefaultRegion: "us-gov-west-1",
   258  			AuthCredentials: map[string]cloud.Credential{
   259  				"supersekrit": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
   260  					"access-key": "super",
   261  					"secret-key": "sekrit",
   262  				}),
   263  			},
   264  		},
   265  	})
   266  }
   267  
   268  func (s *credentialsSuite) TestParseCredentialsUnknownAuthType(c *gc.C) {
   269  	// Unknown auth-type is not validated by ParseCredentials.
   270  	// Validation is deferred to FinalizeCredential.
   271  	s.testParseCredentials(c, []byte(`
   272  credentials:
   273    cloud-name:
   274      credential-name:
   275        auth-type: woop
   276  `[1:]), map[string]cloud.CloudCredential{
   277  		"cloud-name": cloud.CloudCredential{
   278  			AuthCredentials: map[string]cloud.Credential{
   279  				"credential-name": cloud.NewCredential("woop", nil),
   280  			},
   281  		},
   282  	})
   283  }
   284  
   285  func (s *credentialsSuite) testParseCredentials(c *gc.C, input []byte, expect map[string]cloud.CloudCredential) {
   286  	output, err := cloud.ParseCredentials(input)
   287  	c.Assert(err, jc.ErrorIsNil)
   288  	c.Assert(output, jc.DeepEquals, expect)
   289  }
   290  
   291  func (s *credentialsSuite) TestParseCredentialsMissingAuthType(c *gc.C) {
   292  	s.testParseCredentialsError(c, []byte(`
   293  credentials:
   294    cloud-name:
   295      credential-name:
   296        doesnt: really-matter
   297  `[1:]), "credentials.cloud-name.credential-name: missing auth-type")
   298  }
   299  
   300  func (s *credentialsSuite) TestParseCredentialsNonStringValue(c *gc.C) {
   301  	s.testParseCredentialsError(c, []byte(`
   302  credentials:
   303    cloud-name:
   304      credential-name:
   305        non-string-value: 123
   306  `[1:]), `credentials\.cloud-name\.credential-name\.non-string-value: expected string, got int\(123\)`)
   307  }
   308  
   309  func (s *credentialsSuite) testParseCredentialsError(c *gc.C, input []byte, expect string) {
   310  	_, err := cloud.ParseCredentials(input)
   311  	c.Assert(err, gc.ErrorMatches, expect)
   312  }
   313  
   314  func (s *credentialsSuite) TestFinalizeCredential(c *gc.C) {
   315  	cred := cloud.NewCredential(
   316  		cloud.UserPassAuthType,
   317  		map[string]string{
   318  			"key": "value",
   319  		},
   320  	)
   321  	schema := cloud.CredentialSchema{
   322  		{
   323  			"key",
   324  			cloud.CredentialAttr{
   325  				Description: "key credential",
   326  				Hidden:      true,
   327  			},
   328  		},
   329  	}
   330  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   331  		cloud.UserPassAuthType: schema,
   332  	}, readFileNotSupported)
   333  	c.Assert(err, jc.ErrorIsNil)
   334  }
   335  
   336  func (s *credentialsSuite) TestFinalizeCredentialFileAttr(c *gc.C) {
   337  	cred := cloud.NewCredential(
   338  		cloud.UserPassAuthType,
   339  		map[string]string{
   340  			"key-file": "path",
   341  			"quay":     "value",
   342  		},
   343  	)
   344  	schema := cloud.CredentialSchema{
   345  		{
   346  			"key",
   347  			cloud.CredentialAttr{
   348  				Description: "key credential",
   349  				Hidden:      true,
   350  				FileAttr:    "key-file",
   351  			},
   352  		}, {
   353  			"quay", cloud.CredentialAttr{FileAttr: "quay-file"},
   354  		},
   355  	}
   356  	readFile := func(s string) ([]byte, error) {
   357  		c.Assert(s, gc.Equals, "path")
   358  		return []byte("file-value"), nil
   359  	}
   360  	newCred, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   361  		cloud.UserPassAuthType: schema,
   362  	}, readFile)
   363  	c.Assert(err, jc.ErrorIsNil)
   364  	c.Assert(newCred.Attributes(), jc.DeepEquals, map[string]string{
   365  		"key":  "file-value",
   366  		"quay": "value",
   367  	})
   368  }
   369  
   370  func (s *credentialsSuite) TestFinalizeCredentialFileEmpty(c *gc.C) {
   371  	cred := cloud.NewCredential(
   372  		cloud.UserPassAuthType,
   373  		map[string]string{
   374  			"key-file": "path",
   375  		},
   376  	)
   377  	schema := cloud.CredentialSchema{
   378  		{
   379  			"key",
   380  			cloud.CredentialAttr{
   381  				Description: "key credential",
   382  				Hidden:      true,
   383  				FileAttr:    "key-file",
   384  			},
   385  		},
   386  	}
   387  	readFile := func(string) ([]byte, error) {
   388  		return nil, nil
   389  	}
   390  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   391  		cloud.UserPassAuthType: schema,
   392  	}, readFile)
   393  	c.Assert(err, gc.ErrorMatches, `empty file for "key" not valid`)
   394  }
   395  
   396  func (s *credentialsSuite) TestFinalizeCredentialFileAttrNeither(c *gc.C) {
   397  	cred := cloud.NewCredential(
   398  		cloud.UserPassAuthType,
   399  		map[string]string{},
   400  	)
   401  	schema := cloud.CredentialSchema{
   402  		{
   403  			"key",
   404  			cloud.CredentialAttr{
   405  				Description: "key credential",
   406  				Hidden:      true,
   407  				FileAttr:    "key-file",
   408  			},
   409  		},
   410  	}
   411  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   412  		cloud.UserPassAuthType: schema,
   413  	}, readFileNotSupported)
   414  	c.Assert(err, gc.ErrorMatches, `either "key" or "key-file" must be specified`)
   415  }
   416  
   417  func (s *credentialsSuite) TestFinalizeCredentialFileAttrBoth(c *gc.C) {
   418  	cred := cloud.NewCredential(
   419  		cloud.UserPassAuthType,
   420  		map[string]string{
   421  			"key":      "value",
   422  			"key-file": "path",
   423  		},
   424  	)
   425  	schema := cloud.CredentialSchema{
   426  		{
   427  			"key",
   428  			cloud.CredentialAttr{
   429  				Description: "key credential",
   430  				Hidden:      true,
   431  				FileAttr:    "key-file",
   432  			},
   433  		},
   434  	}
   435  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   436  		cloud.UserPassAuthType: schema,
   437  	}, readFileNotSupported)
   438  	c.Assert(err, gc.ErrorMatches, `specifying both "key" and "key-file" not valid`)
   439  }
   440  
   441  func (s *credentialsSuite) TestFinalizeCredentialInvalid(c *gc.C) {
   442  	cred := cloud.NewCredential(
   443  		cloud.UserPassAuthType,
   444  		map[string]string{},
   445  	)
   446  	schema := cloud.CredentialSchema{
   447  		{
   448  			"key",
   449  			cloud.CredentialAttr{
   450  				Description: "key credential",
   451  				Hidden:      true,
   452  			},
   453  		},
   454  	}
   455  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   456  		cloud.UserPassAuthType: schema,
   457  	}, readFileNotSupported)
   458  	c.Assert(err, gc.ErrorMatches, "key: expected string, got nothing")
   459  }
   460  
   461  func (s *credentialsSuite) TestFinalizeCredentialNotSupported(c *gc.C) {
   462  	cred := cloud.NewCredential(
   463  		cloud.OAuth2AuthType,
   464  		map[string]string{},
   465  	)
   466  	_, err := cloud.FinalizeCredential(
   467  		cred, map[cloud.AuthType]cloud.CredentialSchema{}, readFileNotSupported,
   468  	)
   469  	c.Assert(err, jc.Satisfies, errors.IsNotSupported)
   470  	c.Assert(err, gc.ErrorMatches, `auth-type "oauth2" not supported`)
   471  }
   472  
   473  func readFileNotSupported(f string) ([]byte, error) {
   474  	return nil, errors.NotSupportedf("reading file %q", f)
   475  }
   476  
   477  func (s *credentialsSuite) TestFinalizeCredentialMandatoryFieldMissing(c *gc.C) {
   478  	cred := cloud.NewCredential(
   479  		cloud.UserPassAuthType,
   480  		map[string]string{
   481  			"password": "secret",
   482  			"domain":   "domain",
   483  		},
   484  	)
   485  	schema := cloud.CredentialSchema{
   486  		{
   487  			"username", cloud.CredentialAttr{Optional: false},
   488  		}, {
   489  			"password", cloud.CredentialAttr{Hidden: true},
   490  		}, {
   491  			"domain", cloud.CredentialAttr{},
   492  		},
   493  	}
   494  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   495  		cloud.UserPassAuthType: schema,
   496  	}, nil)
   497  	c.Assert(err, gc.ErrorMatches, "username: expected string, got nothing")
   498  }
   499  
   500  func (s *credentialsSuite) TestFinalizeCredentialMandatoryFieldFromFile(c *gc.C) {
   501  	cred := cloud.NewCredential(
   502  		cloud.UserPassAuthType,
   503  		map[string]string{
   504  			"key-file": "path",
   505  		},
   506  	)
   507  	schema := cloud.CredentialSchema{
   508  		{
   509  			"key",
   510  			cloud.CredentialAttr{
   511  				Description: "key credential",
   512  				Optional:    false,
   513  				FileAttr:    "key-file",
   514  			},
   515  		},
   516  	}
   517  	readFile := func(s string) ([]byte, error) {
   518  		c.Assert(s, gc.Equals, "path")
   519  		return []byte("file-value"), nil
   520  	}
   521  	newCred, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   522  		cloud.UserPassAuthType: schema,
   523  	}, readFile)
   524  	c.Assert(err, jc.ErrorIsNil)
   525  	c.Assert(newCred.Attributes(), jc.DeepEquals, map[string]string{
   526  		"key": "file-value",
   527  	})
   528  }
   529  
   530  func (s *credentialsSuite) TestFinalizeCredentialExtraField(c *gc.C) {
   531  	cred := cloud.NewCredential(
   532  		cloud.UserPassAuthType,
   533  		map[string]string{
   534  			"username":   "user",
   535  			"password":   "secret",
   536  			"domain":     "domain",
   537  			"access-key": "access-key",
   538  		},
   539  	)
   540  	schema := cloud.CredentialSchema{
   541  		{
   542  			"username", cloud.CredentialAttr{Optional: false},
   543  		}, {
   544  			"password", cloud.CredentialAttr{Hidden: true},
   545  		}, {
   546  			"domain", cloud.CredentialAttr{},
   547  		},
   548  	}
   549  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   550  		cloud.UserPassAuthType: schema,
   551  	}, nil)
   552  	c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`unknown key "access-key" (value "access-key")`))
   553  }
   554  
   555  func (s *credentialsSuite) TestFinalizeCredentialInvalidChoice(c *gc.C) {
   556  	cred := cloud.NewCredential(
   557  		cloud.UserPassAuthType,
   558  		map[string]string{
   559  			"username":  "user",
   560  			"password":  "secret",
   561  			"algorithm": "foo",
   562  		},
   563  	)
   564  	schema := cloud.CredentialSchema{
   565  		{
   566  			"username", cloud.CredentialAttr{Optional: false},
   567  		}, {
   568  			"password", cloud.CredentialAttr{Hidden: true},
   569  		}, {
   570  			"algorithm", cloud.CredentialAttr{Options: []interface{}{"bar", "foobar"}},
   571  		},
   572  	}
   573  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   574  		cloud.UserPassAuthType: schema,
   575  	}, nil)
   576  	c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`algorithm: expected one of [bar foobar], got "foo"`))
   577  }
   578  
   579  func (s *credentialsSuite) TestFinalizeCredentialFilePath(c *gc.C) {
   580  	dir := c.MkDir()
   581  	filename := filepath.Join(dir, "filename")
   582  	err := ioutil.WriteFile(filename, []byte{}, 0600)
   583  	c.Assert(err, jc.ErrorIsNil)
   584  
   585  	cred := cloud.NewCredential(
   586  		cloud.JSONFileAuthType,
   587  		map[string]string{
   588  			"file": filename,
   589  		},
   590  	)
   591  	schema := cloud.CredentialSchema{
   592  		{
   593  			"file", cloud.CredentialAttr{FilePath: true},
   594  		},
   595  	}
   596  	newCred, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   597  		cloud.JSONFileAuthType: schema,
   598  	}, nil)
   599  	c.Assert(err, jc.ErrorIsNil)
   600  	c.Assert(newCred.Attributes(), jc.DeepEquals, map[string]string{
   601  		"file": filename,
   602  	})
   603  }
   604  
   605  func (s *credentialsSuite) TestFinalizeCredentialInvalidFilePath(c *gc.C) {
   606  	cred := cloud.NewCredential(
   607  		cloud.JSONFileAuthType,
   608  		map[string]string{
   609  			"file": filepath.Join(c.MkDir(), "somefile"),
   610  		},
   611  	)
   612  	schema := cloud.CredentialSchema{
   613  		{
   614  			"file", cloud.CredentialAttr{FilePath: true},
   615  		},
   616  	}
   617  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   618  		cloud.JSONFileAuthType: schema,
   619  	}, nil)
   620  	c.Assert(err, gc.ErrorMatches, "invalid file path: .*")
   621  }
   622  
   623  func (s *credentialsSuite) TestFinalizeCredentialRelativeFilePath(c *gc.C) {
   624  	cred := cloud.NewCredential(
   625  		cloud.JSONFileAuthType,
   626  		map[string]string{
   627  			"file": "file",
   628  		},
   629  	)
   630  	schema := cloud.CredentialSchema{
   631  		{
   632  			"file", cloud.CredentialAttr{FilePath: true},
   633  		},
   634  	}
   635  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   636  		cloud.JSONFileAuthType: schema,
   637  	}, nil)
   638  	c.Assert(err, gc.ErrorMatches, "file path must be an absolute path: file")
   639  }
   640  
   641  func (s *credentialsSuite) TestRemoveSecrets(c *gc.C) {
   642  	cred := cloud.NewCredential(
   643  		cloud.UserPassAuthType,
   644  		map[string]string{
   645  			"username": "user",
   646  			"password": "secret",
   647  		},
   648  	)
   649  	schema := cloud.CredentialSchema{
   650  		{
   651  			"username", cloud.CredentialAttr{},
   652  		}, {
   653  			"password", cloud.CredentialAttr{Hidden: true},
   654  		},
   655  	}
   656  	sanitisedCred, err := cloud.RemoveSecrets(cred, map[cloud.AuthType]cloud.CredentialSchema{
   657  		cloud.UserPassAuthType: schema,
   658  	})
   659  	c.Assert(err, jc.ErrorIsNil)
   660  	c.Assert(sanitisedCred.Attributes(), jc.DeepEquals, map[string]string{
   661  		"username": "user",
   662  	})
   663  }