github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/api/client/cloud/cloud_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  	"reflect"
     9  	"sort"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names/v5"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/kr/pretty"
    15  	"go.uber.org/mock/gomock"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	basemocks "github.com/juju/juju/api/base/mocks"
    19  	cloudapi "github.com/juju/juju/api/client/cloud"
    20  	apiservererrors "github.com/juju/juju/apiserver/errors"
    21  	"github.com/juju/juju/cloud"
    22  	"github.com/juju/juju/rpc/params"
    23  	coretesting "github.com/juju/juju/testing"
    24  )
    25  
    26  type cloudSuite struct {
    27  }
    28  
    29  var _ = gc.Suite(&cloudSuite{})
    30  
    31  func (s *cloudSuite) SetUpTest(c *gc.C) {
    32  }
    33  
    34  func (s *cloudSuite) TestCloud(c *gc.C) {
    35  	ctrl := gomock.NewController(c)
    36  	defer ctrl.Finish()
    37  
    38  	args := params.Entities{
    39  		Entities: []params.Entity{{Tag: "cloud-foo"}},
    40  	}
    41  	res := new(params.CloudResults)
    42  	results := params.CloudResults{
    43  		Results: []params.CloudResult{{
    44  			Cloud: &params.Cloud{
    45  				Type:      "dummy",
    46  				AuthTypes: []string{"empty", "userpass"},
    47  				Regions:   []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}},
    48  			}},
    49  		},
    50  	}
    51  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
    52  	mockFacadeCaller.EXPECT().FacadeCall("Cloud", args, res).SetArg(2, results).Return(nil)
    53  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
    54  
    55  	result, err := client.Cloud(names.NewCloudTag("foo"))
    56  	c.Assert(err, jc.ErrorIsNil)
    57  	c.Assert(result, jc.DeepEquals, cloud.Cloud{
    58  		Name:      "foo",
    59  		Type:      "dummy",
    60  		AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType},
    61  		Regions:   []cloud.Region{{Name: "nether", Endpoint: "endpoint"}},
    62  	})
    63  }
    64  
    65  func (s *cloudSuite) TestCloudInfo(c *gc.C) {
    66  	ctrl := gomock.NewController(c)
    67  	defer ctrl.Finish()
    68  
    69  	args := params.Entities{
    70  		Entities: []params.Entity{
    71  			{Tag: "cloud-foo"}, {Tag: "cloud-bar"},
    72  		},
    73  	}
    74  	res := new(params.CloudInfoResults)
    75  	results := params.CloudInfoResults{
    76  		Results: []params.CloudInfoResult{{
    77  			Result: &params.CloudInfo{
    78  				CloudDetails: params.CloudDetails{
    79  					Type:      "dummy",
    80  					AuthTypes: []string{"empty", "userpass"},
    81  					Regions:   []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}},
    82  				},
    83  				Users: []params.CloudUserInfo{{
    84  					UserName:    "fred",
    85  					DisplayName: "Fred",
    86  					Access:      "admin",
    87  				}, {
    88  					UserName:    "bob",
    89  					DisplayName: "Bob",
    90  					Access:      "add-model",
    91  				}},
    92  			},
    93  		}, {
    94  			Result: &params.CloudInfo{
    95  				CloudDetails: params.CloudDetails{
    96  					Type:      "dummy",
    97  					AuthTypes: []string{"empty", "userpass"},
    98  					Regions:   []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}},
    99  				},
   100  				Users: []params.CloudUserInfo{{
   101  					UserName:    "mary",
   102  					DisplayName: "Mary",
   103  					Access:      "admin",
   104  				}},
   105  			},
   106  		}},
   107  	}
   108  
   109  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   110  	mockFacadeCaller.EXPECT().FacadeCall("CloudInfo", args, res).SetArg(2, results).Return(nil)
   111  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   112  
   113  	result, err := client.CloudInfo([]names.CloudTag{
   114  		names.NewCloudTag("foo"),
   115  		names.NewCloudTag("bar"),
   116  	})
   117  	c.Assert(err, jc.ErrorIsNil)
   118  	c.Assert(result, jc.DeepEquals, []cloudapi.CloudInfo{{
   119  		Cloud: cloud.Cloud{
   120  			Name:      "foo",
   121  			Type:      "dummy",
   122  			AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType},
   123  			Regions:   []cloud.Region{{Name: "nether", Endpoint: "endpoint"}},
   124  		},
   125  		Users: map[string]cloudapi.CloudUserInfo{
   126  			"bob": {
   127  				DisplayName: "Bob",
   128  				Access:      "add-model",
   129  			},
   130  			"fred": {
   131  				DisplayName: "Fred",
   132  				Access:      "admin",
   133  			},
   134  		},
   135  	}, {
   136  		Cloud: cloud.Cloud{
   137  			Name:      "bar",
   138  			Type:      "dummy",
   139  			AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType},
   140  			Regions:   []cloud.Region{{Name: "nether", Endpoint: "endpoint"}},
   141  		},
   142  		Users: map[string]cloudapi.CloudUserInfo{
   143  			"mary": {
   144  				DisplayName: "Mary",
   145  				Access:      "admin",
   146  			},
   147  		},
   148  	}})
   149  }
   150  
   151  func (s *cloudSuite) TestClouds(c *gc.C) {
   152  	ctrl := gomock.NewController(c)
   153  	defer ctrl.Finish()
   154  
   155  	res := new(params.CloudsResult)
   156  	results := params.CloudsResult{
   157  		Clouds: map[string]params.Cloud{
   158  			"cloud-foo": {
   159  				Type: "bar",
   160  			},
   161  			"cloud-baz": {
   162  				Type:      "qux",
   163  				AuthTypes: []string{"empty", "userpass"},
   164  				Regions:   []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}},
   165  			},
   166  		}}
   167  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   168  	mockFacadeCaller.EXPECT().FacadeCall("Clouds", nil, res).SetArg(2, results).Return(nil)
   169  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   170  
   171  	clouds, err := client.Clouds()
   172  	c.Assert(err, jc.ErrorIsNil)
   173  	c.Assert(clouds, jc.DeepEquals, map[names.CloudTag]cloud.Cloud{
   174  		names.NewCloudTag("foo"): {
   175  			Name: "foo",
   176  			Type: "bar",
   177  		},
   178  		names.NewCloudTag("baz"): {
   179  			Name:      "baz",
   180  			Type:      "qux",
   181  			AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType},
   182  			Regions:   []cloud.Region{{Name: "nether", Endpoint: "endpoint"}},
   183  		},
   184  	})
   185  }
   186  
   187  func (s *cloudSuite) TestUserCredentials(c *gc.C) {
   188  	ctrl := gomock.NewController(c)
   189  	defer ctrl.Finish()
   190  
   191  	args := params.UserClouds{UserClouds: []params.UserCloud{{
   192  		UserTag:  "user-bob",
   193  		CloudTag: "cloud-foo",
   194  	}}}
   195  	res := new(params.StringsResults)
   196  	results := params.StringsResults{
   197  		Results: []params.StringsResult{{
   198  			Result: []string{
   199  				"cloudcred-foo_bob_one",
   200  				"cloudcred-foo_bob_two",
   201  			},
   202  		}},
   203  	}
   204  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   205  	mockFacadeCaller.EXPECT().FacadeCall("UserCredentials", args, res).SetArg(2, results).Return(nil)
   206  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   207  
   208  	result, err := client.UserCredentials(names.NewUserTag("bob"), names.NewCloudTag("foo"))
   209  	c.Assert(err, jc.ErrorIsNil)
   210  	c.Assert(result, jc.SameContents, []names.CloudCredentialTag{
   211  		names.NewCloudCredentialTag("foo/bob/one"),
   212  		names.NewCloudCredentialTag("foo/bob/two"),
   213  	})
   214  }
   215  
   216  func (s *cloudSuite) TestUpdateCredential(c *gc.C) {
   217  	ctrl := gomock.NewController(c)
   218  	defer ctrl.Finish()
   219  
   220  	args := params.UpdateCredentialArgs{
   221  		Credentials: []params.TaggedCredential{{
   222  			Tag: "cloudcred-foo_bob_bar",
   223  			Credential: params.CloudCredential{
   224  				AuthType: "userpass",
   225  				Attributes: map[string]string{
   226  					"username": "admin",
   227  					"password": "adm1n",
   228  				},
   229  			},
   230  		}}}
   231  	res := new(params.UpdateCredentialResults)
   232  	results := params.UpdateCredentialResults{
   233  		Results: []params.UpdateCredentialResult{{}},
   234  	}
   235  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   236  	mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil)
   237  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   238  
   239  	result, err := client.UpdateCredentialsCheckModels(testCredentialTag, testCredential)
   240  	c.Assert(err, jc.ErrorIsNil)
   241  	c.Assert(result, gc.IsNil)
   242  }
   243  
   244  func (s *cloudSuite) TestUpdateCredentialError(c *gc.C) {
   245  	ctrl := gomock.NewController(c)
   246  	defer ctrl.Finish()
   247  
   248  	args := params.UpdateCredentialArgs{
   249  		Credentials: []params.TaggedCredential{{
   250  			Tag: "cloudcred-foo_bob_bar",
   251  			Credential: params.CloudCredential{
   252  				AuthType: "userpass",
   253  				Attributes: map[string]string{
   254  					"username": "admin",
   255  					"password": "adm1n",
   256  				},
   257  			},
   258  		}}}
   259  	res := new(params.UpdateCredentialResults)
   260  	results := params.UpdateCredentialResults{
   261  		Results: []params.UpdateCredentialResult{
   262  			{
   263  				CredentialTag: "cloudcred-foo_bob_bar",
   264  				Error:         apiservererrors.ServerError(errors.New("validation failure")),
   265  			},
   266  		},
   267  	}
   268  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   269  	mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil)
   270  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   271  
   272  	errs, err := client.UpdateCredentialsCheckModels(testCredentialTag, testCredential)
   273  	c.Assert(err, gc.ErrorMatches, "validation failure")
   274  	c.Assert(errs, gc.IsNil)
   275  }
   276  
   277  func (s *cloudSuite) TestUpdateCredentialManyResults(c *gc.C) {
   278  	ctrl := gomock.NewController(c)
   279  	defer ctrl.Finish()
   280  
   281  	args := params.UpdateCredentialArgs{
   282  		Credentials: []params.TaggedCredential{{
   283  			Tag: "cloudcred-foo_bob_bar",
   284  			Credential: params.CloudCredential{
   285  				AuthType: "userpass",
   286  				Attributes: map[string]string{
   287  					"username": "admin",
   288  					"password": "adm1n",
   289  				},
   290  			},
   291  		}}}
   292  	res := new(params.UpdateCredentialResults)
   293  	results := params.UpdateCredentialResults{
   294  		Results: []params.UpdateCredentialResult{
   295  			{},
   296  			{},
   297  		}}
   298  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   299  	mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil)
   300  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   301  
   302  	result, err := client.UpdateCredentialsCheckModels(testCredentialTag, testCredential)
   303  	c.Assert(err, gc.ErrorMatches, `expected 1 result got 2 when updating credentials`)
   304  	c.Assert(result, gc.IsNil)
   305  }
   306  
   307  func (s *cloudSuite) TestUpdateCredentialModelErrors(c *gc.C) {
   308  	ctrl := gomock.NewController(c)
   309  	defer ctrl.Finish()
   310  
   311  	args := params.UpdateCredentialArgs{
   312  		Credentials: []params.TaggedCredential{{
   313  			Tag: "cloudcred-foo_bob_bar",
   314  			Credential: params.CloudCredential{
   315  				AuthType: "userpass",
   316  				Attributes: map[string]string{
   317  					"username": "admin",
   318  					"password": "adm1n",
   319  				},
   320  			},
   321  		}}}
   322  	res := new(params.UpdateCredentialResults)
   323  	results := params.UpdateCredentialResults{
   324  		Results: []params.UpdateCredentialResult{
   325  			{
   326  				CredentialTag: testCredentialTag.String(),
   327  				Models: []params.UpdateCredentialModelResult{
   328  					{
   329  						ModelUUID: coretesting.ModelTag.Id(),
   330  						ModelName: "test-model",
   331  						Errors: []params.ErrorResult{
   332  							{apiservererrors.ServerError(errors.New("validation failure one"))},
   333  							{apiservererrors.ServerError(errors.New("validation failure two"))},
   334  						},
   335  					},
   336  				},
   337  			},
   338  		}}
   339  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   340  	mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil)
   341  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   342  
   343  	errs, err := client.UpdateCredentialsCheckModels(testCredentialTag, testCredential)
   344  	c.Assert(err, jc.ErrorIsNil)
   345  	c.Assert(errs, gc.DeepEquals, []params.UpdateCredentialModelResult{
   346  		{
   347  			ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d",
   348  			ModelName: "test-model",
   349  			Errors: []params.ErrorResult{
   350  				{Error: &params.Error{Message: "validation failure one", Code: ""}},
   351  				{Error: &params.Error{Message: "validation failure two", Code: ""}},
   352  			},
   353  		},
   354  	})
   355  }
   356  
   357  var (
   358  	testCredentialTag = names.NewCloudCredentialTag("foo/bob/bar")
   359  	testCredential    = cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
   360  		"username": "admin",
   361  		"password": "adm1n",
   362  	})
   363  )
   364  
   365  func (s *cloudSuite) TestRevokeCredential(c *gc.C) {
   366  	ctrl := gomock.NewController(c)
   367  	defer ctrl.Finish()
   368  
   369  	args := params.RevokeCredentialArgs{
   370  		Credentials: []params.RevokeCredentialArg{
   371  			{Tag: "cloudcred-foo_bob_bar", Force: true},
   372  		},
   373  	}
   374  	res := new(params.ErrorResults)
   375  	results := params.ErrorResults{
   376  		Results: []params.ErrorResult{{}},
   377  	}
   378  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   379  	mockFacadeCaller.EXPECT().FacadeCall("RevokeCredentialsCheckModels", args, res).SetArg(2, results).Return(nil)
   380  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   381  
   382  	tag := names.NewCloudCredentialTag("foo/bob/bar")
   383  	err := client.RevokeCredential(tag, true)
   384  	c.Assert(err, jc.ErrorIsNil)
   385  }
   386  
   387  func (s *cloudSuite) TestCredentials(c *gc.C) {
   388  	ctrl := gomock.NewController(c)
   389  	defer ctrl.Finish()
   390  
   391  	args := params.Entities{Entities: []params.Entity{{
   392  		Tag: "cloudcred-foo_bob_bar",
   393  	}}}
   394  	res := new(params.CloudCredentialResults)
   395  	results := params.CloudCredentialResults{
   396  		Results: []params.CloudCredentialResult{
   397  			{
   398  				Result: &params.CloudCredential{
   399  					AuthType:   "userpass",
   400  					Attributes: map[string]string{"username": "fred"},
   401  					Redacted:   []string{"password"},
   402  				},
   403  			}, {
   404  				Result: &params.CloudCredential{
   405  					AuthType:   "userpass",
   406  					Attributes: map[string]string{"username": "mary"},
   407  					Redacted:   []string{"password"},
   408  				},
   409  			},
   410  		},
   411  	}
   412  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   413  	mockFacadeCaller.EXPECT().FacadeCall("Credential", args, res).SetArg(2, results).Return(nil)
   414  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   415  
   416  	tag := names.NewCloudCredentialTag("foo/bob/bar")
   417  	result, err := client.Credentials(tag)
   418  	c.Assert(err, jc.ErrorIsNil)
   419  	c.Assert(result, jc.DeepEquals, []params.CloudCredentialResult{
   420  		{
   421  			Result: &params.CloudCredential{
   422  				AuthType:   "userpass",
   423  				Attributes: map[string]string{"username": "fred"},
   424  				Redacted:   []string{"password"},
   425  			},
   426  		}, {
   427  			Result: &params.CloudCredential{
   428  				AuthType:   "userpass",
   429  				Attributes: map[string]string{"username": "mary"},
   430  				Redacted:   []string{"password"},
   431  			},
   432  		},
   433  	})
   434  }
   435  
   436  var testCloud = cloud.Cloud{
   437  	Name:      "foo",
   438  	Type:      "dummy",
   439  	AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType},
   440  	Regions:   []cloud.Region{{Name: "nether", Endpoint: "endpoint"}},
   441  }
   442  
   443  func (s *cloudSuite) TestAddCloudForce(c *gc.C) {
   444  	ctrl := gomock.NewController(c)
   445  	defer ctrl.Finish()
   446  
   447  	force := true
   448  	args := params.AddCloudArgs{
   449  		Name:  "foo",
   450  		Cloud: cloudapi.CloudToParams(testCloud),
   451  		Force: &force,
   452  	}
   453  
   454  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   455  	mockFacadeCaller.EXPECT().FacadeCall("AddCloud", args, nil).Return(nil)
   456  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   457  
   458  	err := client.AddCloud(testCloud, force)
   459  	c.Assert(err, jc.ErrorIsNil)
   460  }
   461  
   462  func (s *cloudSuite) TestCredentialContentsArgumentCheck(c *gc.C) {
   463  	ctrl := gomock.NewController(c)
   464  	defer ctrl.Finish()
   465  
   466  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   467  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   468  
   469  	// Check supplying cloud name without credential name is invalid.
   470  	result, err := client.CredentialContents("cloud", "", true)
   471  	c.Assert(result, gc.IsNil)
   472  	c.Assert(err, gc.ErrorMatches, "credential name must be supplied")
   473  
   474  	// Check supplying credential name without cloud name is invalid.
   475  	result, err = client.CredentialContents("", "credential", true)
   476  	c.Assert(result, gc.IsNil)
   477  	c.Assert(err, gc.ErrorMatches, "cloud name must be supplied")
   478  }
   479  
   480  func (s *cloudSuite) TestCredentialContentsAll(c *gc.C) {
   481  	ctrl := gomock.NewController(c)
   482  	defer ctrl.Finish()
   483  
   484  	expectedResults := []params.CredentialContentResult{
   485  		{
   486  			Result: &params.ControllerCredentialInfo{
   487  				Content: params.CredentialContent{
   488  					Cloud:    "cloud-name",
   489  					Name:     "credential-name",
   490  					AuthType: "userpass",
   491  					Attributes: map[string]string{
   492  						"username": "fred",
   493  						"password": "sekret"},
   494  				},
   495  				Models: []params.ModelAccess{
   496  					{Model: "abcmodel", Access: "admin"},
   497  					{Model: "xyzmodel", Access: "read"},
   498  					{Model: "no-access-model"}, // no access
   499  				},
   500  			},
   501  		}, {
   502  			Error: apiservererrors.ServerError(errors.New("boom")),
   503  		},
   504  	}
   505  
   506  	args := params.CloudCredentialArgs{
   507  		IncludeSecrets: true,
   508  	}
   509  	res := new(params.CredentialContentResults)
   510  	results := params.CredentialContentResults{
   511  		Results: expectedResults,
   512  	}
   513  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   514  	mockFacadeCaller.EXPECT().FacadeCall("CredentialContents", args, res).SetArg(2, results).Return(nil)
   515  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   516  
   517  	ress, err := client.CredentialContents("", "", true)
   518  	c.Assert(err, jc.ErrorIsNil)
   519  	c.Assert(ress, jc.DeepEquals, expectedResults)
   520  }
   521  
   522  func (s *cloudSuite) TestCredentialContentsOne(c *gc.C) {
   523  	ctrl := gomock.NewController(c)
   524  	defer ctrl.Finish()
   525  
   526  	args := params.CloudCredentialArgs{
   527  		IncludeSecrets: true,
   528  		Credentials: []params.CloudCredentialArg{
   529  			{CloudName: "cloud-name", CredentialName: "credential-name"},
   530  		},
   531  	}
   532  	res := new(params.CredentialContentResults)
   533  	ress := params.CredentialContentResults{
   534  		Results: []params.CredentialContentResult{
   535  			{},
   536  		},
   537  	}
   538  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   539  	mockFacadeCaller.EXPECT().FacadeCall("CredentialContents", args, res).SetArg(2, ress).Return(nil)
   540  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   541  
   542  	results, err := client.CredentialContents("cloud-name", "credential-name", true)
   543  	c.Assert(err, jc.ErrorIsNil)
   544  	c.Assert(results, gc.HasLen, 1)
   545  }
   546  
   547  func (s *cloudSuite) TestCredentialContentsGotMoreThanBargainedFor(c *gc.C) {
   548  	ctrl := gomock.NewController(c)
   549  	defer ctrl.Finish()
   550  
   551  	args := params.CloudCredentialArgs{
   552  		IncludeSecrets: true,
   553  		Credentials: []params.CloudCredentialArg{
   554  			{CloudName: "cloud-name", CredentialName: "credential-name"},
   555  		},
   556  	}
   557  	res := new(params.CredentialContentResults)
   558  	ress := params.CredentialContentResults{
   559  		Results: []params.CredentialContentResult{
   560  			{},
   561  			{},
   562  		},
   563  	}
   564  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   565  	mockFacadeCaller.EXPECT().FacadeCall("CredentialContents", args, res).SetArg(2, ress).Return(nil)
   566  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   567  
   568  	results, err := client.CredentialContents("cloud-name", "credential-name", true)
   569  	c.Assert(results, gc.IsNil)
   570  	c.Assert(err, gc.ErrorMatches, "expected 1 result for credential \"cloud-name\" on cloud \"credential-name\", got 2")
   571  }
   572  
   573  func (s *cloudSuite) TestCredentialContentsServerError(c *gc.C) {
   574  	ctrl := gomock.NewController(c)
   575  	defer ctrl.Finish()
   576  
   577  	args := params.CloudCredentialArgs{
   578  		IncludeSecrets: true,
   579  	}
   580  	res := new(params.CredentialContentResults)
   581  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   582  	mockFacadeCaller.EXPECT().FacadeCall("CredentialContents", args, res).Return(errors.New("boom"))
   583  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   584  
   585  	results, err := client.CredentialContents("", "", true)
   586  	c.Assert(results, gc.IsNil)
   587  	c.Assert(err, gc.ErrorMatches, "boom")
   588  }
   589  
   590  func (s *cloudSuite) TestRemoveCloud(c *gc.C) {
   591  	ctrl := gomock.NewController(c)
   592  	defer ctrl.Finish()
   593  
   594  	args := params.Entities{
   595  		Entities: []params.Entity{{Tag: "cloud-foo"}},
   596  	}
   597  	res := new(params.ErrorResults)
   598  	ress := params.ErrorResults{
   599  		Results: []params.ErrorResult{{
   600  			Error: &params.Error{Message: "FAIL"},
   601  		}},
   602  	}
   603  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   604  	mockFacadeCaller.EXPECT().FacadeCall("RemoveClouds", args, res).SetArg(2, ress).Return(nil)
   605  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   606  
   607  	err := client.RemoveCloud("foo")
   608  	c.Assert(err, gc.ErrorMatches, "FAIL")
   609  }
   610  
   611  func (s *cloudSuite) TestRemoveCloudErrorMapping(c *gc.C) {
   612  	ctrl := gomock.NewController(c)
   613  	defer ctrl.Finish()
   614  
   615  	args := params.Entities{
   616  		Entities: []params.Entity{{Tag: "cloud-foo"}},
   617  	}
   618  	res := new(params.ErrorResults)
   619  	ress := params.ErrorResults{
   620  		Results: []params.ErrorResult{{
   621  			Error: &params.Error{
   622  				Code:    params.CodeNotFound,
   623  				Message: `cloud "cloud-foo" not found`,
   624  			}},
   625  		},
   626  	}
   627  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   628  	mockFacadeCaller.EXPECT().FacadeCall("RemoveClouds", args, res).SetArg(2, ress).Return(nil)
   629  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   630  
   631  	err := client.RemoveCloud("foo")
   632  	c.Assert(err, jc.Satisfies, errors.IsNotFound, gc.Commentf("expected client to be map server error into a NotFound error"))
   633  }
   634  
   635  func (s *cloudSuite) TestGrantCloud(c *gc.C) {
   636  	ctrl := gomock.NewController(c)
   637  	defer ctrl.Finish()
   638  
   639  	args := params.ModifyCloudAccessRequest{
   640  		Changes: []params.ModifyCloudAccess{
   641  			{UserTag: "user-fred", CloudTag: "cloud-fluffy", Action: "grant", Access: "admin"},
   642  		},
   643  	}
   644  	res := new(params.ErrorResults)
   645  	ress := params.ErrorResults{
   646  		Results: []params.ErrorResult{{
   647  			Error: &params.Error{Message: "FAIL"}},
   648  		},
   649  	}
   650  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   651  	mockFacadeCaller.EXPECT().FacadeCall("ModifyCloudAccess", args, res).SetArg(2, ress).Return(nil)
   652  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   653  
   654  	err := client.GrantCloud("fred", "admin", "fluffy")
   655  	c.Assert(err, gc.ErrorMatches, "FAIL")
   656  }
   657  
   658  func (s *cloudSuite) TestRevokeCloud(c *gc.C) {
   659  	ctrl := gomock.NewController(c)
   660  	defer ctrl.Finish()
   661  
   662  	args := params.ModifyCloudAccessRequest{
   663  		Changes: []params.ModifyCloudAccess{
   664  			{UserTag: "user-fred", CloudTag: "cloud-fluffy", Action: "revoke", Access: "admin"},
   665  		},
   666  	}
   667  	res := new(params.ErrorResults)
   668  	ress := params.ErrorResults{
   669  		Results: []params.ErrorResult{{
   670  			Error: &params.Error{Message: "FAIL"}},
   671  		},
   672  	}
   673  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   674  	mockFacadeCaller.EXPECT().FacadeCall("ModifyCloudAccess", args, res).SetArg(2, ress).Return(nil)
   675  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   676  
   677  	err := client.RevokeCloud("fred", "admin", "fluffy")
   678  	c.Assert(err, gc.ErrorMatches, "FAIL")
   679  }
   680  
   681  func createCredentials(n int) map[string]cloud.Credential {
   682  	result := map[string]cloud.Credential{}
   683  	for i := 0; i < n; i++ {
   684  		result[names.NewCloudCredentialTag(fmt.Sprintf("foo/bob/bar%d", i)).String()] = testCredential
   685  	}
   686  	return result
   687  }
   688  
   689  func (s *cloudSuite) TestUpdateCloud(c *gc.C) {
   690  	ctrl := gomock.NewController(c)
   691  	defer ctrl.Finish()
   692  
   693  	updatedCloud := cloud.Cloud{
   694  		Name:      "foo",
   695  		Type:      "dummy",
   696  		AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType},
   697  		Regions:   []cloud.Region{{Name: "nether", Endpoint: "endpoint"}},
   698  	}
   699  
   700  	args := params.UpdateCloudArgs{Clouds: []params.AddCloudArgs{{
   701  		Name:  "foo",
   702  		Cloud: cloudapi.CloudToParams(updatedCloud),
   703  	}}}
   704  	res := new(params.ErrorResults)
   705  	results := params.ErrorResults{
   706  		Results: []params.ErrorResult{{}},
   707  	}
   708  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   709  	mockFacadeCaller.EXPECT().FacadeCall("UpdateCloud", args, res).SetArg(2, results).Return(nil)
   710  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   711  
   712  	err := client.UpdateCloud(updatedCloud)
   713  
   714  	c.Assert(err, jc.ErrorIsNil)
   715  }
   716  
   717  func (s *cloudSuite) TestUpdateCloudsCredentials(c *gc.C) {
   718  	ctrl := gomock.NewController(c)
   719  	defer ctrl.Finish()
   720  
   721  	args := params.UpdateCredentialArgs{
   722  		Force: true,
   723  		Credentials: []params.TaggedCredential{{
   724  			Tag: "cloudcred-foo_bob_bar0",
   725  			Credential: params.CloudCredential{
   726  				AuthType: "userpass",
   727  				Attributes: map[string]string{
   728  					"username": "admin",
   729  					"password": "adm1n",
   730  				},
   731  			},
   732  		}}}
   733  	res := new(params.UpdateCredentialResults)
   734  	results := params.UpdateCredentialResults{
   735  		Results: []params.UpdateCredentialResult{{}},
   736  	}
   737  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   738  	mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil)
   739  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   740  
   741  	result, err := client.UpdateCloudsCredentials(createCredentials(1), true)
   742  	c.Assert(err, jc.ErrorIsNil)
   743  	c.Assert(result, gc.DeepEquals, []params.UpdateCredentialResult{{}})
   744  }
   745  
   746  func (s *cloudSuite) TestUpdateCloudsCredentialsError(c *gc.C) {
   747  	ctrl := gomock.NewController(c)
   748  	defer ctrl.Finish()
   749  
   750  	args := params.UpdateCredentialArgs{
   751  		Force: false,
   752  		Credentials: []params.TaggedCredential{{
   753  			Tag: "cloudcred-foo_bob_bar0",
   754  			Credential: params.CloudCredential{
   755  				AuthType: "userpass",
   756  				Attributes: map[string]string{
   757  					"username": "admin",
   758  					"password": "adm1n",
   759  				},
   760  			},
   761  		}}}
   762  	res := new(params.UpdateCredentialResults)
   763  	results := params.UpdateCredentialResults{
   764  		Results: []params.UpdateCredentialResult{
   765  			{CredentialTag: "cloudcred-foo_bob_bar0",
   766  				Error: apiservererrors.ServerError(errors.New("validation failure")),
   767  			},
   768  		},
   769  	}
   770  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   771  	mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil)
   772  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   773  
   774  	errs, err := client.UpdateCloudsCredentials(createCredentials(1), false)
   775  	c.Assert(err, jc.ErrorIsNil)
   776  	c.Assert(errs, gc.DeepEquals, []params.UpdateCredentialResult{
   777  		{CredentialTag: "cloudcred-foo_bob_bar0", Error: apiservererrors.ServerError(errors.New("validation failure"))},
   778  	})
   779  }
   780  
   781  func (s *cloudSuite) TestUpdateCloudsCredentialsManyResults(c *gc.C) {
   782  	ctrl := gomock.NewController(c)
   783  	defer ctrl.Finish()
   784  
   785  	args := params.UpdateCredentialArgs{
   786  		Force: false,
   787  		Credentials: []params.TaggedCredential{{
   788  			Tag: "cloudcred-foo_bob_bar0",
   789  			Credential: params.CloudCredential{
   790  				AuthType: "userpass",
   791  				Attributes: map[string]string{
   792  					"username": "admin",
   793  					"password": "adm1n",
   794  				},
   795  			},
   796  		}}}
   797  	res := new(params.UpdateCredentialResults)
   798  	results := params.UpdateCredentialResults{
   799  		Results: []params.UpdateCredentialResult{
   800  			{},
   801  			{},
   802  		}}
   803  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   804  	mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil)
   805  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   806  
   807  	result, err := client.UpdateCloudsCredentials(createCredentials(1), false)
   808  	c.Assert(err, gc.ErrorMatches, `expected 1 result got 2 when updating credentials`)
   809  	c.Assert(result, gc.IsNil)
   810  }
   811  
   812  func (s *cloudSuite) TestUpdateCloudsCredentialsManyMatchingResults(c *gc.C) {
   813  	ctrl := gomock.NewController(c)
   814  	defer ctrl.Finish()
   815  
   816  	args := params.UpdateCredentialArgs{
   817  		Force: false,
   818  	}
   819  	count := 2
   820  	for tag, credential := range createCredentials(count) {
   821  		args.Credentials = append(args.Credentials, params.TaggedCredential{
   822  			Tag: tag,
   823  			Credential: params.CloudCredential{
   824  				AuthType:   string(credential.AuthType()),
   825  				Attributes: credential.Attributes(),
   826  			},
   827  		})
   828  	}
   829  
   830  	res := new(params.UpdateCredentialResults)
   831  	results := params.UpdateCredentialResults{
   832  		Results: []params.UpdateCredentialResult{
   833  			{},
   834  			{},
   835  		}}
   836  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   837  	mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", cloudCredentialMatcher{args}, res).SetArg(2, results).Return(nil)
   838  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   839  
   840  	result, err := client.UpdateCloudsCredentials(createCredentials(count), false)
   841  	c.Assert(err, jc.ErrorIsNil)
   842  	c.Assert(result, gc.HasLen, count)
   843  }
   844  
   845  func (s *cloudSuite) TestUpdateCloudsCredentialsModelErrors(c *gc.C) {
   846  	ctrl := gomock.NewController(c)
   847  	defer ctrl.Finish()
   848  
   849  	args := params.UpdateCredentialArgs{
   850  		Force: false,
   851  		Credentials: []params.TaggedCredential{{
   852  			Tag: "cloudcred-foo_bob_bar0",
   853  			Credential: params.CloudCredential{
   854  				AuthType: "userpass",
   855  				Attributes: map[string]string{
   856  					"username": "admin",
   857  					"password": "adm1n",
   858  				},
   859  			},
   860  		}}}
   861  	res := new(params.UpdateCredentialResults)
   862  	results := params.UpdateCredentialResults{
   863  		Results: []params.UpdateCredentialResult{
   864  			{
   865  				CredentialTag: testCredentialTag.String(),
   866  				Models: []params.UpdateCredentialModelResult{
   867  					{
   868  						ModelUUID: coretesting.ModelTag.Id(),
   869  						ModelName: "test-model",
   870  						Errors: []params.ErrorResult{
   871  							{apiservererrors.ServerError(errors.New("validation failure one"))},
   872  							{apiservererrors.ServerError(errors.New("validation failure two"))},
   873  						},
   874  					},
   875  				},
   876  			},
   877  		}}
   878  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   879  	mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil)
   880  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   881  
   882  	errs, err := client.UpdateCloudsCredentials(createCredentials(1), false)
   883  	c.Assert(err, jc.ErrorIsNil)
   884  	c.Assert(errs, gc.DeepEquals, []params.UpdateCredentialResult{
   885  		{CredentialTag: "cloudcred-foo_bob_bar",
   886  			Models: []params.UpdateCredentialModelResult{
   887  				{ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d",
   888  					ModelName: "test-model",
   889  					Errors: []params.ErrorResult{
   890  						{Error: apiservererrors.ServerError(errors.New("validation failure one"))},
   891  						{Error: apiservererrors.ServerError(errors.New("validation failure two"))},
   892  					},
   893  				},
   894  			},
   895  		},
   896  	})
   897  }
   898  
   899  func (s *cloudSuite) TestAddCloudsCredentials(c *gc.C) {
   900  	ctrl := gomock.NewController(c)
   901  	defer ctrl.Finish()
   902  
   903  	args := params.UpdateCredentialArgs{
   904  		Credentials: []params.TaggedCredential{{
   905  			Tag: "cloudcred-foo_bob_bar0",
   906  			Credential: params.CloudCredential{
   907  				AuthType: "userpass",
   908  				Attributes: map[string]string{
   909  					"username": "admin",
   910  					"password": "adm1n",
   911  				},
   912  			},
   913  		}}}
   914  	res := new(params.UpdateCredentialResults)
   915  	results := params.UpdateCredentialResults{
   916  		Results: []params.UpdateCredentialResult{{}},
   917  	}
   918  	mockFacadeCaller := basemocks.NewMockFacadeCaller(ctrl)
   919  	mockFacadeCaller.EXPECT().FacadeCall("UpdateCredentialsCheckModels", args, res).SetArg(2, results).Return(nil)
   920  	client := cloudapi.NewClientFromCaller(mockFacadeCaller)
   921  
   922  	result, err := client.AddCloudsCredentials(createCredentials(1))
   923  	c.Assert(err, jc.ErrorIsNil)
   924  	c.Assert(result, gc.DeepEquals, []params.UpdateCredentialResult{{}})
   925  }
   926  
   927  type cloudCredentialMatcher struct {
   928  	arg params.UpdateCredentialArgs
   929  }
   930  
   931  func (c cloudCredentialMatcher) Matches(x interface{}) bool {
   932  	cred, ok := x.(params.UpdateCredentialArgs)
   933  	if !ok {
   934  		return false
   935  	}
   936  	if len(cred.Credentials) != len(c.arg.Credentials) {
   937  		return false
   938  	}
   939  	// sort both input and expected slices the same way to avoid ordering discrepancies when ranging.
   940  	sort.Slice(cred.Credentials, func(i, j int) bool { return cred.Credentials[i].Tag < cred.Credentials[j].Tag })
   941  	sort.Slice(c.arg.Credentials, func(i, j int) bool { return c.arg.Credentials[i].Tag < c.arg.Credentials[j].Tag })
   942  	for idx, taggedCred := range cred.Credentials {
   943  		if taggedCred.Tag != c.arg.Credentials[idx].Tag {
   944  			return false
   945  		}
   946  		if !reflect.DeepEqual(taggedCred.Credential, c.arg.Credentials[idx].Credential) {
   947  			return false
   948  		}
   949  	}
   950  
   951  	if cred.Force != c.arg.Force {
   952  		return false
   953  	}
   954  	return true
   955  }
   956  
   957  func (c cloudCredentialMatcher) String() string {
   958  	return pretty.Sprint(c.arg)
   959  }