From 31e87ee3d0c8774bed2cda258d3e980147bebb7d Mon Sep 17 00:00:00 2001 From: Kian Parvin <46668016+kian99@users.noreply.github.com> Date: Tue, 30 Jan 2024 14:49:49 +0200 Subject: [PATCH] CSS-6952 add service account command (#1141) * Add CLI command and tests for addServiceAccount * Hook up GoCheck to test runner * Test for addServiceAccount CLI method * Extend test * PR comments * PR comments updated godoc --- api/jimm.go | 5 + cmd/jimmctl/cmd/addcloudtocontroller_test.go | 7 +- cmd/jimmctl/cmd/addcontroller_test.go | 7 +- cmd/jimmctl/cmd/controllerinfo_test.go | 3 +- cmd/jimmctl/cmd/crossmodelquery_test.go | 5 +- cmd/jimmctl/cmd/grantauditlogaccess_test.go | 7 +- cmd/jimmctl/cmd/group_test.go | 33 +++--- .../cmd/importcloudcredentials_test.go | 5 +- cmd/jimmctl/cmd/importmodel_test.go | 17 +-- cmd/jimmctl/cmd/listauditevents_test.go | 7 +- cmd/jimmctl/cmd/listcontrollers_test.go | 7 +- cmd/jimmctl/cmd/migratemodel_test.go | 9 +- cmd/jimmctl/cmd/modelstatus_test.go | 7 +- cmd/jimmctl/cmd/purge_logs_test.go | 16 +-- cmd/jimmctl/cmd/relation_test.go | 39 +++---- .../cmd/removecloudfromcontroller_test.go | 11 +- cmd/jimmctl/cmd/removecontroller_test.go | 7 +- cmd/jimmctl/cmd/revokeauditlogaccess_test.go | 7 +- .../cmd/setcontrollerdeprecated_test.go | 7 +- cmd/jimmctl/cmd/updatemigratedmodel_test.go | 15 +-- cmd/serviceaccounts/cmd/addserviceaccount.go | 100 ++++++++++++++++++ .../cmd/addserviceaccount_test.go | 47 ++++++++ cmd/serviceaccounts/cmd/export_test.go | 23 ++++ cmd/serviceaccounts/cmd/package_test.go | 13 +++ .../main.go | 3 +- .../cmdtest/jimmsuite.go | 20 ++-- internal/jimm/service_account.go | 1 + internal/jujuapi/jimm.go | 4 + 28 files changed, 324 insertions(+), 108 deletions(-) create mode 100644 cmd/serviceaccounts/cmd/addserviceaccount.go create mode 100644 cmd/serviceaccounts/cmd/addserviceaccount_test.go create mode 100644 cmd/serviceaccounts/cmd/export_test.go create mode 100644 cmd/serviceaccounts/cmd/package_test.go rename cmd/{service-accounts => serviceaccounts}/main.go (85%) rename cmd/jimmctl/cmd/jimmsuite_test.go => internal/cmdtest/jimmsuite.go (89%) diff --git a/api/jimm.go b/api/jimm.go index be08b1333..a98573801 100644 --- a/api/jimm.go +++ b/api/jimm.go @@ -190,3 +190,8 @@ func (c *Client) MigrateModel(req *params.MigrateModelRequest) (*jujuparams.Init err := c.caller.APICall("JIMM", 4, "", "MigrateModel", req, &response) return &response, err } + +// AddServiceAccount binds a service account to a user allowing them to manage it. +func (c *Client) AddServiceAccount(req *params.AddServiceAccountRequest) error { + return c.caller.APICall("JIMM", 4, "", "AddServiceAccount", req, nil) +} diff --git a/cmd/jimmctl/cmd/addcloudtocontroller_test.go b/cmd/jimmctl/cmd/addcloudtocontroller_test.go index e99d92393..98e59d71f 100644 --- a/cmd/jimmctl/cmd/addcloudtocontroller_test.go +++ b/cmd/jimmctl/cmd/addcloudtocontroller_test.go @@ -15,6 +15,7 @@ import ( gc "gopkg.in/check.v1" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" "github.com/canonical/jimm/internal/dbmodel" "github.com/canonical/jimm/internal/errors" "github.com/canonical/jimm/internal/openfga" @@ -22,13 +23,13 @@ import ( ) type addCloudToControllerSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&addCloudToControllerSuite{}) func (s *addCloudToControllerSuite) SetUpTest(c *gc.C) { - s.jimmSuite.SetUpTest(c) + s.JimmCmdSuite.SetUpTest(c) // We add user bob, who is a JIMM administrator. err := s.JIMM.Database.UpdateIdentity(context.Background(), &dbmodel.Identity{ @@ -166,7 +167,7 @@ clouds: c.Log(test.about) tmpfile, cleanupFunc := writeTempFile(c, test.cloudInfo) - bClient := s.userBakeryClient("bob@external") + bClient := s.UserBakeryClient("bob@external") // Running the command succeeds newCmd := cmd.NewAddCloudToControllerCommandForTesting(s.ClientStore(), bClient, test.cloudByNameFunc) var err error diff --git a/cmd/jimmctl/cmd/addcontroller_test.go b/cmd/jimmctl/cmd/addcontroller_test.go index cba5847f6..b142d5668 100644 --- a/cmd/jimmctl/cmd/addcontroller_test.go +++ b/cmd/jimmctl/cmd/addcontroller_test.go @@ -14,11 +14,12 @@ import ( apiparams "github.com/canonical/jimm/api/params" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" "github.com/canonical/jimm/internal/jimmtest" ) type addControllerSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&addControllerSuite{}) @@ -37,7 +38,7 @@ func (s *addControllerSuite) TestAddControllerSuperuser(c *gc.C) { defer os.RemoveAll(tmpdir) // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") ctx, err := cmdtesting.RunCommand(c, cmd.NewAddControllerCommandForTesting(s.ClientStore(), bClient), tmpfile) c.Assert(err, gc.IsNil) c.Assert(cmdtesting.Stdout(ctx), gc.Matches, `name: controller-1 @@ -100,7 +101,7 @@ func (s *addControllerSuite) TestAddController(c *gc.C) { defer os.RemoveAll(tmpdir) // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewAddControllerCommandForTesting(s.ClientStore(), bClient), tmpfile) c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } diff --git a/cmd/jimmctl/cmd/controllerinfo_test.go b/cmd/jimmctl/cmd/controllerinfo_test.go index b93322143..e7d46ebc9 100644 --- a/cmd/jimmctl/cmd/controllerinfo_test.go +++ b/cmd/jimmctl/cmd/controllerinfo_test.go @@ -11,10 +11,11 @@ import ( gc "gopkg.in/check.v1" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" ) type controllerInfoSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&controllerInfoSuite{}) diff --git a/cmd/jimmctl/cmd/crossmodelquery_test.go b/cmd/jimmctl/cmd/crossmodelquery_test.go index eb73d3129..2e27f5d73 100644 --- a/cmd/jimmctl/cmd/crossmodelquery_test.go +++ b/cmd/jimmctl/cmd/crossmodelquery_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" "github.com/canonical/jimm/internal/jimmtest" "github.com/juju/cmd/v3/cmdtesting" jujuparams "github.com/juju/juju/rpc/params" @@ -15,7 +16,7 @@ import ( ) type crossModelQuerySuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&crossModelQuerySuite{}) @@ -23,7 +24,7 @@ var _ = gc.Suite(&crossModelQuerySuite{}) func (s *crossModelQuerySuite) TestCrossModelQueryCommand(c *gc.C) { // Test setup. store := s.ClientStore() - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") s.AddController(c, "controller-2", s.APIInfo(c)) cct := names.NewCloudCredentialTag(jimmtest.TestCloudName + "/alice@external/cred") diff --git a/cmd/jimmctl/cmd/grantauditlogaccess_test.go b/cmd/jimmctl/cmd/grantauditlogaccess_test.go index 288b08f30..6036df171 100644 --- a/cmd/jimmctl/cmd/grantauditlogaccess_test.go +++ b/cmd/jimmctl/cmd/grantauditlogaccess_test.go @@ -7,10 +7,11 @@ import ( gc "gopkg.in/check.v1" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" ) type grantAuditLogAccessSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } // TODO (alesstimec) uncomment once granting/revoking is reimplemented @@ -18,14 +19,14 @@ type grantAuditLogAccessSuite struct { func (s *grantAuditLogAccessSuite) TestGrantAuditLogAccessSuperuser(c *gc.C) { // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") _, err := cmdtesting.RunCommand(c, cmd.NewGrantAuditLogAccessCommandForTesting(s.ClientStore(), bClient), "bob@external") c.Assert(err, gc.IsNil) } func (s *grantAuditLogAccessSuite) TestGrantAuditLogAccess(c *gc.C) { // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewGrantAuditLogAccessCommandForTesting(s.ClientStore(), bClient), "bob@external") c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } diff --git a/cmd/jimmctl/cmd/group_test.go b/cmd/jimmctl/cmd/group_test.go index ddfd63d6a..ce3b02427 100644 --- a/cmd/jimmctl/cmd/group_test.go +++ b/cmd/jimmctl/cmd/group_test.go @@ -11,23 +11,24 @@ import ( gc "gopkg.in/check.v1" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" "github.com/canonical/jimm/internal/dbmodel" ) type groupSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&groupSuite{}) func (s *groupSuite) TestAddGroupSuperuser(c *gc.C) { // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") _, err := cmdtesting.RunCommand(c, cmd.NewAddGroupCommandForTesting(s.ClientStore(), bClient), "test-group") c.Assert(err, gc.IsNil) group := &dbmodel.GroupEntry{Name: "test-group"} - err = s.jimmSuite.JIMM.Database.GetGroup(context.TODO(), group) + err = s.JimmCmdSuite.JIMM.Database.GetGroup(context.TODO(), group) c.Assert(err, gc.IsNil) c.Assert(group.ID, gc.Equals, uint(1)) c.Assert(group.Name, gc.Equals, "test-group") @@ -35,23 +36,23 @@ func (s *groupSuite) TestAddGroupSuperuser(c *gc.C) { func (s *groupSuite) TestAddGroup(c *gc.C) { // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewAddGroupCommandForTesting(s.ClientStore(), bClient), "test-group") c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } func (s *groupSuite) TestRenameGroupSuperuser(c *gc.C) { // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") - err := s.jimmSuite.JIMM.Database.AddGroup(context.TODO(), "test-group") + err := s.JimmCmdSuite.JIMM.Database.AddGroup(context.TODO(), "test-group") c.Assert(err, gc.IsNil) _, err = cmdtesting.RunCommand(c, cmd.NewRenameGroupCommandForTesting(s.ClientStore(), bClient), "test-group", "renamed-group") c.Assert(err, gc.IsNil) group := &dbmodel.GroupEntry{Name: "renamed-group"} - err = s.jimmSuite.JIMM.Database.GetGroup(context.TODO(), group) + err = s.JimmCmdSuite.JIMM.Database.GetGroup(context.TODO(), group) c.Assert(err, gc.IsNil) c.Assert(group.ID, gc.Equals, uint(1)) c.Assert(group.Name, gc.Equals, "renamed-group") @@ -59,29 +60,29 @@ func (s *groupSuite) TestRenameGroupSuperuser(c *gc.C) { func (s *groupSuite) TestRenameGroup(c *gc.C) { // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewRenameGroupCommandForTesting(s.ClientStore(), bClient), "test-group", "renamed-group") c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } func (s *groupSuite) TestRemoveGroupSuperuser(c *gc.C) { // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") - err := s.jimmSuite.JIMM.Database.AddGroup(context.TODO(), "test-group") + err := s.JimmCmdSuite.JIMM.Database.AddGroup(context.TODO(), "test-group") c.Assert(err, gc.IsNil) _, err = cmdtesting.RunCommand(c, cmd.NewRemoveGroupCommandForTesting(s.ClientStore(), bClient), "test-group", "-y") c.Assert(err, gc.IsNil) group := &dbmodel.GroupEntry{Name: "test-group"} - err = s.jimmSuite.JIMM.Database.GetGroup(context.TODO(), group) + err = s.JimmCmdSuite.JIMM.Database.GetGroup(context.TODO(), group) c.Assert(err, gc.ErrorMatches, "record not found") } func (s *groupSuite) TestRemoveGroupWithoutFlag(c *gc.C) { // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") _, err := cmdtesting.RunCommand(c, cmd.NewRemoveGroupCommandForTesting(s.ClientStore(), bClient), "test-group") c.Assert(err.Error(), gc.Matches, "Failed to read from input.") @@ -89,17 +90,17 @@ func (s *groupSuite) TestRemoveGroupWithoutFlag(c *gc.C) { func (s *groupSuite) TestRemoveGroup(c *gc.C) { // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewRemoveGroupCommandForTesting(s.ClientStore(), bClient), "test-group", "-y") c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } func (s *groupSuite) TestListGroupsSuperuser(c *gc.C) { // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") for i := 0; i < 3; i++ { - err := s.jimmSuite.JIMM.Database.AddGroup(context.TODO(), fmt.Sprint("test-group", i)) + err := s.JimmCmdSuite.JIMM.Database.AddGroup(context.TODO(), fmt.Sprint("test-group", i)) c.Assert(err, gc.IsNil) } @@ -113,7 +114,7 @@ func (s *groupSuite) TestListGroupsSuperuser(c *gc.C) { func (s *groupSuite) TestListGroups(c *gc.C) { // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewListGroupsCommandForTesting(s.ClientStore(), bClient), "test-group") c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } diff --git a/cmd/jimmctl/cmd/importcloudcredentials_test.go b/cmd/jimmctl/cmd/importcloudcredentials_test.go index 69407397e..2dea10c8b 100644 --- a/cmd/jimmctl/cmd/importcloudcredentials_test.go +++ b/cmd/jimmctl/cmd/importcloudcredentials_test.go @@ -11,11 +11,12 @@ import ( gc "gopkg.in/check.v1" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" "github.com/canonical/jimm/internal/dbmodel" ) type importCloudCredentialsSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&importCloudCredentialsSuite{}) @@ -62,7 +63,7 @@ func (s *importCloudCredentialsSuite) TestImportCloudCredentials(c *gc.C) { c.Assert(err, gc.IsNil) // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") _, err = cmdtesting.RunCommand(c, cmd.NewImportCloudCredentialsCommandForTesting(s.ClientStore(), bClient), tmpfile) c.Assert(err, gc.IsNil) diff --git a/cmd/jimmctl/cmd/importmodel_test.go b/cmd/jimmctl/cmd/importmodel_test.go index fcea71caa..e034b056d 100644 --- a/cmd/jimmctl/cmd/importmodel_test.go +++ b/cmd/jimmctl/cmd/importmodel_test.go @@ -13,12 +13,13 @@ import ( gc "gopkg.in/check.v1" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" "github.com/canonical/jimm/internal/dbmodel" "github.com/canonical/jimm/internal/jimmtest" ) type importModelSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&importModelSuite{}) @@ -42,7 +43,7 @@ func (s *importModelSuite) TestImportModelSuperuser(c *gc.C) { defer m.Close() // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") _, err = cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-1", m.ModelUUID()) c.Assert(err, gc.IsNil) @@ -69,7 +70,7 @@ func (s *importModelSuite) TestImportModelFromLocalUser(c *gc.C) { c.Assert(err, gc.Equals, nil) // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") _, err = cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-1", mt.Id(), "--owner", "alice@external") c.Assert(err, gc.IsNil) @@ -100,31 +101,31 @@ func (s *importModelSuite) TestImportModelUnauthorized(c *gc.C) { defer m.Close() // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err = cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-1", m.ModelUUID()) c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } func (s *importModelSuite) TestImportModelNoController(c *gc.C) { - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient)) c.Assert(err, gc.ErrorMatches, `controller not specified`) } func (s *importModelSuite) TestImportModelNoModelUUID(c *gc.C) { - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-id") c.Assert(err, gc.ErrorMatches, `model uuid not specified`) } func (s *importModelSuite) TestImportModelInvalidModelUUID(c *gc.C) { - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-id", "not-a-uuid") c.Assert(err, gc.ErrorMatches, `invalid model uuid`) } func (s *importModelSuite) TestImportModelTooManyArgs(c *gc.C) { - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-id", "not-a-uuid", "spare-argument") c.Assert(err, gc.ErrorMatches, `too many args`) } diff --git a/cmd/jimmctl/cmd/listauditevents_test.go b/cmd/jimmctl/cmd/listauditevents_test.go index a16411dfa..065060f2a 100644 --- a/cmd/jimmctl/cmd/listauditevents_test.go +++ b/cmd/jimmctl/cmd/listauditevents_test.go @@ -9,11 +9,12 @@ import ( gc "gopkg.in/check.v1" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" "github.com/canonical/jimm/internal/jimmtest" ) type listAuditEventsSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&listAuditEventsSuite{}) @@ -26,7 +27,7 @@ func (s *listAuditEventsSuite) TestListAuditEventsSuperuser(c *gc.C) { s.AddModel(c, names.NewUserTag("charlie@external"), "model-2", names.NewCloudTag(jimmtest.TestCloudName), jimmtest.TestCloudRegionName, cct) // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") context, err := cmdtesting.RunCommand(c, cmd.NewListAuditEventsCommandForTesting(s.ClientStore(), bClient)) c.Assert(err, gc.IsNil) c.Assert(cmdtesting.Stdout(context), gc.Matches, @@ -65,7 +66,7 @@ func (s *listAuditEventsSuite) TestListAuditEventsStatus(c *gc.C) { s.AddModel(c, names.NewUserTag("charlie@external"), "model-2", names.NewCloudTag(jimmtest.TestCloudName), jimmtest.TestCloudRegionName, cct) // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewListAuditEventsCommandForTesting(s.ClientStore(), bClient)) c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } diff --git a/cmd/jimmctl/cmd/listcontrollers_test.go b/cmd/jimmctl/cmd/listcontrollers_test.go index 061a5337e..fcb1b859a 100644 --- a/cmd/jimmctl/cmd/listcontrollers_test.go +++ b/cmd/jimmctl/cmd/listcontrollers_test.go @@ -7,6 +7,7 @@ import ( gc "gopkg.in/check.v1" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" "github.com/canonical/jimm/internal/jimmtest" ) @@ -67,7 +68,7 @@ var ( ) type listControllersSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&listControllersSuite{}) @@ -76,7 +77,7 @@ func (s *listControllersSuite) TestListControllersSuperuser(c *gc.C) { s.AddController(c, "controller-1", s.APIInfo(c)) // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") context, err := cmdtesting.RunCommand(c, cmd.NewListControllersCommandForTesting(s.ClientStore(), bClient)) c.Assert(err, gc.IsNil) c.Assert(cmdtesting.Stdout(context), gc.Matches, expectedSuperuserOutput) @@ -86,7 +87,7 @@ func (s *listControllersSuite) TestListControllers(c *gc.C) { s.AddController(c, "controller-1", s.APIInfo(c)) // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") context, err := cmdtesting.RunCommand(c, cmd.NewListControllersCommandForTesting(s.ClientStore(), bClient)) c.Assert(err, gc.IsNil) c.Assert(cmdtesting.Stdout(context), gc.Matches, expectedOutput) diff --git a/cmd/jimmctl/cmd/migratemodel_test.go b/cmd/jimmctl/cmd/migratemodel_test.go index 479b6e161..9eaa1086a 100644 --- a/cmd/jimmctl/cmd/migratemodel_test.go +++ b/cmd/jimmctl/cmd/migratemodel_test.go @@ -9,11 +9,12 @@ import ( gc "gopkg.in/check.v1" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" "github.com/canonical/jimm/internal/jimmtest" ) type migrateModelSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&migrateModelSuite{}) @@ -46,7 +47,7 @@ func (s *migrateModelSuite) TestMigrateModelCommandSuperuser(c *gc.C) { mt2 := s.AddModel(c, names.NewUserTag("charlie@external"), "model-2", names.NewCloudTag(jimmtest.TestCloudName), jimmtest.TestCloudRegionName, cct) // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") context, err := cmdtesting.RunCommand(c, cmd.NewMigrateModelCommandForTesting(s.ClientStore(), bClient), "controller-1", mt.String(), mt2.String()) c.Assert(err, gc.IsNil) c.Assert(cmdtesting.Stdout(context), gc.Matches, migrationResultRegex) @@ -60,13 +61,13 @@ func (s *migrateModelSuite) TestMigrateModelCommandFailsWithInvalidModelTag(c *g s.AddModel(c, names.NewUserTag("charlie@external"), "model-2", names.NewCloudTag(jimmtest.TestCloudName), jimmtest.TestCloudRegionName, cct) // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") _, err := cmdtesting.RunCommand(c, cmd.NewMigrateModelCommandForTesting(s.ClientStore(), bClient), "controller-1", "model-001", "model-002") c.Assert(err, gc.ErrorMatches, ".* is not a valid model tag") } func (s *migrateModelSuite) TestMigrateModelCommandFailsWithMissingArgs(c *gc.C) { - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") _, err := cmdtesting.RunCommand(c, cmd.NewMigrateModelCommandForTesting(s.ClientStore(), bClient), "myController") c.Assert(err, gc.ErrorMatches, "Missing controller and model tag arguments") } diff --git a/cmd/jimmctl/cmd/modelstatus_test.go b/cmd/jimmctl/cmd/modelstatus_test.go index c078b7d9e..27dfce70f 100644 --- a/cmd/jimmctl/cmd/modelstatus_test.go +++ b/cmd/jimmctl/cmd/modelstatus_test.go @@ -9,6 +9,7 @@ import ( gc "gopkg.in/check.v1" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" "github.com/canonical/jimm/internal/jimmtest" ) @@ -47,7 +48,7 @@ volumes: \[\] ) type modelStatusSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&modelStatusSuite{}) @@ -60,7 +61,7 @@ func (s *modelStatusSuite) TestModelStatusSuperuser(c *gc.C) { mt := s.AddModel(c, names.NewUserTag("charlie@external"), "model-2", names.NewCloudTag(jimmtest.TestCloudName), jimmtest.TestCloudRegionName, cct) // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") context, err := cmdtesting.RunCommand(c, cmd.NewModelStatusCommandForTesting(s.ClientStore(), bClient), mt.Id()) c.Assert(err, gc.IsNil) c.Assert(cmdtesting.Stdout(context), gc.Matches, expectedModelStatusOutput) @@ -74,7 +75,7 @@ func (s *modelStatusSuite) TestModelStatus(c *gc.C) { mt := s.AddModel(c, names.NewUserTag("charlie@external"), "model-2", names.NewCloudTag(jimmtest.TestCloudName), jimmtest.TestCloudRegionName, cct) // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewModelStatusCommandForTesting(s.ClientStore(), bClient), mt.Id()) c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } diff --git a/cmd/jimmctl/cmd/purge_logs_test.go b/cmd/jimmctl/cmd/purge_logs_test.go index 49268816a..1b20acf5f 100644 --- a/cmd/jimmctl/cmd/purge_logs_test.go +++ b/cmd/jimmctl/cmd/purge_logs_test.go @@ -5,22 +5,24 @@ import ( "context" "time" - "github.com/canonical/jimm/cmd/jimmctl/cmd" - "github.com/canonical/jimm/internal/dbmodel" "github.com/juju/cmd/v3/cmdtesting" "github.com/juju/names/v4" gc "gopkg.in/check.v1" + + "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" + "github.com/canonical/jimm/internal/dbmodel" ) type purgeLogsSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&purgeLogsSuite{}) func (s *purgeLogsSuite) TestPurgeLogsSuperuser(c *gc.C) { // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") datastring := "2021-01-01T00:00:00Z" cmdCtx, err := cmdtesting.RunCommand(c, cmd.NewPurgeLogsCommandForTesting(s.ClientStore(), bClient), datastring) c.Assert(err, gc.IsNil) @@ -31,7 +33,7 @@ func (s *purgeLogsSuite) TestPurgeLogsSuperuser(c *gc.C) { func (s *purgeLogsSuite) TestInvalidISO8601Date(c *gc.C) { // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") datastring := "13/01/2021" _, err := cmdtesting.RunCommand(c, cmd.NewPurgeLogsCommandForTesting(s.ClientStore(), bClient), datastring) c.Assert(err, gc.ErrorMatches, `invalid date. Expected ISO8601 date`) @@ -40,7 +42,7 @@ func (s *purgeLogsSuite) TestInvalidISO8601Date(c *gc.C) { func (s *purgeLogsSuite) TestPurgeLogs(c *gc.C) { // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewPurgeLogsCommandForTesting(s.ClientStore(), bClient), "2021-01-01T00:00:00Z") c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } @@ -82,7 +84,7 @@ func (s *purgeLogsSuite) TestPurgeLogsFromDb(c *gc.C) { tomorrow := relativeNow.AddDate(0, 0, 1).Format(layout) //alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") cmdCtx, err := cmdtesting.RunCommand(c, cmd.NewPurgeLogsCommandForTesting(s.ClientStore(), bClient), tomorrow) c.Assert(err, gc.IsNil) // check that logs have been deleted diff --git a/cmd/jimmctl/cmd/relation_test.go b/cmd/jimmctl/cmd/relation_test.go index be79fe3a0..7da41f007 100644 --- a/cmd/jimmctl/cmd/relation_test.go +++ b/cmd/jimmctl/cmd/relation_test.go @@ -22,6 +22,7 @@ import ( apiparams "github.com/canonical/jimm/api/params" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" "github.com/canonical/jimm/internal/db" "github.com/canonical/jimm/internal/dbmodel" "github.com/canonical/jimm/internal/openfga" @@ -30,14 +31,14 @@ import ( ) type relationSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&relationSuite{}) func (s *relationSuite) TestAddRelationSuperuser(c *gc.C) { // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") group1 := "testGroup1" group2 := "testGroup2" type tuple struct { @@ -81,9 +82,9 @@ func (s *relationSuite) TestAddRelationSuperuser(c *gc.C) { }, } - err := s.jimmSuite.JIMM.Database.AddGroup(context.Background(), group1) + err := s.JimmCmdSuite.JIMM.Database.AddGroup(context.Background(), group1) c.Assert(err, gc.IsNil) - err = s.jimmSuite.JIMM.Database.AddGroup(context.Background(), group2) + err = s.JimmCmdSuite.JIMM.Database.AddGroup(context.Background(), group2) c.Assert(err, gc.IsNil) for i, tc := range tests { @@ -94,7 +95,7 @@ func (s *relationSuite) TestAddRelationSuperuser(c *gc.C) { c.Assert(strings.Contains(err.Error(), tc.message), gc.Equals, true) } else { c.Assert(err, gc.IsNil) - tuples, ct, err := s.jimmSuite.JIMM.OpenFGAClient.ReadRelatedObjects(context.Background(), openfga.Tuple{}, 50, "") + tuples, ct, err := s.JimmCmdSuite.JIMM.OpenFGAClient.ReadRelatedObjects(context.Background(), openfga.Tuple{}, 50, "") c.Assert(err, gc.IsNil) c.Assert(ct, gc.Equals, "") // NOTE: this is a bad test because it relies on the number of related objects. So all the @@ -108,7 +109,7 @@ func (s *relationSuite) TestAddRelationSuperuser(c *gc.C) { func (s *relationSuite) TestMissingParamsAddRelationSuperuser(c *gc.C) { // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") _, err := cmdtesting.RunCommand(c, cmd.NewAddRelationCommandForTesting(s.ClientStore(), bClient), "foo", "bar") c.Assert(err, gc.ErrorMatches, "target object not specified") @@ -121,7 +122,7 @@ func (s *relationSuite) TestMissingParamsAddRelationSuperuser(c *gc.C) { func (s *relationSuite) TestAddRelationViaFileSuperuser(c *gc.C) { // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") group1 := "testGroup1" group2 := "testGroup2" group3 := "testGroup3" @@ -143,21 +144,21 @@ func (s *relationSuite) TestAddRelationViaFileSuperuser(c *gc.C) { _, err = cmdtesting.RunCommand(c, cmd.NewAddRelationCommandForTesting(s.ClientStore(), bClient), "-f", file.Name()) c.Assert(err, gc.IsNil) - tuples, ct, err := s.jimmSuite.JIMM.OpenFGAClient.ReadRelatedObjects(context.Background(), openfga.Tuple{}, 50, "") + tuples, ct, err := s.JimmCmdSuite.JIMM.OpenFGAClient.ReadRelatedObjects(context.Background(), openfga.Tuple{}, 50, "") c.Assert(err, gc.IsNil) c.Assert(ct, gc.Equals, "") c.Assert(len(tuples), gc.Equals, 4) } func (s *relationSuite) TestAddRelationRejectsUnauthorisedUsers(c *gc.C) { - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewAddRelationCommandForTesting(s.ClientStore(), bClient), "test-group1", "member", "test-group2") c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } func (s *relationSuite) TestRemoveRelationSuperuser(c *gc.C) { // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") group1 := "testGroup1" group2 := "testGroup2" type tuple struct { @@ -175,9 +176,9 @@ func (s *relationSuite) TestRemoveRelationSuperuser(c *gc.C) { } //Create groups and relation - err := s.jimmSuite.JIMM.Database.AddGroup(context.Background(), group1) + err := s.JimmCmdSuite.JIMM.Database.AddGroup(context.Background(), group1) c.Assert(err, gc.IsNil) - err = s.jimmSuite.JIMM.Database.AddGroup(context.Background(), group2) + err = s.JimmCmdSuite.JIMM.Database.AddGroup(context.Background(), group2) c.Assert(err, gc.IsNil) totalKeys := 2 for _, tc := range tests { @@ -193,7 +194,7 @@ func (s *relationSuite) TestRemoveRelationSuperuser(c *gc.C) { c.Assert(err, gc.ErrorMatches, tc.message) } else { c.Assert(err, gc.IsNil) - tuples, ct, err := s.jimmSuite.JIMM.OpenFGAClient.ReadRelatedObjects(context.Background(), openfga.Tuple{}, 50, "") + tuples, ct, err := s.JimmCmdSuite.JIMM.OpenFGAClient.ReadRelatedObjects(context.Background(), openfga.Tuple{}, 50, "") c.Assert(err, gc.IsNil) c.Assert(ct, gc.Equals, "") totalKeys-- @@ -203,7 +204,7 @@ func (s *relationSuite) TestRemoveRelationSuperuser(c *gc.C) { } func (s *relationSuite) TestRemoveRelationViaFileSuperuser(c *gc.C) { - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") group1 := "testGroup1" group2 := "testGroup2" group3 := "testGroup3" @@ -228,7 +229,7 @@ func (s *relationSuite) TestRemoveRelationViaFileSuperuser(c *gc.C) { _, err = cmdtesting.RunCommand(c, cmd.NewRemoveRelationCommandForTesting(s.ClientStore(), bClient), "-f", file.Name()) c.Assert(err, gc.IsNil) - tuples, ct, err := s.jimmSuite.JIMM.OpenFGAClient.ReadRelatedObjects(context.Background(), openfga.Tuple{}, 50, "") + tuples, ct, err := s.JimmCmdSuite.JIMM.OpenFGAClient.ReadRelatedObjects(context.Background(), openfga.Tuple{}, 50, "") c.Assert(err, gc.IsNil) c.Assert(ct, gc.Equals, "") c.Logf("existing relations %v", tuples) @@ -249,7 +250,7 @@ func (s *relationSuite) TestRemoveRelationViaFileSuperuser(c *gc.C) { func (s *relationSuite) TestRemoveRelation(c *gc.C) { // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewRemoveRelationCommandForTesting(s.ClientStore(), bClient), "test-group1#member", "member", "test-group2") c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } @@ -344,7 +345,7 @@ func initializeEnvironment(c *gc.C, ctx context.Context, db *db.Database, u dbmo func (s *relationSuite) TestListRelations(c *gc.C) { env := initializeEnvironment(c, context.Background(), &s.JIMM.Database, *s.AdminUser) // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") groups := []string{"group-1", "group-2", "group-3"} for _, group := range groups { @@ -437,7 +438,7 @@ user-eve@external administrator applicationoffer-test-controller-1:alice@exte // TODO: remove boilerplate of env setup and use initialiseEnvironment func (s *relationSuite) TestCheckRelationViaSuperuser(c *gc.C) { ctx := context.TODO() - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") ofgaClient := s.JIMM.OpenFGAClient // Add some resources to check against @@ -612,7 +613,7 @@ func (s *relationSuite) TestCheckRelationViaSuperuser(c *gc.C) { func (s *relationSuite) TestCheckRelation(c *gc.C) { // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand( c, cmd.NewCheckRelationCommandForTesting(s.ClientStore(), bClient), diff --git a/cmd/jimmctl/cmd/removecloudfromcontroller_test.go b/cmd/jimmctl/cmd/removecloudfromcontroller_test.go index 2a7b1b046..f3c693346 100644 --- a/cmd/jimmctl/cmd/removecloudfromcontroller_test.go +++ b/cmd/jimmctl/cmd/removecloudfromcontroller_test.go @@ -9,10 +9,11 @@ import ( apiparams "github.com/canonical/jimm/api/params" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" ) type removeCloudFromControllerSuite struct { - jimmSuite + cmdtest.JimmCmdSuite api *fakeRemoveCloudFromControllerAPI } @@ -20,12 +21,12 @@ type removeCloudFromControllerSuite struct { var _ = gc.Suite(&removeCloudFromControllerSuite{}) func (s *removeCloudFromControllerSuite) SetUpTest(c *gc.C) { - s.jimmSuite.SetUpTest(c) + s.JimmCmdSuite.SetUpTest(c) s.api = &fakeRemoveCloudFromControllerAPI{} } func (s *removeCloudFromControllerSuite) TestRemoveCloudFromController(c *gc.C) { - bClient := s.userBakeryClient("alice@external") + bClient := s.UserBakeryClient("alice@external") command := cmd.NewRemoveCloudFromControllerCommandForTesting( s.ClientStore(), @@ -47,7 +48,7 @@ func (s *removeCloudFromControllerSuite) TestRemoveCloudFromController(c *gc.C) } func (s *removeCloudFromControllerSuite) TestRemoveCloudFromControllerWrongArguments(c *gc.C) { - bClient := s.userBakeryClient("alice@external") + bClient := s.UserBakeryClient("alice@external") command := cmd.NewRemoveCloudFromControllerCommandForTesting( s.ClientStore(), @@ -62,7 +63,7 @@ func (s *removeCloudFromControllerSuite) TestRemoveCloudFromControllerWrongArgum } func (s *removeCloudFromControllerSuite) TestRemoveCloudFromControllerCloudNotFound(c *gc.C) { - bClient := s.userBakeryClient("alice@external") + bClient := s.UserBakeryClient("alice@external") command := cmd.NewRemoveCloudFromControllerCommandForTesting( s.ClientStore(), diff --git a/cmd/jimmctl/cmd/removecontroller_test.go b/cmd/jimmctl/cmd/removecontroller_test.go index 22131f0af..5f6f2578f 100644 --- a/cmd/jimmctl/cmd/removecontroller_test.go +++ b/cmd/jimmctl/cmd/removecontroller_test.go @@ -7,11 +7,12 @@ import ( gc "gopkg.in/check.v1" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" "github.com/canonical/jimm/internal/jimmtest" ) type removeControllerSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&removeControllerSuite{}) @@ -20,7 +21,7 @@ func (s *removeControllerSuite) TestRemoveControllerSuperuser(c *gc.C) { s.AddController(c, "controller-1", s.APIInfo(c)) // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") context, err := cmdtesting.RunCommand(c, cmd.NewRemoveControllerCommandForTesting(s.ClientStore(), bClient), "controller-1", "--force") c.Assert(err, gc.IsNil) c.Assert(cmdtesting.Stdout(context), gc.Matches, `name: controller-1 @@ -69,7 +70,7 @@ func (s *removeControllerSuite) TestRemoveController(c *gc.C) { s.AddController(c, "controller-1", s.APIInfo(c)) // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewRemoveControllerCommandForTesting(s.ClientStore(), bClient), "controller-1", "--force") c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } diff --git a/cmd/jimmctl/cmd/revokeauditlogaccess_test.go b/cmd/jimmctl/cmd/revokeauditlogaccess_test.go index 00a4b5485..f4b20f372 100644 --- a/cmd/jimmctl/cmd/revokeauditlogaccess_test.go +++ b/cmd/jimmctl/cmd/revokeauditlogaccess_test.go @@ -7,10 +7,11 @@ import ( gc "gopkg.in/check.v1" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" ) type revokeAuditLogAccessSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } // TODO (alesstimec) uncomment when grant/revoke is implemented @@ -18,14 +19,14 @@ type revokeAuditLogAccessSuite struct { func (s *revokeAuditLogAccessSuite) TestRevokeAuditLogAccessSuperuser(c *gc.C) { // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") _, err := cmdtesting.RunCommand(c, cmd.NewRevokeAuditLogAccessCommandForTesting(s.ClientStore(), bClient), "bob@external") c.Assert(err, gc.IsNil) } func (s *revokeAuditLogAccessSuite) TestRevokeAuditLogAccess(c *gc.C) { // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewRevokeAuditLogAccessCommandForTesting(s.ClientStore(), bClient), "bob@external") c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } diff --git a/cmd/jimmctl/cmd/setcontrollerdeprecated_test.go b/cmd/jimmctl/cmd/setcontrollerdeprecated_test.go index d5d0c434b..1afba77f7 100644 --- a/cmd/jimmctl/cmd/setcontrollerdeprecated_test.go +++ b/cmd/jimmctl/cmd/setcontrollerdeprecated_test.go @@ -7,11 +7,12 @@ import ( gc "gopkg.in/check.v1" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" "github.com/canonical/jimm/internal/jimmtest" ) type setControllerDeprecatedSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&setControllerDeprecatedSuite{}) @@ -20,7 +21,7 @@ func (s *setControllerDeprecatedSuite) TestSetControllerDeprecatedSuperuser(c *g s.AddController(c, "controller-1", s.APIInfo(c)) // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") context, err := cmdtesting.RunCommand(c, cmd.NewSetControllerDeprecatedCommandForTesting(s.ClientStore(), bClient), "controller-1") c.Assert(err, gc.IsNil) c.Assert(cmdtesting.Stdout(context), gc.Matches, `name: controller-1 @@ -69,7 +70,7 @@ func (s *setControllerDeprecatedSuite) TestSetControllerDeprecated(c *gc.C) { s.AddController(c, "controller-1", s.APIInfo(c)) // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewSetControllerDeprecatedCommandForTesting(s.ClientStore(), bClient), "controller-1") c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } diff --git a/cmd/jimmctl/cmd/updatemigratedmodel_test.go b/cmd/jimmctl/cmd/updatemigratedmodel_test.go index dd4ba9a30..26e5db7cb 100644 --- a/cmd/jimmctl/cmd/updatemigratedmodel_test.go +++ b/cmd/jimmctl/cmd/updatemigratedmodel_test.go @@ -11,12 +11,13 @@ import ( gc "gopkg.in/check.v1" "github.com/canonical/jimm/cmd/jimmctl/cmd" + "github.com/canonical/jimm/internal/cmdtest" "github.com/canonical/jimm/internal/dbmodel" "github.com/canonical/jimm/internal/jimmtest" ) type updateMigratedModelSuite struct { - jimmSuite + cmdtest.JimmCmdSuite } var _ = gc.Suite(&updateMigratedModelSuite{}) @@ -34,7 +35,7 @@ func (s *updateMigratedModelSuite) TestUpdateMigratedModelSuperuser(c *gc.C) { s.AddController(c, "controller-2", s.APIInfo(c)) // alice is superuser - bClient := s.userBakeryClient("alice") + bClient := s.UserBakeryClient("alice") _, err = cmdtesting.RunCommand(c, cmd.NewUpdateMigratedModelCommandForTesting(s.ClientStore(), bClient), "controller-2", mt.Id()) c.Assert(err, gc.IsNil) @@ -54,31 +55,31 @@ func (s *updateMigratedModelSuite) TestUpdateMigratedModelUnauthorized(c *gc.C) mt := s.AddModel(c, names.NewUserTag("charlie@external"), "model-2", names.NewCloudTag(jimmtest.TestCloudName), jimmtest.TestCloudRegionName, cct) // bob is not superuser - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewUpdateMigratedModelCommandForTesting(s.ClientStore(), bClient), "controller-1", mt.Id()) c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) } func (s *updateMigratedModelSuite) TestUpdateMigratedModelNoController(c *gc.C) { - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewUpdateMigratedModelCommandForTesting(s.ClientStore(), bClient)) c.Assert(err, gc.ErrorMatches, `controller not specified`) } func (s *updateMigratedModelSuite) TestUpdateMigratedModelNoModelUUID(c *gc.C) { - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewUpdateMigratedModelCommandForTesting(s.ClientStore(), bClient), "controller-id") c.Assert(err, gc.ErrorMatches, `model uuid not specified`) } func (s *updateMigratedModelSuite) TestUpdateMigratedModelInvalidModelUUID(c *gc.C) { - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewUpdateMigratedModelCommandForTesting(s.ClientStore(), bClient), "controller-id", "not-a-uuid") c.Assert(err, gc.ErrorMatches, `invalid model uuid`) } func (s *updateMigratedModelSuite) TestUpdateMigratedModelTooManyArgs(c *gc.C) { - bClient := s.userBakeryClient("bob") + bClient := s.UserBakeryClient("bob") _, err := cmdtesting.RunCommand(c, cmd.NewUpdateMigratedModelCommandForTesting(s.ClientStore(), bClient), "controller-id", "not-a-uuid", "spare-argument") c.Assert(err, gc.ErrorMatches, `too many args`) } diff --git a/cmd/serviceaccounts/cmd/addserviceaccount.go b/cmd/serviceaccounts/cmd/addserviceaccount.go new file mode 100644 index 000000000..35aace399 --- /dev/null +++ b/cmd/serviceaccounts/cmd/addserviceaccount.go @@ -0,0 +1,100 @@ +// Copyright 2024 Canonical Ltd. + +package cmd + +import ( + "github.com/juju/cmd/v3" + "github.com/juju/gnuflag" + jujuapi "github.com/juju/juju/api" + jujucmd "github.com/juju/juju/cmd" + "github.com/juju/juju/cmd/modelcmd" + "github.com/juju/juju/jujuclient" + + "github.com/canonical/jimm/api" + apiparams "github.com/canonical/jimm/api/params" + "github.com/canonical/jimm/internal/errors" +) + +var ( + addServiceCommandDoc = ` +add command binds a service account to your user, giving you administrator access over the service account. + +Example: + juju service-account add +` +) + +// NewAddControllerCommand returns a command to add a service account +func NewAddServiceAccountCommand() cmd.Command { + cmd := &addServiceAccountCommand{ + store: jujuclient.NewFileClientStore(), + } + + return modelcmd.WrapBase(cmd) +} + +// addServiceAccountCommand binds a service account to a user. +type addServiceAccountCommand struct { + modelcmd.ControllerCommandBase + out cmd.Output + + store jujuclient.ClientStore + dialOpts *jujuapi.DialOpts + clientID string +} + +// Info implements Command.Info. +func (c *addServiceAccountCommand) Info() *cmd.Info { + return jujucmd.Info(&cmd.Info{ + Name: "add", + Purpose: "Add service account", + Doc: addServiceCommandDoc, + }) +} + +// SetFlags implements the cmd.Command interface. +func (c *addServiceAccountCommand) SetFlags(f *gnuflag.FlagSet) { + c.CommandBase.SetFlags(f) + c.out.AddFlags(f, "yaml", map[string]cmd.Formatter{ + "yaml": cmd.FormatYaml, + "json": cmd.FormatJson, + }) +} + +// Init implements the cmd.Command interface. +func (c *addServiceAccountCommand) Init(args []string) error { + if len(args) < 1 { + return errors.E("clientID not specified") + } + c.clientID = args[0] + if len(args) > 1 { + return errors.E("too many args") + } + return nil +} + +// Run implements Command.Run. +func (c *addServiceAccountCommand) Run(ctxt *cmd.Context) error { + currentController, err := c.store.CurrentController() + if err != nil { + return errors.E(err, "could not determine controller") + } + + apiCaller, err := c.NewAPIRootWithDialOpts(c.store, currentController, "", c.dialOpts) + if err != nil { + return err + } + + params := apiparams.AddServiceAccountRequest{ClientID: c.clientID} + client := api.NewClient(apiCaller) + err = client.AddServiceAccount(¶ms) + if err != nil { + return errors.E(err) + } + + err = c.out.Write(ctxt, "service account added successfully") + if err != nil { + return errors.E(err) + } + return nil +} diff --git a/cmd/serviceaccounts/cmd/addserviceaccount_test.go b/cmd/serviceaccounts/cmd/addserviceaccount_test.go new file mode 100644 index 000000000..92ca1abf2 --- /dev/null +++ b/cmd/serviceaccounts/cmd/addserviceaccount_test.go @@ -0,0 +1,47 @@ +// Copyright 2021 Canonical Ltd. + +package cmd_test + +import ( + "context" + + "github.com/juju/cmd/v3/cmdtesting" + "github.com/juju/names/v4" + gc "gopkg.in/check.v1" + + "github.com/canonical/jimm/cmd/serviceaccounts/cmd" + "github.com/canonical/jimm/internal/cmdtest" + "github.com/canonical/jimm/internal/openfga" + ofganames "github.com/canonical/jimm/internal/openfga/names" + jimmnames "github.com/canonical/jimm/pkg/names" +) + +type addServiceAccountSuite struct { + cmdtest.JimmCmdSuite +} + +var _ = gc.Suite(&addServiceAccountSuite{}) + +func (s *addServiceAccountSuite) TestAddServiceAccount(c *gc.C) { + clientID := "abda51b2-d735-4794-a8bd-49c506baa4af" + // alice is superuser + bClient := s.UserBakeryClient("alice") + _, err := cmdtesting.RunCommand(c, cmd.NewAddServiceAccountCommandForTesting(s.ClientStore(), bClient), clientID) + c.Assert(err, gc.IsNil) + tuple := openfga.Tuple{ + Object: ofganames.ConvertTag(names.NewUserTag("alice@external")), + Relation: ofganames.AdministratorRelation, + Target: ofganames.ConvertTag(jimmnames.NewServiceAccountTag(clientID)), + } + // Check alice has access. + ok, err := s.JIMM.OpenFGAClient.CheckRelation(context.Background(), tuple, false) + c.Assert(err, gc.IsNil) + c.Assert(ok, gc.Equals, true) + // Check that re-running the command doesn't return an error for Alice. + _, err = cmdtesting.RunCommand(c, cmd.NewAddServiceAccountCommandForTesting(s.ClientStore(), bClient), clientID) + c.Assert(err, gc.IsNil) + // Check that re-running the command for a different user returns an error. + bClientBob := s.UserBakeryClient("bob") + _, err = cmdtesting.RunCommand(c, cmd.NewAddServiceAccountCommandForTesting(s.ClientStore(), bClientBob), clientID) + c.Assert(err, gc.ErrorMatches, "service account already owned") +} diff --git a/cmd/serviceaccounts/cmd/export_test.go b/cmd/serviceaccounts/cmd/export_test.go new file mode 100644 index 000000000..fdeb80d90 --- /dev/null +++ b/cmd/serviceaccounts/cmd/export_test.go @@ -0,0 +1,23 @@ +// Copyright 2021 Canonical Ltd. + +package cmd + +import ( + "github.com/go-macaroon-bakery/macaroon-bakery/v3/httpbakery" + "github.com/juju/cmd/v3" + jujuapi "github.com/juju/juju/api" + "github.com/juju/juju/cmd/modelcmd" + "github.com/juju/juju/jujuclient" +) + +func NewAddServiceAccountCommandForTesting(store jujuclient.ClientStore, bClient *httpbakery.Client) cmd.Command { + cmd := &addServiceAccountCommand{ + store: store, + dialOpts: &jujuapi.DialOpts{ + InsecureSkipVerify: true, + BakeryClient: bClient, + }, + } + + return modelcmd.WrapBase(cmd) +} diff --git a/cmd/serviceaccounts/cmd/package_test.go b/cmd/serviceaccounts/cmd/package_test.go new file mode 100644 index 000000000..fb57779d4 --- /dev/null +++ b/cmd/serviceaccounts/cmd/package_test.go @@ -0,0 +1,13 @@ +// Copyright 2021 Canonical Ltd. + +package cmd_test + +import ( + "testing" + + jujutesting "github.com/juju/juju/testing" +) + +func TestPackage(t *testing.T) { + jujutesting.MgoTestPackage(t) +} diff --git a/cmd/service-accounts/main.go b/cmd/serviceaccounts/main.go similarity index 85% rename from cmd/service-accounts/main.go rename to cmd/serviceaccounts/main.go index a57fc45eb..d052142a2 100644 --- a/cmd/service-accounts/main.go +++ b/cmd/serviceaccounts/main.go @@ -6,6 +6,7 @@ import ( "fmt" "os" + "github.com/canonical/jimm/cmd/serviceaccounts/cmd" jujucmd "github.com/juju/cmd/v3" ) @@ -19,7 +20,7 @@ func NewSuperCommand() *jujucmd.SuperCommand { Doc: serviceAccountDoc, }) // Register commands here: - // serviceAccountCmd.Register(cmd.NewCommand()) + serviceAccountCmd.Register(cmd.NewAddServiceAccountCommand()) return serviceAccountCmd } diff --git a/cmd/jimmctl/cmd/jimmsuite_test.go b/internal/cmdtest/jimmsuite.go similarity index 89% rename from cmd/jimmctl/cmd/jimmsuite_test.go rename to internal/cmdtest/jimmsuite.go index 9fc1c5b0a..16baec22f 100644 --- a/cmd/jimmctl/cmd/jimmsuite_test.go +++ b/internal/cmdtest/jimmsuite.go @@ -1,6 +1,8 @@ // Copyright 2021 Canonical Ltd. -package cmd_test +// Package cmdtest provides the test suite used for CLI tests +// as well as helper functions used for integration based CLI tests. +package cmdtest import ( "bytes" @@ -32,7 +34,7 @@ import ( ofganames "github.com/canonical/jimm/internal/openfga/names" ) -type jimmSuite struct { +type JimmCmdSuite struct { jimmtest.CandidSuite corejujutesting.JujuConnSuite @@ -49,7 +51,7 @@ type jimmSuite struct { COFGAParams *cofga.OpenFGAParams } -func (s *jimmSuite) SetUpTest(c *gc.C) { +func (s *JimmCmdSuite) SetUpTest(c *gc.C) { ctx, cancel := context.WithCancel(context.Background()) s.cancel = cancel @@ -140,7 +142,7 @@ func (s *jimmSuite) SetUpTest(c *gc.C) { // commands that use NewAPIRootWithDialOpts. Each invocation of the NewAPIRootWithDialOpts function // updates the ClientStore and removes local IPs thus removing JIMM's IP. // Call this function in your table tests after each test run. -func (s *jimmSuite) RefreshControllerAddress(c *gc.C) { +func (s *JimmCmdSuite) RefreshControllerAddress(c *gc.C) { jimm, ok := s.ClientStore().Controllers["JIMM"] c.Assert(ok, gc.Equals, true) u, err := url.Parse(s.HTTP.URL) @@ -149,7 +151,7 @@ func (s *jimmSuite) RefreshControllerAddress(c *gc.C) { s.ClientStore().Controllers["JIMM"] = jimm } -func (s *jimmSuite) TearDownTest(c *gc.C) { +func (s *JimmCmdSuite) TearDownTest(c *gc.C) { if s.cancel != nil { s.cancel() } @@ -163,7 +165,7 @@ func (s *jimmSuite) TearDownTest(c *gc.C) { s.JujuConnSuite.TearDownTest(c) } -func (s *jimmSuite) userBakeryClient(username string) *httpbakery.Client { +func (s *JimmCmdSuite) UserBakeryClient(username string) *httpbakery.Client { s.Candid.AddUser(username) key := s.Candid.UserPublicKey(username) bClient := httpbakery.NewClient() @@ -181,7 +183,7 @@ func (s *jimmSuite) userBakeryClient(username string) *httpbakery.Client { return bClient } -func (s *jimmSuite) AddController(c *gc.C, name string, info *api.Info) { +func (s *JimmCmdSuite) AddController(c *gc.C, name string, info *api.Info) { ctl := &dbmodel.Controller{ UUID: info.ControllerUUID, Name: name, @@ -205,7 +207,7 @@ func (s *jimmSuite) AddController(c *gc.C, name string, info *api.Info) { c.Assert(err, gc.Equals, nil) } -func (s *jimmSuite) UpdateCloudCredential(c *gc.C, tag names.CloudCredentialTag, cred jujuparams.CloudCredential) { +func (s *JimmCmdSuite) UpdateCloudCredential(c *gc.C, tag names.CloudCredentialTag, cred jujuparams.CloudCredential) { ctx := context.Background() u := dbmodel.Identity{ Name: tag.Owner().Id(), @@ -221,7 +223,7 @@ func (s *jimmSuite) UpdateCloudCredential(c *gc.C, tag names.CloudCredentialTag, c.Assert(err, gc.Equals, nil) } -func (s *jimmSuite) AddModel(c *gc.C, owner names.UserTag, name string, cloud names.CloudTag, region string, cred names.CloudCredentialTag) names.ModelTag { +func (s *JimmCmdSuite) AddModel(c *gc.C, owner names.UserTag, name string, cloud names.CloudTag, region string, cred names.CloudCredentialTag) names.ModelTag { ctx := context.Background() u := openfga.NewUser( &dbmodel.Identity{ diff --git a/internal/jimm/service_account.go b/internal/jimm/service_account.go index 0d431efde..4f3ec4463 100644 --- a/internal/jimm/service_account.go +++ b/internal/jimm/service_account.go @@ -17,6 +17,7 @@ import ( // and then adds a relation between the logged in user and the service account. func (j *JIMM) AddServiceAccount(ctx context.Context, u *openfga.User, clientId string) error { op := errors.Op("jimm.AddServiceAccount") + svcTag := jimmnames.NewServiceAccountTag(clientId) key := openfga.Tuple{ Relation: ofganames.AdministratorRelation, diff --git a/internal/jujuapi/jimm.go b/internal/jujuapi/jimm.go index b6be9d368..b49da379b 100644 --- a/internal/jujuapi/jimm.go +++ b/internal/jujuapi/jimm.go @@ -51,6 +51,8 @@ func init() { migrateModel := rpc.Method(r.MigrateModel) addServiceAccountMethod := rpc.Method(r.AddServiceAccount) updateServiceAccountCredentials := rpc.Method(r.UpdateServiceAccountCredentials) + listServiceAccountCredentials := rpc.Method(r.ListServiceAccountCredentials) + grantServiceAccountAccess := rpc.Method(r.GrantServiceAccountAccess) // JIMM Generic RPC r.AddMethod("JIMM", 4, "AddController", addControllerMethod) @@ -82,6 +84,8 @@ func init() { // JIMM Service Accounts r.AddMethod("JIMM", 4, "AddServiceAccount", addServiceAccountMethod) r.AddMethod("JIMM", 4, "UpdateServiceAccountCredentials", updateServiceAccountCredentials) + r.AddMethod("JIMM", 4, "ListServiceAccountCredentials", listServiceAccountCredentials) + r.AddMethod("JIMM", 4, "GrantServiceAccountAccess", grantServiceAccountAccess) return []int{4} }