From b795e922aedd77444262b80bddbc67236bc64eae Mon Sep 17 00:00:00 2001 From: SimoneDutto Date: Tue, 3 Sep 2024 17:36:16 +0200 Subject: [PATCH] wip --- internal/db/resources.go | 105 ++++++++++++++++++++++++++++++ internal/db/resources_test.go | 81 +++++++++++++++++++++++ internal/rebac_admin/backend.go | 1 + internal/rebac_admin/resources.go | 32 +++++++++ 4 files changed, 219 insertions(+) create mode 100644 internal/db/resources.go create mode 100644 internal/db/resources_test.go create mode 100644 internal/rebac_admin/resources.go diff --git a/internal/db/resources.go b/internal/db/resources.go new file mode 100644 index 000000000..f825a0afb --- /dev/null +++ b/internal/db/resources.go @@ -0,0 +1,105 @@ +// Copyright 2024 Canonical. +package db + +import ( + "context" + "database/sql" + + "github.com/canonical/jimm/v3/internal/errors" + "github.com/canonical/jimm/v3/internal/servermon" +) + +const MUTIPLE_PAGE_SQL = ` +( + SELECT 'controller' AS type, + uuid AS id, + name AS name, + '' AS parent_id, + '' AS parent_name, + '' AS parent_type + FROM controllers +) +UNION +( + SELECT 'model' AS type, + models.uuid AS id, + models.name AS name, + controllers.uuid AS parent_id, + controllers.name AS parent_name, + 'controller' AS parent_type + FROM models + JOIN controllers ON models.controller_id = controllers.id +) +UNION +( + SELECT 'application_offer' AS type, + application_offers.uuid AS id, + application_offers.name AS name, + models.uuid AS parent_id, + models.name AS parent_name, + 'model' AS parent_type + FROM application_offers + JOIN models ON application_offers.model_id = models.id +) +UNION +( + SELECT 'cloud' AS type, + clouds.name AS id, + clouds.name AS name, + '' AS parent_id, + '' AS parent_name, + '' AS parent_type + FROM clouds +) +UNION +( + SELECT 'service_account' AS type, + identities.name AS id, + identities.name AS name, + '' AS parent_id, + '' AS parent_name, + '' AS parent_type + FROM identities + WHERE name NOT LIKE '%@%' +) +ORDER BY id +OFFSET ? +LIMIT ?; +` + +type Resource struct { + Type string + UUID sql.NullString + Name string + ParentId sql.NullString + ParentName string + ParentType string +} + +func (d *Database) GetResources(ctx context.Context, limit, offset int) (_ []Resource, err error) { + const op = errors.Op("db.GetMultipleModels") + if err := d.ready(); err != nil { + return nil, errors.E(op, err) + } + + durationObserver := servermon.DurationObserver(servermon.DBQueryDurationHistogram, string(op)) + defer durationObserver() + defer servermon.ErrorCounter(servermon.DBQueryErrorCount, &err, string(op)) + + db := d.DB.WithContext(ctx) + rows, err := db.Raw(MUTIPLE_PAGE_SQL, offset, limit).Rows() + if err != nil { + return nil, err + } + defer rows.Close() + resources := make([]Resource, 0) + for rows.Next() { + var res Resource + err := db.ScanRows(rows, &res) + if err != nil { + return nil, err + } + resources = append(resources, res) + } + return resources, nil +} diff --git a/internal/db/resources_test.go b/internal/db/resources_test.go new file mode 100644 index 000000000..7606c8f06 --- /dev/null +++ b/internal/db/resources_test.go @@ -0,0 +1,81 @@ +// Copyright 2024 Canonical. +package db_test + +import ( + "context" + "database/sql" + + qt "github.com/frankban/quicktest" + "github.com/juju/juju/state" + + "github.com/canonical/jimm/v3/internal/db" + "github.com/canonical/jimm/v3/internal/dbmodel" +) + +func (s *dbSuite) Setup(c *qt.C) { + err := s.Database.Migrate(context.Background(), true) + c.Assert(err, qt.Equals, nil) + + u, err := dbmodel.NewIdentity("bob@canonical.com") + c.Assert(err, qt.IsNil) + c.Assert(s.Database.DB.Create(&u).Error, qt.IsNil) + + cloud := dbmodel.Cloud{ + Name: "test-cloud", + Type: "test-provider", + Regions: []dbmodel.CloudRegion{{ + Name: "test-region", + }}, + } + c.Assert(s.Database.DB.Create(&cloud).Error, qt.IsNil) + + cred := dbmodel.CloudCredential{ + Name: "test-cred", + Cloud: cloud, + Owner: *u, + AuthType: "empty", + } + c.Assert(s.Database.DB.Create(&cred).Error, qt.IsNil) + + controller := dbmodel.Controller{ + Name: "test-controller", + UUID: "00000000-0000-0000-0000-0000-0000000000001", + CloudName: "test-cloud", + CloudRegion: "test-region", + } + err = s.Database.AddController(context.Background(), &controller) + c.Assert(err, qt.Equals, nil) + + model := dbmodel.Model{ + Name: "test-model-1", + UUID: sql.NullString{ + String: "00000001-0000-0000-0000-0000-000000000001", + Valid: true, + }, + OwnerIdentityName: u.Name, + ControllerID: controller.ID, + CloudRegionID: cloud.Regions[0].ID, + CloudCredentialID: cred.ID, + Type: "iaas", + DefaultSeries: "warty", + Life: state.Alive.String(), + Status: dbmodel.Status{ + Status: "available", + Since: db.Now(), + }, + SLA: dbmodel.SLA{ + Level: "unsupported", + }, + } + err = s.Database.AddModel(context.Background(), &model) + c.Assert(err, qt.Equals, nil) +} + +func (s *dbSuite) TestGetResources(c *qt.C) { + // create one model, one controller, one cloud + s.Setup(c) + ctx := context.Background() + res, err := s.Database.GetResources(ctx, 10, 0) + c.Assert(err, qt.Equals, nil) + c.Assert(res, qt.HasLen, 3) +} diff --git a/internal/rebac_admin/backend.go b/internal/rebac_admin/backend.go index 0af81f3b1..b021ff9ce 100644 --- a/internal/rebac_admin/backend.go +++ b/internal/rebac_admin/backend.go @@ -21,6 +21,7 @@ func SetupBackend(ctx context.Context, jimm jujuapi.JIMM) (*rebac_handlers.ReBAC Entitlements: newEntitlementService(), Groups: newGroupService(jimm), Identities: newidentitiesService(jimm), + Resources: newResourcesService(jimm), }) if err != nil { zapctx.Error(ctx, "failed to create rebac admin backend", zap.Error(err)) diff --git a/internal/rebac_admin/resources.go b/internal/rebac_admin/resources.go new file mode 100644 index 000000000..cce36921c --- /dev/null +++ b/internal/rebac_admin/resources.go @@ -0,0 +1,32 @@ +// Copyright 2024 Canonical. + +package rebac_admin + +import ( + "context" + + "github.com/canonical/rebac-admin-ui-handlers/v1/resources" + + "github.com/canonical/jimm/v3/internal/jujuapi" + "github.com/canonical/jimm/v3/internal/rebac_admin/utils" +) + +// resourcesService implements the `resourcesService` interface. +type resourcesService struct { + jimm jujuapi.JIMM +} + +func newResourcesService(jimm jujuapi.JIMM) *resourcesService { + return &resourcesService{ + jimm: jimm, + } +} + +// resourcesService defines an abstract backend to handle Resources related operations. +func (s *resourcesService) ListResources(ctx context.Context, params *resources.GetResourcesParams) (*resources.PaginatedResponse[resources.Resource], error) { + _, err := utils.GetUserFromContext(ctx) + if err != nil { + return nil, err + } + return nil, nil +}