Make it safe to run new config CRUD APIs in DS API server

Using `configCrudUseNewApis` passed through for DS API servers from the
`grafana-enterprise` codebase.
This commit is contained in:
beejeebus 2025-09-03 15:48:24 -04:00 committed by beejeebus
parent c3f34efb41
commit b7c73a9bfc
4 changed files with 117 additions and 30 deletions

View File

@ -0,0 +1,59 @@
package datasource
import (
"context"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
)
var (
_ rest.Scoper = (*connectionAccess)(nil)
_ rest.SingularNameProvider = (*connectionAccess)(nil)
_ rest.Getter = (*connectionAccess)(nil)
_ rest.Lister = (*connectionAccess)(nil)
_ rest.Storage = (*connectionAccess)(nil)
)
type connectionAccess struct {
resourceInfo utils.ResourceInfo
tableConverter rest.TableConvertor
datasources PluginDatasourceProvider
}
func (s *connectionAccess) New() runtime.Object {
return s.resourceInfo.NewFunc()
}
func (s *connectionAccess) Destroy() {}
func (s *connectionAccess) NamespaceScoped() bool {
return true
}
func (s *connectionAccess) GetSingularName() string {
return s.resourceInfo.GetSingularName()
}
func (s *connectionAccess) ShortNames() []string {
return s.resourceInfo.GetShortNames()
}
func (s *connectionAccess) NewList() runtime.Object {
return s.resourceInfo.NewListFunc()
}
func (s *connectionAccess) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
}
func (s *connectionAccess) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
return s.datasources.GetDataSource(ctx, name)
}
func (s *connectionAccess) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
return s.datasources.ListDataSources(ctx)
}

View File

