From 439edebcd605a1b63bf3a9b0ab5c2b83341cd5cd Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 5 Dec 2023 14:31:49 -0800 Subject: [PATCH] K8s: fix standalone command and add hack scripts (#79052) Co-authored-by: Charandas Batra --- .github/CODEOWNERS | 1 + .gitignore | 3 + .vscode/launch.json | 10 ++ .../feature-toggles/index.md | 1 + hack/README.md | 25 ++++ hack/boilerplate.go.txt | 2 + hack/update-codegen.sh | 45 +++++++ .../src/types/featureToggles.gen.ts | 1 + pkg/cmd/grafana/apiserver/README.md | 33 ++++- pkg/cmd/grafana/apiserver/cmd.go | 24 +--- pkg/cmd/grafana/apiserver/server.go | 120 +++++++++++++++--- pkg/components/simplejson/simplejson.go | 18 +++ pkg/registry/apis/example/register.go | 12 +- pkg/services/featuremgmt/registry.go | 8 ++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 + .../endpoints/request/namespace.go | 2 +- pkg/services/grafana-apiserver/openapi.go | 2 +- .../grafana-apiserver/request_handler.go | 2 +- pkg/services/grafana-apiserver/service.go | 39 ++---- .../grafana-apiserver/utils/clientConfig.go | 35 +++++ 21 files changed, 307 insertions(+), 81 deletions(-) create mode 100644 hack/README.md create mode 100644 hack/boilerplate.go.txt create mode 100755 hack/update-codegen.sh create mode 100644 pkg/services/grafana-apiserver/utils/clientConfig.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 41ffe5a8816..a78c8be5cd9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -65,6 +65,7 @@ /.golangci.toml @grafana/backend-platform /build.go @grafana/backend-platform /scripts/modowners/ @grafana/backend-platform +/hack/ @grafana/grafana-app-platform-squad /pkg/api/ @grafana/backend-platform /pkg/apis/ @grafana/grafana-app-platform-squad diff --git a/.gitignore b/.gitignore index 763bef7b2be..771a993d2a7 100644 --- a/.gitignore +++ b/.gitignore @@ -80,6 +80,9 @@ public/css/*.min.css apiserver.local.config/ default.etcd/ +# kubeconfig path used by example apiserver +example-apiserver/ + # devenv /devenv/docker-compose.yaml /devenv/docker-compose.override.yaml diff --git a/.vscode/launch.json b/.vscode/launch.json index 792addee950..d2f9c3b1584 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,6 +11,16 @@ "cwd": "${workspaceFolder}", "args": ["server", "--homepath", "${workspaceFolder}", "--packaging", "dev"] }, + { + "name": "Run API Server (k8s)", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/pkg/cmd/grafana/", + "env": {}, + "cwd": "${workspaceFolder}", + "args": ["apiserver", "example.grafana.app"] + }, { "name": "Attach to Chrome", "port": 9222, diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 979ac9330ff..6b9f0821a47 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -143,6 +143,7 @@ Experimental features might be changed or removed without prior notice. | `enableNativeHTTPHistogram` | Enables native HTTP Histograms | | `formatString` | Enable format string transformer | | `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s | +| `kubernetesSnapshots` | Use the kubernetes API in the frontend to support playlists | | `recoveryThreshold` | Enables feature recovery threshold (aka hysteresis) for threshold server-side expression | | `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | | `teamHttpHeaders` | Enables datasources to apply team headers to the client requests | diff --git a/hack/README.md b/hack/README.md new file mode 100644 index 00000000000..226636e032e --- /dev/null +++ b/hack/README.md @@ -0,0 +1,25 @@ +# Kubernetes HACK Alert + +This is a hack folder for kubernetes codegen scripts. Oddly, a /hack/ folder seems to be standard kubernetes development practice ¯\_(ツ)\_/¯ + +The workflow is a WIP, however we are trying to leverage as many off-the-shelf patterns as possible. + +For these scripts to work, your local GOROOT/src/grafana/grafana must point to this git checkout. For my setup this is: + +``` +❯ pwd +/Users/ryan/go/src/github.com/grafana +❯ ls -l +total 0 +lrwxr-xr-x 1 ryan staff 37 Oct 5 09:34 grafana -> /Users/ryan/workspace/grafana/grafana +``` + +The current workflow (sorry!) is to: + +1. update the script to point to the group+version you want +2. run the `update-codegen.sh` script. This will produce a bunch of new files +3. move `pkg/generated/openapi/zz_generated.openapi.go` to `pkg/apis/{group/version}/zz_generated.openapi.go`. +4. edit the package name so it is {version} and remove the boilerplate k8s kinds +5. `rm -rf pkg/generated` -- we are not yet using most of the generated client stuff + +Once we are more comfortable with the outputs and process, we will build these steps into a more standard codegen pattern, but until then... happy hacking! diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt new file mode 100644 index 00000000000..dd609cc5c9f --- /dev/null +++ b/hack/boilerplate.go.txt @@ -0,0 +1,2 @@ +// SPDX-License-Identifier: AGPL-3.0-only + diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh new file mode 100755 index 00000000000..115b201183a --- /dev/null +++ b/hack/update-codegen.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo $GOPATH/pkg/mod/k8s.io/code-generator@v0.27.1)} + +OUTDIR="${HOME}/go/src" + +echo $OUTDIR + +CLIENTSET_NAME_VERSIONED=clientset \ +CLIENTSET_PKG_NAME=clientset \ +"${CODEGEN_PKG}/generate-groups.sh" "all" \ + github.com/grafana/grafana/pkg/generated \ + github.com/grafana/grafana/pkg/apis \ + "snapshots:v0alpha1" \ + --output-base "${OUTDIR}" \ + --go-header-file "${SCRIPT_ROOT}/hack/boilerplate.go.txt" + +CLIENTSET_NAME_VERSIONED=clientset \ +CLIENTSET_PKG_NAME=clientset \ +"${CODEGEN_PKG}/generate-internal-groups.sh" "deepcopy,defaulter,conversion,openapi" \ + github.com/grafana/grafana/pkg/generated \ + github.com/grafana/grafana/pkg/apis \ + github.com/grafana/grafana/pkg/apis \ + "snapshots:v0alpha1" \ + --output-base "${OUTDIR}" \ + --go-header-file "${SCRIPT_ROOT}/hack/boilerplate.go.txt" diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 6eb6c218fb4..54a418cca8e 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -135,6 +135,7 @@ export interface FeatureToggles { formatString?: boolean; transformationsVariableSupport?: boolean; kubernetesPlaylists?: boolean; + kubernetesSnapshots?: boolean; cloudWatchBatchQueries?: boolean; recoveryThreshold?: boolean; lokiStructuredMetadata?: boolean; diff --git a/pkg/cmd/grafana/apiserver/README.md b/pkg/cmd/grafana/apiserver/README.md index 1c13a23cc66..4e934974317 100644 --- a/pkg/cmd/grafana/apiserver/README.md +++ b/pkg/cmd/grafana/apiserver/README.md @@ -4,22 +4,45 @@ The example-apiserver closely resembles the [sample-apiserver](https://github.com/kubernetes/sample-apiserver/tree/master) project in code and thus allows the same [CLI flags](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/) as kube-apiserver. -It is currently used for testing our deployment pipelines for aggregated servers. +It is currently used for testing our deployment pipelines for aggregated servers. You can optionally omit the +aggregation path altogether and just run this example apiserver as a standalone process. -## Prerequisites: +## Standalone Mode + +### Usage + +```shell +go run ./pkg/cmd/grafana apiserver example.grafana.app \ + --secure-port 8443 +``` + +### Verify that all works + +```shell +export KUBECONFIG=./example-apiserver/kubeconfig + +kubectl api-resources +NAME SHORTNAMES APIVERSION NAMESPACED KIND +dummy example.grafana.app/v0alpha1 true DummyResource +runtime example.grafana.app/v0alpha1 false RuntimeInfo +``` + +## Aggregated Mode + +### Prerequisites: 1. kind: you will need kind (or another local K8s setup) if you want to test aggregation. ``` go install sigs.k8s.io/kind@v0.20.0 && kind create cluster ``` -## Usage +### Usage You can start the example-apiserver with an invocation as shown below. The Authn / Authz flags are set up so that the kind cluster can be used as a root server for this example-apiserver (in aggregated mode). Here, it's assumed that you have a local kind cluster and that you can provide its kubeconfig in the parameters to the example-apiserver. ```shell -go run ./pkg/cmd/grafana apiserver example.grafana.app\ +go run ./pkg/cmd/grafana apiserver example.grafana.app \ --authentication-kubeconfig ~/.kube/config \ --authorization-kubeconfig ~/.kube/config \ --kubeconfig ~/.kube/config \ @@ -35,7 +58,7 @@ kubectl deploy -k ./deploy/darwin # or /linux ``` -## Verify that all works +### Verify that all works With kubectl configured against `kind-kind` context, you can run the following: diff --git a/pkg/cmd/grafana/apiserver/cmd.go b/pkg/cmd/grafana/apiserver/cmd.go index a11628ce1e1..11cb3e4db91 100644 --- a/pkg/cmd/grafana/apiserver/cmd.go +++ b/pkg/cmd/grafana/apiserver/cmd.go @@ -1,7 +1,6 @@ package apiserver import ( - "fmt" "os" "github.com/spf13/cobra" @@ -10,9 +9,7 @@ import ( "k8s.io/component-base/cli" ) -func newCommandStartExampleAPIServer(o *ExampleServerOptions, stopCh <-chan struct{}) *cobra.Command { - // While this exists as an experimental feature, we require adding the scarry looking command line - devAcknowledgementFlag := "grafana-enable-experimental-apiserver" +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" cmd := &cobra.Command{ @@ -20,22 +17,14 @@ func newCommandStartExampleAPIServer(o *ExampleServerOptions, stopCh <-chan stru Short: "Run the grafana apiserver", Long: "Run a standalone kubernetes based apiserver that can be aggregated by a root apiserver. " + devAcknowledgementNotice, - Example: fmt.Sprintf("grafana apiserver example.grafana.app --%s", devAcknowledgementFlag), - PersistentPreRun: func(cmd *cobra.Command, args []string) { - ok, err := cmd.Flags().GetBool(devAcknowledgementFlag) - if !ok || err != nil { - fmt.Printf("requires running with the flag: --%s\n\n%s\n\n", - devAcknowledgementFlag, devAcknowledgementNotice) - os.Exit(1) - } - }, + 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 { return err } - // Finish the config (applies all defaults) + // Finish the config (a noop for now) if err := o.Complete(); err != nil { return err } @@ -45,16 +34,13 @@ func newCommandStartExampleAPIServer(o *ExampleServerOptions, stopCh <-chan stru return err } - if err := o.RunExampleServer(config, stopCh); err != nil { + if err := o.RunAPIServer(config, stopCh); err != nil { return err } return nil }, } - // Register grafana flags - cmd.PersistentFlags().Bool(devAcknowledgementFlag, false, devAcknowledgementNotice) - // Register standard k8s flags with the command line o.RecommendedOptions = options.NewRecommendedOptions( defaultEtcdPathPrefix, @@ -68,7 +54,7 @@ func newCommandStartExampleAPIServer(o *ExampleServerOptions, stopCh <-chan stru func RunCLI() int { stopCh := genericapiserver.SetupSignalHandler() - options := newExampleServerOptions(os.Stdout, os.Stderr) + options := newAPIServerOptions(os.Stdout, os.Stderr) cmd := newCommandStartExampleAPIServer(options, stopCh) return cli.Run(cmd) diff --git a/pkg/cmd/grafana/apiserver/server.go b/pkg/cmd/grafana/apiserver/server.go index a5b251f8c80..8289d342b8a 100644 --- a/pkg/cmd/grafana/apiserver/server.go +++ b/pkg/cmd/grafana/apiserver/server.go @@ -4,21 +4,30 @@ import ( "fmt" "io" "net" - - "github.com/grafana/grafana/pkg/registry/apis/example" - grafanaAPIServer "github.com/grafana/grafana/pkg/services/grafana-apiserver" + "path" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" utilerrors "k8s.io/apimachinery/pkg/util/errors" + openapinamer "k8s.io/apiserver/pkg/endpoints/openapi" genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/apiserver/pkg/server/options" + "k8s.io/apiserver/pkg/util/openapi" + "k8s.io/client-go/tools/clientcmd" netutils "k8s.io/utils/net" + + "github.com/grafana/grafana/pkg/services/grafana-apiserver/utils" + + "github.com/grafana/grafana/pkg/registry/apis/example" + grafanaAPIServer "github.com/grafana/grafana/pkg/services/grafana-apiserver" ) -const defaultEtcdPathPrefix = "/registry/example.grafana.app" +const ( + defaultEtcdPathPrefix = "/registry/grafana.app" + dataPath = "data/grafana-apiserver" // same as grafana core +) var ( Scheme = runtime.NewScheme() @@ -41,8 +50,8 @@ func init() { Scheme.AddUnversionedTypes(unversionedVersion, unversionedTypes...) } -// ExampleServerOptions contains the state for the apiserver -type ExampleServerOptions struct { +// APIServerOptions contains the state for the apiserver +type APIServerOptions struct { builders []grafanaAPIServer.APIGroupBuilder RecommendedOptions *options.RecommendedOptions AlternateDNS []string @@ -51,20 +60,20 @@ type ExampleServerOptions struct { StdErr io.Writer } -func newExampleServerOptions(out, errOut io.Writer) *ExampleServerOptions { - return &ExampleServerOptions{ +func newAPIServerOptions(out, errOut io.Writer) *APIServerOptions { + return &APIServerOptions{ StdOut: out, StdErr: errOut, } } -func (o *ExampleServerOptions) 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.TestingAPIBuilder{}) + o.builders = append(o.builders, example.NewTestingAPIBuilder()) default: return fmt.Errorf("unknown group: %s", g) } @@ -83,8 +92,49 @@ func (o *ExampleServerOptions) LoadAPIGroupBuilders(args []string) error { return nil } -func (o *ExampleServerOptions) Config() (*genericapiserver.RecommendedConfig, error) { - if err := o.RecommendedOptions.SecureServing.MaybeDefaultWithSelfSignedCerts("localhost", o.AlternateDNS, []net.IP{netutils.ParseIPSloppy("127.0.0.1")}); err != nil { +// A copy of ApplyTo in recommended.go, but for >= 0.28, server pkg in apiserver does a bit extra causing +// a panic when CoreAPI is set to nil +func (o *APIServerOptions) ModifiedApplyTo(config *genericapiserver.RecommendedConfig) error { + if err := o.RecommendedOptions.Etcd.ApplyTo(&config.Config); err != nil { + return err + } + if err := o.RecommendedOptions.EgressSelector.ApplyTo(&config.Config); err != nil { + return err + } + if err := o.RecommendedOptions.Traces.ApplyTo(config.Config.EgressSelector, &config.Config); err != nil { + return err + } + if err := o.RecommendedOptions.SecureServing.ApplyTo(&config.Config.SecureServing, &config.Config.LoopbackClientConfig); err != nil { + return err + } + if err := o.RecommendedOptions.Authentication.ApplyTo(&config.Config.Authentication, config.SecureServing, config.OpenAPIConfig); err != nil { + return err + } + if err := o.RecommendedOptions.Authorization.ApplyTo(&config.Config.Authorization); err != nil { + return err + } + if err := o.RecommendedOptions.Audit.ApplyTo(&config.Config); err != nil { + return err + } + if err := o.RecommendedOptions.Features.ApplyTo(&config.Config); err != nil { + return err + } + + if err := o.RecommendedOptions.CoreAPI.ApplyTo(config); err != nil { + return err + } + + _, err := o.RecommendedOptions.ExtraAdmissionInitializers(config) + if err != nil { + return err + } + return nil +} + +func (o *APIServerOptions) Config() (*genericapiserver.RecommendedConfig, error) { + if err := o.RecommendedOptions.SecureServing.MaybeDefaultWithSelfSignedCerts( + "localhost", o.AlternateDNS, []net.IP{netutils.ParseIPSloppy("127.0.0.1")}, + ); err != nil { return nil, fmt.Errorf("error creating self-signed certificates: %v", err) } @@ -92,33 +142,55 @@ func (o *ExampleServerOptions) Config() (*genericapiserver.RecommendedConfig, er o.RecommendedOptions.Authorization.RemoteKubeConfigFileOptional = true o.RecommendedOptions.Admission = nil - o.RecommendedOptions.CoreAPI = nil o.RecommendedOptions.Etcd = nil + if o.RecommendedOptions.CoreAPI.CoreAPIKubeconfigPath == "" { + o.RecommendedOptions.CoreAPI = nil + } + serverConfig := genericapiserver.NewRecommendedConfig(Codecs) - if err := o.RecommendedOptions.ApplyTo(serverConfig); err != nil { - return nil, err + if o.RecommendedOptions.CoreAPI == nil { + if err := o.ModifiedApplyTo(serverConfig); err != nil { + return nil, err + } + } else { + if err := o.RecommendedOptions.ApplyTo(serverConfig); err != nil { + return nil, err + } } + // Add OpenAPI specs for each group+version + defsGetter := grafanaAPIServer.GetOpenAPIDefinitions(o.builders) + serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig( + openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(defsGetter), + openapinamer.NewDefinitionNamer(Scheme)) + + serverConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config( + openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(defsGetter), + openapinamer.NewDefinitionNamer(Scheme)) + + // Add the custom routes to service discovery + serverConfig.OpenAPIV3Config.PostProcessSpec3 = grafanaAPIServer.GetOpenAPIPostProcessor(o.builders) + return serverConfig, nil } -// Validate validates ExampleServerOptions +// Validate validates APIServerOptions // NOTE: we don't call validate on the top level recommended options as it doesn't like skipping etcd-servers // the function is left here for troubleshooting any other config issues -func (o *ExampleServerOptions) Validate(args []string) error { +func (o *APIServerOptions) Validate(args []string) error { errors := []error{} errors = append(errors, o.RecommendedOptions.Validate()...) return utilerrors.NewAggregate(errors) } // Complete fills in fields required to have valid data -func (o *ExampleServerOptions) Complete() error { +func (o *APIServerOptions) Complete() error { return nil } -func (o *ExampleServerOptions) RunExampleServer(config *genericapiserver.RecommendedConfig, stopCh <-chan struct{}) error { +func (o *APIServerOptions) RunAPIServer(config *genericapiserver.RecommendedConfig, stopCh <-chan struct{}) error { delegationTarget := genericapiserver.NewEmptyDelegate() completedConfig := config.Complete() server, err := completedConfig.New("example-apiserver", delegationTarget) @@ -141,5 +213,15 @@ func (o *ExampleServerOptions) RunExampleServer(config *genericapiserver.Recomme } } + // in standalone mode, write the local config to disk + if o.RecommendedOptions.CoreAPI == nil { + if err = clientcmd.WriteToFile( + utils.FormatKubeConfig(server.LoopbackClientConfig), + path.Join(dataPath, "grafana.kubeconfig"), + ); err != nil { + return err + } + } + return server.PrepareRun().Run(stopCh) } diff --git a/pkg/components/simplejson/simplejson.go b/pkg/components/simplejson/simplejson.go index a24671d419f..d7759ac3c2b 100644 --- a/pkg/components/simplejson/simplejson.go +++ b/pkg/components/simplejson/simplejson.go @@ -60,6 +60,24 @@ func (j *Json) Value() (driver.Value, error) { return j.ToDB() } +// DeepCopyInto creates a copy by serializing JSON +func (j *Json) DeepCopyInto(out *Json) { + b, err := j.Encode() + if err == nil { + _ = out.UnmarshalJSON(b) + } +} + +// DeepCopy will make a deep copy of the JSON object +func (j *Json) DeepCopy() *Json { + if j == nil { + return nil + } + out := new(Json) + j.DeepCopyInto(out) + return out +} + // NewJson returns a pointer to a new `Json` object // after unmarshaling `body` bytes func NewJson(body []byte) (*Json, error) { diff --git a/pkg/registry/apis/example/register.go b/pkg/registry/apis/example/register.go index ea2ee6b2367..509e3ac40c1 100644 --- a/pkg/registry/apis/example/register.go +++ b/pkg/registry/apis/example/register.go @@ -36,14 +36,18 @@ type TestingAPIBuilder struct { gv schema.GroupVersion } +func NewTestingAPIBuilder() *TestingAPIBuilder { + return &TestingAPIBuilder{ + gv: schema.GroupVersion{Group: GroupName, Version: VersionID}, + } +} + func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration grafanaapiserver.APIRegistrar) *TestingAPIBuilder { if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { return nil // skip registration unless opting into experimental apis } - builder := &TestingAPIBuilder{ - gv: schema.GroupVersion{Group: GroupName, Version: VersionID}, - } - apiregistration.RegisterAPI(builder) + builder := NewTestingAPIBuilder() + apiregistration.RegisterAPI(NewTestingAPIBuilder()) return builder } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 0cb1951ced1..0470ca4ec1e 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -990,6 +990,14 @@ var ( RequiresRestart: true, // changes the API routing Created: time.Date(2023, time.November, 8, 12, 0, 0, 0, time.UTC), }, + { + Name: "kubernetesSnapshots", + Description: "Use the kubernetes API in the frontend to support playlists", + Stage: FeatureStageExperimental, + Owner: grafanaAppPlatformSquad, + RequiresRestart: true, // changes the API routing + Created: time.Date(2023, time.December, 4, 12, 0, 0, 0, time.UTC), + }, { Name: "cloudWatchBatchQueries", Description: "Runs CloudWatch metrics queries as separate batches", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 3458bc1df3f..3597d856386 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -116,6 +116,7 @@ enableNativeHTTPHistogram,experimental,@grafana/hosted-grafana-team,2023-10-03,f formatString,experimental,@grafana/grafana-bi-squad,2023-10-13,false,false,false,true transformationsVariableSupport,preview,@grafana/grafana-bi-squad,2023-10-04,false,false,false,true kubernetesPlaylists,experimental,@grafana/grafana-app-platform-squad,2023-11-08,false,false,true,false +kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,2023-12-04,false,false,true,false cloudWatchBatchQueries,preview,@grafana/aws-datasources,2023-10-20,false,false,false,false recoveryThreshold,experimental,@grafana/alerting-squad,2023-10-10,false,false,true,false lokiStructuredMetadata,experimental,@grafana/observability-logs,2023-11-16,false,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 900645c168a..2163ebee00a 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -475,6 +475,10 @@ const ( // Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s FlagKubernetesPlaylists = "kubernetesPlaylists" + // FlagKubernetesSnapshots + // Use the kubernetes API in the frontend to support playlists + FlagKubernetesSnapshots = "kubernetesSnapshots" + // FlagCloudWatchBatchQueries // Runs CloudWatch metrics queries as separate batches FlagCloudWatchBatchQueries = "cloudWatchBatchQueries" diff --git a/pkg/services/grafana-apiserver/endpoints/request/namespace.go b/pkg/services/grafana-apiserver/endpoints/request/namespace.go index 4848c24386c..c35ace9ce0f 100644 --- a/pkg/services/grafana-apiserver/endpoints/request/namespace.go +++ b/pkg/services/grafana-apiserver/endpoints/request/namespace.go @@ -41,7 +41,7 @@ func GetNamespaceMapper(cfg *setting.Cfg) NamespaceMapper { func NamespaceInfoFrom(ctx context.Context, requireOrgID bool) (NamespaceInfo, error) { info, err := ParseNamespace(request.NamespaceValue(ctx)) if err == nil && requireOrgID && info.OrgID < 1 { - return info, fmt.Errorf("expected valid orgId") + return info, fmt.Errorf("expected valid orgId in namespace") } return info, err } diff --git a/pkg/services/grafana-apiserver/openapi.go b/pkg/services/grafana-apiserver/openapi.go index a086f09c45c..742c8d7e94f 100644 --- a/pkg/services/grafana-apiserver/openapi.go +++ b/pkg/services/grafana-apiserver/openapi.go @@ -9,7 +9,7 @@ import ( ) // This should eventually live in grafana-app-sdk -func getOpenAPIDefinitions(builders []APIGroupBuilder) common.GetOpenAPIDefinitions { +func GetOpenAPIDefinitions(builders []APIGroupBuilder) common.GetOpenAPIDefinitions { return func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { defs := getStandardOpenAPIDefinitions(ref) for _, builder := range builders { diff --git a/pkg/services/grafana-apiserver/request_handler.go b/pkg/services/grafana-apiserver/request_handler.go index a7dc46482c9..3091c4cf6e5 100644 --- a/pkg/services/grafana-apiserver/request_handler.go +++ b/pkg/services/grafana-apiserver/request_handler.go @@ -139,7 +139,7 @@ func (h *methodNotAllowedHandler) ServeHTTP(w http.ResponseWriter, req *http.Req // Modify the the OpenAPI spec to include the additional routes. // Currently this requires: https://github.com/kubernetes/kube-openapi/pull/420 // In future k8s release, the hook will use Config3 rather than the same hook for both v2 and v3 -func getOpenAPIPostProcessor(builders []APIGroupBuilder) func(*spec3.OpenAPI) (*spec3.OpenAPI, error) { +func GetOpenAPIPostProcessor(builders []APIGroupBuilder) func(*spec3.OpenAPI) (*spec3.OpenAPI, error) { return func(s *spec3.OpenAPI) (*spec3.OpenAPI, error) { if s.Paths == nil { return s, nil diff --git a/pkg/services/grafana-apiserver/service.go b/pkg/services/grafana-apiserver/service.go index a960b26b0ce..25ba3ad4d30 100644 --- a/pkg/services/grafana-apiserver/service.go +++ b/pkg/services/grafana-apiserver/service.go @@ -29,10 +29,11 @@ import ( "k8s.io/client-go/kubernetes/scheme" clientrest "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "k8s.io/component-base/logs" "k8s.io/klog/v2" + "github.com/grafana/grafana/pkg/services/grafana-apiserver/utils" + "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/infra/appcontext" "github.com/grafana/grafana/pkg/infra/tracing" @@ -270,7 +271,7 @@ func (s *service) start(ctx context.Context) error { serverConfig.TracerProvider = s.tracing.GetTracerProvider() // Add OpenAPI specs for each group+version - defsGetter := getOpenAPIDefinitions(builders) + defsGetter := GetOpenAPIDefinitions(builders) serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig( openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(defsGetter), openapinamer.NewDefinitionNamer(Scheme, scheme.Scheme)) @@ -280,7 +281,7 @@ func (s *service) start(ctx context.Context) error { openapinamer.NewDefinitionNamer(Scheme, scheme.Scheme)) // Add the custom routes to service discovery - serverConfig.OpenAPIV3Config.PostProcessSpec3 = getOpenAPIPostProcessor(builders) + serverConfig.OpenAPIV3Config.PostProcessSpec3 = GetOpenAPIPostProcessor(builders) // Set the swagger build versions serverConfig.OpenAPIConfig.Info.Version = setting.BuildVersion @@ -392,34 +393,10 @@ func (s *service) running(ctx context.Context) error { } func (s *service) ensureKubeConfig() error { - clusters := make(map[string]*clientcmdapi.Cluster) - clusters["default-cluster"] = &clientcmdapi.Cluster{ - Server: s.restConfig.Host, - InsecureSkipTLSVerify: true, - } - - contexts := make(map[string]*clientcmdapi.Context) - contexts["default-context"] = &clientcmdapi.Context{ - Cluster: "default-cluster", - Namespace: "default", - AuthInfo: "default", - } - - authinfos := make(map[string]*clientcmdapi.AuthInfo) - authinfos["default"] = &clientcmdapi.AuthInfo{ - Token: s.restConfig.BearerToken, - } - - clientConfig := clientcmdapi.Config{ - Kind: "Config", - APIVersion: "v1", - Clusters: clusters, - Contexts: contexts, - CurrentContext: "default-context", - AuthInfos: authinfos, - } - - return clientcmd.WriteToFile(clientConfig, path.Join(s.config.dataPath, "grafana.kubeconfig")) + return clientcmd.WriteToFile( + utils.FormatKubeConfig(s.restConfig), + path.Join(s.config.dataPath, "grafana.kubeconfig"), + ) } type roundTripperFunc struct { diff --git a/pkg/services/grafana-apiserver/utils/clientConfig.go b/pkg/services/grafana-apiserver/utils/clientConfig.go new file mode 100644 index 00000000000..9714c9d2e1f --- /dev/null +++ b/pkg/services/grafana-apiserver/utils/clientConfig.go @@ -0,0 +1,35 @@ +package utils + +import ( + clientrest "k8s.io/client-go/rest" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +func FormatKubeConfig(restConfig *clientrest.Config) clientcmdapi.Config { + clusters := make(map[string]*clientcmdapi.Cluster) + clusters["default-cluster"] = &clientcmdapi.Cluster{ + Server: restConfig.Host, + InsecureSkipTLSVerify: true, + } + + contexts := make(map[string]*clientcmdapi.Context) + contexts["default-context"] = &clientcmdapi.Context{ + Cluster: "default-cluster", + Namespace: "default", + AuthInfo: "default", + } + + authinfos := make(map[string]*clientcmdapi.AuthInfo) + authinfos["default"] = &clientcmdapi.AuthInfo{ + Token: restConfig.BearerToken, + } + + return clientcmdapi.Config{ + Kind: "Config", + APIVersion: "v1", + Clusters: clusters, + Contexts: contexts, + CurrentContext: "default-context", + AuthInfos: authinfos, + } +}