feat(apiserver): refactor the hooking mechanism for standalone apiserver (#93001)
It now uses a full build handler chain that can be passed from up top to `SetupConfig`. Co-authored-by: Charandas Batra <charandas.batra@grafana.com>
This commit is contained in:
co-authored by
Charandas Batra
parent
a1a18922e5
commit
368c4e53f1
+596
File diff suppressed because it is too large
Load Diff
@@ -122,7 +122,7 @@ func (o *APIServerOptions) Config(tracer tracing.Tracer) (*genericapiserver.Reco
|
|||||||
setting.BuildVersion,
|
setting.BuildVersion,
|
||||||
setting.BuildCommit,
|
setting.BuildCommit,
|
||||||
setting.BuildBranch,
|
setting.BuildBranch,
|
||||||
o.factory.GetOptionalMiddlewares(tracer)...,
|
o.factory.GetBuildHandlerChainFunc(tracer, o.builders),
|
||||||
)
|
)
|
||||||
return serverConfig, err
|
return serverConfig, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,16 +23,16 @@ import (
|
|||||||
k8stracing "k8s.io/component-base/tracing"
|
k8stracing "k8s.io/component-base/tracing"
|
||||||
"k8s.io/kube-openapi/pkg/common"
|
"k8s.io/kube-openapi/pkg/common"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/web"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apiserver/endpoints/filters"
|
"github.com/grafana/grafana/pkg/apiserver/endpoints/filters"
|
||||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/options"
|
"github.com/grafana/grafana/pkg/services/apiserver/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: this is a temporary hack to make rest.Connecter work with resource level routes
|
type BuildHandlerChainFunc = func(delegateHandler http.Handler, c *genericapiserver.Config) http.Handler
|
||||||
var pathRewriters = []filters.PathRewriter{
|
|
||||||
|
// PathRewriters is a temporary hack to make rest.Connecter work with resource level routes (TODO)
|
||||||
|
var PathRewriters = []filters.PathRewriter{
|
||||||
{
|
{
|
||||||
Pattern: regexp.MustCompile(`(/apis/scope.grafana.app/v0alpha1/namespaces/.*/)find/(.*)$`),
|
Pattern: regexp.MustCompile(`(/apis/scope.grafana.app/v0alpha1/namespaces/.*/)find/(.*)$`),
|
||||||
ReplaceFunc: func(matches []string) string {
|
ReplaceFunc: func(matches []string) string {
|
||||||
@@ -53,6 +53,37 @@ var pathRewriters = []filters.PathRewriter{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDefaultBuildHandlerChainFunc(builders []APIGroupBuilder) BuildHandlerChainFunc {
|
||||||
|
return func(delegateHandler http.Handler, c *genericapiserver.Config) http.Handler {
|
||||||
|
requestHandler, err := GetCustomRoutesHandler(
|
||||||
|
delegateHandler,
|
||||||
|
c.LoopbackClientConfig,
|
||||||
|
builders)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("could not build the request handler for specified API builders: %s", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needs to run last in request chain to function as expected, hence we register it first.
|
||||||
|
handler := filters.WithTracingHTTPLoggingAttributes(requestHandler)
|
||||||
|
|
||||||
|
// filters.WithRequester needs to be after the K8s chain because it depends on the K8s user in context
|
||||||
|
handler = filters.WithRequester(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
|
||||||
|
handler = genericapiserver.DefaultBuildHandlerChain(handler, c)
|
||||||
|
|
||||||
|
handler = filters.WithAcceptHeader(handler)
|
||||||
|
handler = filters.WithPathRewriters(handler, PathRewriters)
|
||||||
|
handler = k8stracing.WithTracing(handler, c.TracerProvider, "KubernetesAPI")
|
||||||
|
// Configure filters.WithPanicRecovery to not crash on panic
|
||||||
|
utilruntime.ReallyCrash = false
|
||||||
|
|
||||||
|
return handler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func SetupConfig(
|
func SetupConfig(
|
||||||
scheme *runtime.Scheme,
|
scheme *runtime.Scheme,
|
||||||
serverConfig *genericapiserver.RecommendedConfig,
|
serverConfig *genericapiserver.RecommendedConfig,
|
||||||
@@ -61,7 +92,7 @@ func SetupConfig(
|
|||||||
buildVersion string,
|
buildVersion string,
|
||||||
buildCommit string,
|
buildCommit string,
|
||||||
buildBranch string,
|
buildBranch string,
|
||||||
optionalMiddlewares ...web.Middleware,
|
buildHandlerChainFunc func(delegateHandler http.Handler, c *genericapiserver.Config) http.Handler,
|
||||||
) error {
|
) error {
|
||||||
defsGetter := GetOpenAPIDefinitions(builders)
|
defsGetter := GetOpenAPIDefinitions(builders)
|
||||||
serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(
|
serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(
|
||||||
@@ -91,38 +122,10 @@ func SetupConfig(
|
|||||||
serverConfig.OpenAPIV3Config.Info.Version = buildVersion
|
serverConfig.OpenAPIV3Config.Info.Version = buildVersion
|
||||||
|
|
||||||
serverConfig.SkipOpenAPIInstallation = false
|
serverConfig.SkipOpenAPIInstallation = false
|
||||||
serverConfig.BuildHandlerChainFunc = func(delegateHandler http.Handler, c *genericapiserver.Config) http.Handler {
|
serverConfig.BuildHandlerChainFunc = getDefaultBuildHandlerChainFunc(builders)
|
||||||
// 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()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Needs to run last in request chain to function as expected, hence we register it first.
|
if buildHandlerChainFunc != nil {
|
||||||
handler := filters.WithTracingHTTPLoggingAttributes(requestHandler)
|
serverConfig.BuildHandlerChainFunc = buildHandlerChainFunc
|
||||||
// filters.WithRequester needs to be after the K8s chain because it depends on the K8s user in context
|
|
||||||
handler = filters.WithRequester(handler)
|
|
||||||
handler = genericapiserver.DefaultBuildHandlerChain(handler, c)
|
|
||||||
|
|
||||||
// If optional middlewares include auth function, they need to happen before DefaultBuildHandlerChain
|
|
||||||
if len(optionalMiddlewares) > 0 {
|
|
||||||
for _, m := range optionalMiddlewares {
|
|
||||||
handler = m(handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handler = filters.WithAcceptHeader(handler)
|
|
||||||
handler = filters.WithPathRewriters(handler, pathRewriters)
|
|
||||||
handler = k8stracing.WithTracing(handler, serverConfig.TracerProvider, "KubernetesAPI")
|
|
||||||
// Configure filters.WithPanicRecovery to not crash on panic
|
|
||||||
utilruntime.ReallyCrash = false
|
|
||||||
|
|
||||||
return handler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serverConfig.EffectiveVersion = utilversion.DefaultKubeEffectiveVersion()
|
serverConfig.EffectiveVersion = utilversion.DefaultKubeEffectiveVersion()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type requestHandler struct {
|
|||||||
router *mux.Router
|
router *mux.Router
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAPIHandler(delegateHandler http.Handler, restConfig *restclient.Config, builders []APIGroupBuilder) (http.Handler, error) {
|
func GetCustomRoutesHandler(delegateHandler http.Handler, restConfig *restclient.Config, builders []APIGroupBuilder) (http.Handler, error) {
|
||||||
useful := false // only true if any routes exist anywhere
|
useful := false // only true if any routes exist anywhere
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
|
|
||||||
|
|||||||
@@ -336,6 +336,7 @@ func (s *service) start(ctx context.Context) error {
|
|||||||
s.cfg.BuildVersion,
|
s.cfg.BuildVersion,
|
||||||
s.cfg.BuildCommit,
|
s.cfg.BuildCommit,
|
||||||
s.cfg.BuildBranch,
|
s.cfg.BuildBranch,
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
"github.com/grafana/grafana/pkg/web"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@@ -32,8 +31,8 @@ type APIServerFactory interface {
|
|||||||
// Given the flags, what can we produce
|
// Given the flags, what can we produce
|
||||||
GetEnabled(runtime []RuntimeConfig) ([]schema.GroupVersion, error)
|
GetEnabled(runtime []RuntimeConfig) ([]schema.GroupVersion, error)
|
||||||
|
|
||||||
// Any optional middlewares this factory wants configured via apiserver's BuildHandlerChain facility
|
// Optional override for apiserver's BuildHandlerChainFunc, return nil if you want to use the grafana's default chain func defined in pkg/services/apiserver/builder/helper.go
|
||||||
GetOptionalMiddlewares(tracer tracing.Tracer) []web.Middleware
|
GetBuildHandlerChainFunc(tracer tracing.Tracer, builders []builder.APIGroupBuilder) builder.BuildHandlerChainFunc
|
||||||
|
|
||||||
// Make an API server for a given group+version
|
// Make an API server for a given group+version
|
||||||
MakeAPIServer(ctx context.Context, tracer tracing.Tracer, gv schema.GroupVersion) (builder.APIGroupBuilder, error)
|
MakeAPIServer(ctx context.Context, tracer tracing.Tracer, gv schema.GroupVersion) (builder.APIGroupBuilder, error)
|
||||||
@@ -52,8 +51,8 @@ func (p *DummyAPIFactory) GetOptions() options.OptionsProvider {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DummyAPIFactory) GetOptionalMiddlewares(_ tracing.Tracer) []web.Middleware {
|
func (p *DummyAPIFactory) GetBuildHandlerChainFunc(_ tracing.Tracer, builders []builder.APIGroupBuilder) builder.BuildHandlerChainFunc {
|
||||||
return []web.Middleware{}
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DummyAPIFactory) GetEnabled(runtime []RuntimeConfig) ([]schema.GroupVersion, error) {
|
func (p *DummyAPIFactory) GetEnabled(runtime []RuntimeConfig) ([]schema.GroupVersion, error) {
|
||||||
|
|||||||
+1
-1
@@ -142,7 +142,7 @@ func mwFromHandler(handler Handler) Middleware {
|
|||||||
// a convenience function that is provided for users of contexthandler package (standalone apiservers)
|
// a convenience function that is provided for users of contexthandler package (standalone apiservers)
|
||||||
// who have an implicit dependency on Macron in context but don't want to take a dependency on
|
// who have an implicit dependency on Macron in context but don't want to take a dependency on
|
||||||
// router additionally
|
// router additionally
|
||||||
func EmptyMacronMiddleware(next http.Handler) http.Handler {
|
func EmptyMacaronMiddleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||||
m := New()
|
m := New()
|
||||||
c := m.createContext(writer, request)
|
c := m.createContext(writer, request)
|
||||||
|
|||||||
Reference in New Issue
Block a user