@ -3,8 +3,10 @@ package datasource
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"maps" "maps"
"path/filepath"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -24,11 +26,13 @@ import (
"github.com/grafana/grafana/pkg/configprovider" "github.com/grafana/grafana/pkg/configprovider"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager/sources"
"github.com/grafana/grafana/pkg/promlib/models" "github.com/grafana/grafana/pkg/promlib/models"
"github.com/grafana/grafana/pkg/registry/apis/query/queryschema" "github.com/grafana/grafana/pkg/registry/apis/query/queryschema"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource/kinds" "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource/kinds"
) )
@ -40,13 +44,14 @@ var (
type DataSourceAPIBuilder struct { type DataSourceAPIBuilder struct {
datasourceResourceInfo utils.ResourceInfo datasourceResourceInfo utils.ResourceInfo
pluginJSON plugins.JSONData pluginJSON plugins.JSONData
client PluginClient // will only ever be called with the same plugin id! client PluginClient // will only ever be called with the same plugin id!
datasources PluginDatasourceProvider datasources PluginDatasourceProvider
contextProvider PluginContextWrapper contextProvider PluginContextWrapper
accessControl accesscontrol.AccessControl accessControl accesscontrol.AccessControl
queryTypes *queryV0.QueryTypeDefinitionList queryTypes *queryV0.QueryTypeDefinitionList
log log.Logger log log.Logger
configCrudUseNewApis bool
} }
func RegisterAPIService( func RegisterAPIService(
@ -109,16 +114,12 @@ func RegisterAPIService(
contextProvider, contextProvider,
accessControl, accessControl,
features.IsEnabledGlobally(featuremgmt.FlagDatasourceQueryTypes), features.IsEnabledGlobally(featuremgmt.FlagDatasourceQueryTypes),
false,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO: load the schema provider from a static manifest
// if ds.ID == "grafana-testdata-datasource" {
// builder.schemaProvider = hardcoded.TestdataOpenAPIExtension
// }
apiRegistrar.RegisterAPI(builder) apiRegistrar.RegisterAPI(builder)
} }
return builder, nil // only used for wire return builder, nil // only used for wire
@ -140,6 +141,7 @@ func NewDataSourceAPIBuilder(
contextProvider PluginContextWrapper, contextProvider PluginContextWrapper,
accessControl accesscontrol.AccessControl, accessControl accesscontrol.AccessControl,
loadQueryTypes bool, loadQueryTypes bool,
configCrudUseNewApis bool,
) (*DataSourceAPIBuilder, error) { ) (*DataSourceAPIBuilder, error) {
group, err := plugins.GetDatasourceGroupNameFromPluginID(plugin.ID) group, err := plugins.GetDatasourceGroupNameFromPluginID(plugin.ID)
if err != nil { if err != nil {
@ -154,6 +156,7 @@ func NewDataSourceAPIBuilder(
contextProvider: contextProvider, contextProvider: contextProvider,
accessControl: accessControl, accessControl: accessControl,
log: log.New("grafana-apiserver.datasource"), log: log.New("grafana-apiserver.datasource"),
configCrudUseNewApis: configCrudUseNewApis,
} }
if loadQueryTypes { if loadQueryTypes {
// In the future, this will somehow come from the plugin // In the future, this will somehow come from the plugin
@ -232,19 +235,6 @@ func (b *DataSourceAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver
// Register the raw datasource connection // Register the raw datasource connection
ds := b.datasourceResourceInfo ds := b.datasourceResourceInfo
legacyStore := &legacyStorage{
datasources: b.datasources,
resourceInfo: &ds,
}
unified, err := grafanaregistry.NewRegistryStore(opts.Scheme, ds, opts.OptsGetter)
if err != nil {
return err
}
storage[ds.StoragePath()], err = opts.DualWriteBuilder(ds.GroupResource(), legacyStore, unified)
if err != nil {
return err
}
storage[ds.StoragePath("query")] = &subQueryREST{builder: b} storage[ds.StoragePath("query")] = &subQueryREST{builder: b}
storage[ds.StoragePath("health")] = &subHealthREST{builder: b} storage[ds.StoragePath("health")] = &subHealthREST{builder: b}
storage[ds.StoragePath("resource")] = &subResourceREST{builder: b} storage[ds.StoragePath("resource")] = &subResourceREST{builder: b}
@ -255,13 +245,34 @@ func (b *DataSourceAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver
storage["connections"] = &noopREST{} // hidden from openapi storage["connections"] = &noopREST{} // hidden from openapi
storage["connections/query"] = storage[ds.StoragePath("query")] // deprecated in openapi storage["connections/query"] = storage[ds.StoragePath("query")] // deprecated in openapi
if b.configCrudUseNewApis {
legacyStore := &legacyStorage{
datasources: b.datasources,
resourceInfo: &ds,
}
unified, err := grafanaregistry.NewRegistryStore(opts.Scheme, ds, opts.OptsGetter)
if err != nil {
return err
}
storage[ds.StoragePath()], err = opts.DualWriteBuilder(ds.GroupResource(), legacyStore, unified)
if err != nil {
return err
}
} else {
storage[ds.StoragePath()] = &connectionAccess{
datasources: b.datasources,
resourceInfo: ds,
tableConverter: ds.TableConverter(),
}
}
// Frontend proxy // Frontend proxy
if len(b.pluginJSON.Routes) > 0 { if len(b.pluginJSON.Routes) > 0 {
storage[ds.StoragePath("proxy")] = &subProxyREST{pluginJSON: b.pluginJSON} storage[ds.StoragePath("proxy")] = &subProxyREST{pluginJSON: b.pluginJSON}
} }
// Register hardcoded query schemas // Register hardcoded query schemas
err = queryschema.RegisterQueryTypes(b.queryTypes, storage) err := queryschema.RegisterQueryTypes(b.queryTypes, storage)
if err != nil { if err != nil {
return err return err
} }
@ -287,3 +298,22 @@ func (b *DataSourceAPIBuilder) GetOpenAPIDefinitions() openapi.GetOpenAPIDefinit
return defs return defs
} }
} }
func getCorePlugins(cfg *setting.Cfg) ([]plugins.JSONData, error) {
coreDataSourcesPath := filepath.Join(cfg.StaticRootPath, "app", "plugins", "datasource")
coreDataSourcesSrc := sources.NewLocalSource(
plugins.ClassCore,
[]string{coreDataSourcesPath},
)
res, err := coreDataSourcesSrc.Discover(context.Background())
if err != nil {
return nil, errors.New("failed to load core data source plugins")
}
pluginJSONs := make([]plugins.JSONData, 0, len(res))
for _, p := range res {
pluginJSONs = append(pluginJSONs, p.Primary.JSONData)
}
return pluginJSONs, nil
}

View File

@ -2,8 +2,7 @@
"metadata": { "metadata": {
"name": "cejobd88i85j4d", "name": "cejobd88i85j4d",
"namespace": "org-0", "namespace": "org-0",
"uid": "boDNh7zU3nXj46rOXIJI7r44qaxjs8yy9I9dOj1MyBoX", "uid": "boDNh7zU3nXj46rOXIJI7r44qaxjs8yy9I9dOj1MyBoX"
"creationTimestamp": null
}, },
"spec": { "spec": {
"jsonData": null, "jsonData": null,

View File

@ -3,8 +3,7 @@
"name": "cejobd88i85j4d", "name": "cejobd88i85j4d",
"namespace": "org-0", "namespace": "org-0",
"uid": "boDNh7zU3nXj46rOXIJI7r44qaxjs8yy9I9dOj1MyBoX", "uid": "boDNh7zU3nXj46rOXIJI7r44qaxjs8yy9I9dOj1MyBoX",
"generation": 2, "generation": 2
"creationTimestamp": null
}, },
"spec": { "spec": {
"access": "proxy", "access": "proxy",