diff --git a/staging/src/k8s.io/apiserver/pkg/server/BUILD b/staging/src/k8s.io/apiserver/pkg/server/BUILD index 058f8d37a25..80afc805f86 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/server/BUILD @@ -115,6 +115,7 @@ go_library( "//vendor/k8s.io/klog:go_default_library", "//vendor/k8s.io/kube-openapi/pkg/builder:go_default_library", "//vendor/k8s.io/kube-openapi/pkg/common:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/handler:go_default_library", "//vendor/k8s.io/kube-openapi/pkg/util:go_default_library", "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", ], diff --git a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go index 2c31271405b..450488e13dc 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go +++ b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go @@ -25,6 +25,7 @@ import ( "time" systemd "github.com/coreos/go-systemd/daemon" + "github.com/go-openapi/spec" "k8s.io/klog" "k8s.io/apimachinery/pkg/api/meta" @@ -46,6 +47,7 @@ import ( restclient "k8s.io/client-go/rest" openapibuilder "k8s.io/kube-openapi/pkg/builder" openapicommon "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/handler" openapiutil "k8s.io/kube-openapi/pkg/util" openapiproto "k8s.io/kube-openapi/pkg/util/proto" ) @@ -122,6 +124,14 @@ type GenericAPIServer struct { // Enable swagger and/or OpenAPI if these configs are non-nil. openAPIConfig *openapicommon.Config + // OpenAPIVersionedService controls the /openapi/v2 endpoint, and can be used to update the served spec. + // It is set during PrepareRun. + OpenAPIVersionedService *handler.OpenAPIService + + // StaticOpenAPISpec is the spec derived from the restful container endpoints. + // It is set during PrepareRun. + StaticOpenAPISpec *spec.Swagger + // PostStartHooks are each called after the server has started listening, in a separate go func for each // with no guarantee 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. @@ -239,7 +249,7 @@ type preparedGenericAPIServer struct { // PrepareRun does post API installation setup steps. func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer { if s.openAPIConfig != nil { - routes.OpenAPI{ + s.OpenAPIVersionedService, s.StaticOpenAPISpec = routes.OpenAPI{ Config: s.openAPIConfig, }.Install(s.Handler.GoRestfulContainer, s.Handler.NonGoRestfulMux) } diff --git a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go index cd9e57bc210..c63953500e3 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go @@ -31,6 +31,7 @@ import ( "testing" "time" + "github.com/go-openapi/spec" openapi "github.com/go-openapi/spec" "github.com/stretchr/testify/assert" @@ -338,6 +339,46 @@ func TestPrepareRun(t *testing.T) { assert.Equal(http.StatusOK, resp.StatusCode) } +func TestUpdateOpenAPISpec(t *testing.T) { + s, _, assert := newMaster(t) + s.PrepareRun() + s.RunPostStartHooks(make(chan struct{})) + + server := httptest.NewServer(s.Handler.Director) + defer server.Close() + + // verify the static spec in record is what we currently serve + oldSpec, err := json.Marshal(s.StaticOpenAPISpec) + assert.NoError(err) + + resp, err := http.Get(server.URL + "/openapi/v2") + assert.NoError(err) + assert.Equal(http.StatusOK, resp.StatusCode) + + body, err := ioutil.ReadAll(resp.Body) + assert.NoError(err) + assert.Equal(oldSpec, body) + resp.Body.Close() + + // verify we are able to update the served spec using the exposed service + newSpec := []byte(`{"swagger":"2.0","info":{"title":"Test Updated Generic API Server Swagger","version":"v0.1.0"},"paths":null}`) + swagger := new(spec.Swagger) + err = json.Unmarshal(newSpec, swagger) + assert.NoError(err) + + err = s.OpenAPIVersionedService.UpdateSpec(swagger) + assert.NoError(err) + + resp, err = http.Get(server.URL + "/openapi/v2") + assert.NoError(err) + defer resp.Body.Close() + assert.Equal(http.StatusOK, resp.StatusCode) + + body, err = ioutil.ReadAll(resp.Body) + assert.NoError(err) + assert.Equal(newSpec, body) +} + // TestCustomHandlerChain verifies the handler chain with custom handler chain builder functions. func TestCustomHandlerChain(t *testing.T) { config, _ := setUp(t) diff --git a/staging/src/k8s.io/apiserver/pkg/server/routes/BUILD b/staging/src/k8s.io/apiserver/pkg/server/routes/BUILD index 62511517fa1..3e157eae31a 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/routes/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/server/routes/BUILD @@ -27,8 +27,10 @@ go_library( "//staging/src/k8s.io/apiserver/pkg/server/mux:go_default_library", "//staging/src/k8s.io/apiserver/pkg/storage/etcd/metrics:go_default_library", "//vendor/github.com/emicklei/go-restful:go_default_library", + "//vendor/github.com/go-openapi/spec:go_default_library", "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", "//vendor/k8s.io/klog:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/builder:go_default_library", "//vendor/k8s.io/kube-openapi/pkg/common:go_default_library", "//vendor/k8s.io/kube-openapi/pkg/handler:go_default_library", ], diff --git a/staging/src/k8s.io/apiserver/pkg/server/routes/openapi.go b/staging/src/k8s.io/apiserver/pkg/server/routes/openapi.go index 4d0cc3ee452..23bbee99f7e 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/routes/openapi.go +++ b/staging/src/k8s.io/apiserver/pkg/server/routes/openapi.go @@ -18,9 +18,11 @@ package routes import ( restful "github.com/emicklei/go-restful" + "github.com/go-openapi/spec" "k8s.io/klog" "k8s.io/apiserver/pkg/server/mux" + "k8s.io/kube-openapi/pkg/builder" "k8s.io/kube-openapi/pkg/common" "k8s.io/kube-openapi/pkg/handler" ) @@ -31,9 +33,14 @@ type OpenAPI struct { } // Install adds the SwaggerUI webservice to the given mux. -func (oa OpenAPI) Install(c *restful.Container, mux *mux.PathRecorderMux) { - _, err := handler.BuildAndRegisterOpenAPIVersionedService("/openapi/v2", c.RegisteredWebServices(), oa.Config, mux) +func (oa OpenAPI) Install(c *restful.Container, mux *mux.PathRecorderMux) (*handler.OpenAPIService, *spec.Swagger) { + spec, err := builder.BuildOpenAPISpec(c.RegisteredWebServices(), oa.Config) + if err != nil { + klog.Fatalf("Failed to build open api spec for root: %v", err) + } + openAPIVersionedService, err := handler.RegisterOpenAPIVersionedService(spec, "/openapi/v2", mux) if err != nil { klog.Fatalf("Failed to register versioned open api spec for root: %v", err) } + return openAPIVersionedService, spec }