diff --git a/cmd/jimmctl/cmd/purge_logs_test.go b/cmd/jimmctl/cmd/purge_logs_test.go index 5283f5be4..b94683480 100644 --- a/cmd/jimmctl/cmd/purge_logs_test.go +++ b/cmd/jimmctl/cmd/purge_logs_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package cmd_test import ( diff --git a/cmd/jimmsrv/service/service.go b/cmd/jimmsrv/service/service.go index 890416d37..65a617393 100644 --- a/cmd/jimmsrv/service/service.go +++ b/cmd/jimmsrv/service/service.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. // service defines the methods necessary to start a JIMM server // alongside all the config options that can be supplied to configure JIMM. diff --git a/internal/auth/oauth2_test.go b/internal/auth/oauth2_test.go index 35a5b2b23..55ce58d4a 100644 --- a/internal/auth/oauth2_test.go +++ b/internal/auth/oauth2_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package auth_test diff --git a/internal/db/applicationoffer_test.go b/internal/db/applicationoffer_test.go index ff9f922aa..4c16a6dbf 100644 --- a/internal/db/applicationoffer_test.go +++ b/internal/db/applicationoffer_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package db_test diff --git a/internal/db/auditlog_test.go b/internal/db/auditlog_test.go index 9decee04c..52587d35b 100644 --- a/internal/db/auditlog_test.go +++ b/internal/db/auditlog_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package db_test diff --git a/internal/db/cloud_test.go b/internal/db/cloud_test.go index 7d4954014..a4b4a81d1 100644 --- a/internal/db/cloud_test.go +++ b/internal/db/cloud_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package db_test @@ -29,15 +29,11 @@ func (s *dbSuite) TestAddCloud(c *qt.C) { ctx := context.Background() cl := dbmodel.Cloud{ - Name: "test-cloud", - Type: "test-provider", - Endpoint: "https://example.com", - IdentityEndpoint: "https://identity.example.com", - StorageEndpoint: "https://storage.example.com", + Name: "test-cloud", + Type: "test-provider", Regions: []dbmodel.CloudRegion{{ Name: "test-cloud-region", }}, - CACertificates: dbmodel.Strings{"CACERT 1", "CACERT 2"}, } err := s.Database.AddCloud(ctx, &cl) @@ -90,15 +86,11 @@ func (s *dbSuite) TestGetCloud(c *qt.C) { c.Check(errors.ErrorCode(err), qt.Equals, errors.CodeNotFound) cl2 := dbmodel.Cloud{ - Name: "test-cloud", - Type: "test-provider", - Endpoint: "https://example.com", - IdentityEndpoint: "https://identity.example.com", - StorageEndpoint: "https://storage.example.com", + Name: "test-cloud", + Type: "test-provider", Regions: []dbmodel.CloudRegion{{ Name: "test-cloud-region", }}, - CACertificates: dbmodel.Strings{"CACERT 1", "CACERT 2"}, } err = s.Database.AddCloud(ctx, &cl2) @@ -131,15 +123,11 @@ func (s *dbSuite) TestGetClouds(c *qt.C) { c.Check(clouds, qt.HasLen, 0) cl := dbmodel.Cloud{ - Name: "test-cloud", - Type: "test-provider", - Endpoint: "https://example.com", - IdentityEndpoint: "https://identity.example.com", - StorageEndpoint: "https://storage.example.com", + Name: "test-cloud", + Type: "test-provider", Regions: []dbmodel.CloudRegion{{ Name: "test-cloud-region", }}, - CACertificates: dbmodel.Strings{"CACERT 1", "CACERT 2"}, } err = s.Database.AddCloud(ctx, &cl) @@ -163,15 +151,11 @@ func (s *dbSuite) TestUpdateCloud(c *qt.C) { ctx := context.Background() cl := dbmodel.Cloud{ - Name: "test-cloud", - Type: "test-provider", - Endpoint: "https://example.com", - IdentityEndpoint: "https://identity.example.com", - StorageEndpoint: "https://storage.example.com", + Name: "test-cloud", + Type: "test-provider", Regions: []dbmodel.CloudRegion{{ Name: "test-cloud-region", }}, - CACertificates: dbmodel.Strings{"CACERT 1", "CACERT 2"}, } err := s.Database.UpdateCloud(ctx, &cl) @@ -191,9 +175,6 @@ func (s *dbSuite) TestUpdateCloud(c *qt.C) { c.Assert(err, qt.IsNil) c.Check(cl2, jimmtest.DBObjectEquals, cl) - cl2.Endpoint = "https://new.example.com" - cl2.IdentityEndpoint = "https://new.identity.example.com" - cl2.StorageEndpoint = "https://new.storage.example.com" cl2.Regions = append(cl2.Regions, dbmodel.CloudRegion{ Name: "test-cloud-region-2", Endpoint: "https://new.region.example.com", @@ -327,15 +308,11 @@ func (s *dbSuite) TestDeleteCloud(c *qt.C) { ctx := context.Background() cl := dbmodel.Cloud{ - Name: "test-cloud", - Type: "test-provider", - Endpoint: "https://example.com", - IdentityEndpoint: "https://identity.example.com", - StorageEndpoint: "https://storage.example.com", + Name: "test-cloud", + Type: "test-provider", Regions: []dbmodel.CloudRegion{{ Name: "test-cloud-region", }}, - CACertificates: dbmodel.Strings{"CACERT 1", "CACERT 2"}, } err := s.Database.DeleteCloud(ctx, &cl) diff --git a/internal/db/cloudcredential_test.go b/internal/db/cloudcredential_test.go index f91d2bdae..6268688bb 100644 --- a/internal/db/cloudcredential_test.go +++ b/internal/db/cloudcredential_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package db_test diff --git a/internal/db/clouddefaults_test.go b/internal/db/clouddefaults_test.go index bfcc70bdf..13b4bb40c 100644 --- a/internal/db/clouddefaults_test.go +++ b/internal/db/clouddefaults_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package db_test diff --git a/internal/db/controller_test.go b/internal/db/controller_test.go index e1049b07a..30750c55b 100644 --- a/internal/db/controller_test.go +++ b/internal/db/controller_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package db_test diff --git a/internal/db/db.go b/internal/db/db.go index 1ff91257d..34afbf282 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. // Package db contains routines to store and retrieve data from a database. package db diff --git a/internal/db/db_test.go b/internal/db/db_test.go index 4b27ccace..88be1d139 100644 --- a/internal/db/db_test.go +++ b/internal/db/db_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package db_test diff --git a/internal/db/export_test.go b/internal/db/export_test.go index 1cab6ad6a..3bb578289 100644 --- a/internal/db/export_test.go +++ b/internal/db/export_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package db diff --git a/internal/db/group_test.go b/internal/db/group_test.go index e1f9491e6..f4cfb7f95 100644 --- a/internal/db/group_test.go +++ b/internal/db/group_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package db_test diff --git a/internal/db/identity_test.go b/internal/db/identity_test.go index b3977a4e3..df077ba37 100644 --- a/internal/db/identity_test.go +++ b/internal/db/identity_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package db_test diff --git a/internal/db/model_test.go b/internal/db/model_test.go index 92b6b8735..b22253570 100644 --- a/internal/db/model_test.go +++ b/internal/db/model_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package db_test diff --git a/internal/db/resource_test.go b/internal/db/resource_test.go index ecf645efd..454df2d84 100644 --- a/internal/db/resource_test.go +++ b/internal/db/resource_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package db_test import ( diff --git a/internal/db/role_test.go b/internal/db/role_test.go index d1b0d8767..08db5f2dd 100644 --- a/internal/db/role_test.go +++ b/internal/db/role_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package db_test diff --git a/internal/db/rootkeys_test.go b/internal/db/rootkeys_test.go index 75460013d..7fec27f57 100644 --- a/internal/db/rootkeys_test.go +++ b/internal/db/rootkeys_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package db_test diff --git a/internal/db/secrets_test.go b/internal/db/secrets_test.go index 2cfc423bc..977ce52bc 100644 --- a/internal/db/secrets_test.go +++ b/internal/db/secrets_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package db_test diff --git a/internal/dbmodel/cloud.go b/internal/dbmodel/cloud.go index a35c8a385..f945813ed 100644 --- a/internal/dbmodel/cloud.go +++ b/internal/dbmodel/cloud.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package dbmodel @@ -26,29 +26,8 @@ type Cloud struct { // cloud is hosted. HostCloudRegion string - // AuthTypes is the authentication types supported by this cloud. - AuthTypes Strings - - // Endpoint is the API endpoint URL for the cloud. - Endpoint string - - // IdentityEndpoint is the API endpoint URL of the cloud identity - // service. - IdentityEndpoint string - - // StorageEndpoint is the API endpoint URL of the cloud storage - // service. - StorageEndpoint string - // Regions contains the regions associated with this cloud. Regions []CloudRegion `gorm:"foreignKey:CloudName;references:Name"` - - // CACertificates contains the CA Certificates associated with this - // cloud. - CACertificates Strings - - // Config contains the configuration associated with this cloud. - Config Map } // Tag returns a names.Tag for this cloud. @@ -82,14 +61,14 @@ func (c Cloud) Region(name string) CloudRegion { // ToJujuCloud converts the Cloud object into a jujuparams.Cloud. The // cloud must have its regions association filled out. -func (c Cloud) ToJujuCloud() jujuparams.Cloud { +func (c Cloud) ToJujuCloud(jujuCloud jujuparams.Cloud) jujuparams.Cloud { var cl jujuparams.Cloud cl.Type = c.Type cl.HostCloudRegion = c.HostCloudRegion - cl.AuthTypes = []string(c.AuthTypes) - cl.Endpoint = c.Endpoint - cl.IdentityEndpoint = c.IdentityEndpoint - cl.StorageEndpoint = c.StorageEndpoint + cl.AuthTypes = []string(jujuCloud.AuthTypes) + cl.Endpoint = jujuCloud.Endpoint + cl.IdentityEndpoint = jujuCloud.IdentityEndpoint + cl.StorageEndpoint = jujuCloud.StorageEndpoint cl.Regions = make([]jujuparams.CloudRegion, len(c.Regions)) cl.RegionConfig = make(map[string]map[string]interface{}, len(c.Regions)) for i, r := range c.Regions { @@ -98,8 +77,8 @@ func (c Cloud) ToJujuCloud() jujuparams.Cloud { cl.RegionConfig[r.Name] = map[string]interface{}(r.Config) } } - cl.CACertificates = []string(c.CACertificates) - cl.Config = map[string]interface{}(c.Config) + cl.CACertificates = []string(jujuCloud.CACertificates) + cl.Config = map[string]interface{}(jujuCloud.Config) return cl } @@ -108,12 +87,6 @@ func (c Cloud) ToJujuCloud() jujuparams.Cloud { func (c *Cloud) FromJujuCloud(cld jujuparams.Cloud) { c.Type = cld.Type c.HostCloudRegion = cld.HostCloudRegion - c.AuthTypes = Strings(cld.AuthTypes) - c.Endpoint = cld.Endpoint - c.IdentityEndpoint = cld.IdentityEndpoint - c.StorageEndpoint = cld.StorageEndpoint - c.CACertificates = Strings(cld.CACertificates) - c.Config = Map(cld.Config) regions := make([]CloudRegion, 0, len(c.Regions)) for _, r := range cld.Regions { reg := c.Region(r.Name) @@ -124,35 +97,6 @@ func (c *Cloud) FromJujuCloud(cld jujuparams.Cloud) { c.Regions = regions } -// ToJujuCloudDetails converts the Cloud object into a -// jujuparams.CloudDetails. The cloud must have its regions association -// filled out. -func (c Cloud) ToJujuCloudDetails() jujuparams.CloudDetails { - var cd jujuparams.CloudDetails - cd.Type = c.Type - cd.AuthTypes = []string(c.AuthTypes) - cd.Endpoint = c.Endpoint - cd.IdentityEndpoint = c.IdentityEndpoint - cd.StorageEndpoint = c.StorageEndpoint - cd.Regions = make([]jujuparams.CloudRegion, len(c.Regions)) - for i, r := range c.Regions { - cd.Regions[i] = r.ToJujuCloudRegion() - } - return cd -} - -// ToJujuCloudInfo converts the Cloud object into a -// jujuparams.CloudInfo. The cloud must have its regions and users -// associations filled out. -func (c Cloud) ToJujuCloudInfo() jujuparams.CloudInfo { - var ci jujuparams.CloudInfo - ci.CloudDetails = c.ToJujuCloudDetails() - // TODO(Kian) CSS-6040 Determine whether to combine OpenFGA Tuples - // with Postgres data objects for a consolidated view. - ci.Users = nil - return ci -} - // A CloudRegion is a region of a cloud. type CloudRegion struct { gorm.Model diff --git a/internal/dbmodel/cloud_test.go b/internal/dbmodel/cloud_test.go index ed1291b30..5ffd8a701 100644 --- a/internal/dbmodel/cloud_test.go +++ b/internal/dbmodel/cloud_test.go @@ -37,18 +37,9 @@ func TestCloud(t *testing.T) { c.Check(result.Error, qt.Equals, gorm.ErrRecordNotFound) cl1 := dbmodel.Cloud{ - Name: "test-cloud", - Type: "test-provider", - HostCloudRegion: "test-cloud/test-region", - Endpoint: "https://cloud.example.com", - IdentityEndpoint: "https://identity.cloud.example.com", - StorageEndpoint: "https://storage.cloud.example.com", - CACertificates: dbmodel.Strings{"cert1", "cert2"}, - Config: dbmodel.Map{ - "k1": float64(1), - "k2": "A", - "k3": map[string]interface{}{"k": []interface{}{"v"}}, - }, + Name: "test-cloud", + Type: "test-provider", + HostCloudRegion: "test-cloud/test-region", } result = db.Create(&cl1) c.Assert(result.Error, qt.IsNil) @@ -59,11 +50,6 @@ func TestCloud(t *testing.T) { c.Assert(result.Error, qt.IsNil) c.Check(cl2, qt.DeepEquals, cl1) - cl2.CACertificates = dbmodel.Strings{"cert2", "cert3"} - result = db.Save(&cl2) - c.Assert(result.Error, qt.IsNil) - c.Check(result.RowsAffected, qt.Equals, int64(1)) - var cl3 dbmodel.Cloud result = db.Where("name = ?", "test-cloud").First(&cl3) c.Assert(result.Error, qt.IsNil) @@ -76,63 +62,6 @@ func TestCloud(t *testing.T) { c.Check(result.Error, qt.ErrorMatches, `.*violates unique constraint "clouds_name_key".*`) } -func TestToJujuCloud(t *testing.T) { - c := qt.New(t) - - cl := dbmodel.Cloud{ - Name: "test-cloud", - Type: "test-provider", - HostCloudRegion: "test-cloud/test-region", - Endpoint: "https://cloud.example.com", - IdentityEndpoint: "https://identity.cloud.example.com", - StorageEndpoint: "https://storage.cloud.example.com", - CACertificates: dbmodel.Strings{"cert1", "cert2"}, - Regions: []dbmodel.CloudRegion{{ - Name: "test-region", - Endpoint: "https://region.example.com", - IdentityEndpoint: "https://identity.region.example.com", - StorageEndpoint: "https://storage.region.example.com", - Config: dbmodel.Map{ - "k1": float64(2), - "k2": "B", - "k3": map[string]interface{}{"k": []interface{}{"V"}}, - }, - }}, - Config: dbmodel.Map{ - "k1": float64(1), - "k2": "A", - "k3": map[string]interface{}{"k": []interface{}{"v"}}, - }, - } - pc := cl.ToJujuCloud() - c.Check(pc, qt.DeepEquals, jujuparams.Cloud{ - Type: "test-provider", - HostCloudRegion: "test-cloud/test-region", - Endpoint: "https://cloud.example.com", - IdentityEndpoint: "https://identity.cloud.example.com", - StorageEndpoint: "https://storage.cloud.example.com", - Regions: []jujuparams.CloudRegion{{ - Name: "test-region", - Endpoint: "https://region.example.com", - IdentityEndpoint: "https://identity.region.example.com", - StorageEndpoint: "https://storage.region.example.com", - }}, - CACertificates: []string{"cert1", "cert2"}, - Config: map[string]interface{}{ - "k1": float64(1), - "k2": "A", - "k3": map[string]interface{}{"k": []interface{}{string("v")}}, - }, - RegionConfig: map[string]map[string]interface{}{ - "test-region": { - "k1": float64(2), - "k2": "B", - "k3": map[string]interface{}{"k": []interface{}{string("V")}}, - }, - }, - }) -} - func TestFromJujuCloud(t *testing.T) { c := qt.New(t) @@ -170,13 +99,9 @@ func TestFromJujuCloud(t *testing.T) { cl.FromJujuCloud(jcld) c.Check(cl, qt.DeepEquals, dbmodel.Cloud{ - Name: "test-cloud", - Type: "test-provider", - HostCloudRegion: "test-cloud/test-region", - AuthTypes: dbmodel.Strings{"empty"}, - Endpoint: "https://cloud.example.com", - IdentityEndpoint: "https://identity.cloud.example.com", - StorageEndpoint: "https://storage.cloud.example.com", + Name: "test-cloud", + Type: "test-provider", + HostCloudRegion: "test-cloud/test-region", Regions: []dbmodel.CloudRegion{{ Name: "test-region", Endpoint: "https://region.example.com", @@ -188,87 +113,16 @@ func TestFromJujuCloud(t *testing.T) { "k3": map[string]interface{}{"k": []interface{}{string("V")}}, }, }}, - CACertificates: dbmodel.Strings{"cert1", "cert2"}, - Config: dbmodel.Map{ - "k1": float64(1), - "k2": "A", - "k3": map[string]interface{}{"k": []interface{}{string("v")}}, - }, }) } -func TestToJujuCloudInfo(t *testing.T) { - c := qt.New(t) - - cl := dbmodel.Cloud{ - Name: "test-cloud", - Type: "test-provider", - HostCloudRegion: "test-cloud/test-region", - Endpoint: "https://cloud.example.com", - IdentityEndpoint: "https://identity.cloud.example.com", - StorageEndpoint: "https://storage.cloud.example.com", - CACertificates: dbmodel.Strings{"cert1", "cert2"}, - Regions: []dbmodel.CloudRegion{{ - Name: "test-region", - Endpoint: "https://region.example.com", - IdentityEndpoint: "https://identity.region.example.com", - StorageEndpoint: "https://storage.region.example.com", - Config: dbmodel.Map{ - "k1": float64(2), - "k2": "B", - "k3": map[string]interface{}{"k": []interface{}{"V"}}, - }, - }}, - Config: dbmodel.Map{ - "k1": float64(1), - "k2": "A", - "k3": map[string]interface{}{"k": []interface{}{"v"}}, - }, - } - pci := cl.ToJujuCloudInfo() - c.Check(pci, qt.DeepEquals, jujuparams.CloudInfo{ - CloudDetails: jujuparams.CloudDetails{ - Type: "test-provider", - Endpoint: "https://cloud.example.com", - IdentityEndpoint: "https://identity.cloud.example.com", - StorageEndpoint: "https://storage.cloud.example.com", - Regions: []jujuparams.CloudRegion{{ - Name: "test-region", - Endpoint: "https://region.example.com", - IdentityEndpoint: "https://identity.region.example.com", - StorageEndpoint: "https://storage.region.example.com", - }}, - }, - }) -} - -func TestCloudAuthTypes(t *testing.T) { - c := qt.New(t) - db := gormDB(c) - - cl1 := dbmodel.Cloud{ - Name: "test-cloud", - AuthTypes: dbmodel.Strings{"empty", "userpass"}, - } - result := db.Create(&cl1) - c.Assert(result.Error, qt.IsNil) - - var cl2 dbmodel.Cloud - result = db.Where("name = ?", "test-cloud").First(&cl2) - c.Assert(result.Error, qt.IsNil) - c.Check(cl2, qt.DeepEquals, cl1) -} - func TestCloudRegions(t *testing.T) { c := qt.New(t) db := gormDB(c) cl1 := dbmodel.Cloud{ - Name: "test-cloud", - Type: "test-provider", - Endpoint: "https://cloud.example.com", - IdentityEndpoint: "https://identity.cloud.example.com", - StorageEndpoint: "https://storage.cloud.example.com", + Name: "test-cloud", + Type: "test-provider", } cr1 := dbmodel.CloudRegion{ Name: "region1", diff --git a/internal/dbmodel/gorm_test.go b/internal/dbmodel/gorm_test.go index 4d35a6f79..a0ea25682 100644 --- a/internal/dbmodel/gorm_test.go +++ b/internal/dbmodel/gorm_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package dbmodel_test diff --git a/internal/dbmodel/sql/postgres/019_simplify_cloud_table.up.sql b/internal/dbmodel/sql/postgres/019_simplify_cloud_table.up.sql new file mode 100644 index 000000000..db92384bd --- /dev/null +++ b/internal/dbmodel/sql/postgres/019_simplify_cloud_table.up.sql @@ -0,0 +1,3 @@ +-- remove non essential fields from cloud. +ALTER TABLE clouds DROP COLUMN auth_types, DROP COLUMN endpoint, DROP COLUMN identity_endpoint, + DROP COLUMN storage_endpoint, DROP COLUMN ca_certificates, DROP COLUMN config; diff --git a/internal/jimm/applicationoffer.go b/internal/jimm/applicationoffer.go index 5cb1842c6..0e022f0ed 100644 --- a/internal/jimm/applicationoffer.go +++ b/internal/jimm/applicationoffer.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jimm diff --git a/internal/jimm/cloud.go b/internal/jimm/cloud.go index d0f8a1178..c13a5417b 100644 --- a/internal/jimm/cloud.go +++ b/internal/jimm/cloud.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jimm @@ -20,35 +20,172 @@ import ( ofganames "github.com/canonical/jimm/v3/internal/openfga/names" ) +// GetUserCloudAccess returns users access level for the specified cloud. +func (j *JIMM) GetUserCloudAccess(ctx context.Context, user *openfga.User, cloud names.CloudTag) (string, error) { + accessLevel := user.GetCloudAccess(ctx, cloud) + return permissions.ToCloudAccessString(accessLevel), nil +} + +// GetCloud retrieves the cloud for the given cloud tag. If the cloud +// cannot be found then an error with the code CodeNotFound is +// returned. If the user does not have permission to view the cloud then an +// error with a code of CodeUnauthorized is returned. If the user only has +// add-model access to the cloud then the returned Users field will only +// contain the authentcated user. +func (j *JIMM) GetCloud(ctx context.Context, user *openfga.User, tag names.CloudTag) (jujuparams.Cloud, error) { + const op = errors.Op("jimm.CloudInfo") + zapctx.Info(ctx, string(op)) + var cl dbmodel.Cloud + cl.SetTag(tag) + + if err := j.Database.GetCloud(ctx, &cl); err != nil { + return jujuparams.Cloud{}, errors.E(op, err) + } + // TODO (SimoneDutto): refactor this to use `user.IsCloudAdmin()` + accessLevel, err := j.GetUserCloudAccess(ctx, user, tag) + if err != nil { + return jujuparams.Cloud{}, errors.E(op, err) + } + if accessLevel != "admin" { + return jujuparams.Cloud{}, errors.E(op, errors.CodeUnauthorized, "unauthorized") + } + + return j.getCloudFromController(ctx, cl) +} + +func (j *JIMM) getCloudFromController(ctx context.Context, cl dbmodel.Cloud) (jujuparams.Cloud, error) { + const op = errors.Op("jimm.getCloudFromController") + + var jCl jujuparams.Cloud + controllers := make([]dbmodel.Controller, 0) + for _, cr := range cl.Regions { + for _, c := range cr.Controllers { + controllers = append(controllers, c.Controller) + } + } + err := j.firstSuccessfulController(ctx, controllers, func(a API) error { + return a.Cloud(ctx, cl.ResourceTag(), &jCl) + }) + if err != nil { + return jCl, errors.E(op, err) + } + return jCl, nil +} + +func (j *JIMM) GetClouds(ctx context.Context, user *openfga.User) (map[string]jujuparams.Cloud, error) { + const op = errors.Op("jimm.GetClouds") + zapctx.Info(ctx, string(op)) + + var clouds []dbmodel.Cloud + + err := j.ForEachCloud(ctx, user, func(c *dbmodel.Cloud) error { + clouds = append(clouds, *c) + return nil + }) + if err != nil { + return nil, err + } + results := make(map[string]jujuparams.Cloud, 0) + for _, cl := range clouds { + cloud, err := j.getCloudFromController(ctx, cl) + if err != nil { + return nil, errors.E(op, err) + } + results[cl.ResourceTag().String()] = cloud + } + + return results, err +} + // GetCloud retrieves the cloud for the given cloud tag. If the cloud // cannot be found then an error with the code CodeNotFound is // returned. If the user does not have permission to view the cloud then an // error with a code of CodeUnauthorized is returned. If the user only has // add-model access to the cloud then the returned Users field will only // contain the authentcated user. -func (j *JIMM) GetCloud(ctx context.Context, user *openfga.User, tag names.CloudTag) (dbmodel.Cloud, error) { - const op = errors.Op("jimm.GetCloud") +func (j *JIMM) GetCloudInfo(ctx context.Context, user *openfga.User, tag names.CloudTag) (jujuparams.CloudInfo, error) { + const op = errors.Op("jimm.CloudInfo") + zapctx.Info(ctx, string(op)) var cl dbmodel.Cloud cl.SetTag(tag) if err := j.Database.GetCloud(ctx, &cl); err != nil { - return cl, errors.E(op, err) + return jujuparams.CloudInfo{}, errors.E(op, err) + } + // TODO (SimoneDutto): refactor this to use `user.IsCloudAdmin()` + accessLevel, err := j.permissionManager.GetUserCloudAccess(ctx, user, cl.ResourceTag()) + if err != nil { + return jujuparams.CloudInfo{}, errors.E(op, err) + } + if accessLevel == "" { + return jujuparams.CloudInfo{}, errors.E(op, errors.CodeUnauthorized, "unauthorized") } - accessLevel, err := j.permissionManager.GetUserCloudAccess(ctx, user, tag) + return j.getCloudInfoFromController(ctx, cl) +} + +func (j *JIMM) getCloudInfoFromController(ctx context.Context, cl dbmodel.Cloud) (jujuparams.CloudInfo, error) { + const op = errors.Op("jimm.getCloudInfoFromController") + var gCl jujuparams.CloudInfo + + controllers := make([]dbmodel.Controller, 0) + for _, cr := range cl.Regions { + for _, c := range cr.Controllers { + controllers = append(controllers, c.Controller) + } + } + err := j.firstSuccessfulController(ctx, controllers, func(api API) error { + return api.CloudInfo(ctx, cl.ResourceTag(), &gCl) + }) if err != nil { - return dbmodel.Cloud{}, errors.E(op, err) + return gCl, errors.E(op, err) } - switch accessLevel { - case "": - return dbmodel.Cloud{}, errors.E(op, errors.CodeUnauthorized, "unauthorized") - case "admin": - return cl, nil - default: - return cl, nil + return jujuparams.CloudInfo{ + CloudDetails: gCl.CloudDetails, + // TODO(Kian) CSS-6040 Determine whether to combine OpenFGA Tuples + // with Postgres data objects for a consolidated view. + Users: nil, + }, nil +} + +func (j *JIMM) ListCloudsInfo(ctx context.Context, user *openfga.User, all bool) ([]jujuparams.ListCloudInfoResult, error) { + const op = errors.Op("jimm.ListCloudsInfo") + zapctx.Info(ctx, string(op)) + + var clouds []dbmodel.Cloud + var err error + if all { + err = j.ForEachCloud(ctx, user, func(c *dbmodel.Cloud) error { + clouds = append(clouds, *c) + return nil + }) + } else { + err = j.ForEachUserCloud(ctx, user, func(c *dbmodel.Cloud) error { + clouds = append(clouds, *c) + return nil + }) } + if err != nil { + return nil, err + } + var result []jujuparams.ListCloudInfoResult + for _, cl := range clouds { + cloudInfo, err := j.getCloudInfoFromController(ctx, cl) + if err != nil { + return nil, errors.E(op, err) + } + cloudAccessLevel := permissions.ToCloudAccessString(user.GetCloudAccess(ctx, cl.ResourceTag())) + result = append(result, jujuparams.ListCloudInfoResult{ + Result: &jujuparams.ListCloudInfo{ + CloudDetails: cloudInfo.CloudDetails, + Access: cloudAccessLevel, + }, + }) + } + return result, nil + } // ForEachUserCloud iterates through all of the clouds a user has access to diff --git a/internal/jimm/cloud_test.go b/internal/jimm/cloud_test.go index e80ff0b0f..7e223f52d 100644 --- a/internal/jimm/cloud_test.go +++ b/internal/jimm/cloud_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jimm_test @@ -18,12 +18,72 @@ import ( "github.com/canonical/jimm/v3/internal/testutils/jimmtest" ) +const getCloudTestEnv = `clouds: +- name: test-cloud + type: test-provider + regions: + - name: test-region +- name: test-cloud-1 + type: maas + host-cloud-region: test-provider/test-region + regions: + - name: default +controllers: +- name: test-controller + uuid: 00000001-0000-0000-0000-000000000001 + cloud: test-cloud + region: test-region + cloud-regions: + - cloud: test-cloud + region: test-region + priority: 1 + - cloud: test-cloud-1 + region: default + priority: 1 +` + func TestGetCloud(t *testing.T) { c := qt.New(t) ctx := context.Background() - j := jimmtest.NewJIMM(c, nil) + api := &jimmtest.API{ + Cloud_: func(_ context.Context, tag names.CloudTag, cld *jujuparams.Cloud) error { + if tag.Id() == "test-cloud" { + cld.Type = "kubernetes" + } else { + cld.Type = "maas" + } + cld.HostCloudRegion = "test-provider/test-region" + cld.AuthTypes = []string{"empty", "userpass"} + cld.Endpoint = "https://example.com" + cld.IdentityEndpoint = "https://example.com/identity" + cld.StorageEndpoint = "https://example.com/storage" + cld.Regions = []jujuparams.CloudRegion{{ + Name: "default", + }} + cld.CACertificates = []string{"CACERT"} + cld.Config = map[string]interface{}{"A": "a"} + cld.RegionConfig = map[string]map[string]interface{}{ + "default": {"B": 2}, + } + return nil + }, + AddCloud_: func(context.Context, names.CloudTag, jujuparams.Cloud, bool) error { + return nil + }, + GrantCloudAccess_: func(context.Context, names.CloudTag, names.UserTag, string) error { + return nil + }, + } + + dialer := &jimmtest.Dialer{ + API: api, + } + + j := jimmtest.NewJIMM(c, &jimm.Parameters{ + Dialer: dialer, + }) aliceIdentity, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) @@ -59,13 +119,11 @@ func TestGetCloud(t *testing.T) { ofganames.AdministratorRelation, ) c.Assert(err, qt.IsNil) - - cloud := &dbmodel.Cloud{ - Name: "test-cloud-1", + env := jimmtest.ParseEnvironment(c, getCloudTestEnv) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) + cloud := dbmodel.Cloud{ + Name: "test-cloud", } - err = j.Database.AddCloud(ctx, cloud) - c.Assert(err, qt.IsNil) - err = j.OpenFGAClient.AddCloudController(context.Background(), cloud.ResourceTag(), j.ResourceTag()) c.Assert(err, qt.IsNil) @@ -74,63 +132,81 @@ func TestGetCloud(t *testing.T) { err = bob.SetCloudAccess(context.Background(), cloud.ResourceTag(), ofganames.CanAddModelRelation) c.Assert(err, qt.IsNil) - - cloud2 := &dbmodel.Cloud{ - Name: "test-cloud-2", + cloud1 := dbmodel.Cloud{ + Name: "test-cloud-1", } - err = j.Database.AddCloud(ctx, cloud2) c.Assert(err, qt.IsNil) - err = j.OpenFGAClient.AddCloudController(context.Background(), cloud2.ResourceTag(), j.ResourceTag()) + err = j.OpenFGAClient.AddCloudController(context.Background(), cloud1.ResourceTag(), j.ResourceTag()) c.Assert(err, qt.IsNil) - err = j.EveryoneUser().SetCloudAccess(context.Background(), cloud2.ResourceTag(), ofganames.CanAddModelRelation) + err = j.EveryoneUser().SetCloudAccess(context.Background(), cloud1.ResourceTag(), ofganames.CanAddModelRelation) c.Assert(err, qt.IsNil) _, err = j.GetCloud(ctx, alice, names.NewCloudTag("test-cloud-0")) c.Check(errors.ErrorCode(err), qt.Equals, errors.CodeNotFound) - _, err = j.GetCloud(ctx, charlie, names.NewCloudTag("test-cloud-1")) + _, err = j.GetCloud(ctx, charlie, cloud.ResourceTag()) c.Check(errors.ErrorCode(err), qt.Equals, errors.CodeUnauthorized) - cld, err := j.GetCloud(ctx, alice, names.NewCloudTag("test-cloud-1")) + cld, err := j.GetCloud(ctx, alice, cloud.ResourceTag()) c.Assert(err, qt.IsNil) - c.Check(cld, qt.DeepEquals, dbmodel.Cloud{ - ID: 1, - CreatedAt: now, - UpdatedAt: now, - Name: "test-cloud-1", - Regions: []dbmodel.CloudRegion{}, + c.Assert(cld, qt.DeepEquals, jujuparams.Cloud{ + Type: "kubernetes", + HostCloudRegion: "test-provider/test-region", + AuthTypes: []string{"empty", "userpass"}, + Endpoint: "https://example.com", + IdentityEndpoint: "https://example.com/identity", + StorageEndpoint: "https://example.com/storage", + Regions: []jujuparams.CloudRegion{{Name: "default"}}, + CACertificates: []string{"CACERT"}, + Config: map[string]any{"A": string("a")}, + RegionConfig: map[string]map[string]any{"default": {"B": int(2)}}, }) - cld, err = j.GetCloud(ctx, bob, names.NewCloudTag("test-cloud-1")) + cld, err = j.GetCloud(ctx, bob, cloud.ResourceTag()) c.Assert(err, qt.IsNil) - c.Check(cld, qt.DeepEquals, dbmodel.Cloud{ - ID: 1, - CreatedAt: now, - UpdatedAt: now, - Name: "test-cloud-1", - Regions: []dbmodel.CloudRegion{}, + c.Assert(cld, qt.DeepEquals, jujuparams.Cloud{ + Type: "kubernetes", + HostCloudRegion: "test-provider/test-region", + AuthTypes: []string{"empty", "userpass"}, + Endpoint: "https://example.com", + IdentityEndpoint: "https://example.com/identity", + StorageEndpoint: "https://example.com/storage", + Regions: []jujuparams.CloudRegion{{Name: "default"}}, + CACertificates: []string{"CACERT"}, + Config: map[string]any{"A": string("a")}, + RegionConfig: map[string]map[string]any{"default": {"B": int(2)}}, }) - cld, err = j.GetCloud(ctx, daphne, names.NewCloudTag("test-cloud-1")) + cld, err = j.GetCloud(ctx, daphne, cloud1.ResourceTag()) c.Assert(err, qt.IsNil) - c.Check(cld, qt.DeepEquals, dbmodel.Cloud{ - ID: 1, - CreatedAt: now, - UpdatedAt: now, - Name: "test-cloud-1", - Regions: []dbmodel.CloudRegion{}, + c.Assert(cld, qt.DeepEquals, jujuparams.Cloud{ + Type: "maas", + HostCloudRegion: "test-provider/test-region", + AuthTypes: []string{"empty", "userpass"}, + Endpoint: "https://example.com", + IdentityEndpoint: "https://example.com/identity", + StorageEndpoint: "https://example.com/storage", + Regions: []jujuparams.CloudRegion{{Name: "default"}}, + CACertificates: []string{"CACERT"}, + Config: map[string]any{"A": string("a")}, + RegionConfig: map[string]map[string]any{"default": {"B": int(2)}}, }) - cld, err = j.GetCloud(ctx, charlie, names.NewCloudTag("test-cloud-2")) + cld, err = j.GetCloud(ctx, charlie, cloud1.ResourceTag()) c.Assert(err, qt.IsNil) - c.Check(cld, qt.DeepEquals, dbmodel.Cloud{ - ID: 2, - CreatedAt: now, - UpdatedAt: now, - Name: "test-cloud-2", - Regions: []dbmodel.CloudRegion{}, + c.Assert(cld, qt.DeepEquals, jujuparams.Cloud{ + Type: "maas", + HostCloudRegion: "test-provider/test-region", + AuthTypes: []string{"empty", "userpass"}, + Endpoint: "https://example.com", + IdentityEndpoint: "https://example.com/identity", + StorageEndpoint: "https://example.com/storage", + Regions: []jujuparams.CloudRegion{{Name: "default"}}, + CACertificates: []string{"CACERT"}, + Config: map[string]any{"A": string("a")}, + RegionConfig: map[string]map[string]any{"default": {"B": int(2)}}, }) } @@ -357,7 +433,7 @@ var addHostedCloudTests = []struct { username string cloudName string cloud jujuparams.Cloud - expectCloud dbmodel.Cloud + expectCloud jujuparams.Cloud expectError string expectErrorCode errors.Code }{{ @@ -395,29 +471,17 @@ var addHostedCloudTests = []struct { IdentityEndpoint: "https://example.com/identity", StorageEndpoint: "https://example.com/storage", }, - expectCloud: dbmodel.Cloud{ - Name: "new-cloud", + expectCloud: jujuparams.Cloud{ Type: "kubernetes", HostCloudRegion: "test-provider/test-region", AuthTypes: []string{"empty", "userpass"}, Endpoint: "https://example.com", IdentityEndpoint: "https://example.com/identity", StorageEndpoint: "https://example.com/storage", - Regions: []dbmodel.CloudRegion{{ - Name: "default", - Config: dbmodel.Map{"B": float64(2)}, - Controllers: []dbmodel.CloudRegionControllerPriority{{ - Controller: dbmodel.Controller{ - Name: "test-controller", - UUID: "00000001-0000-0000-0000-000000000001", - CloudName: "test-cloud", - CloudRegion: "test-region", - }, - Priority: 1, - }}, - }}, - CACertificates: dbmodel.Strings{"CACERT"}, - Config: dbmodel.Map{"A": string("a")}, + Regions: []jujuparams.CloudRegion{{Name: "default"}}, + CACertificates: []string{"CACERT"}, + Config: map[string]any{"A": string("a")}, + RegionConfig: map[string]map[string]any{"default": {"B": int(2)}}, }, }, { name: "Success - with cloud name and region", @@ -454,29 +518,17 @@ var addHostedCloudTests = []struct { IdentityEndpoint: "https://example.com/identity", StorageEndpoint: "https://example.com/storage", }, - expectCloud: dbmodel.Cloud{ - Name: "new-cloud", + expectCloud: jujuparams.Cloud{ Type: "kubernetes", HostCloudRegion: "test-cloud/test-region", AuthTypes: []string{"empty", "userpass"}, Endpoint: "https://example.com", IdentityEndpoint: "https://example.com/identity", StorageEndpoint: "https://example.com/storage", - Regions: []dbmodel.CloudRegion{{ - Name: "default", - Config: dbmodel.Map{"B": float64(2)}, - Controllers: []dbmodel.CloudRegionControllerPriority{{ - Controller: dbmodel.Controller{ - Name: "test-controller", - UUID: "00000001-0000-0000-0000-000000000001", - CloudName: "test-cloud", - CloudRegion: "test-region", - }, - Priority: 1, - }}, - }}, - CACertificates: dbmodel.Strings{"CACERT"}, - Config: dbmodel.Map{"A": string("a")}, + Regions: []jujuparams.CloudRegion{{Name: "default"}}, + CACertificates: []string{"CACERT"}, + Config: map[string]any{"A": string("a")}, + RegionConfig: map[string]map[string]any{"default": {"B": int(2)}}, }, }, { name: "Success - with cloud name", @@ -513,29 +565,17 @@ var addHostedCloudTests = []struct { IdentityEndpoint: "https://example.com/identity", StorageEndpoint: "https://example.com/storage", }, - expectCloud: dbmodel.Cloud{ - Name: "new-cloud", + expectCloud: jujuparams.Cloud{ Type: "kubernetes", HostCloudRegion: "test-cloud/test-region", AuthTypes: []string{"empty", "userpass"}, Endpoint: "https://example.com", IdentityEndpoint: "https://example.com/identity", StorageEndpoint: "https://example.com/storage", - Regions: []dbmodel.CloudRegion{{ - Name: "default", - Config: dbmodel.Map{"B": float64(2)}, - Controllers: []dbmodel.CloudRegionControllerPriority{{ - Controller: dbmodel.Controller{ - Name: "test-controller", - UUID: "00000001-0000-0000-0000-000000000001", - CloudName: "test-cloud", - CloudRegion: "test-region", - }, - Priority: 1, - }}, - }}, - CACertificates: dbmodel.Strings{"CACERT"}, - Config: dbmodel.Map{"A": string("a")}, + Regions: []jujuparams.CloudRegion{{Name: "default"}}, + CACertificates: []string{"CACERT"}, + Config: map[string]any{"A": string("a")}, + RegionConfig: map[string]map[string]any{"default": {"B": int(2)}}, }, }, { name: "CloudWithReservedName", @@ -713,7 +753,7 @@ var addHostedCloudToControllerTests = []struct { controllerName string cloudName string cloud jujuparams.Cloud - expectCloud dbmodel.Cloud + expectCloud jujuparams.Cloud expectError string expectErrorCode errors.Code }{{ @@ -751,29 +791,17 @@ var addHostedCloudToControllerTests = []struct { IdentityEndpoint: "https://example.com/identity", StorageEndpoint: "https://example.com/storage", }, - expectCloud: dbmodel.Cloud{ - Name: "new-cloud", + expectCloud: jujuparams.Cloud{ Type: "maas", HostCloudRegion: "test-provider/test-region", AuthTypes: []string{"empty", "userpass"}, Endpoint: "https://example.com", IdentityEndpoint: "https://example.com/identity", StorageEndpoint: "https://example.com/storage", - Regions: []dbmodel.CloudRegion{{ - Name: "default", - Config: dbmodel.Map{"B": float64(2)}, - Controllers: []dbmodel.CloudRegionControllerPriority{{ - Controller: dbmodel.Controller{ - Name: "test-controller", - UUID: "00000001-0000-0000-0000-000000000001", - CloudName: "test-cloud", - CloudRegion: "test-region", - }, - Priority: 1, - }}, - }}, - CACertificates: dbmodel.Strings{"CACERT"}, - Config: dbmodel.Map{"A": string("a")}, + Regions: []jujuparams.CloudRegion{{Name: "default"}}, + CACertificates: []string{"CACERT"}, + Config: map[string]any{"A": string("a")}, + RegionConfig: map[string]map[string]any{"default": {"B": int(2)}}, }, }, { name: "Controller not found", @@ -1243,13 +1271,9 @@ var updateCloudTests = []struct { }}, }, expectCloud: dbmodel.Cloud{ - Name: "test", - Type: "kubernetes", - HostCloudRegion: "test-cloud/test-cloud-region", - AuthTypes: []string{"empty", "userpass"}, - Endpoint: "https://k8s.example.com", - IdentityEndpoint: "https://k8s.identity.example.com", - StorageEndpoint: "https://k8s.storage.example.com", + Name: "test", + Type: "kubernetes", + HostCloudRegion: "test-cloud/test-cloud-region", Regions: []dbmodel.CloudRegion{{ Name: "default", Controllers: []dbmodel.CloudRegionControllerPriority{{ diff --git a/internal/jimm/jimm.go b/internal/jimm/jimm.go index 0b743c9e4..c6b1070e5 100644 --- a/internal/jimm/jimm.go +++ b/internal/jimm/jimm.go @@ -479,6 +479,9 @@ type API interface { // Clouds returns the set of clouds supported by the controller. Clouds(context.Context) (map[names.CloudTag]jujuparams.Cloud, error) + // ListCloudInfo returns the list of cloud information supported by the controller. + ListCloudInfo(context.Context, jujuparams.ListCloudsRequest) (jujuparams.ListCloudInfoResults, error) + // ControllerModelSummary fetches the model summary of the model on the // controller that hosts the controller machines. ControllerModelSummary(context.Context, *jujuparams.ModelSummary) error @@ -633,6 +636,27 @@ func (j *JIMM) forEachController(ctx context.Context, controllers []dbmodel.Cont return eg.Wait() } +func (j *JIMM) firstSuccessfulController(ctx context.Context, controllers []dbmodel.Controller, f func(API) error) error { + const op = errors.Op("jimm.firstSuccessfulController") + + for i, _ := range controllers { + api, err := j.dial(ctx, &controllers[i], names.ModelTag{}) + if err != nil { + zapctx.Error(ctx, "error dialing controller", zap.Error(err)) + continue + } + defer api.Close() + err = f(api) + if err != nil { + zapctx.Error(ctx, "error dialing controller", zap.Error(err)) + continue + } + return nil + } + zapctx.Error(ctx, "error dialing all controllers") + return errors.E(op, "error dialing all controllers") +} + // addAuditLogEntry causes an entry to be added the the audit log. func (j *JIMM) AddAuditLogEntry(ale *dbmodel.AuditLogEntry) { ctx := context.Background() diff --git a/internal/jimm/model.go b/internal/jimm/model.go index 76f98f1ee..56561ac4d 100644 --- a/internal/jimm/model.go +++ b/internal/jimm/model.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jimm diff --git a/internal/jimm/service_account.go b/internal/jimm/service_account.go index 98a7fcf79..143f85683 100644 --- a/internal/jimm/service_account.go +++ b/internal/jimm/service_account.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jimm diff --git a/internal/jimm/service_account_test.go b/internal/jimm/service_account_test.go index c2b9b1495..5775b0ff0 100644 --- a/internal/jimm/service_account_test.go +++ b/internal/jimm/service_account_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jimm_test diff --git a/internal/jimmhttp/auth_handler_test.go b/internal/jimmhttp/auth_handler_test.go index 3d741d214..ea35713f8 100644 --- a/internal/jimmhttp/auth_handler_test.go +++ b/internal/jimmhttp/auth_handler_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jimmhttp_test import ( diff --git a/internal/jimmhttp/httpproxy_handler.go b/internal/jimmhttp/httpproxy_handler.go index fc1fb4049..241fa7305 100644 --- a/internal/jimmhttp/httpproxy_handler.go +++ b/internal/jimmhttp/httpproxy_handler.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jimmhttp diff --git a/internal/jimmhttp/rebac_admin/capabilities_test.go b/internal/jimmhttp/rebac_admin/capabilities_test.go index 22bb00f88..ecae42401 100644 --- a/internal/jimmhttp/rebac_admin/capabilities_test.go +++ b/internal/jimmhttp/rebac_admin/capabilities_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package rebac_admin_test diff --git a/internal/jimmhttp/rebac_admin/groups.go b/internal/jimmhttp/rebac_admin/groups.go index eaf1dae0e..603c90768 100644 --- a/internal/jimmhttp/rebac_admin/groups.go +++ b/internal/jimmhttp/rebac_admin/groups.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package rebac_admin diff --git a/internal/jimmhttp/rebac_admin/groups_test.go b/internal/jimmhttp/rebac_admin/groups_test.go index 8f4eb58fc..fa1c7be1b 100644 --- a/internal/jimmhttp/rebac_admin/groups_test.go +++ b/internal/jimmhttp/rebac_admin/groups_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package rebac_admin_test diff --git a/internal/jimmhttp/rebac_admin/identities.go b/internal/jimmhttp/rebac_admin/identities.go index ebb860567..d03f1c40b 100644 --- a/internal/jimmhttp/rebac_admin/identities.go +++ b/internal/jimmhttp/rebac_admin/identities.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package rebac_admin diff --git a/internal/jimmhttp/rebac_admin/identities_integration_test.go b/internal/jimmhttp/rebac_admin/identities_integration_test.go index 3a9559503..c6dd2b590 100644 --- a/internal/jimmhttp/rebac_admin/identities_integration_test.go +++ b/internal/jimmhttp/rebac_admin/identities_integration_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package rebac_admin_test import ( diff --git a/internal/jimmhttp/rebac_admin/identities_test.go b/internal/jimmhttp/rebac_admin/identities_test.go index 22d9ff169..7508d7852 100644 --- a/internal/jimmhttp/rebac_admin/identities_test.go +++ b/internal/jimmhttp/rebac_admin/identities_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package rebac_admin_test diff --git a/internal/jimmhttp/rebac_admin/roles.go b/internal/jimmhttp/rebac_admin/roles.go index efea142b5..3d32da522 100644 --- a/internal/jimmhttp/rebac_admin/roles.go +++ b/internal/jimmhttp/rebac_admin/roles.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package rebac_admin diff --git a/internal/jimmhttp/rebac_admin/roles_test.go b/internal/jimmhttp/rebac_admin/roles_test.go index 6dc239d8d..a201fbec4 100644 --- a/internal/jimmhttp/rebac_admin/roles_test.go +++ b/internal/jimmhttp/rebac_admin/roles_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package rebac_admin_test diff --git a/internal/jujuapi/access_control.go b/internal/jujuapi/access_control.go index a6245758a..5a4ee04e3 100644 --- a/internal/jujuapi/access_control.go +++ b/internal/jujuapi/access_control.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jujuapi diff --git a/internal/jujuapi/admin.go b/internal/jujuapi/admin.go index 49a5bb925..4982f8086 100644 --- a/internal/jujuapi/admin.go +++ b/internal/jujuapi/admin.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jujuapi diff --git a/internal/jujuapi/applicationoffers.go b/internal/jujuapi/applicationoffers.go index dac4025da..b2592d33b 100644 --- a/internal/jujuapi/applicationoffers.go +++ b/internal/jujuapi/applicationoffers.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jujuapi diff --git a/internal/jujuapi/cloud.go b/internal/jujuapi/cloud.go index 927e67c98..cccbc0175 100644 --- a/internal/jujuapi/cloud.go +++ b/internal/jujuapi/cloud.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jujuapi @@ -15,7 +15,6 @@ import ( "github.com/canonical/jimm/v3/internal/dbmodel" "github.com/canonical/jimm/v3/internal/errors" "github.com/canonical/jimm/v3/internal/jimm" - "github.com/canonical/jimm/v3/internal/jimm/permissions" "github.com/canonical/jimm/v3/internal/jujuapi/rpc" "github.com/canonical/jimm/v3/internal/openfga" ofganames "github.com/canonical/jimm/v3/internal/openfga/names" @@ -89,13 +88,13 @@ func (r *controllerRoot) Cloud(ctx context.Context, ents jujuparams.Entities) (j cloudResults[i].Error = mapError(errors.E(op, errors.CodeBadRequest, err)) continue } + cloud, err := r.jimm.GetCloud(ctx, r.user, tag) if err != nil { cloudResults[i].Error = mapError(errors.E(op, err)) continue } - cloudResults[i].Cloud = new(jujuparams.Cloud) - *cloudResults[i].Cloud = cloud.ToJujuCloud() + cloudResults[i].Cloud = &cloud } return jujuparams.CloudResults{ Results: cloudResults, @@ -109,14 +108,13 @@ func (r *controllerRoot) Clouds(ctx context.Context) (jujuparams.CloudsResult, e res := jujuparams.CloudsResult{ Clouds: make(map[string]jujuparams.Cloud), } - err := r.jimm.ForEachUserCloud(ctx, r.user, func(cld *dbmodel.Cloud) error { - res.Clouds[cld.Tag().String()] = cld.ToJujuCloud() - return nil - }) + clouds, err := r.jimm.GetClouds(ctx, r.user) if err != nil { return res, errors.E(op, err) } - return res, nil + return jujuparams.CloudsResult{ + Clouds: clouds, + }, nil } // UserCredentials implements the UserCredentials method of the Cloud facade. @@ -493,14 +491,12 @@ func (r *controllerRoot) CloudInfo(ctx context.Context, args jujuparams.Entities results[i].Error = mapError(errors.E(op, err, errors.CodeBadRequest)) continue } - cloud, err := r.jimm.GetCloud(ctx, r.user, tag) + cloud, err := r.jimm.GetCloudInfo(ctx, r.user, tag) if err != nil { results[i].Error = mapError(errors.E(op, err)) continue } - - results[i].Result = new(jujuparams.CloudInfo) - *results[i].Result = cloud.ToJujuCloudInfo() + results[i].Result = &cloud } return jujuparams.CloudInfoResults{ Results: results, @@ -511,21 +507,7 @@ func (r *controllerRoot) CloudInfo(ctx context.Context, args jujuparams.Entities func (r *controllerRoot) ListCloudInfo(ctx context.Context, args jujuparams.ListCloudsRequest) (jujuparams.ListCloudInfoResults, error) { const op = errors.Op("jujuapi.ListCloudInfo") - listF := r.jimm.ForEachUserCloud - if args.All { - listF = r.jimm.ForEachCloud - } - - var results []jujuparams.ListCloudInfoResult - err := listF(ctx, r.user, func(c *dbmodel.Cloud) error { - results = append(results, jujuparams.ListCloudInfoResult{ - Result: &jujuparams.ListCloudInfo{ - CloudDetails: c.ToJujuCloudDetails(), - Access: permissions.ToCloudAccessString(r.user.GetCloudAccess(ctx, c.ResourceTag())), - }, - }) - return nil - }) + results, err := r.jimm.ListCloudsInfo(ctx, r.user, args.All) if err != nil { return jujuparams.ListCloudInfoResults{}, errors.E(op, err) } diff --git a/internal/jujuapi/controller.go b/internal/jujuapi/controller.go index f424b0e4d..0f25d2104 100644 --- a/internal/jujuapi/controller.go +++ b/internal/jujuapi/controller.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jujuapi diff --git a/internal/jujuapi/controllerroot.go b/internal/jujuapi/controllerroot.go index a21700e6a..81e3dd5a6 100644 --- a/internal/jujuapi/controllerroot.go +++ b/internal/jujuapi/controllerroot.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jujuapi diff --git a/internal/jujuapi/interface.go b/internal/jujuapi/interface.go index c05e56aea..ae6767bf8 100644 --- a/internal/jujuapi/interface.go +++ b/internal/jujuapi/interface.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jujuapi @@ -7,7 +7,6 @@ import ( "time" "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" - "github.com/juju/juju/api/base" jujuparams "github.com/juju/juju/rpc/params" "github.com/juju/names/v5" @@ -27,6 +26,7 @@ type JIMM interface { AddCloudToController(ctx context.Context, user *openfga.User, controllerName string, tag names.CloudTag, cloud jujuparams.Cloud, force bool) error AddHostedCloud(ctx context.Context, user *openfga.User, tag names.CloudTag, cloud jujuparams.Cloud, force bool) error AddServiceAccount(ctx context.Context, u *openfga.User, clientId string) error + GetClouds(ctx context.Context, user *openfga.User) (map[string]jujuparams.Cloud, error) CopyServiceAccountCredential(ctx context.Context, u *openfga.User, svcAcc *openfga.User, cloudCredentialTag names.CloudCredentialTag) (names.CloudCredentialTag, []jujuparams.UpdateCredentialModelResult, error) DestroyOffer(ctx context.Context, user *openfga.User, offerURL string, force bool) error FindApplicationOffers(ctx context.Context, user *openfga.User, filters ...jujuparams.OfferFilter) ([]jujuparams.ApplicationOfferAdminDetailsV5, error) @@ -36,20 +36,21 @@ type JIMM interface { ForEachUserCloudCredential(ctx context.Context, u *dbmodel.Identity, ct names.CloudTag, f func(cred *dbmodel.CloudCredential) error) error GetApplicationOffer(ctx context.Context, user *openfga.User, offerURL string) (*jujuparams.ApplicationOfferAdminDetailsV5, error) GetApplicationOfferConsumeDetails(ctx context.Context, user *openfga.User, details *jujuparams.ConsumeOfferDetails, v bakery.Version) error - GetCloud(ctx context.Context, u *openfga.User, tag names.CloudTag) (dbmodel.Cloud, error) + GetCloud(ctx context.Context, u *openfga.User, tag names.CloudTag) (jujuparams.Cloud, error) + GetCloudInfo(ctx context.Context, user *openfga.User, tag names.CloudTag) (jujuparams.CloudInfo, error) GetCloudCredential(ctx context.Context, user *openfga.User, tag names.CloudCredentialTag) (*dbmodel.CloudCredential, error) GetCloudCredentialAttributes(ctx context.Context, u *openfga.User, cred *dbmodel.CloudCredential, hidden bool) (attrs map[string]string, redacted []string, err error) RoleManager() jimm.RoleManager GroupManager() jimm.GroupManager IdentityManager() jimm.IdentityManager + InitiateInternalMigration(ctx context.Context, user *openfga.User, modelNameOrUUID string, targetController string) (jujuparams.InitiateMigrationResult, error) LoginManager() jimm.LoginManager PermissionManager() jimm.PermissionManager - InitiateInternalMigration(ctx context.Context, user *openfga.User, modelNameOrUUID string, targetController string) (jujuparams.InitiateMigrationResult, error) InitiateMigration(ctx context.Context, user *openfga.User, spec jujuparams.MigrationSpec) (jujuparams.InitiateMigrationResult, error) ListApplicationOffers(ctx context.Context, user *openfga.User, filters ...jujuparams.OfferFilter) ([]jujuparams.ApplicationOfferAdminDetailsV5, error) - ListModels(ctx context.Context, user *openfga.User) ([]base.UserModel, error) ListResources(ctx context.Context, user *openfga.User, filter pagination.LimitOffsetPagination, namePrefixFilter, typeFilter string) ([]db.Resource, error) + ListCloudsInfo(ctx context.Context, user *openfga.User, all bool) ([]jujuparams.ListCloudInfoResult, error) Offer(ctx context.Context, user *openfga.User, offer jimm.AddApplicationOfferParams) error PubSubHub() *pubsub.Hub PurgeLogs(ctx context.Context, user *openfga.User, before time.Time) (int64, error) diff --git a/internal/jujuapi/jimm.go b/internal/jujuapi/jimm.go index 5c0aa6829..2cc40825a 100644 --- a/internal/jujuapi/jimm.go +++ b/internal/jujuapi/jimm.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jujuapi diff --git a/internal/jujuapi/jimm_relation.go b/internal/jujuapi/jimm_relation.go index 1104b7d99..e11dd5bcd 100644 --- a/internal/jujuapi/jimm_relation.go +++ b/internal/jujuapi/jimm_relation.go @@ -1,3 +1,3 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jujuapi diff --git a/internal/jujuapi/jimm_test.go b/internal/jujuapi/jimm_test.go index d8e34d112..0ce11a438 100644 --- a/internal/jujuapi/jimm_test.go +++ b/internal/jujuapi/jimm_test.go @@ -664,7 +664,6 @@ func (s *jimmSuite) TestAddCloudToController(c *gc.C) { cloud, err := s.JIMM.GetCloud(context.Background(), user, names.NewCloudTag("test-cloud")) c.Assert(err, gc.IsNil) - c.Assert(cloud.Name, gc.DeepEquals, "test-cloud") c.Assert(cloud.Type, gc.DeepEquals, "kubernetes") } @@ -701,10 +700,14 @@ func (s *jimmSuite) TestAddExistingCloudToController(c *gc.C) { user := openfga.NewUser(u, s.OFGAClient) cloud, err := s.JIMM.GetCloud(context.Background(), user, names.NewCloudTag("test-cloud")) c.Assert(err, gc.IsNil) - c.Assert(cloud.Name, gc.DeepEquals, "test-cloud") c.Assert(cloud.Type, gc.DeepEquals, "MAAS") // Simulate the cloud being present on the Juju controller but not in JIMM. - err = s.JIMM.Database.DeleteCloud(ctx, &cloud) + cloudDb := dbmodel.Cloud{ + Name: "test-cloud", + } + err = s.JIMM.Database.GetCloud(ctx, &cloudDb) + c.Assert(err, gc.IsNil) + err = s.JIMM.Database.DeleteCloud(ctx, &cloudDb) c.Assert(err, gc.IsNil) cloud, err = s.JIMM.GetCloud(context.Background(), user, names.NewCloudTag("test-cloud")) c.Assert(err, gc.NotNil) @@ -713,7 +716,6 @@ func (s *jimmSuite) TestAddExistingCloudToController(c *gc.C) { c.Assert(err, gc.Equals, nil) cloud, err = s.JIMM.GetCloud(context.Background(), user, names.NewCloudTag("test-cloud")) c.Assert(err, gc.IsNil) - c.Assert(cloud.Name, gc.DeepEquals, "test-cloud") c.Assert(cloud.Type, gc.DeepEquals, "MAAS") } diff --git a/internal/jujuapi/modelmanager.go b/internal/jujuapi/modelmanager.go index 9657280f7..881d21620 100644 --- a/internal/jujuapi/modelmanager.go +++ b/internal/jujuapi/modelmanager.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jujuapi @@ -7,6 +7,7 @@ import ( "fmt" "time" + "github.com/juju/juju/api/base" jujuparams "github.com/juju/juju/rpc/params" "github.com/juju/names/v5" "github.com/juju/zaputil/zapctx" @@ -71,6 +72,7 @@ type ModelManager interface { ImportModel(ctx context.Context, user *openfga.User, controllerName string, modelTag names.ModelTag, newOwner string) error ModelDefaultsForCloud(ctx context.Context, user *dbmodel.Identity, cloudTag names.CloudTag) (jujuparams.ModelDefaultsResult, error) ModelInfo(ctx context.Context, u *openfga.User, mt names.ModelTag) (*jujuparams.ModelInfo, error) + ListModels(ctx context.Context, user *openfga.User) ([]base.UserModel, error) ListModelSummaries(ctx context.Context, user *openfga.User, maskingControllerUUID string) (jujuparams.ModelSummaryResults, error) ModelStatus(ctx context.Context, u *openfga.User, mt names.ModelTag) (*jujuparams.ModelStatus, error) QueryModelsJq(ctx context.Context, models []string, jqQuery string) (params.CrossModelQueryResponse, error) diff --git a/internal/jujuapi/service_account.go b/internal/jujuapi/service_account.go index b89b83c11..9c3dcddf6 100644 --- a/internal/jujuapi/service_account.go +++ b/internal/jujuapi/service_account.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jujuapi diff --git a/internal/jujuapi/streamproxy.go b/internal/jujuapi/streamproxy.go index f86de3c9a..5500c849a 100644 --- a/internal/jujuapi/streamproxy.go +++ b/internal/jujuapi/streamproxy.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jujuapi diff --git a/internal/jujuapi/websocket.go b/internal/jujuapi/websocket.go index d807f3bb4..e6ae95b85 100644 --- a/internal/jujuapi/websocket.go +++ b/internal/jujuapi/websocket.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jujuapi diff --git a/internal/jujuclient/cloud.go b/internal/jujuclient/cloud.go index 16916eb0f..eba3f15d2 100644 --- a/internal/jujuclient/cloud.go +++ b/internal/jujuclient/cloud.go @@ -286,6 +286,26 @@ func (c Connection) CloudInfo(ctx context.Context, tag names.CloudTag, ci *jujup return nil } +// ListCloudInfo returns the list of cloud information supported by the controller. +func (c Connection) ListCloudInfo(ctx context.Context, args jujuparams.ListCloudsRequest) (jujuparams.ListCloudInfoResults, error) { + const op = errors.Op("jujuclient.ListCloudInfo") + + args = jujuparams.ListCloudsRequest{ + UserTag: c.userTag, + All: true, + } + + resp := jujuparams.ListCloudInfoResults{} + err := c.CallHighestFacadeVersion(ctx, "Cloud", []int{7, 2}, "", "ListCloudInfo", &args, &resp) + if err != nil { + return resp, errors.E(op, jujuerrors.Cause(err)) + } + if resp.Results[0].Error != nil { + return resp, errors.E(op, resp.Results[0].Error) + } + return resp, nil +} + // UpdateCloud updates the given cloud with the given cloud definition. // UpdateCloud uses the UpdateCloud procedure on the cloud facade. func (c Connection) UpdateCloud(ctx context.Context, tag names.CloudTag, cloud jujuparams.Cloud) error { diff --git a/internal/logger/migration_logger.go b/internal/logger/migration_logger.go index c942f0d40..89c564f0f 100644 --- a/internal/logger/migration_logger.go +++ b/internal/logger/migration_logger.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package logger diff --git a/internal/middleware/authn_test.go b/internal/middleware/authn_test.go index df2d6e2e5..26c1f0231 100644 --- a/internal/middleware/authn_test.go +++ b/internal/middleware/authn_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package middleware_test diff --git a/internal/testutils/jimmtest/api.go b/internal/testutils/jimmtest/api.go index a88ef3e50..0c7057578 100644 --- a/internal/testutils/jimmtest/api.go +++ b/internal/testutils/jimmtest/api.go @@ -143,6 +143,7 @@ type API struct { GrantModelAccess_ func(context.Context, names.ModelTag, names.UserTag, jujuparams.UserAccessPermission) error IsBroken_ bool ListApplicationOffers_ func(context.Context, []jujuparams.OfferFilter) ([]jujuparams.ApplicationOfferAdminDetailsV5, error) + ListCloudInfo_ func(context.Context, jujuparams.ListCloudsRequest) (jujuparams.ListCloudInfoResults, error) ModelInfo_ func(context.Context, *jujuparams.ModelInfo) error ModelStatus_ func(context.Context, *jujuparams.ModelStatus) error ModelSummaryWatcherNext_ func(context.Context, string) ([]jujuparams.ModelAbstract, error) @@ -312,6 +313,13 @@ func (a *API) ListApplicationOffers(ctx context.Context, f []jujuparams.OfferFil return a.ListApplicationOffers_(ctx, f) } +func (a *API) ListCloudInfo(ctx context.Context, args jujuparams.ListCloudsRequest) (jujuparams.ListCloudInfoResults, error) { + if a.ListCloudInfo_ == nil { + return jujuparams.ListCloudInfoResults{}, errors.E(errors.CodeNotImplemented) + } + return a.ListCloudInfo_(ctx, args) +} + func (a *API) ModelInfo(ctx context.Context, mi *jujuparams.ModelInfo) error { if a.ModelInfo_ == nil { return errors.E(errors.CodeNotImplemented) diff --git a/internal/testutils/jimmtest/auth.go b/internal/testutils/jimmtest/auth.go index 2432ef8fd..cee67315b 100644 --- a/internal/testutils/jimmtest/auth.go +++ b/internal/testutils/jimmtest/auth.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jimmtest diff --git a/internal/testutils/jimmtest/env.go b/internal/testutils/jimmtest/env.go index f68f0f97c..465b61754 100644 --- a/internal/testutils/jimmtest/env.go +++ b/internal/testutils/jimmtest/env.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package jimmtest diff --git a/internal/testutils/jimmtest/gorm.go b/internal/testutils/jimmtest/gorm.go index f8a24cfd6..9bcad955a 100644 --- a/internal/testutils/jimmtest/gorm.go +++ b/internal/testutils/jimmtest/gorm.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. // Package jimmtest contains useful helpers for testing JIMM. package jimmtest diff --git a/internal/testutils/jimmtest/jimm_mock.go b/internal/testutils/jimmtest/jimm_mock.go index 3e5cdee90..dcad15bc0 100644 --- a/internal/testutils/jimmtest/jimm_mock.go +++ b/internal/testutils/jimmtest/jimm_mock.go @@ -50,13 +50,17 @@ type JIMM struct { ForEachUserCloudCredential_ func(ctx context.Context, u *dbmodel.Identity, ct names.CloudTag, f func(cred *dbmodel.CloudCredential) error) error GetApplicationOffer_ func(ctx context.Context, user *openfga.User, offerURL string) (*jujuparams.ApplicationOfferAdminDetailsV5, error) GetApplicationOfferConsumeDetails_ func(ctx context.Context, user *openfga.User, details *jujuparams.ConsumeOfferDetails, v bakery.Version) error - GetCloud_ func(ctx context.Context, u *openfga.User, tag names.CloudTag) (dbmodel.Cloud, error) + GetCloud_ func(ctx context.Context, u *openfga.User, tag names.CloudTag) (jujuparams.Cloud, error) + GetClouds_ func(ctx context.Context, user *openfga.User) (map[string]jujuparams.Cloud, error) GetCloudCredential_ func(ctx context.Context, user *openfga.User, tag names.CloudCredentialTag) (*dbmodel.CloudCredential, error) GetCloudCredentialAttributes_ func(ctx context.Context, u *openfga.User, cred *dbmodel.CloudCredential, hidden bool) (attrs map[string]string, redacted []string, err error) + GetCloudInfo_ func(ctx context.Context, user *openfga.User, tag names.CloudTag) (jujuparams.CloudInfo, error) GetCredentialStore_ func() jimmcreds.CredentialStore InitiateInternalMigration_ func(ctx context.Context, user *openfga.User, modelNameOrUUID string, targetController string) (jujuparams.InitiateMigrationResult, error) InitiateMigration_ func(ctx context.Context, user *openfga.User, spec jujuparams.MigrationSpec) (jujuparams.InitiateMigrationResult, error) ListApplicationOffers_ func(ctx context.Context, user *openfga.User, filters ...jujuparams.OfferFilter) ([]jujuparams.ApplicationOfferAdminDetailsV5, error) + ListCloudsInfo_ func(ctx context.Context, user *openfga.User, all bool) ([]jujuparams.ListCloudInfoResult, error) + ListIdentities_ func(ctx context.Context, user *openfga.User, pagination pagination.LimitOffsetPagination, match string) ([]openfga.User, error) ListResources_ func(ctx context.Context, user *openfga.User, filter pagination.LimitOffsetPagination, namePrefixFilter, typeFilter string) ([]db.Resource, error) Offer_ func(ctx context.Context, user *openfga.User, offer jimm.AddApplicationOfferParams) error PubSubHub_ func() *pubsub.Hub @@ -154,12 +158,27 @@ func (j *JIMM) GetApplicationOfferConsumeDetails(ctx context.Context, user *open } return j.GetApplicationOfferConsumeDetails_(ctx, user, details, v) } -func (j *JIMM) GetCloud(ctx context.Context, u *openfga.User, tag names.CloudTag) (dbmodel.Cloud, error) { +func (j *JIMM) GetCloud(ctx context.Context, u *openfga.User, tag names.CloudTag) (jujuparams.Cloud, error) { if j.GetCloud_ == nil { - return dbmodel.Cloud{}, errors.E(errors.CodeNotImplemented) + return jujuparams.Cloud{}, errors.E(errors.CodeNotImplemented) } return j.GetCloud_(ctx, u, tag) } + +func (j *JIMM) GetClouds(ctx context.Context, user *openfga.User) (map[string]jujuparams.Cloud, error) { + if j.GetClouds_ == nil { + return nil, errors.E(errors.CodeNotImplemented) + } + return j.GetClouds_(ctx, user) +} + +func (j *JIMM) GetCloudInfo(ctx context.Context, user *openfga.User, tag names.CloudTag) (jujuparams.CloudInfo, error) { + if j.GetCloud_ == nil { + return jujuparams.CloudInfo{}, errors.E(errors.CodeNotImplemented) + } + return j.GetCloudInfo_(ctx, user, tag) +} + func (j *JIMM) GetCloudCredential(ctx context.Context, user *openfga.User, tag names.CloudCredentialTag) (*dbmodel.CloudCredential, error) { if j.GetCloudCredential_ == nil { return nil, errors.E(errors.CodeNotImplemented) @@ -233,6 +252,13 @@ func (j *JIMM) ListApplicationOffers(ctx context.Context, user *openfga.User, fi } return j.ListApplicationOffers_(ctx, user, filters...) } +func (j *JIMM) ListCloudsInfo(ctx context.Context, user *openfga.User, all bool) ([]jujuparams.ListCloudInfoResult, error) { + if j.ListCloudsInfo_ == nil { + return nil, errors.E(errors.CodeNotImplemented) + } + return j.ListCloudsInfo_(ctx, user, all) +} + func (j *JIMM) ListResources(ctx context.Context, user *openfga.User, filter pagination.LimitOffsetPagination, namePrefixFilter, typeFilter string) ([]db.Resource, error) { if j.ListResources_ == nil { return nil, errors.E(errors.CodeNotImplemented) diff --git a/internal/testutils/jimmtest/mocks/jimm_controller_mock.go b/internal/testutils/jimmtest/mocks/jimm_controller_mock.go index 57bf530ac..47e75f309 100644 --- a/internal/testutils/jimmtest/mocks/jimm_controller_mock.go +++ b/internal/testutils/jimmtest/mocks/jimm_controller_mock.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package mocks diff --git a/internal/testutils/jimmtest/mocks/jimm_identity_mock.go b/internal/testutils/jimmtest/mocks/jimm_identity_mock.go index 6a1d18110..8f885c420 100644 --- a/internal/testutils/jimmtest/mocks/jimm_identity_mock.go +++ b/internal/testutils/jimmtest/mocks/jimm_identity_mock.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package mocks diff --git a/internal/testutils/jimmtest/mocks/jimm_relation_mock.go b/internal/testutils/jimmtest/mocks/jimm_relation_mock.go index 319ed45a2..0e2f15193 100644 --- a/internal/testutils/jimmtest/mocks/jimm_relation_mock.go +++ b/internal/testutils/jimmtest/mocks/jimm_relation_mock.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package mocks diff --git a/local/seed_db/main.go b/local/seed_db/main.go index 9bae2bdf1..e6a7cb9da 100644 --- a/local/seed_db/main.go +++ b/local/seed_db/main.go @@ -1,4 +1,4 @@ -// Copyright 2024 Canonical. +// Copyright 2025 Canonical. package main import (