package builder import ( "fmt" "net/http" "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus" serverstorage "k8s.io/apiserver/pkg/server/storage" restclient "k8s.io/client-go/rest" klog "k8s.io/klog/v2" "k8s.io/kube-openapi/pkg/spec3" ) type requestHandler struct { router *mux.Router } func GetCustomRoutesHandler(delegateHandler http.Handler, restConfig *restclient.Config, builders []APIGroupBuilder, metricsRegistry prometheus.Registerer, apiResourceConfig *serverstorage.ResourceConfig) (http.Handler, error) { useful := false // only true if any routes exist anywhere router := mux.NewRouter() metrics := NewCustomRouteMetrics(metricsRegistry) for _, builder := range builders { provider, ok := builder.(APIGroupRouteProvider) if !ok || provider == nil { continue } for _, gv := range GetGroupVersions(builder) { // filter out api groups that are disabled in APIEnablementOptions gvr := gv.WithResource("") if apiResourceConfig != nil && !apiResourceConfig.ResourceEnabled(gvr) { klog.InfoS("Skipping custom route handler for disabled group version", "gv", gv.String()) continue } routes := provider.GetAPIRoutes(gv) if routes == nil { continue } prefix := "/apis/" + gv.String() // Root handlers var sub *mux.Router for _, route := range routes.Root { if sub == nil { sub = router.PathPrefix(prefix).Subrouter() sub.MethodNotAllowedHandler = &methodNotAllowedHandler{} } useful = true methods, err := methodsFromSpec(route.Path, route.Spec) if err != nil { return nil, err } instrumentedHandler := metrics.InstrumentHandler( gv.Group, gv.Version, route.Path, // Use path as resource identifier route.Handler, ) sub.HandleFunc("/"+route.Path, instrumentedHandler). Methods(methods...) } // Namespace handlers sub = nil prefix += "/namespaces/{namespace}" for _, route := range routes.Namespace { if sub == nil { sub = router.PathPrefix(prefix).Subrouter() sub.MethodNotAllowedHandler = &methodNotAllowedHandler{} } useful = true methods, err := methodsFromSpec(route.Path, route.Spec) if err != nil { return nil, err } instrumentedHandler := metrics.InstrumentHandler( gv.Group, gv.Version, route.Path, // Use path as resource identifier route.Handler, ) sub.HandleFunc("/"+route.Path, instrumentedHandler). Methods(methods...) } } } if !useful { return delegateHandler, nil } // Per Gorilla Mux issue here: https://github.com/gorilla/mux/issues/616#issuecomment-798807509 // default handler must come last router.PathPrefix("/").Handler(delegateHandler) return &requestHandler{ router: router, }, nil } func (h *requestHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { h.router.ServeHTTP(w, req) } func methodsFromSpec(slug string, props *spec3.PathProps) ([]string, error) { if props == nil { return []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, nil } methods := make([]string, 0) if props.Get != nil { methods = append(methods, "GET") } if props.Post != nil { methods = append(methods, "POST") } if props.Put != nil { methods = append(methods, "PUT") } if props.Patch != nil { methods = append(methods, "PATCH") } if props.Delete != nil { methods = append(methods, "DELETE") } if len(methods) == 0 { return nil, fmt.Errorf("invalid OpenAPI Spec for slug=%s without any methods in PathProps", slug) } return methods, nil } type methodNotAllowedHandler struct{} func (h *methodNotAllowedHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { w.WriteHeader(405) // method not allowed }