2024-02-01 16:27:30 -06:00
|
|
|
package builder
|
2024-01-10 09:30:16 -06:00
|
|
|
|
|
|
|
import (
|
2024-07-11 15:23:31 -05:00
|
|
|
"context"
|
2024-01-10 09:30:16 -06:00
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2024-05-08 12:35:11 -05:00
|
|
|
"regexp"
|
2024-01-10 09:30:16 -06:00
|
|
|
"time"
|
|
|
|
|
2024-06-20 09:53:07 -05:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2024-01-30 17:17:14 -06:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2024-02-01 16:27:30 -06:00
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
2024-07-11 15:23:31 -05:00
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
2024-02-01 16:27:30 -06:00
|
|
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
2024-06-20 02:10:03 -05:00
|
|
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
2024-01-10 09:30:16 -06:00
|
|
|
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
|
|
|
|
"k8s.io/apiserver/pkg/registry/generic"
|
|
|
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
|
|
|
"k8s.io/apiserver/pkg/util/openapi"
|
2024-08-08 10:39:44 -05:00
|
|
|
utilversion "k8s.io/apiserver/pkg/util/version"
|
2024-02-01 16:27:30 -06:00
|
|
|
k8sscheme "k8s.io/client-go/kubernetes/scheme"
|
2024-04-03 16:39:09 -05:00
|
|
|
k8stracing "k8s.io/component-base/tracing"
|
2024-01-30 17:17:14 -06:00
|
|
|
"k8s.io/kube-openapi/pkg/common"
|
2024-03-01 19:32:59 -06:00
|
|
|
|
2024-07-11 15:23:31 -05:00
|
|
|
"github.com/grafana/grafana/pkg/web"
|
|
|
|
|
2024-03-01 19:32:59 -06:00
|
|
|
"github.com/grafana/grafana/pkg/apiserver/endpoints/filters"
|
2024-07-11 15:23:31 -05:00
|
|
|
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
2024-05-31 12:29:59 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/apiserver/options"
|
2024-01-10 09:30:16 -06:00
|
|
|
)
|
|
|
|
|
2024-05-10 13:01:17 -05:00
|
|
|
// TODO: this is a temporary hack to make rest.Connecter work with resource level routes
|
2024-05-08 12:35:11 -05:00
|
|
|
var pathRewriters = []filters.PathRewriter{
|
|
|
|
{
|
2024-06-05 10:47:36 -05:00
|
|
|
Pattern: regexp.MustCompile(`(/apis/scope.grafana.app/v0alpha1/namespaces/.*/)find/(.*)$`),
|
2024-05-08 12:35:11 -05:00
|
|
|
ReplaceFunc: func(matches []string) string {
|
2024-06-05 10:47:36 -05:00
|
|
|
return matches[1] + matches[2] + "/name" // connector requires a name
|
2024-05-08 12:35:11 -05:00
|
|
|
},
|
|
|
|
},
|
2024-05-10 13:01:17 -05:00
|
|
|
{
|
|
|
|
Pattern: regexp.MustCompile(`(/apis/query.grafana.app/v0alpha1/namespaces/.*/query$)`),
|
|
|
|
ReplaceFunc: func(matches []string) string {
|
|
|
|
return matches[1] + "/name" // connector requires a name
|
|
|
|
},
|
|
|
|
},
|
2024-05-08 12:35:11 -05:00
|
|
|
}
|
|
|
|
|
2024-02-01 16:27:30 -06:00
|
|
|
func SetupConfig(
|
|
|
|
scheme *runtime.Scheme,
|
|
|
|
serverConfig *genericapiserver.RecommendedConfig,
|
|
|
|
builders []APIGroupBuilder,
|
2024-02-23 14:15:43 -06:00
|
|
|
buildTimestamp int64,
|
|
|
|
buildVersion string,
|
|
|
|
buildCommit string,
|
|
|
|
buildBranch string,
|
2024-07-08 14:22:10 -05:00
|
|
|
optionalMiddlewares ...web.Middleware,
|
2024-02-01 16:27:30 -06:00
|
|
|
) error {
|
2024-01-10 09:30:16 -06:00
|
|
|
defsGetter := GetOpenAPIDefinitions(builders)
|
|
|
|
serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(
|
|
|
|
openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(defsGetter),
|
2024-02-01 16:27:30 -06:00
|
|
|
openapinamer.NewDefinitionNamer(scheme, k8sscheme.Scheme))
|
2024-01-10 09:30:16 -06:00
|
|
|
|
|
|
|
serverConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(
|
|
|
|
openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(defsGetter),
|
2024-02-01 16:27:30 -06:00
|
|
|
openapinamer.NewDefinitionNamer(scheme, k8sscheme.Scheme))
|
2024-01-10 09:30:16 -06:00
|
|
|
|
2024-02-01 16:27:30 -06:00
|
|
|
// Add the custom routes to service discovery
|
2024-02-23 14:15:43 -06:00
|
|
|
serverConfig.OpenAPIV3Config.PostProcessSpec = getOpenAPIPostProcessor(buildVersion, builders)
|
2024-01-30 17:17:14 -06:00
|
|
|
serverConfig.OpenAPIV3Config.GetOperationIDAndTagsFromRoute = func(r common.Route) (string, []string, error) {
|
|
|
|
tags := []string{}
|
|
|
|
prop, ok := r.Metadata()["x-kubernetes-group-version-kind"]
|
|
|
|
if ok {
|
|
|
|
gvk, ok := prop.(metav1.GroupVersionKind)
|
|
|
|
if ok && gvk.Kind != "" {
|
|
|
|
tags = append(tags, gvk.Kind)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return r.OperationName(), tags, nil
|
|
|
|
}
|
|
|
|
|
2024-01-10 09:30:16 -06:00
|
|
|
// Set the swagger build versions
|
2024-02-23 14:15:43 -06:00
|
|
|
serverConfig.OpenAPIConfig.Info.Version = buildVersion
|
|
|
|
serverConfig.OpenAPIV3Config.Info.Version = buildVersion
|
2024-01-10 09:30:16 -06:00
|
|
|
|
|
|
|
serverConfig.SkipOpenAPIInstallation = false
|
|
|
|
serverConfig.BuildHandlerChainFunc = func(delegateHandler http.Handler, c *genericapiserver.Config) http.Handler {
|
|
|
|
// Call DefaultBuildHandlerChain on the main entrypoint http.Handler
|
|
|
|
// See https://github.com/kubernetes/apiserver/blob/v0.28.0/pkg/server/config.go#L906
|
|
|
|
// DefaultBuildHandlerChain provides many things, notably CORS, HSTS, cache-control, authz and latency tracking
|
|
|
|
requestHandler, err := getAPIHandler(
|
|
|
|
delegateHandler,
|
|
|
|
c.LoopbackClientConfig,
|
|
|
|
builders)
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("could not build handler chain func: %s", err.Error()))
|
|
|
|
}
|
2024-03-01 19:32:59 -06:00
|
|
|
|
2024-05-22 10:56:34 -05:00
|
|
|
// Needs to run last in request chain to function as expected, hence we register it first.
|
|
|
|
handler := filters.WithTracingHTTPLoggingAttributes(requestHandler)
|
2024-07-11 20:23:43 -05:00
|
|
|
// filters.WithRequester needs to be after the K8s chain because it depends on the K8s user in context
|
2024-06-20 09:53:07 -05:00
|
|
|
handler = filters.WithRequester(handler)
|
2024-05-22 10:56:34 -05:00
|
|
|
handler = genericapiserver.DefaultBuildHandlerChain(handler, c)
|
2024-07-08 14:22:10 -05:00
|
|
|
|
|
|
|
// If optional middlewares include auth function, they need to happen before DefaultBuildHandlerChain
|
|
|
|
if len(optionalMiddlewares) > 0 {
|
|
|
|
for _, m := range optionalMiddlewares {
|
|
|
|
handler = m(handler)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-01 19:32:59 -06:00
|
|
|
handler = filters.WithAcceptHeader(handler)
|
2024-05-08 12:35:11 -05:00
|
|
|
handler = filters.WithPathRewriters(handler, pathRewriters)
|
2024-04-11 06:28:23 -05:00
|
|
|
handler = k8stracing.WithTracing(handler, serverConfig.TracerProvider, "KubernetesAPI")
|
2024-06-20 02:10:03 -05:00
|
|
|
// Configure filters.WithPanicRecovery to not crash on panic
|
|
|
|
utilruntime.ReallyCrash = false
|
2024-03-01 19:32:59 -06:00
|
|
|
|
|
|
|
return handler
|
2024-01-10 09:30:16 -06:00
|
|
|
}
|
|
|
|
|
2024-08-08 10:39:44 -05:00
|
|
|
serverConfig.EffectiveVersion = utilversion.DefaultKubeEffectiveVersion()
|
|
|
|
|
2024-01-10 09:30:16 -06:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-11 15:23:31 -05:00
|
|
|
type ServerLockService interface {
|
|
|
|
LockExecuteAndRelease(ctx context.Context, actionName string, maxInterval time.Duration, fn func(ctx context.Context)) error
|
|
|
|
}
|
|
|
|
|
2024-02-01 16:27:30 -06:00
|
|
|
func InstallAPIs(
|
|
|
|
scheme *runtime.Scheme,
|
|
|
|
codecs serializer.CodecFactory,
|
|
|
|
server *genericapiserver.GenericAPIServer,
|
2024-01-10 09:30:16 -06:00
|
|
|
optsGetter generic.RESTOptionsGetter,
|
|
|
|
builders []APIGroupBuilder,
|
2024-05-31 12:29:59 -05:00
|
|
|
storageOpts *options.StorageOptions,
|
2024-06-14 04:01:49 -05:00
|
|
|
reg prometheus.Registerer,
|
2024-07-11 15:23:31 -05:00
|
|
|
kvStore grafanarest.NamespacedKVStore,
|
|
|
|
serverLock ServerLockService,
|
2024-01-10 09:30:16 -06:00
|
|
|
) error {
|
2024-05-31 12:29:59 -05:00
|
|
|
// dual writing is only enabled when the storage type is not legacy.
|
|
|
|
// this is needed to support setting a default RESTOptionsGetter for new APIs that don't
|
|
|
|
// support the legacy storage type.
|
2024-07-11 15:23:31 -05:00
|
|
|
var dualWrite grafanarest.DualWriteBuilder
|
|
|
|
if storageOpts.StorageType != options.StorageTypeLegacy {
|
|
|
|
dualWrite = func(gr schema.GroupResource, legacy grafanarest.LegacyStorage, storage grafanarest.Storage) (grafanarest.Storage, error) {
|
|
|
|
key := gr.String() // ${resource}.{group} eg playlists.playlist.grafana.app
|
|
|
|
|
|
|
|
// Get the option from custom.ini/command line
|
|
|
|
// when missing this will default to mode zero (legacy only)
|
|
|
|
mode := storageOpts.DualWriterDesiredModes[key]
|
|
|
|
|
|
|
|
// Moving from one version to the next can only happen after the previous step has
|
|
|
|
// successfully synchronized.
|
|
|
|
currentMode, err := grafanarest.SetDualWritingMode(context.Background(), kvStore, legacy, storage, key, mode, reg)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-07-29 06:57:44 -05:00
|
|
|
switch currentMode {
|
|
|
|
case grafanarest.Mode0:
|
2024-07-11 15:23:31 -05:00
|
|
|
return legacy, nil
|
2024-07-29 06:57:44 -05:00
|
|
|
case grafanarest.Mode4:
|
|
|
|
return storage, nil
|
|
|
|
default:
|
2024-07-11 15:23:31 -05:00
|
|
|
}
|
2024-07-29 06:57:44 -05:00
|
|
|
return grafanarest.NewDualWriter(currentMode, legacy, storage, reg, key), nil
|
2024-07-11 15:23:31 -05:00
|
|
|
}
|
|
|
|
}
|
2024-05-31 12:29:59 -05:00
|
|
|
|
2024-01-10 09:30:16 -06:00
|
|
|
for _, b := range builders {
|
2024-07-11 15:23:31 -05:00
|
|
|
g, err := b.GetAPIGroupInfo(scheme, codecs, optsGetter, dualWrite)
|
2024-01-10 09:30:16 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if g == nil || len(g.PrioritizedVersions) < 1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
err = server.InstallAPIGroup(g)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|