From 810d14d88faae97d8fa4a6aad821ae6d015fe193 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 1 Feb 2024 22:06:28 -0800 Subject: [PATCH] K8s: use --runtime-config={group}/{version}=true to enabled APIs (#81614) --- pkg/cmd/grafana/apiserver/cmd.go | 52 +++++++++++++++++++++++- pkg/cmd/grafana/apiserver/cmd_test.go | 22 ++++++++++ pkg/cmd/grafana/apiserver/server.go | 18 ++++---- pkg/registry/apis/datasource/register.go | 8 +++- 4 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 pkg/cmd/grafana/apiserver/cmd_test.go diff --git a/pkg/cmd/grafana/apiserver/cmd.go b/pkg/cmd/grafana/apiserver/cmd.go index d24829dba19..50f6ee979bf 100644 --- a/pkg/cmd/grafana/apiserver/cmd.go +++ b/pkg/cmd/grafana/apiserver/cmd.go @@ -1,7 +1,9 @@ package apiserver import ( + "fmt" "os" + "strings" "github.com/spf13/cobra" genericapiserver "k8s.io/apiserver/pkg/server" @@ -13,6 +15,7 @@ import ( 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 := "" cmd := &cobra.Command{ Use: "apiserver [api group(s)]", @@ -21,8 +24,13 @@ 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) + if err != nil { + return err + } + // Load each group from the args - if err := o.loadAPIGroupBuilders(args[1:]); err != nil { + if err := o.loadAPIGroupBuilders(apis); err != nil { return err } @@ -43,6 +51,8 @@ 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.") + // Register standard k8s flags with the command line o.RecommendedOptions = options.NewRecommendedOptions( defaultEtcdPathPrefix, @@ -61,3 +71,43 @@ 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: +// +// /=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 +} diff --git a/pkg/cmd/grafana/apiserver/cmd_test.go b/pkg/cmd/grafana/apiserver/cmd_test.go new file mode 100644 index 00000000000..bf9b4b920c7 --- /dev/null +++ b/pkg/cmd/grafana/apiserver/cmd_test.go @@ -0,0 +1,22 @@ +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) +} diff --git a/pkg/cmd/grafana/apiserver/server.go b/pkg/cmd/grafana/apiserver/server.go index cd36da5abda..ed9c4df6946 100644 --- a/pkg/cmd/grafana/apiserver/server.go +++ b/pkg/cmd/grafana/apiserver/server.go @@ -47,11 +47,15 @@ func newAPIServerOptions(out, errOut io.Writer) *APIServerOptions { } } -func (o *APIServerOptions) loadAPIGroupBuilders(args []string) error { +func (o *APIServerOptions) loadAPIGroupBuilders(runtime []apiConfig) error { o.builders = []builder.APIGroupBuilder{} - for _, g := range args { - switch g { - // No dependencies for testing + 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 @@ -69,18 +73,18 @@ func (o *APIServerOptions) loadAPIGroupBuilders(args []string) error { ), ) case "testdata.datasource.grafana.app": - ds, err := server.InitializeDataSourceAPIServer(g) + ds, err := server.InitializeDataSourceAPIServer(gv.group) if err != nil { return err } o.builders = append(o.builders, ds) default: - return fmt.Errorf("unknown group: %s", g) + return fmt.Errorf("unsupported runtime-config: %v", gv) } } if len(o.builders) < 1 { - return fmt.Errorf("expected group name(s) in the command line arguments") + return fmt.Errorf("no apis matched ") } // Install schemas diff --git a/pkg/registry/apis/datasource/register.go b/pkg/registry/apis/datasource/register.go index b5069a13cef..b16e9987d4a 100644 --- a/pkg/registry/apis/datasource/register.go +++ b/pkg/registry/apis/datasource/register.go @@ -210,7 +210,13 @@ func (b *DataSourceAPIBuilder) getPluginContext(ctx context.Context, uid string) } func (b *DataSourceAPIBuilder) GetOpenAPIDefinitions() openapi.GetOpenAPIDefinitions { - return v0alpha1.GetOpenAPIDefinitions + return func(ref openapi.ReferenceCallback) map[string]openapi.OpenAPIDefinition { + defs := query.GetOpenAPIDefinitions(ref) // required when running standalone + for k, v := range v0alpha1.GetOpenAPIDefinitions(ref) { + defs[k] = v + } + return defs + } } // Register additional routes with the server