From 6be672443391e0c856b099c6bbb1cd44ec0c3a06 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 10 Jan 2024 07:45:23 -0800 Subject: [PATCH] K8s/Testdata: Expose testdata in standalone apiserver (#80248) --- .vscode/launch.json | 4 +- pkg/cmd/grafana/apiserver/cmd.go | 13 +++-- pkg/cmd/grafana/apiserver/server.go | 9 +++- pkg/registry/apis/datasource/README.md | 2 +- pkg/registry/apis/datasource/connections.go | 15 ++++-- pkg/registry/apis/datasource/register.go | 12 ++--- pkg/registry/apis/datasource/standalone.go | 51 +++++++++++++++++++ pkg/registry/apis/example/register.go | 2 +- .../grafana-testdata-datasource/testdata.go | 9 ++++ 9 files changed, 93 insertions(+), 24 deletions(-) create mode 100644 pkg/registry/apis/datasource/standalone.go diff --git a/.vscode/launch.json b/.vscode/launch.json index d2f9c3b1584..51df62f0e34 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,14 +12,14 @@ "args": ["server", "--homepath", "${workspaceFolder}", "--packaging", "dev"] }, { - "name": "Run API Server (k8s)", + "name": "Run API Server (testdata)", "type": "go", "request": "launch", "mode": "auto", "program": "${workspaceFolder}/pkg/cmd/grafana/", "env": {}, "cwd": "${workspaceFolder}", - "args": ["apiserver", "example.grafana.app"] + "args": ["apiserver", "testdata.datasource.grafana.app"] }, { "name": "Attach to Chrome", diff --git a/pkg/cmd/grafana/apiserver/cmd.go b/pkg/cmd/grafana/apiserver/cmd.go index 175387a464b..dc35169e56e 100644 --- a/pkg/cmd/grafana/apiserver/cmd.go +++ b/pkg/cmd/grafana/apiserver/cmd.go @@ -4,18 +4,17 @@ import ( "os" "path" + "github.com/spf13/cobra" + genericapiserver "k8s.io/apiserver/pkg/server" + "k8s.io/apiserver/pkg/server/options" "k8s.io/client-go/tools/clientcmd" + "k8s.io/component-base/cli" "k8s.io/klog/v2" + aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme" "github.com/grafana/grafana/pkg/aggregator" grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver" "github.com/grafana/grafana/pkg/services/grafana-apiserver/utils" - - "github.com/spf13/cobra" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/apiserver/pkg/server/options" - "k8s.io/component-base/cli" - aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme" ) const ( @@ -34,7 +33,7 @@ func newCommandStartExampleAPIServer(o *APIServerOptions, stopCh <-chan struct{} Example: "grafana apiserver example.grafana.app", RunE: func(c *cobra.Command, args []string) error { // Load each group from the args - if err := o.LoadAPIGroupBuilders(args[1:]); err != nil { + if err := o.loadAPIGroupBuilders(args[1:]); err != nil { return err } diff --git a/pkg/cmd/grafana/apiserver/server.go b/pkg/cmd/grafana/apiserver/server.go index 3e02b573b02..9f16b12de19 100644 --- a/pkg/cmd/grafana/apiserver/server.go +++ b/pkg/cmd/grafana/apiserver/server.go @@ -12,6 +12,7 @@ import ( "k8s.io/client-go/tools/clientcmd" netutils "k8s.io/utils/net" + "github.com/grafana/grafana/pkg/registry/apis/datasource" "github.com/grafana/grafana/pkg/registry/apis/example" grafanaAPIServer "github.com/grafana/grafana/pkg/services/grafana-apiserver" "github.com/grafana/grafana/pkg/services/grafana-apiserver/utils" @@ -39,13 +40,19 @@ func newAPIServerOptions(out, errOut io.Writer) *APIServerOptions { } } -func (o *APIServerOptions) LoadAPIGroupBuilders(args []string) error { +func (o *APIServerOptions) loadAPIGroupBuilders(args []string) error { o.builders = []grafanaAPIServer.APIGroupBuilder{} for _, g := range args { switch g { // No dependencies for testing case "example.grafana.app": o.builders = append(o.builders, example.NewTestingAPIBuilder()) + case "testdata.datasource.grafana.app": + ds, err := datasource.NewStandaloneDatasource(g) + if err != nil { + return err + } + o.builders = append(o.builders, ds) default: return fmt.Errorf("unknown group: %s", g) } diff --git a/pkg/registry/apis/datasource/README.md b/pkg/registry/apis/datasource/README.md index 23b737633d3..2e08c86215a 100644 --- a/pkg/registry/apis/datasource/README.md +++ b/pkg/registry/apis/datasource/README.md @@ -2,7 +2,7 @@ Experimental! This is exploring how to expose any datasource as a k8s aggregated API server. -Unlike the other services, this will register other plugins as: +Unlike the other services, this will register datasources as: > {plugin}.datasource.grafana.app diff --git a/pkg/registry/apis/datasource/connections.go b/pkg/registry/apis/datasource/connections.go index e883d0b7c51..4b45076fd2f 100644 --- a/pkg/registry/apis/datasource/connections.go +++ b/pkg/registry/apis/datasource/connections.go @@ -7,6 +7,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" "github.com/grafana/grafana/pkg/apis" @@ -57,32 +58,38 @@ func (s *connectionAccess) ConvertToTable(ctx context.Context, object runtime.Ob } func (s *connectionAccess) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + ns := request.NamespaceValue(ctx) ds, err := s.builder.getDataSource(ctx, name) if err != nil { return nil, err } - return s.asConnection(ds), nil + return s.asConnection(ds, ns), nil } func (s *connectionAccess) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + ns := request.NamespaceValue(ctx) + if ns == "" { + // require a namespace so we do not need to support reverse mappings (yet) + return nil, fmt.Errorf("missing namespace in request URL") + } result := &v0alpha1.DataSourceConnectionList{ Items: []v0alpha1.DataSourceConnection{}, } vals, err := s.builder.getDataSources(ctx) if err == nil { for _, ds := range vals { - result.Items = append(result.Items, *s.asConnection(ds)) + result.Items = append(result.Items, *s.asConnection(ds, ns)) } } return result, err } -func (s *connectionAccess) asConnection(ds *datasources.DataSource) *v0alpha1.DataSourceConnection { +func (s *connectionAccess) asConnection(ds *datasources.DataSource, ns string) *v0alpha1.DataSourceConnection { v := &v0alpha1.DataSourceConnection{ TypeMeta: s.resourceInfo.TypeMeta(), ObjectMeta: metav1.ObjectMeta{ Name: ds.UID, - Namespace: s.builder.namespacer(ds.OrgID), + Namespace: ns, CreationTimestamp: metav1.NewTime(ds.Created), ResourceVersion: fmt.Sprintf("%d", ds.Updated.UnixMilli()), }, diff --git a/pkg/registry/apis/datasource/register.go b/pkg/registry/apis/datasource/register.go index be1d695f9f3..fea3ca3046e 100644 --- a/pkg/registry/apis/datasource/register.go +++ b/pkg/registry/apis/datasource/register.go @@ -37,12 +37,11 @@ var _ grafanaapiserver.APIGroupBuilder = (*DataSourceAPIBuilder)(nil) type DataSourceAPIBuilder struct { connectionResourceInfo apis.ResourceInfo - plugin pluginstore.Plugin + plugin plugins.JSONData client plugins.Client dsService datasources.DataSourceService dsCache datasources.CacheService accessControl accesscontrol.AccessControl - namespacer request.NamespaceMapper } func RegisterAPIService( @@ -67,13 +66,12 @@ func RegisterAPIService( "grafana-testdata-datasource", } - namespacer := request.GetNamespaceMapper(cfg) for _, ds := range all { if !slices.Contains(ids, ds.ID) { continue // skip this one } - builder, err = NewDataSourceAPIBuilder(ds, pluginClient, dsService, dsCache, accessControl, namespacer) + builder, err = NewDataSourceAPIBuilder(ds.JSONData, pluginClient, dsService, dsCache, accessControl) if err != nil { return nil, err } @@ -83,12 +81,11 @@ func RegisterAPIService( } func NewDataSourceAPIBuilder( - plugin pluginstore.Plugin, + plugin plugins.JSONData, client plugins.Client, dsService datasources.DataSourceService, dsCache datasources.CacheService, - accessControl accesscontrol.AccessControl, - namespacer request.NamespaceMapper) (*DataSourceAPIBuilder, error) { + accessControl accesscontrol.AccessControl) (*DataSourceAPIBuilder, error) { group, err := getDatasourceGroupNameFromPluginID(plugin.ID) if err != nil { return nil, err @@ -100,7 +97,6 @@ func NewDataSourceAPIBuilder( dsService: dsService, dsCache: dsCache, accessControl: accessControl, - namespacer: namespacer, }, nil } diff --git a/pkg/registry/apis/datasource/standalone.go b/pkg/registry/apis/datasource/standalone.go new file mode 100644 index 00000000000..d14e5d24826 --- /dev/null +++ b/pkg/registry/apis/datasource/standalone.go @@ -0,0 +1,51 @@ +package datasource + +import ( + "fmt" + "time" + + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" + "github.com/grafana/grafana/pkg/services/datasources" + fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes" + testdatasource "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource" +) + +// This is a helper function to create a new datasource API server for a group +// This currently has no dependencies and only works for testdata. In future iterations +// this will include here (or elsewhere) versions that can load config from HG api or +// the remote SQL directly +func NewStandaloneDatasource(group string) (*DataSourceAPIBuilder, error) { + if group != "testdata.datasource.grafana.app" { + return nil, fmt.Errorf("only testadata is currently supported") + } + + orgId := int64(1) + pluginId := "grafana-testdata-datasource" + now := time.Now() + dss := []*datasources.DataSource{ + { + OrgID: orgId, // default -- used in the list command + Type: pluginId, + UID: "builtin", // fake for now + Created: now, + Updated: now, + Name: "Testdata (builtin)", + }, + { + OrgID: orgId, // default -- used in the list command + Type: pluginId, + UID: "PD8C576611E62080A", // match the gdev version + Created: now, + Updated: now, + Name: "gdev-testdata", + }, + } + return NewDataSourceAPIBuilder( + plugins.JSONData{ID: pluginId}, testdatasource.ProvideService(), + &fakeDatasources.FakeDataSourceService{DataSources: dss}, + &fakeDatasources.FakeCacheService{DataSources: dss}, + // Always allow... but currently not called in standalone! + &actest.FakeAccessControl{ExpectedEvaluate: true}, + ) +} diff --git a/pkg/registry/apis/example/register.go b/pkg/registry/apis/example/register.go index ef6cccc4b99..bd1277615a1 100644 --- a/pkg/registry/apis/example/register.go +++ b/pkg/registry/apis/example/register.go @@ -45,7 +45,7 @@ func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration gra return nil // skip registration unless opting into experimental apis } builder := NewTestingAPIBuilder() - apiregistration.RegisterAPI(NewTestingAPIBuilder()) + apiregistration.RegisterAPI(builder) return builder } diff --git a/pkg/tsdb/grafana-testdata-datasource/testdata.go b/pkg/tsdb/grafana-testdata-datasource/testdata.go index 8c994491358..e0f6bfc7a46 100644 --- a/pkg/tsdb/grafana-testdata-datasource/testdata.go +++ b/pkg/tsdb/grafana-testdata-datasource/testdata.go @@ -9,9 +9,13 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter" "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource/sims" ) +// ensures that testdata implements all client functions +// var _ plugins.Client = &Service{} + func ProvideService() *Service { s := &Service{ queryMux: datasource.NewQueryTypeMux(), @@ -66,3 +70,8 @@ func (s *Service) CheckHealth(_ context.Context, _ *backend.CheckHealthRequest) Message: "Data source is working", }, nil } + +// CollectMetricsHandler handles metric collection. +func (s *Service) CollectMetrics(ctx context.Context, req *backend.CollectMetricsRequest) (*backend.CollectMetricsResult, error) { + return nil, nil +}