K8s: fix standalone command and add hack scripts (#79052)
Co-authored-by: Charandas Batra <charandas.batra@grafana.com>
This commit is contained in:
co-authored by
Charandas Batra
parent
66df17869d
commit
439edebcd6
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Vendored
+10
@@ -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
|
||||||
|
|
||||||
Executable
+45
@@ -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
|
||||||
|
|||||||
|
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user