From 6a3dfacc954bd687db1f288e62a0efce9278fae9 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Tue, 28 Oct 2025 10:43:03 -0600 Subject: [PATCH] Datasources: Add service function to get by group, name, and namespace (#113066) --- pkg/services/datasources/datasources.go | 3 ++ .../fakes/fake_datasource_service.go | 14 +++++++ .../datasources/service/datasource.go | 6 +++ .../datasources/service/datasource_test.go | 14 +++++++ pkg/services/datasources/service/store.go | 37 +++++++++++++++++++ .../datasources/service/store_test.go | 37 +++++++++++++++++++ 6 files changed, 111 insertions(+) diff --git a/pkg/services/datasources/datasources.go b/pkg/services/datasources/datasources.go index 5b45fd4d96a..a04f35e3775 100644 --- a/pkg/services/datasources/datasources.go +++ b/pkg/services/datasources/datasources.go @@ -15,6 +15,9 @@ type DataSourceService interface { // GetDataSource gets a datasource. GetDataSource(ctx context.Context, query *GetDataSourceQuery) (*DataSource, error) + // GetDataSourceInNamespace gets a datasource by namespace, name (datasource uid), and group (datasource type). + GetDataSourceInNamespace(ctx context.Context, namespace, name, group string) (*DataSource, error) + // GetDataSources gets datasources. GetDataSources(ctx context.Context, query *GetDataSourcesQuery) ([]*DataSource, error) diff --git a/pkg/services/datasources/fakes/fake_datasource_service.go b/pkg/services/datasources/fakes/fake_datasource_service.go index 4c5fd79173f..19e7bbb43a5 100644 --- a/pkg/services/datasources/fakes/fake_datasource_service.go +++ b/pkg/services/datasources/fakes/fake_datasource_service.go @@ -4,6 +4,7 @@ import ( "context" "net/http" + "github.com/grafana/authlib/types" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana/pkg/infra/httpclient" @@ -30,6 +31,19 @@ func (s *FakeDataSourceService) GetDataSource(ctx context.Context, query *dataso return nil, datasources.ErrDataSourceNotFound } +func (s *FakeDataSourceService) GetDataSourceInNamespace(ctx context.Context, namespace, name, group string) (*datasources.DataSource, error) { + ns, err := types.ParseNamespace(namespace) + if err != nil { + return nil, err + } + for _, dataSource := range s.DataSources { + if name == dataSource.UID && ns.OrgID == dataSource.OrgID && group == dataSource.Type { + return dataSource, nil + } + } + return nil, datasources.ErrDataSourceNotFound +} + func (s *FakeDataSourceService) GetDataSources(ctx context.Context, query *datasources.GetDataSourcesQuery) ([]*datasources.DataSource, error) { var dataSources []*datasources.DataSource for _, datasource := range s.DataSources { diff --git a/pkg/services/datasources/service/datasource.go b/pkg/services/datasources/service/datasource.go index f5cbe96225b..84a3e14141c 100644 --- a/pkg/services/datasources/service/datasource.go +++ b/pkg/services/datasources/service/datasource.go @@ -117,6 +117,8 @@ func (s *Service) Usage(ctx context.Context, scopeParams *quota.ScopeParameters) type DataSourceRetriever interface { // GetDataSource gets a datasource. GetDataSource(ctx context.Context, query *datasources.GetDataSourceQuery) (*datasources.DataSource, error) + // GetDataSourceInNamespace gets a datasource by namespace, name (datasource uid), and group (datasource type). + GetDataSourceInNamespace(ctx context.Context, namespace, name, group string) (*datasources.DataSource, error) } // NewNameScopeResolver provides an ScopeAttributeResolver able to @@ -176,6 +178,10 @@ func (s *Service) GetDataSource(ctx context.Context, query *datasources.GetDataS return s.SQLStore.GetDataSource(ctx, query) } +func (s *Service) GetDataSourceInNamespace(ctx context.Context, namespace, name, group string) (*datasources.DataSource, error) { + return s.SQLStore.GetDataSourceInNamespace(ctx, namespace, name, group) +} + func (s *Service) GetDataSources(ctx context.Context, query *datasources.GetDataSourcesQuery) ([]*datasources.DataSource, error) { return s.SQLStore.GetDataSources(ctx, query) } diff --git a/pkg/services/datasources/service/datasource_test.go b/pkg/services/datasources/service/datasource_test.go index 0de33c32e5f..1aeca6af087 100644 --- a/pkg/services/datasources/service/datasource_test.go +++ b/pkg/services/datasources/service/datasource_test.go @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/ini.v1" + "github.com/grafana/authlib/types" "github.com/grafana/grafana-plugin-sdk-go/backend" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana/pkg/components/simplejson" @@ -63,6 +64,19 @@ func (d *dataSourceMockRetriever) GetDataSource(ctx context.Context, query *data return nil, datasources.ErrDataSourceNotFound } +func (d *dataSourceMockRetriever) GetDataSourceInNamespace(ctx context.Context, namespace, name, group string) (*datasources.DataSource, error) { + ns, err := types.ParseNamespace(namespace) + if err != nil { + return nil, err + } + for _, dataSource := range d.res { + if name == dataSource.UID && ns.OrgID == dataSource.OrgID && group == dataSource.Type { + return dataSource, nil + } + } + return nil, datasources.ErrDataSourceNotFound +} + func TestIntegrationService_AddDataSource(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) diff --git a/pkg/services/datasources/service/store.go b/pkg/services/datasources/service/store.go index c89200ee2fc..e0ef6a88111 100644 --- a/pkg/services/datasources/service/store.go +++ b/pkg/services/datasources/service/store.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/util/xorm" "github.com/grafana/grafana/pkg/components/simplejson" @@ -24,6 +25,7 @@ import ( // Store is the interface for the datasource Service's storage. type Store interface { GetDataSource(context.Context, *datasources.GetDataSourceQuery) (*datasources.DataSource, error) + GetDataSourceInNamespace(context.Context, string, string, string) (*datasources.DataSource, error) GetDataSources(context.Context, *datasources.GetDataSourcesQuery) ([]*datasources.DataSource, error) GetDataSourcesByType(context.Context, *datasources.GetDataSourcesByTypeQuery) ([]*datasources.DataSource, error) DeleteDataSource(context.Context, *datasources.DeleteDataSourceCommand) error @@ -90,6 +92,41 @@ func (ss *SqlStore) getDataSource(_ context.Context, query *datasources.GetDataS return datasource, nil } +func (ss *SqlStore) GetDataSourceInNamespace(ctx context.Context, namespace, name, group string) (*datasources.DataSource, error) { + var ( + dataSource *datasources.DataSource + err error + ) + ns, err := types.ParseNamespace(namespace) + if err != nil { + return nil, err + } + + return dataSource, ss.db.WithDbSession(ctx, func(sess *db.Session) error { + dataSource, err = ss.getDataSourceInGroup(ctx, ns.OrgID, name, group, sess) + return err + }) +} + +func (ss *SqlStore) getDataSourceInGroup(_ context.Context, orgID int64, name, group string, sess *db.Session) (*datasources.DataSource, error) { + datasource := &datasources.DataSource{ + OrgID: orgID, + Type: group, + UID: name, + } + has, err := sess.Get(datasource) + + if err != nil { + ss.logger.Error("Failed getting data source", "err", err, "name", name, "orgId", orgID, "group", group) + return nil, err + } else if !has { + ss.logger.Debug("Data source not found", "name", name, "orgId", orgID, "group", group) + return nil, datasources.ErrDataSourceNotFound + } + + return datasource, nil +} + func (ss *SqlStore) GetDataSources(ctx context.Context, query *datasources.GetDataSourcesQuery) ([]*datasources.DataSource, error) { var ( sess *xorm.Session diff --git a/pkg/services/datasources/service/store_test.go b/pkg/services/datasources/service/store_test.go index eef123b138b..2de184c5c62 100644 --- a/pkg/services/datasources/service/store_test.go +++ b/pkg/services/datasources/service/store_test.go @@ -407,6 +407,43 @@ func TestIntegrationDataAccess(t *testing.T) { }) }) + t.Run("GetDataSourceInGroup", func(t *testing.T) { + t.Run("Only returns datasource of specified type", func(t *testing.T) { + db := db.InitTestDB(t) + ss := SqlStore{db: db, logger: log.NewNopLogger()} + + ds, err := ss.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{ + OrgID: 10, + Name: "Elasticsearch", + Type: datasources.DS_ES, + Access: datasources.DS_ACCESS_DIRECT, + URL: "http://test", + Database: "site", + ReadOnly: true, + }) + require.NoError(t, err) + + ds2, err := ss.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{ + OrgID: 10, + Name: "Graphite", + Type: datasources.DS_GRAPHITE, + Access: datasources.DS_ACCESS_DIRECT, + URL: "http://test", + Database: "site", + ReadOnly: true, + }) + require.NoError(t, err) + + dataSource, err := ss.GetDataSourceInNamespace(context.Background(), "org-10", ds.UID, datasources.DS_ES) + require.NoError(t, err) + require.Equal(t, ds.UID, dataSource.UID) + + _, err = ss.GetDataSourceInNamespace(context.Background(), "org-10", ds2.UID, datasources.DS_ES) + require.Error(t, err) + require.IsType(t, datasources.ErrDataSourceNotFound, err) + }) + }) + t.Run("GetDataSourcesByType", func(t *testing.T) { t.Run("Only returns datasources of specified type", func(t *testing.T) { db := db.InitTestDB(t)