K8s: fix standalone command and add hack scripts (#79052)

Co-authored-by: Charandas Batra <charandas.batra@grafana.com>
pull/79120/head
Ryan McKinley 2 years ago committed by GitHub
parent 66df17869d
commit 439edebcd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .github/CODEOWNERS
  2. 3
      .gitignore
  3. 10
      .vscode/launch.json
  4. 1
      docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
  5. 25
      hack/README.md
  6. 2
      hack/boilerplate.go.txt
  7. 45
      hack/update-codegen.sh
  8. 1
      packages/grafana-data/src/types/featureToggles.gen.ts
  9. 33
      pkg/cmd/grafana/apiserver/README.md
  10. 24
      pkg/cmd/grafana/apiserver/cmd.go
  11. 120
      pkg/cmd/grafana/apiserver/server.go
  12. 18
      pkg/components/simplejson/simplejson.go
  13. 12
      pkg/registry/apis/example/register.go
  14. 8
      pkg/services/featuremgmt/registry.go
  15. 1
      pkg/services/featuremgmt/toggles_gen.csv
  16. 4
      pkg/services/featuremgmt/toggles_gen.go
  17. 2
      pkg/services/grafana-apiserver/endpoints/request/namespace.go
  18. 2
      pkg/services/grafana-apiserver/openapi.go
  19. 2
      pkg/services/grafana-apiserver/request_handler.go
  20. 39
      pkg/services/grafana-apiserver/service.go
  21. 35
      pkg/services/grafana-apiserver/utils/clientConfig.go

@ -65,6 +65,7 @@
/.golangci.toml @grafana/backend-platform /.golangci.toml @grafana/backend-platform
/build.go @grafana/backend-platform /build.go @grafana/backend-platform
/scripts/modowners/ @grafana/backend-platform /scripts/modowners/ @grafana/backend-platform
/hack/ @grafana/grafana-app-platform-squad
/pkg/api/ @grafana/backend-platform /pkg/api/ @grafana/backend-platform
/pkg/apis/ @grafana/grafana-app-platform-squad /pkg/apis/ @grafana/grafana-app-platform-squad

3
.gitignore vendored

@ -80,6 +80,9 @@ public/css/*.min.css
apiserver.local.config/ apiserver.local.config/
default.etcd/ default.etcd/
# kubeconfig path used by example apiserver
example-apiserver/
# devenv # devenv
/devenv/docker-compose.yaml /devenv/docker-compose.yaml
/devenv/docker-compose.override.yaml /devenv/docker-compose.override.yaml

@ -11,6 +11,16 @@
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"args": ["server", "--homepath", "${workspaceFolder}", "--packaging", "dev"] "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", "name": "Attach to Chrome",
"port": 9222, "port": 9222,

@ -143,6 +143,7 @@ Experimental features might be changed or removed without prior notice.
| `enableNativeHTTPHistogram` | Enables native HTTP Histograms | | `enableNativeHTTPHistogram` | Enables native HTTP Histograms |
| `formatString` | Enable format string transformer | | `formatString` | Enable format string transformer |
| `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s | | `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 | | `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 | | `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 | | `teamHttpHeaders` | Enables datasources to apply team headers to the client requests |

@ -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!

@ -0,0 +1,2 @@
// SPDX-License-Identifier: AGPL-3.0-only

@ -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"

@ -135,6 +135,7 @@ export interface FeatureToggles {
formatString?: boolean; formatString?: boolean;
transformationsVariableSupport?: boolean; transformationsVariableSupport?: boolean;
kubernetesPlaylists?: boolean; kubernetesPlaylists?: boolean;
kubernetesSnapshots?: boolean;
cloudWatchBatchQueries?: boolean; cloudWatchBatchQueries?: boolean;
recoveryThreshold?: boolean; recoveryThreshold?: boolean;
lokiStructuredMetadata?: boolean; lokiStructuredMetadata?: boolean;

@ -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 [sample-apiserver](https://github.com/kubernetes/sample-apiserver/tree/master) project in code and thus
allows the same allows the same
[CLI flags](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/) as kube-apiserver. [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. 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 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 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 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. kind cluster and that you can provide its kubeconfig in the parameters to the example-apiserver.
```shell ```shell
go run ./pkg/cmd/grafana apiserver example.grafana.app\ go run ./pkg/cmd/grafana apiserver example.grafana.app \
--authentication-kubeconfig ~/.kube/config \ --authentication-kubeconfig ~/.kube/config \
--authorization-kubeconfig ~/.kube/config \ --authorization-kubeconfig ~/.kube/config \
--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: With kubectl configured against `kind-kind` context, you can run the following:

@ -1,7 +1,6 @@
package apiserver package apiserver
import ( import (
"fmt"
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -10,9 +9,7 @@ import (
"k8s.io/component-base/cli" "k8s.io/component-base/cli"
) )
func newCommandStartExampleAPIServer(o *ExampleServerOptions, stopCh <-chan struct{}) *cobra.Command { func newCommandStartExampleAPIServer(o *APIServerOptions, 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"
devAcknowledgementNotice := "The apiserver command is in heavy development. The entire setup is subject to change without notice" devAcknowledgementNotice := "The apiserver command is in heavy development. The entire setup is subject to change without notice"
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -20,22 +17,14 @@ func newCommandStartExampleAPIServer(o *ExampleServerOptions, stopCh <-chan stru
Short: "Run the grafana apiserver", Short: "Run the grafana apiserver",
Long: "Run a standalone kubernetes based apiserver that can be aggregated by a root apiserver. " + Long: "Run a standalone kubernetes based apiserver that can be aggregated by a root apiserver. " +
devAcknowledgementNotice, devAcknowledgementNotice,
Example: fmt.Sprintf("grafana apiserver example.grafana.app --%s", devAcknowledgementFlag), Example: "grafana apiserver example.grafana.app",
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)
}
},
RunE: func(c *cobra.Command, args []string) error { RunE: func(c *cobra.Command, args []string) error {
// Load each group from the args // Load each group from the args
if err := o.LoadAPIGroupBuilders(args[1:]); err != nil { if err := o.LoadAPIGroupBuilders(args[1:]); err != nil {
return err return err
} }
// Finish the config (applies all defaults) // Finish the config (a noop for now)
if err := o.Complete(); err != nil { if err := o.Complete(); err != nil {
return err return err
} }
@ -45,16 +34,13 @@ func newCommandStartExampleAPIServer(o *ExampleServerOptions, stopCh <-chan stru
return err return err
} }
if err := o.RunExampleServer(config, stopCh); err != nil { if err := o.RunAPIServer(config, stopCh); err != nil {
return err return err
} }
return nil return nil
}, },
} }
// Register grafana flags
cmd.PersistentFlags().Bool(devAcknowledgementFlag, false, devAcknowledgementNotice)
// Register standard k8s flags with the command line // Register standard k8s flags with the command line
o.RecommendedOptions = options.NewRecommendedOptions( o.RecommendedOptions = options.NewRecommendedOptions(
defaultEtcdPathPrefix, defaultEtcdPathPrefix,
@ -68,7 +54,7 @@ func newCommandStartExampleAPIServer(o *ExampleServerOptions, stopCh <-chan stru
func RunCLI() int { func RunCLI() int {
stopCh := genericapiserver.SetupSignalHandler() stopCh := genericapiserver.SetupSignalHandler()
options := newExampleServerOptions(os.Stdout, os.Stderr) options := newAPIServerOptions(os.Stdout, os.Stderr)
cmd := newCommandStartExampleAPIServer(options, stopCh) cmd := newCommandStartExampleAPIServer(options, stopCh)
return cli.Run(cmd) return cli.Run(cmd)

@ -4,21 +4,30 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"path"
"github.com/grafana/grafana/pkg/registry/apis/example"
grafanaAPIServer "github.com/grafana/grafana/pkg/services/grafana-apiserver"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
genericapiserver "k8s.io/apiserver/pkg/server" genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/options" "k8s.io/apiserver/pkg/server/options"
"k8s.io/apiserver/pkg/util/openapi"
"k8s.io/client-go/tools/clientcmd"
netutils "k8s.io/utils/net" 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 ( var (
Scheme = runtime.NewScheme() Scheme = runtime.NewScheme()
@ -41,8 +50,8 @@ func init() {
Scheme.AddUnversionedTypes(unversionedVersion, unversionedTypes...) Scheme.AddUnversionedTypes(unversionedVersion, unversionedTypes...)
} }
// ExampleServerOptions contains the state for the apiserver // APIServerOptions contains the state for the apiserver
type ExampleServerOptions struct { type APIServerOptions struct {
builders []grafanaAPIServer.APIGroupBuilder builders []grafanaAPIServer.APIGroupBuilder
RecommendedOptions *options.RecommendedOptions RecommendedOptions *options.RecommendedOptions
AlternateDNS []string AlternateDNS []string
@ -51,20 +60,20 @@ type ExampleServerOptions struct {
StdErr io.Writer StdErr io.Writer
} }
func newExampleServerOptions(out, errOut io.Writer) *ExampleServerOptions { func newAPIServerOptions(out, errOut io.Writer) *APIServerOptions {
return &ExampleServerOptions{ return &APIServerOptions{
StdOut: out, StdOut: out,
StdErr: errOut, StdErr: errOut,
} }
} }
func (o *ExampleServerOptions) LoadAPIGroupBuilders(args []string) error { func (o *APIServerOptions) LoadAPIGroupBuilders(args []string) error {
o.builders = []grafanaAPIServer.APIGroupBuilder{} o.builders = []grafanaAPIServer.APIGroupBuilder{}
for _, g := range args { for _, g := range args {
switch g { switch g {
// No dependencies for testing // No dependencies for testing
case "example.grafana.app": case "example.grafana.app":
o.builders = append(o.builders, &example.TestingAPIBuilder{}) o.builders = append(o.builders, example.NewTestingAPIBuilder())
default: default:
return fmt.Errorf("unknown group: %s", g) return fmt.Errorf("unknown group: %s", g)
} }
@ -83,8 +92,49 @@ func (o *ExampleServerOptions) LoadAPIGroupBuilders(args []string) error {
return nil return nil
} }
func (o *ExampleServerOptions) Config() (*genericapiserver.RecommendedConfig, error) { // A copy of ApplyTo in recommended.go, but for >= 0.28, server pkg in apiserver does a bit extra causing
if err := o.RecommendedOptions.SecureServing.MaybeDefaultWithSelfSignedCerts("localhost", o.AlternateDNS, []net.IP{netutils.ParseIPSloppy("127.0.0.1")}); err != nil { // 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) 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.Authorization.RemoteKubeConfigFileOptional = true
o.RecommendedOptions.Admission = nil o.RecommendedOptions.Admission = nil
o.RecommendedOptions.CoreAPI = nil
o.RecommendedOptions.Etcd = nil o.RecommendedOptions.Etcd = nil
if o.RecommendedOptions.CoreAPI.CoreAPIKubeconfigPath == "" {
o.RecommendedOptions.CoreAPI = nil
}
serverConfig := genericapiserver.NewRecommendedConfig(Codecs) serverConfig := genericapiserver.NewRecommendedConfig(Codecs)
if err := o.RecommendedOptions.ApplyTo(serverConfig); err != nil { if o.RecommendedOptions.CoreAPI == nil {
return nil, err 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 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 // 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 // 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 := []error{}
errors = append(errors, o.RecommendedOptions.Validate()...) errors = append(errors, o.RecommendedOptions.Validate()...)
return utilerrors.NewAggregate(errors) return utilerrors.NewAggregate(errors)
} }
// Complete fills in fields required to have valid data // Complete fills in fields required to have valid data
func (o *ExampleServerOptions) Complete() error { func (o *APIServerOptions) Complete() error {
return nil 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() delegationTarget := genericapiserver.NewEmptyDelegate()
completedConfig := config.Complete() completedConfig := config.Complete()
server, err := completedConfig.New("example-apiserver", delegationTarget) 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) return server.PrepareRun().Run(stopCh)
} }

@ -60,6 +60,24 @@ func (j *Json) Value() (driver.Value, error) {
return j.ToDB() 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 // NewJson returns a pointer to a new `Json` object
// after unmarshaling `body` bytes // after unmarshaling `body` bytes
func NewJson(body []byte) (*Json, error) { func NewJson(body []byte) (*Json, error) {

@ -36,14 +36,18 @@ type TestingAPIBuilder struct {
gv schema.GroupVersion gv schema.GroupVersion
} }
func NewTestingAPIBuilder() *TestingAPIBuilder {
return &TestingAPIBuilder{
gv: schema.GroupVersion{Group: GroupName, Version: VersionID},
}
}
func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration grafanaapiserver.APIRegistrar) *TestingAPIBuilder { func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration grafanaapiserver.APIRegistrar) *TestingAPIBuilder {
if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
return nil // skip registration unless opting into experimental apis return nil // skip registration unless opting into experimental apis
} }
builder := &TestingAPIBuilder{ builder := NewTestingAPIBuilder()
gv: schema.GroupVersion{Group: GroupName, Version: VersionID}, apiregistration.RegisterAPI(NewTestingAPIBuilder())
}
apiregistration.RegisterAPI(builder)
return builder return builder
} }

@ -990,6 +990,14 @@ var (
RequiresRestart: true, // changes the API routing RequiresRestart: true, // changes the API routing
Created: time.Date(2023, time.November, 8, 12, 0, 0, 0, time.UTC), 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", Name: "cloudWatchBatchQueries",
Description: "Runs CloudWatch metrics queries as separate batches", Description: "Runs CloudWatch metrics queries as separate batches",

@ -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 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 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 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 cloudWatchBatchQueries,preview,@grafana/aws-datasources,2023-10-20,false,false,false,false
recoveryThreshold,experimental,@grafana/alerting-squad,2023-10-10,false,false,true,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 lokiStructuredMetadata,experimental,@grafana/observability-logs,2023-11-16,false,false,false,false

1 Name Stage Owner Created requiresDevMode RequiresLicense RequiresRestart FrontendOnly
116 formatString experimental @grafana/grafana-bi-squad 2023-10-13 false false false true
117 transformationsVariableSupport preview @grafana/grafana-bi-squad 2023-10-04 false false false true
118 kubernetesPlaylists experimental @grafana/grafana-app-platform-squad 2023-11-08 false false true false
119 kubernetesSnapshots experimental @grafana/grafana-app-platform-squad 2023-12-04 false false true false
120 cloudWatchBatchQueries preview @grafana/aws-datasources 2023-10-20 false false false false
121 recoveryThreshold experimental @grafana/alerting-squad 2023-10-10 false false true false
122 lokiStructuredMetadata experimental @grafana/observability-logs 2023-11-16 false false false false

@ -475,6 +475,10 @@ const (
// Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s // Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s
FlagKubernetesPlaylists = "kubernetesPlaylists" FlagKubernetesPlaylists = "kubernetesPlaylists"
// FlagKubernetesSnapshots
// Use the kubernetes API in the frontend to support playlists
FlagKubernetesSnapshots = "kubernetesSnapshots"
// FlagCloudWatchBatchQueries // FlagCloudWatchBatchQueries
// Runs CloudWatch metrics queries as separate batches // Runs CloudWatch metrics queries as separate batches
FlagCloudWatchBatchQueries = "cloudWatchBatchQueries" FlagCloudWatchBatchQueries = "cloudWatchBatchQueries"

@ -41,7 +41,7 @@ func GetNamespaceMapper(cfg *setting.Cfg) NamespaceMapper {
func NamespaceInfoFrom(ctx context.Context, requireOrgID bool) (NamespaceInfo, error) { func NamespaceInfoFrom(ctx context.Context, requireOrgID bool) (NamespaceInfo, error) {
info, err := ParseNamespace(request.NamespaceValue(ctx)) info, err := ParseNamespace(request.NamespaceValue(ctx))
if err == nil && requireOrgID && info.OrgID < 1 { 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 return info, err
} }

@ -9,7 +9,7 @@ import (
) )
// This should eventually live in grafana-app-sdk // 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 { return func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
defs := getStandardOpenAPIDefinitions(ref) defs := getStandardOpenAPIDefinitions(ref)
for _, builder := range builders { for _, builder := range builders {

@ -139,7 +139,7 @@ func (h *methodNotAllowedHandler) ServeHTTP(w http.ResponseWriter, req *http.Req
// Modify the the OpenAPI spec to include the additional routes. // Modify the the OpenAPI spec to include the additional routes.
// Currently this requires: https://github.com/kubernetes/kube-openapi/pull/420 // 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 // 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) { return func(s *spec3.OpenAPI) (*spec3.OpenAPI, error) {
if s.Paths == nil { if s.Paths == nil {
return s, nil return s, nil

@ -29,10 +29,11 @@ import (
"k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/kubernetes/scheme"
clientrest "k8s.io/client-go/rest" clientrest "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/component-base/logs" "k8s.io/component-base/logs"
"k8s.io/klog/v2" "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/api/routing"
"github.com/grafana/grafana/pkg/infra/appcontext" "github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
@ -270,7 +271,7 @@ func (s *service) start(ctx context.Context) error {
serverConfig.TracerProvider = s.tracing.GetTracerProvider() serverConfig.TracerProvider = s.tracing.GetTracerProvider()
// Add OpenAPI specs for each group+version // Add OpenAPI specs for each group+version
defsGetter := getOpenAPIDefinitions(builders) defsGetter := GetOpenAPIDefinitions(builders)
serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig( serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(
openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(defsGetter), openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(defsGetter),
openapinamer.NewDefinitionNamer(Scheme, scheme.Scheme)) openapinamer.NewDefinitionNamer(Scheme, scheme.Scheme))
@ -280,7 +281,7 @@ func (s *service) start(ctx context.Context) error {
openapinamer.NewDefinitionNamer(Scheme, scheme.Scheme)) openapinamer.NewDefinitionNamer(Scheme, scheme.Scheme))
// Add the custom routes to service discovery // Add the custom routes to service discovery
serverConfig.OpenAPIV3Config.PostProcessSpec3 = getOpenAPIPostProcessor(builders) serverConfig.OpenAPIV3Config.PostProcessSpec3 = GetOpenAPIPostProcessor(builders)
// Set the swagger build versions // Set the swagger build versions
serverConfig.OpenAPIConfig.Info.Version = setting.BuildVersion serverConfig.OpenAPIConfig.Info.Version = setting.BuildVersion
@ -392,34 +393,10 @@ func (s *service) running(ctx context.Context) error {
} }
func (s *service) ensureKubeConfig() error { func (s *service) ensureKubeConfig() error {
clusters := make(map[string]*clientcmdapi.Cluster) return clientcmd.WriteToFile(
clusters["default-cluster"] = &clientcmdapi.Cluster{ utils.FormatKubeConfig(s.restConfig),
Server: s.restConfig.Host, path.Join(s.config.dataPath, "grafana.kubeconfig"),
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"))
} }
type roundTripperFunc struct { type roundTripperFunc struct {

@ -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,
}
}
Loading…
Cancel
Save