mirror of https://github.com/grafana/grafana
K8s: Refactor standalone apiserver initialization (#81932)
parent
abaed01d7e
commit
91754bcda5
@ -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) |
||||
} |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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