github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/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  	"regexp"
     9  	"sort"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names/v5"
    13  	gitjujutesting "github.com/juju/testing"
    14  	jc "github.com/juju/testing/checkers"
    15  	"go.uber.org/mock/gomock"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	"github.com/juju/juju/apiserver/common/credentialcommon"
    19  	apiservererrors "github.com/juju/juju/apiserver/errors"
    20  	"github.com/juju/juju/apiserver/facades/client/cloud"
    21  	"github.com/juju/juju/apiserver/facades/client/cloud/mocks"
    22  	apiservertesting "github.com/juju/juju/apiserver/testing"
    23  	k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants"
    24  	jujucloud "github.com/juju/juju/cloud"
    25  	"github.com/juju/juju/core/permission"
    26  	"github.com/juju/juju/environs/context"
    27  	_ "github.com/juju/juju/provider/dummy"
    28  	"github.com/juju/juju/rpc/params"
    29  	"github.com/juju/juju/state"
    30  	statetesting "github.com/juju/juju/state/testing"
    31  	coretesting "github.com/juju/juju/testing"
    32  )
    33  
    34  type cloudSuite struct {
    35  	gitjujutesting.LoggingCleanupSuite
    36  	backend     *mocks.MockBackend
    37  	ctrlBackend *mocks.MockBackend
    38  	pool        *mocks.MockModelPoolBackend
    39  	api         *cloud.CloudAPI
    40  	authorizer  *apiservertesting.FakeAuthorizer
    41  }
    42  
    43  func (s *cloudSuite) setup(c *gc.C, userTag names.UserTag) *gomock.Controller {
    44  	ctrl := gomock.NewController(c)
    45  
    46  	s.backend = mocks.NewMockBackend(ctrl)
    47  	s.backend.EXPECT().ControllerTag().Return(coretesting.ControllerTag).AnyTimes()
    48  
    49  	s.pool = mocks.NewMockModelPoolBackend(ctrl)
    50  	s.authorizer = &apiservertesting.FakeAuthorizer{
    51  		Tag: userTag,
    52  	}
    53  
    54  	s.ctrlBackend = mocks.NewMockBackend(ctrl)
    55  	s.ctrlBackend.EXPECT().ControllerTag().Return(coretesting.ControllerTag).AnyTimes()
    56  
    57  	api, err := cloud.NewCloudAPI(s.backend, s.ctrlBackend, s.pool, s.authorizer)
    58  	c.Assert(err, jc.ErrorIsNil)
    59  	s.api = api
    60  	return ctrl
    61  }
    62  
    63  var _ = gc.Suite(&cloudSuite{})
    64  
    65  func newModelBackend(c *gc.C, aCloud jujucloud.Cloud, uuid string) *mockModelBackend {
    66  	return &mockModelBackend{
    67  		uuid: uuid,
    68  	}
    69  }
    70  
    71  func (s *cloudSuite) TestCloud(c *gc.C) {
    72  	defer s.setup(c, names.NewUserTag("admin")).Finish()
    73  
    74  	backend := s.backend.EXPECT()
    75  	backend.Cloud("my-cloud").Return(jujucloud.Cloud{
    76  		Name:      "dummy",
    77  		Type:      "dummy",
    78  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
    79  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
    80  	}, nil)
    81  
    82  	results, err := s.api.Cloud(params.Entities{
    83  		Entities: []params.Entity{{Tag: "cloud-my-cloud"}, {Tag: "machine-0"}},
    84  	})
    85  	c.Assert(err, jc.ErrorIsNil)
    86  	c.Assert(results.Results, gc.HasLen, 2)
    87  	c.Assert(results.Results[0].Error, gc.IsNil)
    88  	c.Assert(results.Results[0].Cloud, jc.DeepEquals, &params.Cloud{
    89  		Type:      "dummy",
    90  		AuthTypes: []string{"empty", "userpass"},
    91  		Regions:   []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}},
    92  	})
    93  	c.Assert(results.Results[1].Error, jc.DeepEquals, &params.Error{
    94  		Message: `"machine-0" is not a valid cloud tag`,
    95  	})
    96  }
    97  
    98  func (s *cloudSuite) TestCloudNotFound(c *gc.C) {
    99  	defer s.setup(c, names.NewUserTag("admin")).Finish()
   100  
   101  	backend := s.backend.EXPECT()
   102  	backend.Cloud("no-dice").Return(jujucloud.Cloud{}, errors.NotFoundf("cloud \"no-dice\""))
   103  
   104  	results, err := s.api.Cloud(params.Entities{
   105  		Entities: []params.Entity{{Tag: "cloud-no-dice"}},
   106  	})
   107  	c.Assert(err, jc.ErrorIsNil)
   108  	c.Assert(results.Results, gc.HasLen, 1)
   109  	c.Assert(results.Results[0].Error, gc.ErrorMatches, "cloud \"no-dice\" not found")
   110  }
   111  
   112  func (s *cloudSuite) TestClouds(c *gc.C) {
   113  	bruce := names.NewUserTag("bruce")
   114  	defer s.setup(c, bruce).Finish()
   115  
   116  	ctrlBackend := s.ctrlBackend.EXPECT()
   117  
   118  	ctrlBackend.GetCloudAccess("my-cloud",
   119  		bruce).Return(permission.AddModelAccess, nil)
   120  	ctrlBackend.GetCloudAccess("your-cloud",
   121  		bruce).Return(permission.NoAccess, nil)
   122  
   123  	backend := s.backend.EXPECT()
   124  	backend.Clouds().Return(map[names.CloudTag]jujucloud.Cloud{
   125  		names.NewCloudTag("my-cloud"): {
   126  			Name:      "dummy",
   127  			Type:      "dummy",
   128  			AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   129  			Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
   130  		},
   131  		names.NewCloudTag("your-cloud"): {
   132  			Name:      "dummy",
   133  			Type:      "dummy",
   134  			AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   135  			Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
   136  		},
   137  	}, nil)
   138  
   139  	result, err := s.api.Clouds()
   140  	c.Assert(err, jc.ErrorIsNil)
   141  	c.Assert(result.Clouds, jc.DeepEquals, map[string]params.Cloud{
   142  		"cloud-my-cloud": {
   143  			Type:      "dummy",
   144  			AuthTypes: []string{"empty", "userpass"},
   145  			Regions:   []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}},
   146  		},
   147  	})
   148  }
   149  
   150  func (s *cloudSuite) TestCloudInfoAdmin(c *gc.C) {
   151  	ctrl := s.setup(c, names.NewUserTag("admin"))
   152  	defer ctrl.Finish()
   153  
   154  	ctrlBackend := s.ctrlBackend.EXPECT()
   155  	userPerm := map[string]permission.Access{"fred": permission.AddModelAccess,
   156  		"mary": permission.AdminAccess}
   157  	ctrlBackend.GetCloudUsers("my-cloud").Return(userPerm,
   158  		nil)
   159  
   160  	backend := s.backend.EXPECT()
   161  	backend.Cloud("my-cloud").Return(jujucloud.Cloud{
   162  		Name:      "dummy",
   163  		Type:      "dummy",
   164  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   165  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
   166  	}, nil)
   167  
   168  	mary := mocks.NewMockUser(ctrl)
   169  	fred := mocks.NewMockUser(ctrl)
   170  	mary.EXPECT().DisplayName().Return("display-mary")
   171  	fred.EXPECT().DisplayName().Return("display-fred")
   172  
   173  	maryTag := names.NewUserTag("mary")
   174  	backend.User(maryTag).Return(mary, nil)
   175  	fredTag := names.NewUserTag("fred")
   176  	backend.User(fredTag).Return(fred, nil)
   177  
   178  	result, err := s.api.CloudInfo(params.Entities{Entities: []params.Entity{{
   179  		Tag: "cloud-my-cloud",
   180  	}, {
   181  		Tag: "machine-0",
   182  	}}})
   183  	c.Assert(err, jc.ErrorIsNil)
   184  	// Make sure that the slice is sorted in a predictable manor
   185  	sort.Slice(result.Results[0].Result.Users, func(i, j int) bool {
   186  		return result.Results[0].Result.Users[i].UserName < result.Results[0].Result.Users[j].UserName
   187  	})
   188  	c.Assert(result.Results, jc.DeepEquals, []params.CloudInfoResult{
   189  		{
   190  			Result: &params.CloudInfo{
   191  				CloudDetails: params.CloudDetails{
   192  					Type:      "dummy",
   193  					AuthTypes: []string{"empty", "userpass"},
   194  					Regions:   []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}},
   195  				},
   196  				Users: []params.CloudUserInfo{
   197  					{UserName: "fred", DisplayName: "display-fred", Access: "add-model"},
   198  					{UserName: "mary", DisplayName: "display-mary", Access: "admin"},
   199  				},
   200  			},
   201  		}, {
   202  			Error: &params.Error{Message: `"machine-0" is not a valid cloud tag`},
   203  		},
   204  	})
   205  }
   206  
   207  func (s *cloudSuite) TestCloudInfoNonAdmin(c *gc.C) {
   208  	fredTag := names.NewUserTag("fred")
   209  	ctrl := s.setup(c, fredTag)
   210  	defer ctrl.Finish()
   211  
   212  	fred := mocks.NewMockUser(ctrl)
   213  	fred.EXPECT().DisplayName().Return("display-fred")
   214  
   215  	ctrlBackend := s.ctrlBackend.EXPECT()
   216  	ctrlBackend.GetCloudAccess("my-cloud",
   217  		fredTag).Return(permission.AddModelAccess, nil)
   218  	userPerm := map[string]permission.Access{"fred": permission.AddModelAccess,
   219  		"mary": permission.AdminAccess}
   220  	ctrlBackend.GetCloudUsers("my-cloud").Return(userPerm,
   221  		nil)
   222  
   223  	backend := s.backend.EXPECT()
   224  	backend.User(fredTag).Return(fred, nil)
   225  	backend.Cloud("my-cloud").Return(jujucloud.Cloud{
   226  		Name:      "dummy",
   227  		Type:      "dummy",
   228  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   229  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
   230  	}, nil)
   231  
   232  	result, err := s.api.CloudInfo(params.Entities{Entities: []params.Entity{{
   233  		Tag: "cloud-my-cloud",
   234  	}, {
   235  		Tag: "machine-0",
   236  	}}})
   237  	c.Assert(err, jc.ErrorIsNil)
   238  	c.Assert(result.Results, gc.HasLen, 2)
   239  	c.Assert(result.Results, jc.DeepEquals, []params.CloudInfoResult{
   240  		{
   241  			Result: &params.CloudInfo{
   242  				CloudDetails: params.CloudDetails{
   243  					Type:      "dummy",
   244  					AuthTypes: []string{"empty", "userpass"},
   245  					Regions:   []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}},
   246  				},
   247  				Users: []params.CloudUserInfo{
   248  					{UserName: "fred", DisplayName: "display-fred", Access: "add-model"},
   249  				},
   250  			},
   251  		}, {
   252  			Error: &params.Error{Message: `"machine-0" is not a valid cloud tag`},
   253  		},
   254  	})
   255  }
   256  
   257  func (s *cloudSuite) TestAddCloud(c *gc.C) {
   258  	adminTag := names.NewUserTag("admin")
   259  	defer s.setup(c, adminTag).Finish()
   260  
   261  	newCloud := jujucloud.Cloud{
   262  		Name:      "newcloudname",
   263  		Type:      "maas",
   264  		Endpoint:  "fake-endpoint",
   265  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   266  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "nether-endpoint"}},
   267  	}
   268  
   269  	cloud := jujucloud.Cloud{
   270  		Name: "newcloudname",
   271  		Type: "maas",
   272  	}
   273  
   274  	backend := s.backend.EXPECT()
   275  	backend.ControllerInfo().Return(&state.ControllerInfo{CloudName: "newcloudname"}, nil)
   276  	backend.Cloud("newcloudname").Return(cloud, nil)
   277  	backend.AddCloud(newCloud, adminTag.Name()).Return(nil)
   278  	paramsCloud := params.AddCloudArgs{
   279  		Name: "newcloudname",
   280  		Cloud: params.Cloud{
   281  			Type:      "maas",
   282  			AuthTypes: []string{"empty", "userpass"},
   283  			Endpoint:  "fake-endpoint",
   284  			Regions:   []params.CloudRegion{{Name: "nether", Endpoint: "nether-endpoint"}},
   285  		}}
   286  
   287  	err := s.api.AddCloud(paramsCloud)
   288  	c.Assert(err, jc.ErrorIsNil)
   289  }
   290  
   291  func createAddCloudParam(cloudType string) params.AddCloudArgs {
   292  	if cloudType == "" {
   293  		cloudType = "fake"
   294  	}
   295  	return params.AddCloudArgs{
   296  		Name: "newcloudname",
   297  		Cloud: params.Cloud{
   298  			Type:      cloudType,
   299  			AuthTypes: []string{"empty", "userpass"},
   300  			Endpoint:  "fake-endpoint",
   301  			Regions:   []params.CloudRegion{{Name: "nether", Endpoint: "nether-endpoint"}},
   302  		},
   303  	}
   304  }
   305  
   306  func (s *cloudSuite) TestAddCloudNotWhitelisted(c *gc.C) {
   307  	adminTag := names.NewUserTag("admin")
   308  	defer s.setup(c, adminTag).Finish()
   309  
   310  	cloud := jujucloud.Cloud{
   311  		Name:      "dummy",
   312  		Type:      "dummy",
   313  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   314  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
   315  	}
   316  
   317  	backend := s.backend.EXPECT()
   318  	backend.ControllerInfo().Return(&state.ControllerInfo{CloudName: "dummy"}, nil)
   319  	backend.Cloud("dummy").Return(cloud, nil)
   320  
   321  	err := s.api.AddCloud(createAddCloudParam(""))
   322  	c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`
   323  controller cloud type "dummy" is not whitelisted, current whitelist: 
   324   - controller cloud type "kubernetes" supports [lxd maas openstack]
   325   - controller cloud type "lxd" supports [lxd maas openstack]
   326   - controller cloud type "maas" supports [maas openstack]
   327   - controller cloud type "openstack" supports [openstack]`[1:]))
   328  }
   329  
   330  func (s *cloudSuite) TestAddCloudNotWhitelistedButForceAdded(c *gc.C) {
   331  	adminTag := names.NewUserTag("admin")
   332  	defer s.setup(c, adminTag).Finish()
   333  
   334  	newCloud := jujucloud.Cloud{
   335  		Name:      "newcloudname",
   336  		Type:      "fake",
   337  		Endpoint:  "fake-endpoint",
   338  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   339  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "nether-endpoint"}},
   340  	}
   341  
   342  	cloud := jujucloud.Cloud{
   343  		Name: "newcloudname",
   344  		Type: "maas",
   345  	}
   346  
   347  	backend := s.backend.EXPECT()
   348  	backend.ControllerInfo().Return(&state.ControllerInfo{CloudName: "newcloudname"}, nil)
   349  	backend.Cloud("newcloudname").Return(cloud, nil)
   350  	backend.AddCloud(newCloud, adminTag.Name()).Return(nil)
   351  
   352  	force := true
   353  	addCloudArg := createAddCloudParam("")
   354  	addCloudArg.Force = &force
   355  	err := s.api.AddCloud(addCloudArg)
   356  	c.Assert(err, jc.ErrorIsNil)
   357  }
   358  
   359  func (s *cloudSuite) TestAddCloudControllerInfoErr(c *gc.C) {
   360  	adminTag := names.NewUserTag("admin")
   361  	defer s.setup(c, adminTag).Finish()
   362  
   363  	backend := s.backend.EXPECT()
   364  	backend.ControllerInfo().Return(nil, errors.New("kaboom"))
   365  
   366  	err := s.api.AddCloud(createAddCloudParam(""))
   367  	c.Assert(err, gc.ErrorMatches, "kaboom")
   368  }
   369  
   370  func (s *cloudSuite) TestAddCloudControllerCloudErr(c *gc.C) {
   371  	adminTag := names.NewUserTag("admin")
   372  	defer s.setup(c, adminTag).Finish()
   373  
   374  	backend := s.backend.EXPECT()
   375  	backend.ControllerInfo().Return(&state.ControllerInfo{CloudName: "kaboom"}, nil)
   376  	backend.Cloud("kaboom").Return(jujucloud.Cloud{}, errors.New("kaboom"))
   377  
   378  	err := s.api.AddCloud(createAddCloudParam(""))
   379  	c.Assert(err, gc.ErrorMatches, "kaboom")
   380  }
   381  
   382  func (s *cloudSuite) TestAddCloudK8sForceIrrelevant(c *gc.C) {
   383  	adminTag := names.NewUserTag("admin")
   384  	defer s.setup(c, adminTag).Finish()
   385  
   386  	cloud := jujucloud.Cloud{
   387  		Name:      "newcloudname",
   388  		Type:      string(k8sconstants.CAASProviderType),
   389  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   390  		Endpoint:  "fake-endpoint",
   391  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "nether-endpoint"}},
   392  	}
   393  
   394  	backend := s.backend.EXPECT()
   395  	backend.AddCloud(cloud, adminTag.Name()).Return(nil).Times(2)
   396  
   397  	addCloudArg := createAddCloudParam(string(k8sconstants.CAASProviderType))
   398  
   399  	add := func() {
   400  		err := s.api.AddCloud(addCloudArg)
   401  		c.Assert(err, jc.ErrorIsNil)
   402  	}
   403  	add()
   404  	force := true
   405  	addCloudArg.Force = &force
   406  	add()
   407  }
   408  
   409  func (s *cloudSuite) TestAddCloudNoRegion(c *gc.C) {
   410  	adminTag := names.NewUserTag("admin")
   411  	defer s.setup(c, adminTag).Finish()
   412  
   413  	newCloud := jujucloud.Cloud{
   414  		Name:      "newcloudname",
   415  		Type:      "maas",
   416  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   417  		Endpoint:  "fake-endpoint",
   418  		Regions: []jujucloud.Region{{
   419  			Name: "default",
   420  		}},
   421  	}
   422  
   423  	cloud := jujucloud.Cloud{
   424  		Name: "newcloudname",
   425  		Type: "maas",
   426  	}
   427  
   428  	backend := s.backend.EXPECT()
   429  	backend.ControllerInfo().Return(&state.ControllerInfo{CloudName: "newcloudname"}, nil)
   430  	backend.Cloud("newcloudname").Return(cloud, nil)
   431  	backend.AddCloud(newCloud, adminTag.Name()).Return(nil)
   432  	paramsCloud := params.AddCloudArgs{
   433  		Name: "newcloudname",
   434  		Cloud: params.Cloud{
   435  			Type:      "maas",
   436  			AuthTypes: []string{"empty", "userpass"},
   437  			Endpoint:  "fake-endpoint",
   438  		}}
   439  	err := s.api.AddCloud(paramsCloud)
   440  	c.Assert(err, jc.ErrorIsNil)
   441  
   442  }
   443  
   444  func (s *cloudSuite) TestAddCloudNoAdminPerms(c *gc.C) {
   445  	frankTag := names.NewUserTag("frank")
   446  	defer s.setup(c, frankTag).Finish()
   447  
   448  	paramsCloud := params.AddCloudArgs{
   449  		Name: "newcloudname",
   450  		Cloud: params.Cloud{
   451  			Type:      "fake",
   452  			AuthTypes: []string{"empty", "userpass"},
   453  			Endpoint:  "fake-endpoint",
   454  			Regions:   []params.CloudRegion{{Name: "nether", Endpoint: "nether-endpoint"}},
   455  		}}
   456  	err := s.api.AddCloud(paramsCloud)
   457  	c.Assert(err, gc.ErrorMatches, "permission denied")
   458  }
   459  
   460  func (s *cloudSuite) TestUpdateCloud(c *gc.C) {
   461  	adminTag := names.NewUserTag("admin")
   462  	defer s.setup(c, adminTag).Finish()
   463  
   464  	dummyCloud := jujucloud.Cloud{
   465  		Name:      "dummy",
   466  		Type:      "dummy",
   467  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   468  		Regions:   []jujucloud.Region{{Name: "nether-updated", Endpoint: "endpoint-updated"}},
   469  	}
   470  
   471  	backend := s.backend.EXPECT()
   472  	backend.UpdateCloud(dummyCloud).Return(nil)
   473  
   474  	updatedCloud := jujucloud.Cloud{
   475  		Name:      "dummy",
   476  		Type:      "dummy",
   477  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   478  		Regions:   []jujucloud.Region{{Name: "nether-updated", Endpoint: "endpoint-updated"}},
   479  	}
   480  	results, err := s.api.UpdateCloud(params.UpdateCloudArgs{
   481  		Clouds: []params.AddCloudArgs{{
   482  			Name:  "dummy",
   483  			Cloud: cloud.CloudToParams(updatedCloud),
   484  		}},
   485  	})
   486  	c.Assert(err, jc.ErrorIsNil)
   487  
   488  	c.Assert(results.Results, gc.HasLen, 1)
   489  	c.Assert(results.Results[0].Error, gc.IsNil)
   490  }
   491  
   492  func (s *cloudSuite) TestUpdateCloudNonAdminPerm(c *gc.C) {
   493  	frankTag := names.NewUserTag("frank")
   494  	defer s.setup(c, frankTag).Finish()
   495  
   496  	updatedCloud := jujucloud.Cloud{
   497  		Name:      "dummy",
   498  		Type:      "dummy",
   499  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   500  		Regions:   []jujucloud.Region{{Name: "nether-updated", Endpoint: "endpoint-updated"}},
   501  	}
   502  	results, err := s.api.UpdateCloud(params.UpdateCloudArgs{
   503  		Clouds: []params.AddCloudArgs{{
   504  			Name:  "dummy",
   505  			Cloud: cloud.CloudToParams(updatedCloud),
   506  		}},
   507  	})
   508  	c.Assert(err, gc.ErrorMatches, "permission denied")
   509  	c.Assert(results.Results, gc.HasLen, 1)
   510  	c.Assert(results.Results[0].Error, gc.IsNil)
   511  }
   512  
   513  func (s *cloudSuite) TestUpdateNonExistentCloud(c *gc.C) {
   514  	adminTag := names.NewUserTag("admin")
   515  	defer s.setup(c, adminTag).Finish()
   516  
   517  	dummyCloud := jujucloud.Cloud{
   518  		Name:      "nope",
   519  		Type:      "dummy",
   520  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   521  		Regions:   []jujucloud.Region{{Name: "nether-updated", Endpoint: "endpoint-updated"}},
   522  	}
   523  
   524  	backend := s.backend.EXPECT()
   525  	backend.UpdateCloud(dummyCloud).Return(errors.New("cloud \"nope\" not found"))
   526  
   527  	updatedCloud := jujucloud.Cloud{
   528  		Name:      "nope",
   529  		Type:      "dummy",
   530  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   531  		Regions:   []jujucloud.Region{{Name: "nether-updated", Endpoint: "endpoint-updated"}},
   532  	}
   533  
   534  	results, err := s.api.UpdateCloud(params.UpdateCloudArgs{
   535  		Clouds: []params.AddCloudArgs{{
   536  			Name:  "nope",
   537  			Cloud: cloud.CloudToParams(updatedCloud),
   538  		}},
   539  	})
   540  	c.Assert(err, jc.ErrorIsNil)
   541  	c.Assert(results.Results, gc.HasLen, 1)
   542  	c.Assert(results.Results[0].Error, gc.ErrorMatches, fmt.Sprintf("cloud %q not found", updatedCloud.Name))
   543  }
   544  
   545  func (s *cloudSuite) TestListCloudInfo(c *gc.C) {
   546  	fredTag := names.NewUserTag("admin")
   547  	defer s.setup(c, fredTag).Finish()
   548  
   549  	cloudInfo := []state.CloudInfo{
   550  		{
   551  			Cloud: jujucloud.Cloud{
   552  				Name:      "dummy",
   553  				Type:      "dummy",
   554  				AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   555  				Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
   556  			},
   557  			Access: permission.AddModelAccess,
   558  		},
   559  	}
   560  	ctrlBackend := s.ctrlBackend.EXPECT()
   561  	ctrlBackend.CloudsForUser(fredTag, true).Return(cloudInfo, nil)
   562  
   563  	result, err := s.api.ListCloudInfo(params.ListCloudsRequest{
   564  		UserTag: "user-admin",
   565  		All:     true,
   566  	})
   567  	c.Assert(err, jc.ErrorIsNil)
   568  	c.Assert(result.Results, jc.DeepEquals, []params.ListCloudInfoResult{
   569  		{
   570  			Result: &params.ListCloudInfo{
   571  				CloudDetails: params.CloudDetails{
   572  					Type:      "dummy",
   573  					AuthTypes: []string{"empty", "userpass"},
   574  					Regions:   []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}},
   575  				},
   576  				Access: "add-model",
   577  			},
   578  		},
   579  	})
   580  }
   581  
   582  func (s *cloudSuite) TestUserCredentials(c *gc.C) {
   583  	bruceTag := names.NewUserTag("bruce")
   584  	defer s.setup(c, bruceTag).Finish()
   585  
   586  	credentialOne, tagOne := cloudCredentialTag(credParams{name: "one", owner: "bruce", cloudName: "meep", permission: jujucloud.EmptyAuthType,
   587  		attrs: map[string]string{}}, c)
   588  	credentialTwo, tagTwo := cloudCredentialTag(credParams{name: "two", owner: "bruce", cloudName: "meep", permission: jujucloud.UserPassAuthType,
   589  		attrs: map[string]string{
   590  			"username": "admin",
   591  			"password": "adm1n",
   592  		}}, c)
   593  
   594  	creds := map[string]state.Credential{
   595  		tagOne.Id(): credentialOne,
   596  		tagTwo.Id(): credentialTwo,
   597  	}
   598  
   599  	backend := s.backend.EXPECT()
   600  	backend.CloudCredentials(bruceTag, "meep").Return(creds, nil)
   601  
   602  	results, err := s.api.UserCredentials(params.UserClouds{UserClouds: []params.UserCloud{{
   603  		UserTag:  "machine-0",
   604  		CloudTag: "cloud-meep",
   605  	}, {
   606  		UserTag:  "user-admin",
   607  		CloudTag: "cloud-meep",
   608  	}, {
   609  		UserTag:  "user-bruce",
   610  		CloudTag: "cloud-meep",
   611  	}}})
   612  	c.Assert(err, jc.ErrorIsNil)
   613  	c.Assert(results.Results, gc.HasLen, 3)
   614  	c.Assert(results.Results[0].Error, jc.DeepEquals, &params.Error{
   615  		Message: `"machine-0" is not a valid user tag`,
   616  	})
   617  	c.Assert(results.Results[1].Error, jc.DeepEquals, &params.Error{
   618  		Message: "permission denied", Code: params.CodeUnauthorized,
   619  	})
   620  	c.Assert(results.Results[2].Error, gc.IsNil)
   621  	c.Assert(results.Results[2].Result, jc.SameContents, []string{
   622  		"cloudcred-meep_bruce_one",
   623  		"cloudcred-meep_bruce_two",
   624  	})
   625  }
   626  
   627  func (s *cloudSuite) TestUserCredentialsAdminAccess(c *gc.C) {
   628  	adminTag := names.NewUserTag("admin")
   629  	defer s.setup(c, adminTag).Finish()
   630  
   631  	julia := names.NewUserTag("julia")
   632  	backend := s.backend.EXPECT()
   633  	backend.CloudCredentials(julia, "meep").Return(map[string]state.Credential{}, nil)
   634  
   635  	results, err := s.api.UserCredentials(params.UserClouds{UserClouds: []params.UserCloud{{
   636  		UserTag:  "user-julia",
   637  		CloudTag: "cloud-meep",
   638  	}}})
   639  	c.Assert(err, jc.ErrorIsNil)
   640  	c.Assert(results.Results, gc.HasLen, 1)
   641  	// admin can access others' credentials
   642  	c.Assert(results.Results[0].Error, gc.IsNil)
   643  }
   644  
   645  func (s *cloudSuite) TestUpdateCredentials(c *gc.C) {
   646  	bruceTag := names.NewUserTag("bruce")
   647  	defer s.setup(c, bruceTag).Finish()
   648  
   649  	_, tagOne := cloudCredentialTag(credParams{name: "three", owner: "bruce", cloudName: "meep", permission: jujucloud.EmptyAuthType,
   650  		attrs: map[string]string{}}, c)
   651  	_, tagTwo := cloudCredentialTag(credParams{name: "three", owner: "bruce", cloudName: "badcloud", permission: jujucloud.EmptyAuthType,
   652  		attrs: map[string]string{}}, c)
   653  
   654  	backend := s.backend.EXPECT()
   655  	backend.CredentialModels(tagOne).Return(nil, nil)
   656  	backend.UpdateCloudCredential(tagTwo, jujucloud.NewCredential(
   657  		jujucloud.OAuth1AuthType,
   658  		map[string]string{"token": "foo:bar:baz"},
   659  	)).Return(errors.New("cannot update credential \"three\": controller does not manage cloud \"badcloud\""))
   660  	backend.CredentialModels(tagTwo).Return(nil, nil)
   661  	backend.UpdateCloudCredential(tagOne, jujucloud.NewCredential(
   662  		jujucloud.OAuth1AuthType,
   663  		map[string]string{"token": "foo:bar:baz"},
   664  	)).Return(nil)
   665  
   666  	results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{
   667  		Force: false,
   668  		Credentials: []params.TaggedCredential{{
   669  			Tag: "machine-0",
   670  		}, {
   671  			Tag: "cloudcred-meep_admin_whatever",
   672  		}, {
   673  			Tag: "cloudcred-meep_bruce_three",
   674  			Credential: params.CloudCredential{
   675  				AuthType:   "oauth1",
   676  				Attributes: map[string]string{"token": "foo:bar:baz"},
   677  			},
   678  		}, {
   679  			Tag: "cloudcred-badcloud_bruce_three",
   680  			Credential: params.CloudCredential{
   681  				AuthType:   "oauth1",
   682  				Attributes: map[string]string{"token": "foo:bar:baz"},
   683  			},
   684  		}}})
   685  	c.Assert(err, jc.ErrorIsNil)
   686  	c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{
   687  		Results: []params.UpdateCredentialResult{
   688  			{
   689  				CredentialTag: "machine-0",
   690  				Error:         &params.Error{Message: `"machine-0" is not a valid cloudcred tag`},
   691  			},
   692  			{
   693  				CredentialTag: "cloudcred-meep_admin_whatever",
   694  				Error:         &params.Error{Message: "permission denied", Code: params.CodeUnauthorized},
   695  			},
   696  			{CredentialTag: "cloudcred-meep_bruce_three"},
   697  			{
   698  				CredentialTag: "cloudcred-badcloud_bruce_three",
   699  				Error:         &params.Error{Message: `cannot update credential "three": controller does not manage cloud "badcloud"`},
   700  			},
   701  		},
   702  	})
   703  }
   704  
   705  func (s *cloudSuite) TestUpdateCredentialsAdminAccess(c *gc.C) {
   706  	adminTag := names.NewUserTag("admin")
   707  	defer s.setup(c, adminTag).Finish()
   708  
   709  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
   710  		attrs: map[string]string{}}, c)
   711  
   712  	backend := s.backend.EXPECT()
   713  	backend.CredentialModels(tag).Return(nil, nil)
   714  	backend.UpdateCloudCredential(names.NewCloudCredentialTag("meep/julia/three"),
   715  		jujucloud.Credential{}).Return(nil)
   716  
   717  	results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{
   718  		Force: false,
   719  		Credentials: []params.TaggedCredential{{
   720  			Tag:        "cloudcred-meep_julia_three",
   721  			Credential: params.CloudCredential{},
   722  		}}})
   723  	c.Assert(err, jc.ErrorIsNil)
   724  	c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{
   725  		Results: []params.UpdateCredentialResult{{CredentialTag: "cloudcred-meep_julia_three"}}})
   726  }
   727  
   728  func (s *cloudSuite) TestUpdateCredentialsNoModelsFound(c *gc.C) {
   729  	adminTag := names.NewUserTag("admin")
   730  	defer s.setup(c, adminTag).Finish()
   731  
   732  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
   733  		attrs: map[string]string{}}, c)
   734  
   735  	backend := s.backend.EXPECT()
   736  	backend.CredentialModels(tag).Return(nil, errors.NotFoundf("how about it"))
   737  	backend.UpdateCloudCredential(names.NewCloudCredentialTag("meep/julia/three"),
   738  		jujucloud.Credential{}).Return(nil)
   739  
   740  	results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{
   741  		Force: false,
   742  		Credentials: []params.TaggedCredential{{
   743  			Tag:        "cloudcred-meep_julia_three",
   744  			Credential: params.CloudCredential{},
   745  		}}})
   746  	c.Assert(err, jc.ErrorIsNil)
   747  	c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{
   748  		Results: []params.UpdateCredentialResult{{CredentialTag: "cloudcred-meep_julia_three"}}})
   749  }
   750  
   751  func (s *cloudSuite) TestUpdateCredentialsModelsError(c *gc.C) {
   752  	adminTag := names.NewUserTag("admin")
   753  	defer s.setup(c, adminTag).Finish()
   754  
   755  	_, tag := cloudCredentialTag(credParams{"three", "julia", "meep", jujucloud.EmptyAuthType,
   756  		map[string]string{}}, c)
   757  	backend := s.backend.EXPECT()
   758  	backend.CredentialModels(tag).Return(nil, errors.New("cannot get models"))
   759  
   760  	results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{
   761  		Force: false,
   762  		Credentials: []params.TaggedCredential{{
   763  			Tag:        "cloudcred-meep_julia_three",
   764  			Credential: params.CloudCredential{},
   765  		}}})
   766  	c.Assert(err, jc.ErrorIsNil)
   767  	c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{
   768  		Results: []params.UpdateCredentialResult{
   769  			{
   770  				CredentialTag: "cloudcred-meep_julia_three",
   771  				Error:         &params.Error{Message: "cannot get models"},
   772  			},
   773  		}})
   774  }
   775  
   776  func (s *cloudSuite) TestUpdateCredentialsModelsErrorForce(c *gc.C) {
   777  	adminTag := names.NewUserTag("admin")
   778  	defer s.setup(c, adminTag).Finish()
   779  
   780  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
   781  		attrs: map[string]string{}}, c)
   782  
   783  	backend := s.backend.EXPECT()
   784  	backend.CredentialModels(tag).Return(nil, errors.New("cannot get models"))
   785  	backend.UpdateCloudCredential(tag,
   786  		jujucloud.Credential{}).Return(nil)
   787  
   788  	results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{
   789  		Force: true,
   790  		Credentials: []params.TaggedCredential{{
   791  			Tag:        "cloudcred-meep_julia_three",
   792  			Credential: params.CloudCredential{},
   793  		}}})
   794  	c.Assert(err, jc.ErrorIsNil)
   795  	c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{
   796  		Results: []params.UpdateCredentialResult{
   797  			{
   798  				CredentialTag: "cloudcred-meep_julia_three",
   799  			},
   800  		}})
   801  }
   802  
   803  func (s *cloudSuite) TestUpdateCredentialsOneModelSuccess(c *gc.C) {
   804  	adminTag := names.NewUserTag("admin")
   805  	defer s.setup(c, adminTag).Finish()
   806  
   807  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
   808  		attrs: map[string]string{}}, c)
   809  
   810  	s.PatchValue(cloud.ValidateNewCredentialForModelFunc,
   811  		func(
   812  			_ credentialcommon.PersistentBackend, _ context.ProviderCallContext,
   813  			_ names.CloudCredentialTag, _ *jujucloud.Credential, _ bool, _ bool,
   814  		) (params.ErrorResults, error) {
   815  			return params.ErrorResults{}, nil
   816  		})
   817  
   818  	aCloud := jujucloud.Cloud{
   819  		Name:      "dummy",
   820  		Type:      "dummy",
   821  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   822  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
   823  	}
   824  
   825  	backend := s.backend.EXPECT()
   826  	backend.CredentialModels(tag).Return(map[string]string{
   827  		coretesting.ModelTag.Id(): "testModel1",
   828  	}, nil)
   829  	backend.UpdateCloudCredential(tag, jujucloud.Credential{}).Return(nil)
   830  
   831  	pool := s.pool.EXPECT()
   832  	pool.GetModelCallContext(coretesting.ModelTag.Id()).Return(newModelBackend(c, aCloud, coretesting.ModelTag.Id()),
   833  		context.NewEmptyCloudCallContext(), nil)
   834  
   835  	results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{
   836  		Force: false,
   837  		Credentials: []params.TaggedCredential{{
   838  			Tag:        "cloudcred-meep_julia_three",
   839  			Credential: params.CloudCredential{},
   840  		}}})
   841  	c.Assert(err, jc.ErrorIsNil)
   842  	c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{
   843  		Results: []params.UpdateCredentialResult{{
   844  			CredentialTag: "cloudcred-meep_julia_three",
   845  			Models: []params.UpdateCredentialModelResult{
   846  				{
   847  					ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d",
   848  					ModelName: "testModel1",
   849  				},
   850  			},
   851  		}},
   852  	})
   853  }
   854  
   855  func (s *cloudSuite) TestUpdateCredentialsModelGetError(c *gc.C) {
   856  	adminTag := names.NewUserTag("admin")
   857  	defer s.setup(c, adminTag).Finish()
   858  
   859  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
   860  		attrs: map[string]string{}}, c)
   861  
   862  	backend := s.backend.EXPECT()
   863  	backend.CredentialModels(tag).Return(map[string]string{
   864  		coretesting.ModelTag.Id(): "testModel1",
   865  	}, nil)
   866  
   867  	pool := s.pool.EXPECT()
   868  	pool.GetModelCallContext(coretesting.ModelTag.Id()).Return(nil, nil, errors.New("cannot get a model"))
   869  
   870  	results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{
   871  		Force: false,
   872  		Credentials: []params.TaggedCredential{{
   873  			Tag:        "cloudcred-meep_julia_three",
   874  			Credential: params.CloudCredential{},
   875  		}}})
   876  	c.Assert(err, jc.ErrorIsNil)
   877  	c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{
   878  		Results: []params.UpdateCredentialResult{{
   879  			CredentialTag: "cloudcred-meep_julia_three",
   880  			Models: []params.UpdateCredentialModelResult{
   881  				{
   882  					ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d",
   883  					ModelName: "testModel1",
   884  					Errors:    []params.ErrorResult{{Error: &params.Error{Message: "cannot get a model", Code: ""}}},
   885  				},
   886  			},
   887  		}},
   888  	})
   889  }
   890  
   891  func (s *cloudSuite) TestUpdateCredentialsModelGetErrorForce(c *gc.C) {
   892  	adminTag := names.NewUserTag("admin")
   893  	defer s.setup(c, adminTag)
   894  
   895  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
   896  		attrs: map[string]string{}}, c)
   897  
   898  	backend := s.backend.EXPECT()
   899  	backend.CredentialModels(tag).Return(map[string]string{
   900  		coretesting.ModelTag.Id(): "testModel1",
   901  	}, nil)
   902  	backend.UpdateCloudCredential(tag, jujucloud.Credential{}).Return(nil)
   903  
   904  	pool := s.pool.EXPECT()
   905  	pool.GetModelCallContext(coretesting.ModelTag.Id()).Return(nil, nil, errors.New("cannot get a model"))
   906  
   907  	results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{
   908  		Force: true,
   909  		Credentials: []params.TaggedCredential{{
   910  			Tag:        "cloudcred-meep_julia_three",
   911  			Credential: params.CloudCredential{},
   912  		}}})
   913  	c.Assert(err, jc.ErrorIsNil)
   914  	c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{
   915  		Results: []params.UpdateCredentialResult{{
   916  			CredentialTag: "cloudcred-meep_julia_three",
   917  			Models: []params.UpdateCredentialModelResult{
   918  				{
   919  					ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d",
   920  					ModelName: "testModel1",
   921  					Errors:    []params.ErrorResult{{Error: &params.Error{Message: "cannot get a model", Code: ""}}},
   922  				},
   923  			},
   924  		}},
   925  	})
   926  }
   927  
   928  func (s *cloudSuite) TestUpdateCredentialsModelFailedValidation(c *gc.C) {
   929  	adminTag := names.NewUserTag("admin")
   930  	defer s.setup(c, adminTag)
   931  
   932  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
   933  		attrs: map[string]string{}}, c)
   934  
   935  	backend := s.backend.EXPECT()
   936  	backend.CredentialModels(tag).Return(map[string]string{
   937  		coretesting.ModelTag.Id(): "testModel1",
   938  	}, nil)
   939  
   940  	results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{
   941  		Force: false,
   942  		Credentials: []params.TaggedCredential{{
   943  			Tag:        "cloudcred-meep_julia_three",
   944  			Credential: params.CloudCredential{},
   945  		}}})
   946  	c.Assert(err, jc.ErrorIsNil)
   947  	c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{
   948  		Results: []params.UpdateCredentialResult{{
   949  			CredentialTag: "cloudcred-meep_julia_three",
   950  			Models: []params.UpdateCredentialModelResult{
   951  				{
   952  					ModelUUID: coretesting.ModelTag.Id(),
   953  					ModelName: "testModel1",
   954  					Errors:    []params.ErrorResult{{Error: &params.Error{Message: "not valid for model", Code: ""}}},
   955  				},
   956  			},
   957  		}},
   958  	})
   959  }
   960  
   961  func (s *cloudSuite) TestUpdateCredentialsModelFailedValidationForce(c *gc.C) {
   962  	adminTag := names.NewUserTag("admin")
   963  	defer s.setup(c, adminTag).Finish()
   964  
   965  	s.PatchValue(cloud.ValidateNewCredentialForModelFunc,
   966  		func(backend credentialcommon.PersistentBackend, _ context.ProviderCallContext,
   967  			_ names.CloudCredentialTag, _ *jujucloud.Credential, _ bool, _ bool,
   968  		) (params.ErrorResults, error) {
   969  			return params.ErrorResults{Results: []params.ErrorResult{{Error: &params.Error{Message: "not valid for model"}}}}, nil
   970  		})
   971  
   972  	aCloud := jujucloud.Cloud{
   973  		Name:      "dummy",
   974  		Type:      "dummy",
   975  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
   976  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
   977  	}
   978  
   979  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
   980  		attrs: map[string]string{}}, c)
   981  
   982  	backend := s.backend.EXPECT()
   983  	backend.CredentialModels(tag).Return(map[string]string{
   984  		coretesting.ModelTag.Id(): "testModel1",
   985  	}, nil)
   986  	backend.UpdateCloudCredential(tag, jujucloud.Credential{}).Return(nil)
   987  
   988  	pool := s.pool.EXPECT()
   989  	pool.GetModelCallContext(gomock.Any()).DoAndReturn(func(modelUUID string) (
   990  		credentialcommon.PersistentBackend, context.ProviderCallContext, error,
   991  	) {
   992  		return newModelBackend(c, aCloud,
   993  			modelUUID), context.NewEmptyCloudCallContext(), nil
   994  	}).MinTimes(1)
   995  
   996  	results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{
   997  		Force: true,
   998  		Credentials: []params.TaggedCredential{{
   999  			Tag:        "cloudcred-meep_julia_three",
  1000  			Credential: params.CloudCredential{},
  1001  		}}})
  1002  	c.Assert(err, jc.ErrorIsNil)
  1003  	c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{
  1004  		Results: []params.UpdateCredentialResult{{
  1005  			CredentialTag: "cloudcred-meep_julia_three",
  1006  			Models: []params.UpdateCredentialModelResult{
  1007  				{
  1008  					ModelUUID: coretesting.ModelTag.Id(),
  1009  					ModelName: "testModel1",
  1010  					Errors:    []params.ErrorResult{{Error: &params.Error{Message: "not valid for model", Code: ""}}},
  1011  				},
  1012  			},
  1013  		}},
  1014  	})
  1015  }
  1016  
  1017  func (s *cloudSuite) TestUpdateCredentialsSomeModelsFailedValidation(c *gc.C) {
  1018  	adminTag := names.NewUserTag("admin")
  1019  	defer s.setup(c, adminTag).Finish()
  1020  
  1021  	aCloud := jujucloud.Cloud{
  1022  		Name:      "dummy",
  1023  		Type:      "dummy",
  1024  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
  1025  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
  1026  	}
  1027  
  1028  	s.PatchValue(cloud.ValidateNewCredentialForModelFunc,
  1029  		func(backend credentialcommon.PersistentBackend, _ context.ProviderCallContext,
  1030  			_ names.CloudCredentialTag, _ *jujucloud.Credential, _ bool, _ bool,
  1031  		) (params.ErrorResults, error) {
  1032  			if backend.(*mockModelBackend).uuid == "deadbeef-0bad-400d-8000-4b1d0d06f00d" {
  1033  				return params.ErrorResults{Results: []params.ErrorResult{{Error: &params.Error{Message: "not valid for model"}}}}, nil
  1034  			}
  1035  			return params.ErrorResults{Results: []params.ErrorResult{}}, nil
  1036  		})
  1037  
  1038  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1039  		attrs: map[string]string{}}, c)
  1040  
  1041  	backend := s.backend.EXPECT()
  1042  	backend.CredentialModels(tag).Return(map[string]string{
  1043  		coretesting.ModelTag.Id():              "testModel1",
  1044  		"deadbeef-2f18-4fd2-967d-db9663db7bea": "testModel2",
  1045  	}, nil)
  1046  
  1047  	pool := s.pool.EXPECT()
  1048  	pool.GetModelCallContext(coretesting.ModelTag.Id()).Return(newModelBackend(c, aCloud,
  1049  		coretesting.ModelTag.Id()), context.NewEmptyCloudCallContext(), nil)
  1050  	pool.GetModelCallContext("deadbeef-2f18-4fd2-967d-db9663db7bea").Return(newModelBackend(c, aCloud,
  1051  		"deadbeef-2f18-4fd2-967d-db9663db7bea"), context.NewEmptyCloudCallContext(), nil)
  1052  
  1053  	results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{
  1054  		Force: false,
  1055  		Credentials: []params.TaggedCredential{{
  1056  			Tag:        "cloudcred-meep_julia_three",
  1057  			Credential: params.CloudCredential{},
  1058  		}},
  1059  	})
  1060  	c.Assert(err, jc.ErrorIsNil)
  1061  	c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{
  1062  		Results: []params.UpdateCredentialResult{{
  1063  			CredentialTag: "cloudcred-meep_julia_three",
  1064  			Models: []params.UpdateCredentialModelResult{{
  1065  				ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d",
  1066  				ModelName: "testModel1",
  1067  				Errors: []params.ErrorResult{{
  1068  					Error: &params.Error{Message: "not valid for model", Code: ""},
  1069  				}},
  1070  			}, {
  1071  				ModelUUID: "deadbeef-2f18-4fd2-967d-db9663db7bea",
  1072  				ModelName: "testModel2",
  1073  			}},
  1074  		}},
  1075  	})
  1076  }
  1077  
  1078  func (s *cloudSuite) TestUpdateCredentialsSomeModelsFailedValidationForce(c *gc.C) {
  1079  	adminTag := names.NewUserTag("admin")
  1080  	defer s.setup(c, adminTag).Finish()
  1081  
  1082  	aCloud := jujucloud.Cloud{
  1083  		Name:      "dummy",
  1084  		Type:      "dummy",
  1085  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
  1086  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
  1087  	}
  1088  
  1089  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1090  		attrs: map[string]string{}}, c)
  1091  
  1092  	s.PatchValue(cloud.ValidateNewCredentialForModelFunc,
  1093  		func(
  1094  			backend credentialcommon.PersistentBackend, _ context.ProviderCallContext,
  1095  			_ names.CloudCredentialTag, _ *jujucloud.Credential, _ bool, _ bool,
  1096  		) (params.ErrorResults, error) {
  1097  			if backend.(*mockModelBackend).uuid == "deadbeef-0bad-400d-8000-4b1d0d06f00d" {
  1098  				return params.ErrorResults{Results: []params.ErrorResult{{Error: &params.Error{Message: "not valid for model"}}}}, nil
  1099  			}
  1100  			return params.ErrorResults{Results: []params.ErrorResult{}}, nil
  1101  		})
  1102  
  1103  	backend := s.backend.EXPECT()
  1104  	backend.CredentialModels(tag).Return(map[string]string{
  1105  		coretesting.ModelTag.Id():              "testModel1",
  1106  		"deadbeef-2f18-4fd2-967d-db9663db7bea": "testModel2",
  1107  	}, nil)
  1108  	backend.UpdateCloudCredential(tag, jujucloud.Credential{}).Return(nil)
  1109  
  1110  	pool := s.pool.EXPECT()
  1111  	pool.GetModelCallContext(coretesting.ModelTag.Id()).Return(newModelBackend(c, aCloud, coretesting.ModelTag.Id()),
  1112  		context.NewEmptyCloudCallContext(), nil)
  1113  	pool.GetModelCallContext("deadbeef-2f18-4fd2-967d-db9663db7bea").Return(newModelBackend(c, aCloud,
  1114  		"deadbeef-2f18-4fd2-967d-db9663db7bea"), nil, nil)
  1115  
  1116  	results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{
  1117  		Force: true,
  1118  		Credentials: []params.TaggedCredential{{
  1119  			Tag:        "cloudcred-meep_julia_three",
  1120  			Credential: params.CloudCredential{},
  1121  		}}})
  1122  	c.Assert(err, jc.ErrorIsNil)
  1123  	c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{
  1124  		Results: []params.UpdateCredentialResult{
  1125  			{
  1126  				CredentialTag: "cloudcred-meep_julia_three",
  1127  				Models: []params.UpdateCredentialModelResult{
  1128  					{
  1129  						ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d",
  1130  						ModelName: "testModel1",
  1131  						Errors: []params.ErrorResult{{Error: &params.Error{Message: "not valid for model",
  1132  							Code: ""}}},
  1133  					},
  1134  					{
  1135  						ModelUUID: "deadbeef-2f18-4fd2-967d-db9663db7bea",
  1136  						ModelName: "testModel2",
  1137  					},
  1138  				},
  1139  			},
  1140  		},
  1141  	})
  1142  }
  1143  
  1144  func (s *cloudSuite) TestUpdateCredentialsAllModelsFailedValidation(c *gc.C) {
  1145  	adminTag := names.NewUserTag("admin")
  1146  	defer s.setup(c, adminTag).Finish()
  1147  
  1148  	s.PatchValue(cloud.ValidateNewCredentialForModelFunc,
  1149  		func(_ credentialcommon.PersistentBackend, _ context.ProviderCallContext,
  1150  			_ names.CloudCredentialTag, _ *jujucloud.Credential, _ bool, _ bool,
  1151  		) (params.ErrorResults, error) {
  1152  			return params.ErrorResults{Results: []params.ErrorResult{{Error: &params.Error{Message: "not valid for model"}}}}, nil
  1153  		})
  1154  
  1155  	aCloud := jujucloud.Cloud{
  1156  		Name:      "dummy",
  1157  		Type:      "dummy",
  1158  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
  1159  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
  1160  	}
  1161  
  1162  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1163  		attrs: map[string]string{}}, c)
  1164  
  1165  	backend := s.backend.EXPECT()
  1166  	backend.CredentialModels(tag).Return(map[string]string{
  1167  		coretesting.ModelTag.Id():              "testModel1",
  1168  		"deadbeef-2f18-4fd2-967d-db9663db7bea": "testModel2",
  1169  	}, nil)
  1170  
  1171  	pool := s.pool.EXPECT()
  1172  	pool.GetModelCallContext(gomock.Any()).Return(newModelBackend(c, aCloud, coretesting.ModelTag.Id()),
  1173  		context.NewEmptyCloudCallContext(), nil).Times(2)
  1174  
  1175  	results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{
  1176  		Force: false,
  1177  		Credentials: []params.TaggedCredential{{
  1178  			Tag:        "cloudcred-meep_julia_three",
  1179  			Credential: params.CloudCredential{},
  1180  		}}})
  1181  	c.Assert(err, jc.ErrorIsNil)
  1182  	c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{
  1183  		Results: []params.UpdateCredentialResult{{
  1184  			CredentialTag: "cloudcred-meep_julia_three",
  1185  			Models: []params.UpdateCredentialModelResult{
  1186  				{
  1187  					ModelUUID: coretesting.ModelTag.Id(),
  1188  					ModelName: "testModel1",
  1189  					Errors:    []params.ErrorResult{{Error: &params.Error{Message: "not valid for model"}}},
  1190  				},
  1191  				{
  1192  					ModelUUID: "deadbeef-2f18-4fd2-967d-db9663db7bea",
  1193  					ModelName: "testModel2",
  1194  					Errors:    []params.ErrorResult{{Error: &params.Error{Message: "not valid for model"}}},
  1195  				},
  1196  			},
  1197  		}}},
  1198  	)
  1199  }
  1200  
  1201  func (s *cloudSuite) TestUpdateCredentialsAllModelsFailedValidationForce(c *gc.C) {
  1202  	adminTag := names.NewUserTag("admin")
  1203  	defer s.setup(c, adminTag).Finish()
  1204  
  1205  	s.PatchValue(cloud.ValidateNewCredentialForModelFunc,
  1206  		func(_ credentialcommon.PersistentBackend, _ context.ProviderCallContext,
  1207  			_ names.CloudCredentialTag, _ *jujucloud.Credential, migrating bool, _ bool) (params.ErrorResults,
  1208  			error) {
  1209  			return params.ErrorResults{Results: []params.ErrorResult{{Error: &params.Error{Message: "not valid for model"}}}}, nil
  1210  		})
  1211  
  1212  	aCloud := jujucloud.Cloud{
  1213  		Name:      "dummy",
  1214  		Type:      "dummy",
  1215  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
  1216  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
  1217  	}
  1218  
  1219  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1220  		attrs: map[string]string{}}, c)
  1221  
  1222  	backend := s.backend.EXPECT()
  1223  	backend.CredentialModels(tag).Return(map[string]string{
  1224  		coretesting.ModelTag.Id():              "testModel1",
  1225  		"deadbeef-2f18-4fd2-967d-db9663db7bea": "testModel2",
  1226  	}, nil)
  1227  	backend.UpdateCloudCredential(tag, jujucloud.Credential{}).Return(nil)
  1228  
  1229  	pool := s.pool.EXPECT()
  1230  	pool.GetModelCallContext(gomock.Any()).Return(newModelBackend(c, aCloud, coretesting.ModelTag.Id()),
  1231  		context.NewEmptyCloudCallContext(), nil)
  1232  	pool.GetModelCallContext(gomock.Any()).Return(newModelBackend(c, aCloud, "deadbeef-2f18-4fd2-967d-db9663db7bea"),
  1233  		context.NewEmptyCloudCallContext(), nil)
  1234  
  1235  	results, err := s.api.UpdateCredentialsCheckModels(params.UpdateCredentialArgs{
  1236  		Force: true,
  1237  		Credentials: []params.TaggedCredential{{
  1238  			Tag:        "cloudcred-meep_julia_three",
  1239  			Credential: params.CloudCredential{},
  1240  		}},
  1241  	})
  1242  	c.Assert(err, jc.ErrorIsNil)
  1243  	c.Assert(results, jc.DeepEquals, params.UpdateCredentialResults{
  1244  		Results: []params.UpdateCredentialResult{{
  1245  			CredentialTag: "cloudcred-meep_julia_three",
  1246  			Models: []params.UpdateCredentialModelResult{
  1247  				{
  1248  					ModelUUID: coretesting.ModelTag.Id(),
  1249  					ModelName: "testModel1",
  1250  					Errors:    []params.ErrorResult{{Error: &params.Error{Message: "not valid for model"}}},
  1251  				},
  1252  				{
  1253  					ModelUUID: "deadbeef-2f18-4fd2-967d-db9663db7bea",
  1254  					ModelName: "testModel2",
  1255  					Errors:    []params.ErrorResult{{Error: &params.Error{Message: "not valid for model"}}},
  1256  				},
  1257  			},
  1258  		}}},
  1259  	)
  1260  }
  1261  
  1262  func (s *cloudSuite) TestRevokeCredentials(c *gc.C) {
  1263  	bruceTag := names.NewUserTag("bruce")
  1264  	defer s.setup(c, bruceTag).Finish()
  1265  
  1266  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "bruce", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1267  		attrs: map[string]string{}}, c)
  1268  
  1269  	backend := s.backend.EXPECT()
  1270  	backend.CredentialModels(tag).Return(nil, nil)
  1271  	backend.RemoveCloudCredential(tag).Return(nil)
  1272  	backend.RemoveModelsCredential(tag).Return(nil)
  1273  
  1274  	results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{
  1275  		Credentials: []params.RevokeCredentialArg{
  1276  			{Tag: "machine-0"},
  1277  			{Tag: "cloudcred-meep_admin_whatever"},
  1278  			{Tag: "cloudcred-meep_bruce_three"},
  1279  		},
  1280  	})
  1281  	c.Assert(err, jc.ErrorIsNil)
  1282  	c.Assert(results.Results, gc.HasLen, 3)
  1283  	c.Assert(results.Results[0].Error, jc.DeepEquals, &params.Error{
  1284  		Message: `"machine-0" is not a valid cloudcred tag`,
  1285  	})
  1286  	c.Assert(results.Results[1].Error, jc.DeepEquals, &params.Error{
  1287  		Message: "permission denied", Code: params.CodeUnauthorized,
  1288  	})
  1289  	c.Assert(results.Results[2].Error, gc.IsNil)
  1290  }
  1291  
  1292  func (s *cloudSuite) TestRevokeCredentialsAdminAccess(c *gc.C) {
  1293  	adminTag := names.NewUserTag("admin")
  1294  	defer s.setup(c, adminTag).Finish()
  1295  
  1296  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1297  		attrs: map[string]string{}}, c)
  1298  
  1299  	backend := s.backend.EXPECT()
  1300  	backend.CredentialModels(tag).Return(nil, nil)
  1301  	backend.RemoveCloudCredential(tag).Return(nil)
  1302  	backend.RemoveModelsCredential(tag).Return(nil)
  1303  
  1304  	results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{
  1305  		Credentials: []params.RevokeCredentialArg{
  1306  			{Tag: "cloudcred-meep_julia_three"},
  1307  		},
  1308  	})
  1309  	c.Assert(err, jc.ErrorIsNil)
  1310  	c.Assert(results.Results, gc.HasLen, 1)
  1311  	// admin can revoke others' credentials
  1312  	c.Assert(results.Results[0].Error, gc.IsNil)
  1313  }
  1314  
  1315  func (s *cloudSuite) TestRevokeCredentialsCantGetModels(c *gc.C) {
  1316  	adminTag := names.NewUserTag("admin")
  1317  	defer s.setup(c, adminTag).Finish()
  1318  
  1319  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1320  		attrs: map[string]string{}}, c)
  1321  
  1322  	backend := s.backend.EXPECT()
  1323  	backend.CredentialModels(tag).Return(nil, errors.New("no niet nope"))
  1324  
  1325  	results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{Credentials: []params.RevokeCredentialArg{
  1326  		{Tag: "cloudcred-meep_julia_three"},
  1327  	}})
  1328  	c.Assert(err, jc.ErrorIsNil)
  1329  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
  1330  		Results: []params.ErrorResult{
  1331  			{Error: apiservererrors.ServerError(errors.New("no niet nope"))},
  1332  		},
  1333  	})
  1334  	c.Assert(c.GetTestLog(), jc.Contains, "")
  1335  }
  1336  
  1337  func (s *cloudSuite) TestRevokeCredentialsForceCantGetModels(c *gc.C) {
  1338  	adminTag := names.NewUserTag("admin")
  1339  	defer s.setup(c, adminTag).Finish()
  1340  
  1341  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1342  		attrs: map[string]string{}}, c)
  1343  
  1344  	backend := s.backend.EXPECT()
  1345  	backend.CredentialModels(tag).Return(nil, errors.New("no niet nope"))
  1346  	backend.RemoveCloudCredential(tag).Return(nil)
  1347  	backend.RemoveModelsCredential(tag).Return(nil)
  1348  
  1349  	results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{Credentials: []params.RevokeCredentialArg{
  1350  		{Tag: "cloudcred-meep_julia_three", Force: true},
  1351  	}})
  1352  	c.Assert(err, jc.ErrorIsNil)
  1353  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
  1354  		Results: []params.ErrorResult{
  1355  			{}, // no error: credential deleted
  1356  		},
  1357  	})
  1358  	c.Assert(c.GetTestLog(), jc.Contains,
  1359  		" WARNING juju.apiserver.cloud could not get models that use credential cloudcred-meep_julia_three: no niet nope")
  1360  }
  1361  
  1362  func (s *cloudSuite) TestRevokeCredentialsHasModel(c *gc.C) {
  1363  	adminTag := names.NewUserTag("admin")
  1364  	defer s.setup(c, adminTag).Finish()
  1365  
  1366  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1367  		attrs: map[string]string{}}, c)
  1368  
  1369  	backend := s.backend.EXPECT()
  1370  	backend.CredentialModels(tag).Return(map[string]string{
  1371  		coretesting.ModelTag.Id(): "modelName",
  1372  	}, nil)
  1373  
  1374  	results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{Credentials: []params.RevokeCredentialArg{
  1375  		{Tag: "cloudcred-meep_julia_three"},
  1376  	}})
  1377  	c.Assert(err, jc.ErrorIsNil)
  1378  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
  1379  		Results: []params.ErrorResult{
  1380  			{Error: apiservererrors.ServerError(errors.New("cannot revoke credential cloudcred-meep_julia_three: it is still used by 1 model"))},
  1381  		},
  1382  	})
  1383  	c.Assert(c.GetTestLog(), jc.Contains,
  1384  		" WARNING juju.apiserver.cloud credential cloudcred-meep_julia_three cannot be deleted as it is used by model deadbeef-0bad-400d-8000-4b1d0d06f00d")
  1385  }
  1386  
  1387  func (s *cloudSuite) TestRevokeCredentialsHasModels(c *gc.C) {
  1388  	adminTag := names.NewUserTag("admin")
  1389  	defer s.setup(c, adminTag).Finish()
  1390  
  1391  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1392  		attrs: map[string]string{}}, c)
  1393  
  1394  	backend := s.backend.EXPECT()
  1395  	backend.CredentialModels(tag).Return(map[string]string{
  1396  		coretesting.ModelTag.Id():              "modelName",
  1397  		"deadbeef-1bad-511d-8000-4b1d0d06f00d": "anotherModelName",
  1398  	}, nil)
  1399  
  1400  	results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{Credentials: []params.RevokeCredentialArg{
  1401  		{Tag: "cloudcred-meep_julia_three"},
  1402  	}})
  1403  	c.Assert(err, jc.ErrorIsNil)
  1404  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
  1405  		Results: []params.ErrorResult{
  1406  			{Error: apiservererrors.ServerError(errors.New("cannot revoke credential cloudcred-meep_julia_three: it is still used by 2 models"))},
  1407  		},
  1408  	})
  1409  	c.Assert(c.GetTestLog(), jc.Contains,
  1410  		` WARNING juju.apiserver.cloud credential cloudcred-meep_julia_three cannot be deleted as it is used by models:
  1411  - deadbeef-0bad-400d-8000-4b1d0d06f00d
  1412  - deadbeef-1bad-511d-8000-4b1d0d06f00d`)
  1413  }
  1414  
  1415  func (s *cloudSuite) TestRevokeCredentialsForceHasModel(c *gc.C) {
  1416  	adminTag := names.NewUserTag("admin")
  1417  	defer s.setup(c, adminTag).Finish()
  1418  
  1419  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1420  		attrs: map[string]string{}}, c)
  1421  
  1422  	backend := s.backend.EXPECT()
  1423  	backend.CredentialModels(tag).Return(map[string]string{
  1424  		coretesting.ModelTag.Id(): "modelName",
  1425  	}, nil)
  1426  	backend.RemoveCloudCredential(tag).Return(nil)
  1427  	backend.RemoveModelsCredential(tag).Return(nil)
  1428  
  1429  	results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{Credentials: []params.RevokeCredentialArg{
  1430  		{Tag: "cloudcred-meep_julia_three", Force: true},
  1431  	}})
  1432  	c.Assert(err, jc.ErrorIsNil)
  1433  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
  1434  		Results: []params.ErrorResult{
  1435  			{},
  1436  		},
  1437  	})
  1438  	c.Assert(c.GetTestLog(), jc.Contains,
  1439  		` WARNING juju.apiserver.cloud credential cloudcred-meep_julia_three will be deleted but it is used by model deadbeef-0bad-400d-8000-4b1d0d06f00d`)
  1440  
  1441  }
  1442  
  1443  func (s *cloudSuite) TestRevokeCredentialsForceMany(c *gc.C) {
  1444  	adminTag := names.NewUserTag("admin")
  1445  	defer s.setup(c, adminTag).Finish()
  1446  
  1447  	_, tagOne := cloudCredentialTag(credParams{name: "three", owner: "bruce", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1448  		attrs: map[string]string{}}, c)
  1449  	_, tagTwo := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1450  		attrs: map[string]string{}}, c)
  1451  
  1452  	backend := s.backend.EXPECT()
  1453  	backend.CredentialModels(tagOne).Return(map[string]string{
  1454  		coretesting.ModelTag.Id(): "modelName",
  1455  	}, nil)
  1456  	backend.CredentialModels(tagTwo).Return(map[string]string{
  1457  		coretesting.ModelTag.Id(): "modelName",
  1458  	}, nil)
  1459  	backend.RemoveCloudCredential(gomock.Any()).Return(nil)
  1460  	backend.RemoveModelsCredential(gomock.Any()).Return(nil)
  1461  
  1462  	results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{Credentials: []params.RevokeCredentialArg{
  1463  		{Tag: "cloudcred-meep_julia_three", Force: true},
  1464  		{Tag: "cloudcred-meep_bruce_three"},
  1465  	}})
  1466  	c.Assert(err, jc.ErrorIsNil)
  1467  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
  1468  		Results: []params.ErrorResult{
  1469  			{},
  1470  			{Error: apiservererrors.ServerError(errors.New("cannot revoke credential cloudcred-meep_bruce_three: it is still used by 1 model"))},
  1471  		},
  1472  	})
  1473  	c.Assert(c.GetTestLog(), jc.Contains,
  1474  		` WARNING juju.apiserver.cloud credential cloudcred-meep_julia_three will be deleted but it is used by model deadbeef-0bad-400d-8000-4b1d0d06f00d`)
  1475  	c.Assert(c.GetTestLog(), jc.Contains,
  1476  		` WARNING juju.apiserver.cloud credential cloudcred-meep_bruce_three cannot be deleted as it is used by model deadbeef-0bad-400d-8000-4b1d0d06f00d`)
  1477  }
  1478  
  1479  func (s *cloudSuite) TestRevokeCredentialsClearModelCredentialsError(c *gc.C) {
  1480  	adminTag := names.NewUserTag("admin")
  1481  	defer s.setup(c, adminTag).Finish()
  1482  
  1483  	_, tag := cloudCredentialTag(credParams{name: "three", owner: "julia", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1484  		attrs: map[string]string{}}, c)
  1485  
  1486  	backend := s.backend.EXPECT()
  1487  	backend.CredentialModels(tag).Return(map[string]string{
  1488  		coretesting.ModelTag.Id(): "modelName",
  1489  	}, nil)
  1490  	backend.RemoveCloudCredential(tag).Return(nil)
  1491  	backend.RemoveModelsCredential(tag).Return(errors.New("kaboom"))
  1492  
  1493  	results, err := s.api.RevokeCredentialsCheckModels(params.RevokeCredentialArgs{Credentials: []params.RevokeCredentialArg{
  1494  		{Tag: "cloudcred-meep_julia_three", Force: true},
  1495  	}})
  1496  	c.Assert(err, jc.ErrorIsNil)
  1497  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
  1498  		Results: []params.ErrorResult{
  1499  			{Error: apiservererrors.ServerError(errors.New("kaboom"))},
  1500  		},
  1501  	})
  1502  	c.Assert(c.GetTestLog(), jc.Contains,
  1503  		" WARNING juju.apiserver.cloud credential cloudcred-meep_julia_three will be deleted but it is used by model deadbeef-0bad-400d-8000-4b1d0d06f00d")
  1504  }
  1505  
  1506  func (s *cloudSuite) TestCredential(c *gc.C) {
  1507  	bruceTag := names.NewUserTag("bruce")
  1508  	defer s.setup(c, bruceTag).Finish()
  1509  
  1510  	credentialOne, tagOne := cloudCredentialTag(credParams{name: "foo", owner: "admin", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1511  		attrs: map[string]string{}}, c)
  1512  	credentialTwo, tagTwo := cloudCredentialTag(credParams{name: "two", owner: "bruce", cloudName: "meep", permission: jujucloud.UserPassAuthType,
  1513  		attrs: map[string]string{
  1514  			"username": "admin",
  1515  			"password": "adm1n",
  1516  		}}, c)
  1517  
  1518  	creds := map[string]state.Credential{
  1519  		tagOne.Id(): credentialOne,
  1520  		tagTwo.Id(): credentialTwo,
  1521  	}
  1522  
  1523  	cloud := jujucloud.Cloud{
  1524  		Name:      "meep",
  1525  		Type:      "dummy",
  1526  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
  1527  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
  1528  	}
  1529  
  1530  	backend := s.backend.EXPECT()
  1531  	backend.Cloud("meep").Return(cloud, nil)
  1532  	backend.CloudCredentials(names.NewUserTag("bruce"), "meep").Return(creds, nil)
  1533  
  1534  	results, err := s.api.Credential(params.Entities{Entities: []params.Entity{{
  1535  		Tag: "machine-0",
  1536  	}, {
  1537  		Tag: "cloudcred-meep_admin_foo",
  1538  	}, {
  1539  		Tag: "cloudcred-meep_bruce_two",
  1540  	}}})
  1541  	c.Assert(err, jc.ErrorIsNil)
  1542  	c.Assert(results.Results, gc.HasLen, 3)
  1543  	c.Assert(results.Results[0].Error, jc.DeepEquals, &params.Error{
  1544  		Message: `"machine-0" is not a valid cloudcred tag`,
  1545  	})
  1546  	c.Assert(results.Results[1].Error, jc.DeepEquals, &params.Error{
  1547  		Message: "permission denied", Code: params.CodeUnauthorized,
  1548  	})
  1549  	c.Assert(results.Results[2].Error, gc.IsNil)
  1550  	c.Assert(results.Results[2].Result, jc.DeepEquals, &params.CloudCredential{
  1551  		AuthType:   "userpass",
  1552  		Attributes: map[string]string{"username": "admin"},
  1553  		Redacted:   []string{"password"},
  1554  	})
  1555  }
  1556  
  1557  func (s *cloudSuite) TestCredentialAdminAccess(c *gc.C) {
  1558  	adminTag := names.NewUserTag("admin")
  1559  	defer s.setup(c, adminTag).Finish()
  1560  
  1561  	credential, tag := cloudCredentialTag(credParams{name: "two", owner: "bruce", cloudName: "meep", permission: jujucloud.UserPassAuthType,
  1562  		attrs: map[string]string{
  1563  			"username": "admin",
  1564  			"password": "adm1n",
  1565  		}}, c)
  1566  
  1567  	creds := map[string]state.Credential{
  1568  		tag.Id(): credential,
  1569  	}
  1570  	cloud := jujucloud.Cloud{
  1571  		Name:      "meep",
  1572  		Type:      "dummy",
  1573  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
  1574  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
  1575  	}
  1576  
  1577  	backend := s.backend.EXPECT()
  1578  	backend.Cloud("meep").Return(cloud, nil)
  1579  	backend.CloudCredentials(names.NewUserTag("bruce"), "meep").Return(creds, nil)
  1580  
  1581  	results, err := s.api.Credential(params.Entities{Entities: []params.Entity{{
  1582  		Tag: "cloudcred-meep_bruce_two",
  1583  	}}})
  1584  	c.Assert(err, jc.ErrorIsNil)
  1585  	c.Assert(results.Results, gc.HasLen, 1)
  1586  	// admin can access others' credentials
  1587  	c.Assert(results.Results[0].Error, gc.IsNil)
  1588  }
  1589  
  1590  func (s *cloudSuite) TestModifyCloudAccess(c *gc.C) {
  1591  	adminTag := names.NewUserTag("admin")
  1592  	defer s.setup(c, adminTag).Finish()
  1593  
  1594  	cloud := jujucloud.Cloud{
  1595  		Name:      "fluffy",
  1596  		Type:      "fluffy",
  1597  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
  1598  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
  1599  	}
  1600  
  1601  	backend := s.backend.EXPECT()
  1602  	backend.Cloud("fluffy").Return(cloud, nil).Times(2)
  1603  	fred := names.NewUserTag("fred")
  1604  	mary := names.NewUserTag("mary")
  1605  	backend.CreateCloudAccess("fluffy", fred,
  1606  		permission.AddModelAccess).Return(nil)
  1607  	backend.RemoveCloudAccess("fluffy", mary).Return(nil)
  1608  
  1609  	results, err := s.api.ModifyCloudAccess(params.ModifyCloudAccessRequest{
  1610  		Changes: []params.ModifyCloudAccess{
  1611  			{
  1612  				Action:   params.GrantCloudAccess,
  1613  				CloudTag: names.NewCloudTag("fluffy").String(),
  1614  				UserTag:  names.NewUserTag("fred").String(),
  1615  				Access:   "add-model",
  1616  			}, {
  1617  				Action:   params.RevokeCloudAccess,
  1618  				CloudTag: names.NewCloudTag("fluffy").String(),
  1619  				UserTag:  names.NewUserTag("mary").String(),
  1620  				Access:   "add-model",
  1621  			},
  1622  		},
  1623  	})
  1624  	c.Assert(err, jc.ErrorIsNil)
  1625  	c.Assert(results.Results, gc.DeepEquals, []params.ErrorResult{
  1626  		{}, {},
  1627  	})
  1628  }
  1629  
  1630  func (s *cloudSuite) TestModifyCloudUpdateAccess(c *gc.C) {
  1631  	adminTag := names.NewUserTag("admin")
  1632  	defer s.setup(c, adminTag).Finish()
  1633  
  1634  	cloud := jujucloud.Cloud{
  1635  		Name:      "fluffy",
  1636  		Type:      "fluffy",
  1637  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
  1638  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
  1639  	}
  1640  
  1641  	fredTag := names.NewUserTag("fred")
  1642  
  1643  	backend := s.backend.EXPECT()
  1644  	backend.Cloud("fluffy").Return(cloud, nil)
  1645  	backend.CreateCloudAccess("fluffy", fredTag,
  1646  		permission.AdminAccess).Return(errors.AlreadyExistsf("access %s", permission.AdminAccess))
  1647  	backend.GetCloudAccess("fluffy", fredTag).Return(permission.AddModelAccess,
  1648  		nil)
  1649  	backend.UpdateCloudAccess("fluffy", fredTag,
  1650  		permission.AdminAccess).Return(nil)
  1651  
  1652  	results, err := s.api.ModifyCloudAccess(params.ModifyCloudAccessRequest{
  1653  		Changes: []params.ModifyCloudAccess{
  1654  			{
  1655  				Action:   params.GrantCloudAccess,
  1656  				CloudTag: names.NewCloudTag("fluffy").String(),
  1657  				UserTag:  names.NewUserTag("fred").String(),
  1658  				Access:   "admin",
  1659  			},
  1660  		},
  1661  	})
  1662  	c.Assert(err, jc.ErrorIsNil)
  1663  	c.Assert(results.Results, gc.DeepEquals, []params.ErrorResult{
  1664  		{},
  1665  	})
  1666  }
  1667  
  1668  func (s *cloudSuite) TestModifyCloudAlreadyHasAccess(c *gc.C) {
  1669  	adminTag := names.NewUserTag("admin")
  1670  	defer s.setup(c, adminTag).Finish()
  1671  
  1672  	cloud := jujucloud.Cloud{
  1673  		Name:      "fluffy",
  1674  		Type:      "fluffy",
  1675  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
  1676  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
  1677  	}
  1678  
  1679  	fredTag := names.NewUserTag("fred")
  1680  
  1681  	backend := s.backend.EXPECT()
  1682  	backend.Cloud("fluffy").Return(cloud, nil)
  1683  	backend.CreateCloudAccess("fluffy", fredTag,
  1684  		permission.AdminAccess).Return(errors.AlreadyExistsf("access %s", permission.AdminAccess))
  1685  	backend.GetCloudAccess("fluffy", fredTag).Return(permission.AdminAccess,
  1686  		nil)
  1687  
  1688  	results, err := s.api.ModifyCloudAccess(params.ModifyCloudAccessRequest{
  1689  		Changes: []params.ModifyCloudAccess{
  1690  			{
  1691  				Action:   params.GrantCloudAccess,
  1692  				CloudTag: names.NewCloudTag("fluffy").String(),
  1693  				UserTag:  names.NewUserTag("fred").String(),
  1694  				Access:   "admin",
  1695  			},
  1696  		},
  1697  	})
  1698  	c.Assert(err, jc.ErrorIsNil)
  1699  	c.Assert(results.Results, gc.DeepEquals, []params.ErrorResult{
  1700  		{Error: &params.Error{Message: `could not grant cloud access: user already has "admin" access or greater`}},
  1701  	})
  1702  }
  1703  
  1704  func (s *cloudSuite) TestCredentialContentsAllNoSecrets(c *gc.C) {
  1705  	bruceTag := names.NewUserTag("bruce")
  1706  	defer s.setup(c, bruceTag).Finish()
  1707  
  1708  	credentialOne, tagOne := cloudCredentialTag(credParams{name: "one", owner: "bruce", cloudName: "meep", permission: jujucloud.EmptyAuthType,
  1709  		attrs: map[string]string{}}, c)
  1710  
  1711  	credentialTwo, tagTwo := cloudCredentialTag(credParams{name: "two", owner: "bruce", cloudName: "meep", permission: jujucloud.UserPassAuthType,
  1712  		attrs: map[string]string{
  1713  			"username": "admin",
  1714  		}}, c)
  1715  
  1716  	credentialTwo.Invalid = true
  1717  	creds := []state.Credential{
  1718  		credentialOne,
  1719  		credentialTwo,
  1720  	}
  1721  	cloud := jujucloud.Cloud{
  1722  		Name:      "dummy",
  1723  		Type:      "dummy",
  1724  		AuthTypes: []jujucloud.AuthType{jujucloud.EmptyAuthType, jujucloud.UserPassAuthType},
  1725  		Regions:   []jujucloud.Region{{Name: "nether", Endpoint: "endpoint"}},
  1726  	}
  1727  
  1728  	backend := s.backend.EXPECT()
  1729  	backend.AllCloudCredentials(bruceTag).Return(creds, nil)
  1730  	backend.Cloud("meep").Return(cloud, nil)
  1731  	backend.CredentialModelsAndOwnerAccess(tagOne).Return([]state.CredentialOwnerModelAccess{}, nil)
  1732  	backend.CredentialModelsAndOwnerAccess(tagTwo).Return([]state.CredentialOwnerModelAccess{}, nil)
  1733  
  1734  	results, err := s.api.CredentialContents(params.CloudCredentialArgs{})
  1735  	c.Assert(err, jc.ErrorIsNil)
  1736  
  1737  	_true := true
  1738  	_false := false
  1739  	expected := map[string]params.CredentialContent{
  1740  		"one": {
  1741  			Name:       "one",
  1742  			Cloud:      "meep",
  1743  			AuthType:   "empty",
  1744  			Valid:      &_true,
  1745  			Attributes: map[string]string{},
  1746  		},
  1747  		"two": {
  1748  			Name:     "two",
  1749  			Cloud:    "meep",
  1750  			AuthType: "userpass",
  1751  			Valid:    &_false,
  1752  			Attributes: map[string]string{
  1753  				"username": "admin",
  1754  			},
  1755  		},
  1756  	}
  1757  
  1758  	c.Assert(results.Results, gc.HasLen, len(expected))
  1759  	for _, one := range results.Results {
  1760  		c.Assert(one.Result.Content, gc.DeepEquals, expected[one.Result.Content.Name])
  1761  	}
  1762  }
  1763  
  1764  func cloudCredentialTag(params credParams,
  1765  	c *gc.C) (state.Credential, names.CloudCredentialTag) {
  1766  	cred := statetesting.CloudCredential(params.permission, params.attrs)
  1767  	cred.Name = params.name
  1768  	cred.Owner = params.owner
  1769  	cred.Cloud = params.cloudName
  1770  
  1771  	tag, err := cred.CloudCredentialTag()
  1772  	c.Assert(err, jc.ErrorIsNil)
  1773  
  1774  	return cred, tag
  1775  }
  1776  
  1777  type credParams struct {
  1778  	name       string
  1779  	owner      string
  1780  	cloudName  string
  1781  	permission jujucloud.AuthType
  1782  	attrs      map[string]string
  1783  }
  1784  
  1785  type mockModelBackend struct {
  1786  	credentialcommon.PersistentBackend
  1787  	uuid string
  1788  }