mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 03:41:45 +00:00
Make genericapiserver handler chain customizable
This commit is contained in:
parent
7cfd0150e4
commit
68cee1d9ac
@ -231,8 +231,8 @@ func Run(s *options.ServerRunOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
routes.UIRedirect{}.Install(m.Mux, m.HandlerContainer)
|
||||
routes.Logs{}.Install(m.Mux, m.HandlerContainer)
|
||||
routes.UIRedirect{}.Install(m.HandlerContainer)
|
||||
routes.Logs{}.Install(m.HandlerContainer)
|
||||
|
||||
restOptionsFactory := restOptionsFactory{
|
||||
storageFactory: storageFactory,
|
||||
|
@ -100,6 +100,7 @@ pkg/controller/volume/statusupdater
|
||||
pkg/conversion/queryparams
|
||||
pkg/credentialprovider/aws
|
||||
pkg/genericapiserver/filters
|
||||
pkg/genericapiserver/mux
|
||||
pkg/genericapiserver/routes
|
||||
pkg/hyperkube
|
||||
pkg/kubelet/api
|
||||
|
@ -29,7 +29,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/golang/glog"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
@ -46,6 +45,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||
genericfilters "k8s.io/kubernetes/pkg/genericapiserver/filters"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/mux"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/openapi/common"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/options"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/routes"
|
||||
@ -94,9 +94,6 @@ type Config struct {
|
||||
// Required, the interface for serializing and converting objects to and from the wire
|
||||
Serializer runtime.NegotiatedSerializer
|
||||
|
||||
// If specified, all web services will be registered into this container
|
||||
RestfulContainer *restful.Container
|
||||
|
||||
// If specified, requests will be allocated a random timeout between this value, and twice this value.
|
||||
// Note that it is up to the request handlers to ignore or honor this timeout. In seconds.
|
||||
MinRequestTimeout int
|
||||
@ -152,6 +149,8 @@ type Config struct {
|
||||
// Port names should align with ports defined in ExtraServicePorts
|
||||
ExtraEndpointPorts []api.EndpointPort
|
||||
|
||||
// If non-zero, the "kubernetes" services uses this port as NodePort.
|
||||
// TODO(sttts): move into master
|
||||
KubernetesServiceNodePort int
|
||||
|
||||
// EnableOpenAPISupport enables OpenAPI support. Allow downstream customers to disable OpenAPI spec.
|
||||
@ -173,6 +172,9 @@ type Config struct {
|
||||
|
||||
// Predicate which is true for paths of long-running http requests
|
||||
LongRunningFunc genericfilters.LongRunningRequestCheck
|
||||
|
||||
// Build the handler chains by decorating the apiHandler.
|
||||
BuildHandlerChainsFunc func(apiHandler http.Handler, c *Config) (secure, insecure http.Handler)
|
||||
}
|
||||
|
||||
type ServingInfo struct {
|
||||
@ -323,6 +325,9 @@ func (c *Config) Complete() completedConfig {
|
||||
}
|
||||
c.ExternalHost = hostAndPort
|
||||
}
|
||||
if c.BuildHandlerChainsFunc == nil {
|
||||
c.BuildHandlerChainsFunc = DefaultBuildHandlerChain
|
||||
}
|
||||
return completedConfig{c}
|
||||
}
|
||||
|
||||
@ -391,15 +396,7 @@ func (c completedConfig) New() (*GenericAPIServer, error) {
|
||||
openAPIDefinitions: c.OpenAPIDefinitions,
|
||||
}
|
||||
|
||||
if c.RestfulContainer != nil {
|
||||
s.HandlerContainer = c.RestfulContainer
|
||||
} else {
|
||||
s.HandlerContainer = NewHandlerContainer(http.NewServeMux(), c.Serializer)
|
||||
}
|
||||
// Use CurlyRouter to be able to use regular expressions in paths. Regular expressions are required in paths for example for proxy (where the path is proxy/{kind}/{name}/{*})
|
||||
s.HandlerContainer.Router(restful.CurlyRouter{})
|
||||
s.Mux = apiserver.NewPathRecorderMux(s.HandlerContainer.ServeMux)
|
||||
apiserver.InstallServiceErrorHandler(s.Serializer, s.HandlerContainer)
|
||||
s.HandlerContainer = mux.NewAPIContainer(http.NewServeMux(), c.Serializer)
|
||||
|
||||
if c.ProxyDialer != nil || c.ProxyTLSClientConfig != nil {
|
||||
s.ProxyTransport = utilnet.SetTransportDefaults(&http.Transport{
|
||||
@ -409,50 +406,50 @@ func (c completedConfig) New() (*GenericAPIServer, error) {
|
||||
}
|
||||
|
||||
s.installAPI(c.Config)
|
||||
s.Handler, s.InsecureHandler = s.buildHandlerChains(c.Config, http.Handler(s.Mux.BaseMux().(*http.ServeMux)))
|
||||
|
||||
s.Handler, s.InsecureHandler = c.BuildHandlerChainsFunc(s.HandlerContainer.ServeMux, c.Config)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *GenericAPIServer) buildHandlerChains(c *Config, handler http.Handler) (secure http.Handler, insecure http.Handler) {
|
||||
// filters which insecure and secure have in common
|
||||
handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true")
|
||||
|
||||
// insecure filters
|
||||
insecure = handler
|
||||
insecure = genericfilters.WithPanicRecovery(insecure, c.RequestContextMapper)
|
||||
insecure = apiserverfilters.WithRequestInfo(insecure, NewRequestInfoResolver(c), c.RequestContextMapper)
|
||||
insecure = api.WithRequestContext(insecure, c.RequestContextMapper)
|
||||
insecure = genericfilters.WithTimeoutForNonLongRunningRequests(insecure, c.LongRunningFunc)
|
||||
|
||||
// secure filters
|
||||
func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) (secure, insecure http.Handler) {
|
||||
attributeGetter := apiserverfilters.NewRequestAttributeGetter(c.RequestContextMapper)
|
||||
secure = handler
|
||||
secure = apiserverfilters.WithAuthorization(secure, attributeGetter, c.Authorizer)
|
||||
secure = apiserverfilters.WithImpersonation(secure, c.RequestContextMapper, c.Authorizer)
|
||||
secure = apiserverfilters.WithAudit(secure, attributeGetter, c.AuditWriter) // before impersonation to read original user
|
||||
secure = authhandlers.WithAuthentication(secure, c.RequestContextMapper, c.Authenticator, authhandlers.Unauthorized(c.SupportsBasicAuth))
|
||||
secure = genericfilters.WithPanicRecovery(secure, c.RequestContextMapper)
|
||||
secure = apiserverfilters.WithRequestInfo(secure, NewRequestInfoResolver(c), c.RequestContextMapper)
|
||||
secure = api.WithRequestContext(secure, c.RequestContextMapper)
|
||||
secure = genericfilters.WithTimeoutForNonLongRunningRequests(secure, c.LongRunningFunc)
|
||||
secure = genericfilters.WithMaxInFlightLimit(secure, c.MaxRequestsInFlight, c.LongRunningFunc)
|
||||
|
||||
return
|
||||
generic := func(handler http.Handler) http.Handler {
|
||||
handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true")
|
||||
handler = genericfilters.WithPanicRecovery(handler, c.RequestContextMapper)
|
||||
handler = apiserverfilters.WithRequestInfo(handler, NewRequestInfoResolver(c), c.RequestContextMapper)
|
||||
handler = api.WithRequestContext(handler, c.RequestContextMapper)
|
||||
handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.LongRunningFunc)
|
||||
handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.LongRunningFunc)
|
||||
return handler
|
||||
}
|
||||
audit := func(handler http.Handler) http.Handler {
|
||||
return apiserverfilters.WithAudit(handler, attributeGetter, c.AuditWriter)
|
||||
}
|
||||
protect := func(handler http.Handler) http.Handler {
|
||||
handler = apiserverfilters.WithAuthorization(handler, attributeGetter, c.Authorizer)
|
||||
handler = apiserverfilters.WithImpersonation(handler, c.RequestContextMapper, c.Authorizer)
|
||||
handler = audit(handler) // before impersonation to read original user
|
||||
handler = authhandlers.WithAuthentication(handler, c.RequestContextMapper, c.Authenticator, authhandlers.Unauthorized(c.SupportsBasicAuth))
|
||||
return handler
|
||||
}
|
||||
|
||||
return generic(protect(apiHandler)), generic(audit(apiHandler))
|
||||
}
|
||||
|
||||
func (s *GenericAPIServer) installAPI(c *Config) {
|
||||
if c.EnableIndex {
|
||||
routes.Index{}.Install(s.Mux, s.HandlerContainer)
|
||||
routes.Index{}.Install(s.HandlerContainer)
|
||||
}
|
||||
if c.EnableSwaggerSupport && c.EnableSwaggerUI {
|
||||
routes.SwaggerUI{}.Install(s.Mux, s.HandlerContainer)
|
||||
routes.SwaggerUI{}.Install(s.HandlerContainer)
|
||||
}
|
||||
if c.EnableProfiling {
|
||||
routes.Profiling{}.Install(s.Mux, s.HandlerContainer)
|
||||
routes.Profiling{}.Install(s.HandlerContainer)
|
||||
}
|
||||
if c.EnableVersion {
|
||||
routes.Version{}.Install(s.Mux, s.HandlerContainer)
|
||||
routes.Version{}.Install(s.HandlerContainer)
|
||||
}
|
||||
s.HandlerContainer.Add(s.DynamicApisDiscovery())
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/apimachinery/registered"
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
genericmux "k8s.io/kubernetes/pkg/genericapiserver/mux"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/openapi"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/openapi/common"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
@ -118,9 +119,8 @@ type GenericAPIServer struct {
|
||||
// requestContextMapper provides a way to get the context for a request. It may be nil.
|
||||
requestContextMapper api.RequestContextMapper
|
||||
|
||||
Mux *apiserver.PathRecorderMux
|
||||
HandlerContainer *restful.Container
|
||||
MasterCount int
|
||||
// The registered APIs
|
||||
HandlerContainer *genericmux.APIContainer
|
||||
|
||||
SecureServingInfo *ServingInfo
|
||||
InsecureServingInfo *ServingInfo
|
||||
@ -128,13 +128,9 @@ type GenericAPIServer struct {
|
||||
// ExternalAddress is the address (hostname or IP and port) that should be used in
|
||||
// external (public internet) URLs for this GenericAPIServer.
|
||||
ExternalAddress string
|
||||
|
||||
// ClusterIP is the IP address of the GenericAPIServer within the cluster.
|
||||
ClusterIP net.IP
|
||||
PublicReadWritePort int
|
||||
ServiceReadWriteIP net.IP
|
||||
ServiceReadWritePort int
|
||||
ExtraServicePorts []api.ServicePort
|
||||
ExtraEndpointPorts []api.EndpointPort
|
||||
ClusterIP net.IP
|
||||
|
||||
// storage contains the RESTful endpoints exposed by this GenericAPIServer
|
||||
storage map[string]rest.Storage
|
||||
@ -150,26 +146,31 @@ type GenericAPIServer struct {
|
||||
// Used for custom proxy dialing, and proxy TLS options
|
||||
ProxyTransport http.RoundTripper
|
||||
|
||||
KubernetesServiceNodePort int
|
||||
|
||||
// Map storing information about all groups to be exposed in discovery response.
|
||||
// The map is from name to the group.
|
||||
apiGroupsForDiscoveryLock sync.RWMutex
|
||||
apiGroupsForDiscovery map[string]unversioned.APIGroup
|
||||
|
||||
// See Config.$name for documentation of these flags
|
||||
|
||||
enableOpenAPISupport bool
|
||||
openAPIInfo spec.Info
|
||||
openAPIDefaultResponse spec.Response
|
||||
openAPIDefinitions *common.OpenAPIDefinitions
|
||||
|
||||
// PostStartHooks are each called after the server has started listening, in a separate go func for each
|
||||
// with no guaranteee of ordering between them. The map key is a name used for error reporting.
|
||||
// It may kill the process with a panic if it wishes to by returning an error
|
||||
postStartHooks map[string]PostStartHookFunc
|
||||
postStartHookLock sync.Mutex
|
||||
postStartHooksCalled bool
|
||||
|
||||
// See Config.$name for documentation of these flags:
|
||||
|
||||
enableOpenAPISupport bool
|
||||
openAPIInfo spec.Info
|
||||
openAPIDefaultResponse spec.Response
|
||||
openAPIDefinitions *common.OpenAPIDefinitions
|
||||
MasterCount int
|
||||
KubernetesServiceNodePort int // TODO(sttts): move into master
|
||||
PublicReadWritePort int
|
||||
ServiceReadWriteIP net.IP
|
||||
ServiceReadWritePort int
|
||||
ExtraServicePorts []api.ServicePort
|
||||
ExtraEndpointPorts []api.EndpointPort
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -191,33 +192,6 @@ func (s *GenericAPIServer) MinRequestTimeout() time.Duration {
|
||||
return s.minRequestTimeout
|
||||
}
|
||||
|
||||
// HandleWithAuth adds an http.Handler for pattern to an http.ServeMux
|
||||
// Applies the same authentication and authorization (if any is configured)
|
||||
// to the request is used for the GenericAPIServer's built-in endpoints.
|
||||
func (s *GenericAPIServer) HandleWithAuth(pattern string, handler http.Handler) {
|
||||
// TODO: Add a way for plugged-in endpoints to translate their
|
||||
// URLs into attributes that an Authorizer can understand, and have
|
||||
// sensible policy defaults for plugged-in endpoints. This will be different
|
||||
// for generic endpoints versus REST object endpoints.
|
||||
// TODO: convert to go-restful
|
||||
s.Mux.Handle(pattern, handler)
|
||||
}
|
||||
|
||||
// HandleFuncWithAuth adds an http.Handler for pattern to an http.ServeMux
|
||||
// Applies the same authentication and authorization (if any is configured)
|
||||
// to the request is used for the GenericAPIServer's built-in endpoints.
|
||||
func (s *GenericAPIServer) HandleFuncWithAuth(pattern string, handler func(http.ResponseWriter, *http.Request)) {
|
||||
// TODO: convert to go-restful
|
||||
s.Mux.HandleFunc(pattern, handler)
|
||||
}
|
||||
|
||||
func NewHandlerContainer(mux *http.ServeMux, s runtime.NegotiatedSerializer) *restful.Container {
|
||||
container := restful.NewContainer()
|
||||
container.ServeMux = mux
|
||||
apiserver.InstallRecoverHandler(s, container)
|
||||
return container
|
||||
}
|
||||
|
||||
func (s *GenericAPIServer) Run() {
|
||||
// install APIs which depend on other APIs to be installed
|
||||
if s.enableSwaggerSupport {
|
||||
@ -227,7 +201,7 @@ func (s *GenericAPIServer) Run() {
|
||||
s.InstallOpenAPI()
|
||||
}
|
||||
|
||||
if s.SecureServingInfo != nil {
|
||||
if s.SecureServingInfo != nil && s.Handler != nil {
|
||||
secureServer := &http.Server{
|
||||
Addr: s.SecureServingInfo.BindAddress,
|
||||
Handler: s.Handler,
|
||||
@ -282,7 +256,7 @@ func (s *GenericAPIServer) Run() {
|
||||
}()
|
||||
}
|
||||
|
||||
if s.InsecureServingInfo != nil {
|
||||
if s.InsecureServingInfo != nil && s.InsecureHandler != nil {
|
||||
insecureServer := &http.Server{
|
||||
Addr: s.InsecureServingInfo.BindAddress,
|
||||
Handler: s.InsecureHandler,
|
||||
@ -343,14 +317,14 @@ func (s *GenericAPIServer) InstallAPIGroup(apiGroupInfo *APIGroupInfo) error {
|
||||
apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion
|
||||
}
|
||||
|
||||
if err := apiGroupVersion.InstallREST(s.HandlerContainer); err != nil {
|
||||
if err := apiGroupVersion.InstallREST(s.HandlerContainer.Container); err != nil {
|
||||
return fmt.Errorf("Unable to setup API %v: %v", apiGroupInfo, err)
|
||||
}
|
||||
}
|
||||
// Install the version handler.
|
||||
if apiGroupInfo.IsLegacyGroup {
|
||||
// Add a handler at /api to enumerate the supported api versions.
|
||||
apiserver.AddApiWebService(s.Serializer, s.HandlerContainer, apiPrefix, func(req *restful.Request) *unversioned.APIVersions {
|
||||
apiserver.AddApiWebService(s.Serializer, s.HandlerContainer.Container, apiPrefix, func(req *restful.Request) *unversioned.APIVersions {
|
||||
apiVersionsForDiscovery := unversioned.APIVersions{
|
||||
ServerAddressByClientCIDRs: s.getServerAddressByClientCIDRs(req.Request),
|
||||
Versions: apiVersions,
|
||||
@ -487,7 +461,7 @@ func (s *GenericAPIServer) getSwaggerConfig() *swagger.Config {
|
||||
// of swagger, so that other resource types show up in the documentation.
|
||||
func (s *GenericAPIServer) InstallSwaggerAPI() {
|
||||
// Enable swagger UI and discovery API
|
||||
swagger.RegisterSwaggerService(*s.getSwaggerConfig(), s.HandlerContainer)
|
||||
swagger.RegisterSwaggerService(*s.getSwaggerConfig(), s.HandlerContainer.Container)
|
||||
}
|
||||
|
||||
// InstallOpenAPI installs spec endpoints for each web service.
|
||||
@ -508,7 +482,7 @@ func (s *GenericAPIServer) InstallOpenAPI() {
|
||||
Info: &info,
|
||||
DefaultResponse: &s.openAPIDefaultResponse,
|
||||
OpenAPIDefinitions: s.openAPIDefinitions,
|
||||
}, s.HandlerContainer)
|
||||
}, s.HandlerContainer.Container)
|
||||
if err != nil {
|
||||
glog.Fatalf("Failed to register open api spec for %v: %v", w.RootPath(), err)
|
||||
}
|
||||
@ -521,7 +495,7 @@ func (s *GenericAPIServer) InstallOpenAPI() {
|
||||
Info: &s.openAPIInfo,
|
||||
DefaultResponse: &s.openAPIDefaultResponse,
|
||||
OpenAPIDefinitions: s.openAPIDefinitions,
|
||||
}, s.HandlerContainer)
|
||||
}, s.HandlerContainer.Container)
|
||||
if err != nil {
|
||||
glog.Fatalf("Failed to register open api spec for root: %v", err)
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -32,12 +33,11 @@ import (
|
||||
"k8s.io/kubernetes/pkg/api/rest"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apimachinery/registered"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
genericmux "k8s.io/kubernetes/pkg/genericapiserver/mux"
|
||||
ipallocator "k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
||||
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
|
||||
utilnet "k8s.io/kubernetes/pkg/util/net"
|
||||
@ -138,7 +138,7 @@ func TestInstallAPIGroups(t *testing.T) {
|
||||
s.InstallAPIGroup(&apiGroupsInfo[i])
|
||||
}
|
||||
|
||||
server := httptest.NewServer(s.HandlerContainer.ServeMux)
|
||||
server := httptest.NewServer(s.InsecureHandler)
|
||||
defer server.Close()
|
||||
validPaths := []string{
|
||||
// "/api"
|
||||
@ -158,41 +158,64 @@ func TestInstallAPIGroups(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewHandlerContainer verifies that NewHandlerContainer uses the
|
||||
// mux provided
|
||||
func TestNewHandlerContainer(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
mux := http.NewServeMux()
|
||||
container := NewHandlerContainer(mux, nil)
|
||||
assert.Equal(mux, container.ServeMux, "ServerMux's do not match")
|
||||
}
|
||||
|
||||
// TestHandleWithAuth verifies HandleWithAuth adds the path
|
||||
// to the MuxHelper.RegisteredPaths.
|
||||
func TestHandleWithAuth(t *testing.T) {
|
||||
etcdserver, _, assert := setUp(t)
|
||||
// TestCustomHandlerChain verifies the handler chain with custom handler chain builder functions.
|
||||
func TestCustomHandlerChain(t *testing.T) {
|
||||
etcdserver, config, _ := setUp(t)
|
||||
defer etcdserver.Terminate(t)
|
||||
|
||||
server := &GenericAPIServer{}
|
||||
server.Mux = apiserver.NewPathRecorderMux(http.NewServeMux())
|
||||
handler := func(r http.ResponseWriter, w *http.Request) { w.Write(nil) }
|
||||
server.HandleWithAuth("/test", http.HandlerFunc(handler))
|
||||
var protected, called bool
|
||||
|
||||
assert.Contains(server.Mux.HandledPaths(), "/test", "Path not found in MuxHelper")
|
||||
}
|
||||
config.Serializer = api.Codecs
|
||||
config.BuildHandlerChainsFunc = func(apiHandler http.Handler, c *Config) (secure, insecure http.Handler) {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
protected = true
|
||||
apiHandler.ServeHTTP(w, req)
|
||||
}), http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
protected = false
|
||||
apiHandler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
handler := http.HandlerFunc(func(r http.ResponseWriter, req *http.Request) {
|
||||
called = true
|
||||
})
|
||||
|
||||
// TestHandleFuncWithAuth verifies HandleFuncWithAuth adds the path
|
||||
// to the MuxHelper.RegisteredPaths.
|
||||
func TestHandleFuncWithAuth(t *testing.T) {
|
||||
etcdserver, _, assert := setUp(t)
|
||||
defer etcdserver.Terminate(t)
|
||||
s, err := config.Complete().New()
|
||||
if err != nil {
|
||||
t.Fatalf("Error in bringing up the server: %v", err)
|
||||
}
|
||||
|
||||
server := &GenericAPIServer{}
|
||||
server.Mux = apiserver.NewPathRecorderMux(http.NewServeMux())
|
||||
handler := func(r http.ResponseWriter, w *http.Request) { w.Write(nil) }
|
||||
server.HandleFuncWithAuth("/test", handler)
|
||||
s.HandlerContainer.NonSwaggerRoutes.Handle("/nonswagger", handler)
|
||||
s.HandlerContainer.SecretRoutes.Handle("/secret", handler)
|
||||
|
||||
assert.Contains(server.Mux.HandledPaths(), "/test", "Path not found in MuxHelper")
|
||||
type Test struct {
|
||||
handler http.Handler
|
||||
path string
|
||||
protected bool
|
||||
}
|
||||
for i, test := range []Test{
|
||||
{s.Handler, "/nonswagger", true},
|
||||
{s.Handler, "/secret", true},
|
||||
{s.InsecureHandler, "/nonswagger", false},
|
||||
{s.InsecureHandler, "/secret", false},
|
||||
} {
|
||||
protected, called = false, false
|
||||
|
||||
var w io.Reader
|
||||
req, err := http.NewRequest("GET", test.path, w)
|
||||
if err != nil {
|
||||
t.Errorf("%d: Unexpected http error: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
test.handler.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
if !called {
|
||||
t.Errorf("%d: Expected handler to be called.", i)
|
||||
}
|
||||
if test.protected != protected {
|
||||
t.Errorf("%d: Expected protected=%v, got protected=%v.", i, test.protected, protected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestNotRestRoutesHaveAuth checks that special non-routes are behind authz/authn.
|
||||
@ -267,7 +290,7 @@ func TestInstallSwaggerAPI(t *testing.T) {
|
||||
|
||||
mux := http.NewServeMux()
|
||||
server := &GenericAPIServer{}
|
||||
server.HandlerContainer = NewHandlerContainer(mux, nil)
|
||||
server.HandlerContainer = genericmux.NewAPIContainer(mux, nil)
|
||||
|
||||
// Ensure swagger isn't installed without the call
|
||||
ws := server.HandlerContainer.RegisteredWebServices()
|
||||
@ -286,7 +309,7 @@ func TestInstallSwaggerAPI(t *testing.T) {
|
||||
|
||||
// Empty externalHost verification
|
||||
mux = http.NewServeMux()
|
||||
server.HandlerContainer = NewHandlerContainer(mux, nil)
|
||||
server.HandlerContainer = genericmux.NewAPIContainer(mux, nil)
|
||||
server.ExternalAddress = ""
|
||||
server.ClusterIP = net.IPv4(10, 10, 10, 10)
|
||||
server.PublicReadWritePort = 1010
|
||||
@ -328,7 +351,7 @@ func TestDiscoveryAtAPIS(t *testing.T) {
|
||||
master, etcdserver, _, assert := newMaster(t)
|
||||
defer etcdserver.Terminate(t)
|
||||
|
||||
server := httptest.NewServer(master.HandlerContainer.ServeMux)
|
||||
server := httptest.NewServer(master.InsecureHandler)
|
||||
groupList, err := getGroupList(server)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
|
52
pkg/genericapiserver/mux/container.go
Normal file
52
pkg/genericapiserver/mux/container.go
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
package mux
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// APIContainer is a restful container which in addition support registering
|
||||
// handlers that do not show up in swagger or in /
|
||||
type APIContainer struct {
|
||||
*restful.Container
|
||||
NonSwaggerRoutes PathRecorderMux
|
||||
SecretRoutes Mux
|
||||
}
|
||||
|
||||
// NewAPIContainer constructs a new container for APIs
|
||||
func NewAPIContainer(mux *http.ServeMux, s runtime.NegotiatedSerializer) *APIContainer {
|
||||
c := APIContainer{
|
||||
Container: restful.NewContainer(),
|
||||
NonSwaggerRoutes: PathRecorderMux{
|
||||
mux: mux,
|
||||
},
|
||||
SecretRoutes: mux,
|
||||
}
|
||||
c.Container.ServeMux = mux
|
||||
c.Container.Router(restful.CurlyRouter{}) // e.g. for proxy/{kind}/{name}/{*}
|
||||
|
||||
apiserver.InstallRecoverHandler(s, c.Container)
|
||||
apiserver.InstallServiceErrorHandler(s, c.Container)
|
||||
|
||||
return &c
|
||||
}
|
40
pkg/genericapiserver/mux/container_test.go
Normal file
40
pkg/genericapiserver/mux/container_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
package mux
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewAPIContainer(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
c := NewAPIContainer(mux, nil)
|
||||
assert.Equal(t, mux, c.SecretRoutes.(*http.ServeMux), "SecretRoutes ServeMux's do not match")
|
||||
assert.Equal(t, mux, c.Container.ServeMux, "Container ServeMux's do not match")
|
||||
}
|
||||
|
||||
func TestSecretHandlers(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
c := NewAPIContainer(mux, nil)
|
||||
c.SecretRoutes.HandleFunc("/secret", func(http.ResponseWriter, *http.Request) {})
|
||||
c.NonSwaggerRoutes.HandleFunc("/nonswagger", func(http.ResponseWriter, *http.Request) {})
|
||||
assert.NotContains(t, c.NonSwaggerRoutes.HandledPaths(), "/secret")
|
||||
assert.Contains(t, c.NonSwaggerRoutes.HandledPaths(), "/nonswagger")
|
||||
}
|
18
pkg/genericapiserver/mux/doc.go
Normal file
18
pkg/genericapiserver/mux/doc.go
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
// Package mux contains abstractions for http multiplexing of APIs.
|
||||
package mux
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
Copyright 2016 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.
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package apiserver
|
||||
package mux
|
||||
|
||||
import (
|
||||
"net/http"
|
@ -20,19 +20,17 @@ import (
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/mux"
|
||||
)
|
||||
|
||||
// Index provides a webservice for the http root / listing all known paths.
|
||||
type Index struct{}
|
||||
|
||||
// Install adds the Index webservice to the given mux.
|
||||
func (i Index) Install(mux *apiserver.PathRecorderMux, c *restful.Container) {
|
||||
// do not register this using restful Webservice since we do not want to surface this in api docs.
|
||||
mux.BaseMux().HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
func (i Index) Install(c *mux.APIContainer) {
|
||||
c.SecretRoutes.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
status := http.StatusOK
|
||||
if r.URL.Path != "/" && r.URL.Path != "/index.html" {
|
||||
// Since "/" matches all paths, handleIndex is called for all paths for which there is no handler registered.
|
||||
@ -45,7 +43,7 @@ func (i Index) Install(mux *apiserver.PathRecorderMux, c *restful.Container) {
|
||||
handledPaths = append(handledPaths, ws.RootPath())
|
||||
}
|
||||
// Extract the paths handled using mux handler.
|
||||
handledPaths = append(handledPaths, mux.HandledPaths()...)
|
||||
handledPaths = append(handledPaths, c.NonSwaggerRoutes.HandledPaths()...)
|
||||
sort.Strings(handledPaths)
|
||||
apiserver.WriteRawJSON(status, unversioned.RootPaths{Paths: handledPaths}, w)
|
||||
})
|
||||
|
@ -17,18 +17,17 @@ limitations under the License.
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"net/http/pprof"
|
||||
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/mux"
|
||||
)
|
||||
|
||||
// Profiling adds handlers for pprof under /debug/pprof.
|
||||
type Profiling struct{}
|
||||
|
||||
// Install adds the Profiling webservice to the given mux.
|
||||
func (d Profiling) Install(mux *apiserver.PathRecorderMux, c *restful.Container) {
|
||||
mux.BaseMux().HandleFunc("/debug/pprof/", pprof.Index)
|
||||
mux.BaseMux().HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
mux.BaseMux().HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
func (d Profiling) Install(c *mux.APIContainer) {
|
||||
c.SecretRoutes.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
c.SecretRoutes.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
c.SecretRoutes.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
}
|
||||
|
@ -20,9 +20,8 @@ import (
|
||||
"net/http"
|
||||
|
||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/mux"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/routes/data/swagger"
|
||||
)
|
||||
|
||||
@ -30,12 +29,12 @@ import (
|
||||
type SwaggerUI struct{}
|
||||
|
||||
// Install adds the SwaggerUI webservice to the given mux.
|
||||
func (l SwaggerUI) Install(mux *apiserver.PathRecorderMux, c *restful.Container) {
|
||||
func (l SwaggerUI) Install(c *mux.APIContainer) {
|
||||
fileServer := http.FileServer(&assetfs.AssetFS{
|
||||
Asset: swagger.Asset,
|
||||
AssetDir: swagger.AssetDir,
|
||||
Prefix: "third_party/swagger-ui",
|
||||
})
|
||||
prefix := "/swagger-ui/"
|
||||
mux.Handle(prefix, http.StripPrefix(prefix, fileServer))
|
||||
c.NonSwaggerRoutes.Handle(prefix, http.StripPrefix(prefix, fileServer))
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/mux"
|
||||
"k8s.io/kubernetes/pkg/version"
|
||||
)
|
||||
|
||||
@ -29,7 +30,7 @@ import (
|
||||
type Version struct{}
|
||||
|
||||
// Install registers the APIServer's `/version` handler.
|
||||
func (v Version) Install(mux *apiserver.PathRecorderMux, c *restful.Container) {
|
||||
func (v Version) Install(c *mux.APIContainer) {
|
||||
// Set up a service to return the git code version.
|
||||
versionWS := new(restful.WebService)
|
||||
versionWS.Path("/version")
|
||||
|
@ -192,10 +192,10 @@ func (c completedConfig) New() (*Master, error) {
|
||||
}
|
||||
|
||||
if c.EnableUISupport {
|
||||
routes.UIRedirect{}.Install(s.Mux, s.HandlerContainer)
|
||||
routes.UIRedirect{}.Install(s.HandlerContainer)
|
||||
}
|
||||
if c.EnableLogsSupport {
|
||||
routes.Logs{}.Install(s.Mux, s.HandlerContainer)
|
||||
routes.Logs{}.Install(s.HandlerContainer)
|
||||
}
|
||||
|
||||
m := &Master{
|
||||
@ -284,12 +284,12 @@ func (m *Master) InstallAPIs(c *Config) {
|
||||
Help: "The time since the last successful synchronization of the SSH tunnels for proxy requests.",
|
||||
}, func() float64 { return float64(m.tunneler.SecondsSinceSync()) })
|
||||
}
|
||||
healthz.InstallHandler(m.Mux, healthzChecks...)
|
||||
healthz.InstallHandler(&m.HandlerContainer.NonSwaggerRoutes, healthzChecks...)
|
||||
|
||||
if c.GenericConfig.EnableProfiling {
|
||||
routes.MetricsWithReset{}.Install(m.Mux, m.HandlerContainer)
|
||||
routes.MetricsWithReset{}.Install(m.HandlerContainer)
|
||||
} else {
|
||||
routes.DefaultMetrics{}.Install(m.Mux, m.HandlerContainer)
|
||||
routes.DefaultMetrics{}.Install(m.HandlerContainer)
|
||||
}
|
||||
|
||||
// Install third party resource support if requested
|
||||
@ -612,10 +612,10 @@ func (m *Master) InstallThirdPartyResource(rsrc *extensions.ThirdPartyResource)
|
||||
// the group with the new API
|
||||
if m.hasThirdPartyGroupStorage(path) {
|
||||
m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedataetcd.REST), apiGroup)
|
||||
return thirdparty.UpdateREST(m.HandlerContainer)
|
||||
return thirdparty.UpdateREST(m.HandlerContainer.Container)
|
||||
}
|
||||
|
||||
if err := thirdparty.InstallREST(m.HandlerContainer); err != nil {
|
||||
if err := thirdparty.InstallREST(m.HandlerContainer.Container); err != nil {
|
||||
glog.Errorf("Unable to setup thirdparty api: %v", err)
|
||||
}
|
||||
m.HandlerContainer.Add(apiserver.NewGroupWebService(api.Codecs, path, apiGroup))
|
||||
|
@ -22,13 +22,13 @@ import (
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/mux"
|
||||
)
|
||||
|
||||
// Logs adds handlers for the /logs path serving log files from /var/log.
|
||||
type Logs struct{}
|
||||
|
||||
func (l Logs) Install(mux *apiserver.PathRecorderMux, c *restful.Container) {
|
||||
func (l Logs) Install(c *mux.APIContainer) {
|
||||
// use restful: ws.Route(ws.GET("/logs/{logpath:*}").To(fileHandler))
|
||||
// See github.com/emicklei/go-restful/blob/master/examples/restful-serve-static.go
|
||||
ws := new(restful.WebService)
|
||||
|
@ -20,28 +20,27 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
apiservermetrics "k8s.io/kubernetes/pkg/apiserver/metrics"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/mux"
|
||||
etcdmetrics "k8s.io/kubernetes/pkg/storage/etcd/metrics"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// DefaultMetrics installs the default prometheus metrics handler
|
||||
type DefaultMetrics struct{}
|
||||
|
||||
func (m DefaultMetrics) Install(mux *apiserver.PathRecorderMux, c *restful.Container) {
|
||||
mux.HandleFunc("/metrics", prometheus.Handler().ServeHTTP)
|
||||
func (m DefaultMetrics) Install(c *mux.APIContainer) {
|
||||
c.NonSwaggerRoutes.Handle("/metrics", prometheus.Handler())
|
||||
}
|
||||
|
||||
// MetricsWithReset install the prometheus metrics handler extended with support for the DELETE method
|
||||
// which resets the metrics.
|
||||
type MetricsWithReset struct{}
|
||||
|
||||
func (m MetricsWithReset) Install(mux *apiserver.PathRecorderMux, c *restful.Container) {
|
||||
func (m MetricsWithReset) Install(c *mux.APIContainer) {
|
||||
defaultMetricsHandler := prometheus.Handler().ServeHTTP
|
||||
mux.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) {
|
||||
c.NonSwaggerRoutes.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method == "DELETE" {
|
||||
apiservermetrics.Reset()
|
||||
etcdmetrics.Reset()
|
||||
|
@ -19,9 +19,7 @@ package routes
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/mux"
|
||||
)
|
||||
|
||||
const dashboardPath = "/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard"
|
||||
@ -29,8 +27,8 @@ const dashboardPath = "/api/v1/proxy/namespaces/kube-system/services/kubernetes-
|
||||
// UIRediect redirects /ui to the kube-ui proxy path.
|
||||
type UIRedirect struct{}
|
||||
|
||||
func (r UIRedirect) Install(mux *apiserver.PathRecorderMux, c *restful.Container) {
|
||||
mux.HandleFunc("/ui/", func(w http.ResponseWriter, r *http.Request) {
|
||||
func (r UIRedirect) Install(c *mux.APIContainer) {
|
||||
c.NonSwaggerRoutes.HandleFunc("/ui/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, dashboardPath, http.StatusTemporaryRedirect)
|
||||
})
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ func TestMasterExportsSymbols(t *testing.T) {
|
||||
_ = &master.Config{
|
||||
GenericConfig: &genericapiserver.Config{
|
||||
EnableSwaggerSupport: false,
|
||||
RestfulContainer: nil,
|
||||
},
|
||||
EnableCoreControllers: false,
|
||||
EnableUISupport: false,
|
||||
|
Loading…
Reference in New Issue
Block a user