mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
K8s: Refactor standalone apiserver initialization (#81932)
This commit is contained in:
parent
abaed01d7e
commit
91754bcda5
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@ -19,17 +19,9 @@
|
||||
"program": "${workspaceFolder}/pkg/cmd/grafana/",
|
||||
"env": {},
|
||||
"cwd": "${workspaceFolder}",
|
||||
"args": ["apiserver", "--secure-port=8443", "testdata.datasource.grafana.app"]
|
||||
},
|
||||
{
|
||||
"name": "Run API Server (aggregator)",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/pkg/cmd/grafana/",
|
||||
"env": {},
|
||||
"cwd": "${workspaceFolder}",
|
||||
"args": ["aggregator", "--secure-port", "8443"]
|
||||
"args": ["apiserver",
|
||||
"--secure-port=8443",
|
||||
"--runtime-config=testdata.datasource.grafana.app/v0alpha1=true"]
|
||||
},
|
||||
{
|
||||
"name": "Attach to Chrome",
|
||||
|
@ -1,22 +1,28 @@
|
||||
package apiserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/apiserver/pkg/server/options"
|
||||
"k8s.io/component-base/cli"
|
||||
|
||||
"github.com/grafana/grafana/pkg/server"
|
||||
grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/standalone"
|
||||
)
|
||||
|
||||
func newCommandStartExampleAPIServer(o *APIServerOptions, stopCh <-chan struct{}) *cobra.Command {
|
||||
devAcknowledgementNotice := "The apiserver command is in heavy development. The entire setup is subject to change without notice"
|
||||
runtimeConfig := ""
|
||||
|
||||
factory, err := server.InitializeAPIServerFactory()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
o.factory = factory
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "apiserver [api group(s)]",
|
||||
Short: "Run the grafana apiserver",
|
||||
@ -24,7 +30,11 @@ func newCommandStartExampleAPIServer(o *APIServerOptions, stopCh <-chan struct{}
|
||||
devAcknowledgementNotice,
|
||||
Example: "grafana apiserver example.grafana.app",
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
apis, err := readRuntimeConfig(runtimeConfig)
|
||||
runtime, err := standalone.ReadRuntimeConfig(runtimeConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apis, err := o.factory.GetEnabled(runtime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -52,6 +62,7 @@ func newCommandStartExampleAPIServer(o *APIServerOptions, stopCh <-chan struct{}
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&runtimeConfig, "runtime-config", "", "A set of key=value pairs that enable or disable built-in APIs.")
|
||||
o.factory.InitFlags(cmd.Flags())
|
||||
|
||||
// Register standard k8s flags with the command line
|
||||
o.RecommendedOptions = options.NewRecommendedOptions(
|
||||
@ -71,43 +82,3 @@ func RunCLI() int {
|
||||
|
||||
return cli.Run(cmd)
|
||||
}
|
||||
|
||||
type apiConfig struct {
|
||||
group string
|
||||
version string
|
||||
enabled bool
|
||||
}
|
||||
|
||||
func (a apiConfig) String() string {
|
||||
return fmt.Sprintf("%s/%s=%v", a.group, a.version, a.enabled)
|
||||
}
|
||||
|
||||
// Supported options are:
|
||||
//
|
||||
// <group>/<version>=true|false for a specific API group and version (e.g. dashboards.grafana.app/v0alpha1=true)
|
||||
// api/all=true|false controls all API versions
|
||||
// api/ga=true|false controls all API versions of the form v[0-9]+
|
||||
// api/beta=true|false controls all API versions of the form v[0-9]+beta[0-9]+
|
||||
// api/alpha=true|false controls all API versions of the form v[0-9]+alpha[0-9]+`)
|
||||
//
|
||||
// See: https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/
|
||||
func readRuntimeConfig(cfg string) ([]apiConfig, error) {
|
||||
if cfg == "" {
|
||||
return nil, fmt.Errorf("missing --runtime-config={apiservers}")
|
||||
}
|
||||
parts := strings.Split(cfg, ",")
|
||||
apis := make([]apiConfig, len(parts))
|
||||
for i, part := range parts {
|
||||
idx0 := strings.Index(part, "/")
|
||||
idx1 := strings.LastIndex(part, "=")
|
||||
if idx1 < idx0 || idx0 < 0 {
|
||||
return nil, fmt.Errorf("expected values in the form: group/version=true")
|
||||
}
|
||||
apis[i] = apiConfig{
|
||||
group: part[:idx0],
|
||||
version: part[idx0+1 : idx1],
|
||||
enabled: part[idx1+1:] == "true",
|
||||
}
|
||||
}
|
||||
return apis, nil
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
package apiserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAdminAPIEndpoint(t *testing.T) {
|
||||
out, err := readRuntimeConfig("all/all=true,dashboards.grafana.app/v0alpha1=false")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []apiConfig{
|
||||
{group: "all", version: "all", enabled: true},
|
||||
{group: "dashboards.grafana.app", version: "v0alpha1", enabled: false},
|
||||
}, out)
|
||||
require.Equal(t, "all/all=true", fmt.Sprintf("%v", out[0]))
|
||||
|
||||
// Empty is an error
|
||||
_, err = readRuntimeConfig("")
|
||||
require.Error(t, err)
|
||||
}
|
@ -6,23 +6,17 @@ import (
|
||||
"net"
|
||||
"path"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/apiserver/pkg/server/options"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
netutils "k8s.io/utils/net"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry/apis/example"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/featuretoggle"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/query"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/query/runner"
|
||||
"github.com/grafana/grafana/pkg/server"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
grafanaAPIServer "github.com/grafana/grafana/pkg/services/apiserver"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/standalone"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/utils"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -32,6 +26,7 @@ const (
|
||||
|
||||
// APIServerOptions contains the state for the apiserver
|
||||
type APIServerOptions struct {
|
||||
factory standalone.APIServerFactory
|
||||
builders []builder.APIGroupBuilder
|
||||
RecommendedOptions *options.RecommendedOptions
|
||||
AlternateDNS []string
|
||||
@ -47,40 +42,14 @@ func newAPIServerOptions(out, errOut io.Writer) *APIServerOptions {
|
||||
}
|
||||
}
|
||||
|
||||
func (o *APIServerOptions) loadAPIGroupBuilders(runtime []apiConfig) error {
|
||||
func (o *APIServerOptions) loadAPIGroupBuilders(apis []schema.GroupVersion) error {
|
||||
o.builders = []builder.APIGroupBuilder{}
|
||||
for _, gv := range runtime {
|
||||
if !gv.enabled {
|
||||
return fmt.Errorf("disabling apis is not yet supported")
|
||||
}
|
||||
switch gv.group {
|
||||
case "all":
|
||||
return fmt.Errorf("managing all APIs is not yet supported")
|
||||
case "example.grafana.app":
|
||||
o.builders = append(o.builders, example.NewTestingAPIBuilder())
|
||||
// Only works with testdata
|
||||
case "query.grafana.app":
|
||||
o.builders = append(o.builders, query.NewQueryAPIBuilder(
|
||||
featuremgmt.WithFeatures(),
|
||||
runner.NewDummyTestRunner(),
|
||||
runner.NewDummyRegistry(),
|
||||
))
|
||||
case "featuretoggle.grafana.app":
|
||||
o.builders = append(o.builders,
|
||||
featuretoggle.NewFeatureFlagAPIBuilder(
|
||||
featuremgmt.WithFeatureManager(setting.FeatureMgmtSettings{}, nil), // none... for now
|
||||
&actest.FakeAccessControl{ExpectedEvaluate: false},
|
||||
),
|
||||
)
|
||||
case "testdata.datasource.grafana.app":
|
||||
ds, err := server.InitializeDataSourceAPIServer(gv.group)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.builders = append(o.builders, ds)
|
||||
default:
|
||||
return fmt.Errorf("unsupported runtime-config: %v", gv)
|
||||
for _, gv := range apis {
|
||||
api, err := o.factory.MakeAPIServer(gv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.builders = append(o.builders, api)
|
||||
}
|
||||
|
||||
if len(o.builders) < 1 {
|
||||
|
@ -1,127 +0,0 @@
|
||||
package datasource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
testdatasource "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource"
|
||||
)
|
||||
|
||||
// NewTestDataAPIServer is a helper function to create a new datasource API server for a group.
|
||||
// This currently builds its dependencies manually and only works for testdata.
|
||||
func NewTestDataAPIServer(group string) (*DataSourceAPIBuilder, error) {
|
||||
pluginID := "grafana-testdata-datasource"
|
||||
features := featuremgmt.WithFeatures() // None for now!
|
||||
|
||||
if group != "testdata.datasource.grafana.app" {
|
||||
return nil, fmt.Errorf("only %s is currently supported", pluginID)
|
||||
}
|
||||
|
||||
// Run standalone with zero dependencies
|
||||
if true {
|
||||
return NewDataSourceAPIBuilder(
|
||||
plugins.JSONData{
|
||||
ID: pluginID,
|
||||
},
|
||||
testdatasource.ProvideService(), // the client
|
||||
&pluginDatasourceImpl{
|
||||
startup: v1.Now(),
|
||||
},
|
||||
&pluginDatasourceImpl{}, // stub
|
||||
&actest.FakeAccessControl{ExpectedEvaluate: true},
|
||||
)
|
||||
}
|
||||
|
||||
// Otherwise manually wire up access to testdata
|
||||
cfg, err := setting.NewCfgFromArgs(setting.CommandLineArgs{
|
||||
// TODO: Add support for args?
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accessControl, pluginStore, dsService, dsCache, err := apiBuilderServices(cfg, features, pluginID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
td, exists := pluginStore.Plugin(context.Background(), pluginID)
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("plugin %s not found", pluginID)
|
||||
}
|
||||
|
||||
return NewDataSourceAPIBuilder(
|
||||
td.JSONData,
|
||||
testdatasource.ProvideService(), // the client
|
||||
&defaultPluginDatasourceProvider{
|
||||
dsService: dsService,
|
||||
dsCache: dsCache,
|
||||
},
|
||||
&pluginDatasourceImpl{}, // stub
|
||||
accessControl,
|
||||
)
|
||||
}
|
||||
|
||||
// Simple stub for standalone testing
|
||||
type pluginDatasourceImpl struct {
|
||||
startup v1.Time
|
||||
}
|
||||
|
||||
var (
|
||||
_ PluginDatasourceProvider = (*pluginDatasourceImpl)(nil)
|
||||
)
|
||||
|
||||
// Get implements PluginDatasourceProvider.
|
||||
func (p *pluginDatasourceImpl) Get(ctx context.Context, pluginID string, uid string) (*v0alpha1.DataSourceConnection, error) {
|
||||
all, err := p.List(ctx, pluginID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for idx, v := range all.Items {
|
||||
if v.Name == uid {
|
||||
return &all.Items[idx], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
// List implements PluginConfigProvider.
|
||||
func (p *pluginDatasourceImpl) List(ctx context.Context, pluginID string) (*v0alpha1.DataSourceConnectionList, error) {
|
||||
info, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v0alpha1.DataSourceConnectionList{
|
||||
TypeMeta: v0alpha1.GenericConnectionResourceInfo.TypeMeta(),
|
||||
Items: []v0alpha1.DataSourceConnection{
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "PD8C576611E62080A",
|
||||
Namespace: info.Value, // the raw namespace value
|
||||
CreationTimestamp: p.startup,
|
||||
},
|
||||
Title: "gdev-testdata",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PluginContextForDataSource implements PluginConfigProvider.
|
||||
func (*pluginDatasourceImpl) GetInstanceSettings(ctx context.Context, pluginID, uid string) (*backend.DataSourceInstanceSettings, error) {
|
||||
return &backend.DataSourceInstanceSettings{}, nil
|
||||
}
|
||||
|
||||
// PluginContextWrapper
|
||||
func (*pluginDatasourceImpl) PluginContextForDataSource(ctx context.Context, datasourceSettings *backend.DataSourceInstanceSettings) (backend.PluginContext, error) {
|
||||
return backend.PluginContext{DataSourceInstanceSettings: datasourceSettings}, nil
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
package datasource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/kvstore"
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats/service"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
pCfg "github.com/grafana/grafana/pkg/plugins/config"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/pipeline/bootstrap"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/pipeline/discovery"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/pipeline/initialization"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/pipeline/termination"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/pipeline/validation"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/sources"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/datasources/guardian"
|
||||
datasourceService "github.com/grafana/grafana/pkg/services/datasources/service"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/provider"
|
||||
encryptionService "github.com/grafana/grafana/pkg/services/encryption/service"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/kmsproviders/osskmsproviders"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/config"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/database"
|
||||
kvstoreService "github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
|
||||
"github.com/grafana/grafana/pkg/services/supportbundles/bundleregistry"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamimpl"
|
||||
"github.com/grafana/grafana/pkg/services/user/userimpl"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func apiBuilderServices(cfg *setting.Cfg, features featuremgmt.FeatureToggles, pluginID string) (
|
||||
*acimpl.AccessControl,
|
||||
*pluginstore.Service,
|
||||
*datasourceService.Service,
|
||||
*datasourceService.CacheServiceImpl,
|
||||
error,
|
||||
) {
|
||||
accessControl := acimpl.ProvideAccessControl(cfg)
|
||||
cacheService := localcache.ProvideService()
|
||||
tracingService, err := tracing.ProvideService(cfg)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
routeRegisterImpl := routing.ProvideRegister()
|
||||
featureManager, err := featuremgmt.ProvideManagerService(cfg)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
inProcBus := bus.ProvideBus(tracingService)
|
||||
ossMigrations := migrations.ProvideOSSMigrations(features)
|
||||
sqlStore, err := sqlstore.ProvideService(cfg, features, ossMigrations, inProcBus, tracingService)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
kvStore := kvstore.ProvideService(sqlStore)
|
||||
featureToggles := featuremgmt.ProvideToggles(featureManager)
|
||||
bundleRegistry := bundleregistry.ProvideService()
|
||||
|
||||
quota := quotaimpl.ProvideService(sqlStore, cfg)
|
||||
orgService, err := orgimpl.ProvideService(sqlStore, cfg, quota)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
teamService := teamimpl.ProvideService(sqlStore, cfg)
|
||||
userService, err := userimpl.ProvideService(sqlStore, orgService, cfg, teamService, cacheService, quota, bundleRegistry)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
acimplService, err := acimpl.ProvideService(cfg, sqlStore, routeRegisterImpl, cacheService, accessControl, userService, featureToggles)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
usageStats, err := service.ProvideService(cfg, kvStore, routeRegisterImpl, tracingService, accessControl, acimplService, bundleRegistry)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
secretsStoreImpl := database.ProvideSecretsStore(sqlStore)
|
||||
providerProvider := provider.ProvideEncryptionProvider()
|
||||
serviceService, err := encryptionService.ProvideEncryptionService(providerProvider, usageStats, cfg)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
kmsProviders := osskmsproviders.ProvideService(serviceService, cfg, featureToggles)
|
||||
secretsService, err := manager.ProvideSecretsService(secretsStoreImpl, kmsProviders, serviceService, cfg, featureToggles, usageStats)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
ossImpl := setting.ProvideProvider(cfg)
|
||||
pluginCfg, err := config.ProvideConfig(ossImpl, cfg, featureToggles)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
pluginRegistry := registry.ProvideService()
|
||||
quotaService := quotaimpl.ProvideService(sqlStore, cfg)
|
||||
pluginLoader, err := createLoader(pluginCfg, pluginRegistry)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
pluginStore, err := pluginstore.ProvideService(pluginRegistry, newPluginSource(cfg, pluginID), pluginLoader)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
secretsKVStore, err := kvstoreService.ProvideService(sqlStore, secretsService, pluginStore, kvStore, featureToggles, cfg)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
dsPermissionsService := ossaccesscontrol.ProvideDatasourcePermissionsService()
|
||||
dsService, err := datasourceService.ProvideService(sqlStore, secretsService, secretsKVStore, cfg, featureToggles, accessControl, dsPermissionsService, quotaService, pluginStore)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
ossProvider := guardian.ProvideGuardian()
|
||||
cacheServiceImpl := datasourceService.ProvideCacheService(cacheService, sqlStore, ossProvider)
|
||||
|
||||
return accessControl, pluginStore, dsService, cacheServiceImpl, nil
|
||||
}
|
||||
|
||||
var _ sources.Registry = (*pluginSource)(nil)
|
||||
|
||||
type pluginSource struct {
|
||||
cfg *setting.Cfg
|
||||
pluginID string
|
||||
}
|
||||
|
||||
func newPluginSource(cfg *setting.Cfg, pluginID string) *pluginSource {
|
||||
return &pluginSource{
|
||||
cfg: cfg,
|
||||
pluginID: pluginID,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *pluginSource) List(_ context.Context) []plugins.PluginSource {
|
||||
p := filepath.Join(t.cfg.StaticRootPath, "app/plugins/datasource", t.pluginID)
|
||||
return []plugins.PluginSource{sources.NewLocalSource(plugins.ClassCore, []string{p})}
|
||||
}
|
||||
|
||||
func createLoader(cfg *pCfg.Cfg, pr registry.Service) (loader.Service, error) {
|
||||
d := discovery.New(cfg, discovery.Opts{
|
||||
FindFilterFuncs: []discovery.FindFilterFunc{
|
||||
func(ctx context.Context, _ plugins.Class, b []*plugins.FoundBundle) ([]*plugins.FoundBundle, error) {
|
||||
return discovery.NewDuplicatePluginFilterStep(pr).Filter(ctx, b)
|
||||
},
|
||||
},
|
||||
})
|
||||
b := bootstrap.New(cfg, bootstrap.Opts{
|
||||
DecorateFuncs: []bootstrap.DecorateFunc{}, // no decoration required
|
||||
})
|
||||
v := validation.New(cfg, validation.Opts{
|
||||
ValidateFuncs: []validation.ValidateFunc{
|
||||
validation.SignatureValidationStep(signature.NewValidator(signature.NewUnsignedAuthorizer(cfg))),
|
||||
},
|
||||
})
|
||||
i := initialization.New(cfg, initialization.Opts{
|
||||
InitializeFuncs: []initialization.InitializeFunc{
|
||||
initialization.PluginRegistrationStep(pr),
|
||||
},
|
||||
})
|
||||
t, err := termination.New(cfg, termination.Opts{
|
||||
TerminateFuncs: []termination.TerminateFunc{
|
||||
termination.DeregisterStep(pr),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return loader.New(d, b, v, i, t), nil
|
||||
}
|
@ -35,7 +35,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/middleware/csrf"
|
||||
"github.com/grafana/grafana/pkg/middleware/loggermw"
|
||||
apiregistry "github.com/grafana/grafana/pkg/registry/apis"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/datasource"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
||||
@ -45,6 +44,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/anonymous/anonimpl/anonstore"
|
||||
"github.com/grafana/grafana/pkg/services/apikey/apikeyimpl"
|
||||
grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/standalone"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/auth/idimpl"
|
||||
"github.com/grafana/grafana/pkg/services/auth/jwt"
|
||||
@ -465,7 +465,8 @@ func InitializeModuleServer(cfg *setting.Cfg, opts Options, apiOpts api.ServerOp
|
||||
return &ModuleServer{}, nil
|
||||
}
|
||||
|
||||
func InitializeDataSourceAPIServer(group string) (*datasource.DataSourceAPIBuilder, error) {
|
||||
wire.Build(wireExtsDataSourceApiServerSet)
|
||||
return &datasource.DataSourceAPIBuilder{}, nil
|
||||
// Initialize the standalone APIServer factory
|
||||
func InitializeAPIServerFactory() (standalone.APIServerFactory, error) {
|
||||
wire.Build(wireExtsStandaloneAPIServerSet)
|
||||
return &standalone.DummyAPIFactory{}, nil // Wire will replace this with a real interface
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/datasource"
|
||||
"github.com/grafana/grafana/pkg/registry/backgroundsvcs"
|
||||
"github.com/grafana/grafana/pkg/registry/usagestatssvcs"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
@ -19,6 +18,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/anonymous"
|
||||
"github.com/grafana/grafana/pkg/services/anonymous/anonimpl"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/standalone"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/auth/authimpl"
|
||||
"github.com/grafana/grafana/pkg/services/auth/idimpl"
|
||||
@ -139,6 +139,6 @@ var wireExtsModuleServerSet = wire.NewSet(
|
||||
wireExtsBaseCLISet,
|
||||
)
|
||||
|
||||
var wireExtsDataSourceApiServerSet = wire.NewSet(
|
||||
datasource.NewTestDataAPIServer,
|
||||
var wireExtsStandaloneAPIServerSet = wire.NewSet(
|
||||
standalone.GetDummyAPIFactory,
|
||||
)
|
||||
|
154
pkg/services/apiserver/standalone/factory.go
Normal file
154
pkg/services/apiserver/standalone/factory.go
Normal file
@ -0,0 +1,154 @@
|
||||
package standalone
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/spf13/pflag"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/datasource"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/example"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/featuretoggle"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/query"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/query/runner"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
testdatasource "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource"
|
||||
)
|
||||
|
||||
type APIServerFactory interface {
|
||||
// Called before the groups are loaded so any custom command can be registered
|
||||
InitFlags(flags *pflag.FlagSet)
|
||||
|
||||
// Given the flags, what can we produce
|
||||
GetEnabled(runtime []RuntimeConfig) ([]schema.GroupVersion, error)
|
||||
|
||||
// Make an API server for a given group+version
|
||||
MakeAPIServer(gv schema.GroupVersion) (builder.APIGroupBuilder, error)
|
||||
}
|
||||
|
||||
// Zero dependency provider for testing
|
||||
func GetDummyAPIFactory() APIServerFactory {
|
||||
return &DummyAPIFactory{}
|
||||
}
|
||||
|
||||
type DummyAPIFactory struct{}
|
||||
|
||||
func (p *DummyAPIFactory) InitFlags(flags *pflag.FlagSet) {}
|
||||
|
||||
func (p *DummyAPIFactory) GetEnabled(runtime []RuntimeConfig) ([]schema.GroupVersion, error) {
|
||||
gv := []schema.GroupVersion{}
|
||||
for _, cfg := range runtime {
|
||||
if !cfg.Enabled {
|
||||
return nil, fmt.Errorf("only enabled supported now")
|
||||
}
|
||||
if cfg.Group == "all" {
|
||||
return nil, fmt.Errorf("all not yet supported")
|
||||
}
|
||||
gv = append(gv, schema.GroupVersion{Group: cfg.Group, Version: cfg.Version})
|
||||
}
|
||||
return gv, nil
|
||||
}
|
||||
|
||||
func (p *DummyAPIFactory) MakeAPIServer(gv schema.GroupVersion) (builder.APIGroupBuilder, error) {
|
||||
if gv.Version != "v0alpha1" {
|
||||
return nil, fmt.Errorf("only alpha supported now")
|
||||
}
|
||||
|
||||
switch gv.Group {
|
||||
case "example.grafana.app":
|
||||
return example.NewTestingAPIBuilder(), nil
|
||||
|
||||
// Only works with testdata
|
||||
case "query.grafana.app":
|
||||
return query.NewQueryAPIBuilder(
|
||||
featuremgmt.WithFeatures(),
|
||||
runner.NewDummyTestRunner(),
|
||||
runner.NewDummyRegistry(),
|
||||
), nil
|
||||
|
||||
case "featuretoggle.grafana.app":
|
||||
return featuretoggle.NewFeatureFlagAPIBuilder(
|
||||
featuremgmt.WithFeatureManager(setting.FeatureMgmtSettings{}, nil), // none... for now
|
||||
&actest.FakeAccessControl{ExpectedEvaluate: false},
|
||||
), nil
|
||||
|
||||
case "testdata.datasource.grafana.app":
|
||||
return datasource.NewDataSourceAPIBuilder(
|
||||
plugins.JSONData{
|
||||
ID: "grafana-testdata-datasource",
|
||||
},
|
||||
testdatasource.ProvideService(), // the client
|
||||
&pluginDatasourceImpl{
|
||||
startup: v1.Now(),
|
||||
},
|
||||
&pluginDatasourceImpl{}, // stub
|
||||
&actest.FakeAccessControl{ExpectedEvaluate: true},
|
||||
)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unsupported group")
|
||||
}
|
||||
|
||||
// Simple stub for standalone datasource testing
|
||||
type pluginDatasourceImpl struct {
|
||||
startup v1.Time
|
||||
}
|
||||
|
||||
var (
|
||||
_ datasource.PluginDatasourceProvider = (*pluginDatasourceImpl)(nil)
|
||||
)
|
||||
|
||||
// Get implements PluginDatasourceProvider.
|
||||
func (p *pluginDatasourceImpl) Get(ctx context.Context, pluginID string, uid string) (*v0alpha1.DataSourceConnection, error) {
|
||||
all, err := p.List(ctx, pluginID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for idx, v := range all.Items {
|
||||
if v.Name == uid {
|
||||
return &all.Items[idx], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
// List implements PluginConfigProvider.
|
||||
func (p *pluginDatasourceImpl) List(ctx context.Context, pluginID string) (*v0alpha1.DataSourceConnectionList, error) {
|
||||
info, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v0alpha1.DataSourceConnectionList{
|
||||
TypeMeta: v0alpha1.GenericConnectionResourceInfo.TypeMeta(),
|
||||
Items: []v0alpha1.DataSourceConnection{
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "PD8C576611E62080A",
|
||||
Namespace: info.Value, // the raw namespace value
|
||||
CreationTimestamp: p.startup,
|
||||
},
|
||||
Title: "gdev-testdata",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PluginContextForDataSource implements PluginConfigProvider.
|
||||
func (*pluginDatasourceImpl) GetInstanceSettings(ctx context.Context, pluginID, uid string) (*backend.DataSourceInstanceSettings, error) {
|
||||
return &backend.DataSourceInstanceSettings{}, nil
|
||||
}
|
||||
|
||||
// PluginContextWrapper
|
||||
func (*pluginDatasourceImpl) PluginContextForDataSource(ctx context.Context, datasourceSettings *backend.DataSourceInstanceSettings) (backend.PluginContext, error) {
|
||||
return backend.PluginContext{DataSourceInstanceSettings: datasourceSettings}, nil
|
||||
}
|
46
pkg/services/apiserver/standalone/runtime.go
Normal file
46
pkg/services/apiserver/standalone/runtime.go
Normal file
@ -0,0 +1,46 @@
|
||||
package standalone
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RuntimeConfig struct {
|
||||
Group string
|
||||
Version string
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
func (a RuntimeConfig) String() string {
|
||||
return fmt.Sprintf("%s/%s=%v", a.Group, a.Version, a.Enabled)
|
||||
}
|
||||
|
||||
// Supported options are:
|
||||
//
|
||||
// <group>/<version>=true|false for a specific API group and version (e.g. dashboards.grafana.app/v0alpha1=true)
|
||||
// api/all=true|false controls all API versions
|
||||
// api/ga=true|false controls all API versions of the form v[0-9]+
|
||||
// api/beta=true|false controls all API versions of the form v[0-9]+beta[0-9]+
|
||||
// api/alpha=true|false controls all API versions of the form v[0-9]+alpha[0-9]+`)
|
||||
//
|
||||
// See: https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/
|
||||
func ReadRuntimeConfig(cfg string) ([]RuntimeConfig, error) {
|
||||
if cfg == "" {
|
||||
return nil, fmt.Errorf("missing --runtime-config={apiservers}")
|
||||
}
|
||||
parts := strings.Split(cfg, ",")
|
||||
apis := make([]RuntimeConfig, len(parts))
|
||||
for i, part := range parts {
|
||||
idx0 := strings.Index(part, "/")
|
||||
idx1 := strings.LastIndex(part, "=")
|
||||
if idx1 < idx0 || idx0 < 0 {
|
||||
return nil, fmt.Errorf("expected values in the form: group/version=true")
|
||||
}
|
||||
apis[i] = RuntimeConfig{
|
||||
Group: part[:idx0],
|
||||
Version: part[idx0+1 : idx1],
|
||||
Enabled: part[idx1+1:] == "true",
|
||||
}
|
||||
}
|
||||
return apis, nil
|
||||
}
|
22
pkg/services/apiserver/standalone/runtime_test.go
Normal file
22
pkg/services/apiserver/standalone/runtime_test.go
Normal file
@ -0,0 +1,22 @@
|
||||
package standalone
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestReadRuntimeCOnfig(t *testing.T) {
|
||||
out, err := ReadRuntimeConfig("all/all=true,dashboards.grafana.app/v0alpha1=false")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []RuntimeConfig{
|
||||
{Group: "all", Version: "all", Enabled: true},
|
||||
{Group: "dashboards.grafana.app", Version: "v0alpha1", Enabled: false},
|
||||
}, out)
|
||||
require.Equal(t, "all/all=true", fmt.Sprintf("%v", out[0]))
|
||||
|
||||
// Empty is an error
|
||||
_, err = ReadRuntimeConfig("")
|
||||
require.Error(t, err)
|
||||
}
|
Loading…
Reference in New Issue
Block a user