diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 294bfdf4ab644..7ab9e64eca279 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -3265,35 +3265,35 @@ }, { "ImportPath": "k8s.io/kube-openapi/pkg/aggregator", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/builder", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/common", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/generators", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/handler", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/util", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/util/proto", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/util/proto/validation", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/utils/clock", diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go index 3f5f54e3b6186..81e95b0032c8e 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go @@ -176,6 +176,7 @@ func ClusterRoles() []rbac.ClusterRole { // do not expand this pattern for openapi discovery docs // move to a single openapi endpoint that takes accept/accept-encoding headers "/swagger.json", "/swagger-2.0.0.pb-v1", + "/openapi", "/openapi/*", "/api", "/api/*", "/apis", "/apis/*", ).RuleOrDie(), diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml index 999a01efde805..4a78d8b1c88c7 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml @@ -635,6 +635,8 @@ items: - /apis - /apis/* - /healthz + - /openapi + - /openapi/* - /swagger-2.0.0.pb-v1 - /swagger.json - /swaggerapi diff --git a/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json b/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json index 1cb1e3b22c107..393c78d5f2245 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json +++ b/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json @@ -1984,23 +1984,23 @@ }, { "ImportPath": "k8s.io/kube-openapi/pkg/builder", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/common", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/handler", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/util", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/util/proto", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/equality", diff --git a/staging/src/k8s.io/apimachinery/Godeps/Godeps.json b/staging/src/k8s.io/apimachinery/Godeps/Godeps.json index 5ba1aa546988b..8c309a8d0a962 100644 --- a/staging/src/k8s.io/apimachinery/Godeps/Godeps.json +++ b/staging/src/k8s.io/apimachinery/Godeps/Godeps.json @@ -172,7 +172,7 @@ }, { "ImportPath": "k8s.io/kube-openapi/pkg/util/proto", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" } ] } diff --git a/staging/src/k8s.io/apiserver/Godeps/Godeps.json b/staging/src/k8s.io/apiserver/Godeps/Godeps.json index 5369e114208fc..40d735f73246e 100644 --- a/staging/src/k8s.io/apiserver/Godeps/Godeps.json +++ b/staging/src/k8s.io/apiserver/Godeps/Godeps.json @@ -1720,23 +1720,23 @@ }, { "ImportPath": "k8s.io/kube-openapi/pkg/builder", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/common", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/handler", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/util", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/util/proto", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/client-go/discovery", 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 1bbfacf43abd7..06c723d37531e 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/routes/openapi.go +++ b/staging/src/k8s.io/apiserver/pkg/server/routes/openapi.go @@ -17,7 +17,7 @@ limitations under the License. package routes import ( - "github.com/emicklei/go-restful" + restful "github.com/emicklei/go-restful" "github.com/golang/glog" "k8s.io/apiserver/pkg/server/mux" @@ -32,8 +32,15 @@ type OpenAPI struct { // Install adds the SwaggerUI webservice to the given mux. func (oa OpenAPI) Install(c *restful.Container, mux *mux.PathRecorderMux) { + // NOTE: [DEPRECATION] We will announce deprecation for format-separated endpoints for OpenAPI spec, + // and switch to a single /openapi/v2 endpoint in Kubernetes 1.10. The design doc and deprecation process + // are tracked at: https://docs.google.com/document/d/19lEqE9lc4yHJ3WJAJxS_G7TcORIJXGHyq3wpwcH28nU. _, err := handler.BuildAndRegisterOpenAPIService("/swagger.json", c.RegisteredWebServices(), oa.Config, mux) if err != nil { glog.Fatalf("Failed to register open api spec for root: %v", err) } + _, err = handler.BuildAndRegisterOpenAPIVersionedService("/openapi/v2", c.RegisteredWebServices(), oa.Config, mux) + if err != nil { + glog.Fatalf("Failed to register versioned open api spec for root: %v", err) + } } diff --git a/staging/src/k8s.io/client-go/Godeps/Godeps.json b/staging/src/k8s.io/client-go/Godeps/Godeps.json index 836724fc347b9..05862f0532e50 100644 --- a/staging/src/k8s.io/client-go/Godeps/Godeps.json +++ b/staging/src/k8s.io/client-go/Godeps/Godeps.json @@ -576,7 +576,7 @@ }, { "ImportPath": "k8s.io/kube-openapi/pkg/util/proto", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" } ] } diff --git a/staging/src/k8s.io/client-go/discovery/discovery_client.go b/staging/src/k8s.io/client-go/discovery/discovery_client.go index 24c11f33bd846..3c685a955637d 100644 --- a/staging/src/k8s.io/client-go/discovery/discovery_client.go +++ b/staging/src/k8s.io/client-go/discovery/discovery_client.go @@ -36,8 +36,12 @@ import ( restclient "k8s.io/client-go/rest" ) -// defaultRetries is the number of times a resource discovery is repeated if an api group disappears on the fly (e.g. ThirdPartyResources). -const defaultRetries = 2 +const ( + // defaultRetries is the number of times a resource discovery is repeated if an api group disappears on the fly (e.g. ThirdPartyResources). + defaultRetries = 2 + // protobuf mime type + mimePb = "application/com.github.proto-openapi.spec.v2@v1.0+protobuf" +) // DiscoveryInterface holds the methods that discover server-supported API groups, // versions and resources. @@ -329,9 +333,18 @@ func (d *DiscoveryClient) ServerVersion() (*version.Info, error) { // OpenAPISchema fetches the open api schema using a rest client and parses the proto. func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) { - data, err := d.restClient.Get().AbsPath("/swagger-2.0.0.pb-v1").Do().Raw() + data, err := d.restClient.Get().AbsPath("/openapi/v2").SetHeader("Accept", mimePb).Do().Raw() if err != nil { - return nil, err + if errors.IsForbidden(err) || errors.IsNotFound(err) { + // single endpoint not found/registered in old server, try to fetch old endpoint + // TODO(roycaihw): remove this in 1.11 + data, err = d.restClient.Get().AbsPath("/swagger-2.0.0.pb-v1").Do().Raw() + if err != nil { + return nil, err + } + } else { + return nil, err + } } document := &openapi_v2.Document{} err = proto.Unmarshal(data, document) diff --git a/staging/src/k8s.io/client-go/discovery/discovery_client_test.go b/staging/src/k8s.io/client-go/discovery/discovery_client_test.go index 64249c88ad9f2..ad855139a051e 100644 --- a/staging/src/k8s.io/client-go/discovery/discovery_client_test.go +++ b/staging/src/k8s.io/client-go/discovery/discovery_client_test.go @@ -326,9 +326,14 @@ var returnedOpenAPI = openapi_v2.Document{ }, } -func openapiSchemaFakeServer() (*httptest.Server, error) { +func openapiSchemaDeprecatedFakeServer() (*httptest.Server, error) { var sErr error server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // old server returns 403 on new endpoint request + if req.URL.Path == "/openapi/v2" { + w.WriteHeader(http.StatusForbidden) + return + } if req.URL.Path != "/swagger-2.0.0.pb-v1" { sErr = fmt.Errorf("Unexpected url %v", req.URL) } @@ -349,6 +354,33 @@ func openapiSchemaFakeServer() (*httptest.Server, error) { return server, sErr } +func openapiSchemaFakeServer() (*httptest.Server, error) { + var sErr error + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.URL.Path != "/openapi/v2" { + sErr = fmt.Errorf("Unexpected url %v", req.URL) + } + if req.Method != "GET" { + sErr = fmt.Errorf("Unexpected method %v", req.Method) + } + decipherableFormat := req.Header.Get("Accept") + if decipherableFormat != "application/com.github.proto-openapi.spec.v2@v1.0+protobuf" { + sErr = fmt.Errorf("Unexpected accept mime type %v", decipherableFormat) + } + + mime.AddExtensionType(".pb-v1", "application/com.github.googleapis.gnostic.OpenAPIv2@68f4ded+protobuf") + + output, err := proto.Marshal(&returnedOpenAPI) + if err != nil { + sErr = err + return + } + w.WriteHeader(http.StatusOK) + w.Write(output) + })) + return server, sErr +} + func TestGetOpenAPISchema(t *testing.T) { server, err := openapiSchemaFakeServer() if err != nil { @@ -366,6 +398,23 @@ func TestGetOpenAPISchema(t *testing.T) { } } +func TestGetOpenAPISchemaFallback(t *testing.T) { + server, err := openapiSchemaDeprecatedFakeServer() + if err != nil { + t.Errorf("unexpected error starting fake server: %v", err) + } + defer server.Close() + + client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL}) + got, err := client.OpenAPISchema() + if err != nil { + t.Fatalf("unexpected error getting openapi: %v", err) + } + if e, a := returnedOpenAPI, *got; !reflect.DeepEqual(e, a) { + t.Errorf("expected %v, got %v", e, a) + } +} + func TestServerPreferredResources(t *testing.T) { stable := metav1.APIResourceList{ GroupVersion: "v1", diff --git a/staging/src/k8s.io/code-generator/Godeps/Godeps.json b/staging/src/k8s.io/code-generator/Godeps/Godeps.json index 423702823228a..477d1e4743731 100644 --- a/staging/src/k8s.io/code-generator/Godeps/Godeps.json +++ b/staging/src/k8s.io/code-generator/Godeps/Godeps.json @@ -260,11 +260,11 @@ }, { "ImportPath": "k8s.io/kube-openapi/pkg/common", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/generators", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" } ] } diff --git a/staging/src/k8s.io/kube-aggregator/Godeps/Godeps.json b/staging/src/k8s.io/kube-aggregator/Godeps/Godeps.json index 144e6ddbac7ee..4ee479033f5fb 100644 --- a/staging/src/k8s.io/kube-aggregator/Godeps/Godeps.json +++ b/staging/src/k8s.io/kube-aggregator/Godeps/Godeps.json @@ -1628,27 +1628,27 @@ }, { "ImportPath": "k8s.io/kube-openapi/pkg/aggregator", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/builder", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/common", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/handler", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/util", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/util/proto", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" } ] } diff --git a/staging/src/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator.go b/staging/src/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator.go index c32da4cdd7963..4c1f75bd6d929 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator.go @@ -23,7 +23,7 @@ import ( "sync" "time" - "github.com/emicklei/go-restful" + restful "github.com/emicklei/go-restful" "github.com/go-openapi/spec" "k8s.io/apiserver/pkg/server" @@ -51,7 +51,8 @@ type specAggregator struct { openAPISpecs map[string]*openAPISpecInfo // provided for dynamic OpenAPI spec - openAPIService *handler.OpenAPIService + openAPIService *handler.OpenAPIService + openAPIVersionedService *handler.OpenAPIService } var _ AggregationManager = &specAggregator{} @@ -109,11 +110,19 @@ func BuildAndRegisterAggregator(downloader *Downloader, delegationTarget server. } // Install handler + // NOTE: [DEPRECATION] We will announce deprecation for format-separated endpoints for OpenAPI spec, + // and switch to a single /openapi/v2 endpoint in Kubernetes 1.10. The design doc and deprecation process + // are tracked at: https://docs.google.com/document/d/19lEqE9lc4yHJ3WJAJxS_G7TcORIJXGHyq3wpwcH28nU. s.openAPIService, err = handler.RegisterOpenAPIService( specToServe, "/swagger.json", pathHandler) if err != nil { return nil, err } + s.openAPIVersionedService, err = handler.RegisterOpenAPIVersionedService( + specToServe, "/openapi/v2", pathHandler) + if err != nil { + return nil, err + } return s, nil } @@ -207,14 +216,25 @@ func (s *specAggregator) buildOpenAPISpec() (specToReturn *spec.Swagger, err err // updateOpenAPISpec aggregates all OpenAPI specs. It is not thread-safe. The caller is responsible to hold proper locks. func (s *specAggregator) updateOpenAPISpec() error { - if s.openAPIService == nil { + if s.openAPIService == nil || s.openAPIVersionedService == nil { + // openAPIVersionedService and deprecated openAPIService should be initialized together + if !(s.openAPIService == nil && s.openAPIVersionedService == nil) { + return fmt.Errorf("unexpected openapi service initialization error") + } return nil } specToServe, err := s.buildOpenAPISpec() if err != nil { return err } - return s.openAPIService.UpdateSpec(specToServe) + // openAPIService.UpdateSpec and openAPIVersionedService.UpdateSpec read the same swagger spec + // serially and update their local caches separately. Both endpoints will have same spec in + // their caches if the caller is holding proper locks. + err = s.openAPIService.UpdateSpec(specToServe) + if err != nil { + return err + } + return s.openAPIVersionedService.UpdateSpec(specToServe) } // tryUpdatingServiceSpecs tries updating openAPISpecs map with specified specInfo, and keeps the map intact diff --git a/staging/src/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator_test.go b/staging/src/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator_test.go index 4bebfe506b6aa..15d23e38043c1 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator_test.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator_test.go @@ -93,6 +93,32 @@ func (h handlerTest) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write(h.data) } +type handlerDeprecatedTest struct { + etag string + data []byte +} + +var _ http.Handler = handlerDeprecatedTest{} + +func (h handlerDeprecatedTest) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // old server returns 403 on new endpoint + if r.URL.Path == "/openapi/v2" { + w.WriteHeader(http.StatusForbidden) + return + } + if len(h.etag) > 0 { + w.Header().Add("Etag", h.etag) + } + ifNoneMatches := r.Header["If-None-Match"] + for _, match := range ifNoneMatches { + if match == h.etag { + w.WriteHeader(http.StatusNotModified) + return + } + } + w.Write(h.data) +} + func assertDownloadedSpec(actualSpec *spec.Swagger, actualEtag string, err error, expectedSpecID string, expectedEtag string) error { if err != nil { @@ -132,4 +158,9 @@ func TestDownloadOpenAPISpec(t *testing.T) { actualSpec, actualEtag, _, err = s.Download( handlerTest{data: []byte("{\"id\": \"test\"}"), etag: "etag_test1"}, "etag_test2") assert.NoError(t, assertDownloadedSpec(actualSpec, actualEtag, err, "test", "etag_test1")) + + // Test old server fallback path + actualSpec, actualEtag, _, err = s.Download(handlerDeprecatedTest{data: []byte("{\"id\": \"test\"}")}, "") + assert.NoError(t, assertDownloadedSpec(actualSpec, actualEtag, err, "test", "\"6E8F849B434D4B98A569B9D7718876E9-356ECAB19D7FBE1336BABB1E70F8F3025050DE218BE78256BE81620681CFC9A268508E542B8B55974E17B2184BBFC8FFFAA577E51BE195D32B3CA2547818ABE4\"")) + } diff --git a/staging/src/k8s.io/kube-aggregator/pkg/controllers/openapi/downloader.go b/staging/src/k8s.io/kube-aggregator/pkg/controllers/openapi/downloader.go index 0da4e7e99afa7..4cd359edd050a 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/controllers/openapi/downloader.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/controllers/openapi/downloader.go @@ -99,10 +99,11 @@ func (s *Downloader) Download(handler http.Handler, etag string) (returnSpec *sp handler = request.WithRequestContext(handler, s.contextMapper) handler = http.TimeoutHandler(handler, specDownloadTimeout, "request timed out") - req, err := http.NewRequest("GET", "/swagger.json", nil) + req, err := http.NewRequest("GET", "/openapi/v2", nil) if err != nil { return nil, "", 0, err } + req.Header.Add("Accept", "application/json") // Only pass eTag if it is not generated locally if len(etag) > 0 && !strings.HasPrefix(etag, locallyGeneratedEtagPrefix) { @@ -112,6 +113,23 @@ func (s *Downloader) Download(handler http.Handler, etag string) (returnSpec *sp writer := newInMemoryResponseWriter() handler.ServeHTTP(writer, req) + // single endpoint not found/registered in old server, try to fetch old endpoint + // TODO(roycaihw): remove this in 1.11 + if writer.respCode == http.StatusForbidden || writer.respCode == http.StatusNotFound { + req, err = http.NewRequest("GET", "/swagger.json", nil) + if err != nil { + return nil, "", 0, err + } + + // Only pass eTag if it is not generated locally + if len(etag) > 0 && !strings.HasPrefix(etag, locallyGeneratedEtagPrefix) { + req.Header.Add("If-None-Match", etag) + } + + writer = newInMemoryResponseWriter() + handler.ServeHTTP(writer, req) + } + switch writer.respCode { case http.StatusNotModified: if len(etag) == 0 { diff --git a/staging/src/k8s.io/sample-apiserver/Godeps/Godeps.json b/staging/src/k8s.io/sample-apiserver/Godeps/Godeps.json index aa0331d2ebcd1..a88b4c56c6d7b 100644 --- a/staging/src/k8s.io/sample-apiserver/Godeps/Godeps.json +++ b/staging/src/k8s.io/sample-apiserver/Godeps/Godeps.json @@ -1592,23 +1592,23 @@ }, { "ImportPath": "k8s.io/kube-openapi/pkg/builder", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/common", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/handler", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/util", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" }, { "ImportPath": "k8s.io/kube-openapi/pkg/util/proto", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" } ] } diff --git a/staging/src/k8s.io/sample-controller/Godeps/Godeps.json b/staging/src/k8s.io/sample-controller/Godeps/Godeps.json index 6e8d42126452a..49a65d6425219 100644 --- a/staging/src/k8s.io/sample-controller/Godeps/Godeps.json +++ b/staging/src/k8s.io/sample-controller/Godeps/Godeps.json @@ -900,7 +900,7 @@ }, { "ImportPath": "k8s.io/kube-openapi/pkg/util/proto", - "Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3" + "Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" } ] } diff --git a/vendor/k8s.io/kube-openapi/pkg/aggregator/aggregator.go b/vendor/k8s.io/kube-openapi/pkg/aggregator/aggregator.go index aceaa463560a1..9fb16e6e9c415 100644 --- a/vendor/k8s.io/kube-openapi/pkg/aggregator/aggregator.go +++ b/vendor/k8s.io/kube-openapi/pkg/aggregator/aggregator.go @@ -58,8 +58,10 @@ func (s *referenceWalker) walkRef(ref spec.Ref) spec.Ref { // We do not support external references yet. if !s.alreadyVisited[refStr] && strings.HasPrefix(refStr, definitionPrefix) { s.alreadyVisited[refStr] = true - def := s.root.Definitions[refStr[len(definitionPrefix):]] + k := refStr[len(definitionPrefix):] + def := s.root.Definitions[k] s.walkSchema(&def) + s.root.Definitions[k] = def } return s.walkRefCallback(ref) } @@ -69,23 +71,26 @@ func (s *referenceWalker) walkSchema(schema *spec.Schema) { return } schema.Ref = s.walkRef(schema.Ref) - for _, v := range schema.Definitions { + for k, v := range schema.Definitions { s.walkSchema(&v) + schema.Definitions[k] = v } - for _, v := range schema.Properties { + for k, v := range schema.Properties { s.walkSchema(&v) + schema.Properties[k] = v } - for _, v := range schema.PatternProperties { + for k, v := range schema.PatternProperties { s.walkSchema(&v) + schema.PatternProperties[k] = v } - for _, v := range schema.AllOf { - s.walkSchema(&v) + for i, _ := range schema.AllOf { + s.walkSchema(&schema.AllOf[i]) } - for _, v := range schema.AnyOf { - s.walkSchema(&v) + for i, _ := range schema.AnyOf { + s.walkSchema(&schema.AnyOf[i]) } - for _, v := range schema.OneOf { - s.walkSchema(&v) + for i, _ := range schema.OneOf { + s.walkSchema(&schema.OneOf[i]) } if schema.Not != nil { s.walkSchema(schema.Not) @@ -100,8 +105,8 @@ func (s *referenceWalker) walkSchema(schema *spec.Schema) { if schema.Items.Schema != nil { s.walkSchema(schema.Items.Schema) } - for _, v := range schema.Items.Schemas { - s.walkSchema(&v) + for i, _ := range schema.Items.Schemas { + s.walkSchema(&schema.Items.Schemas[i]) } } } @@ -291,15 +296,29 @@ func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConf from, to string } renames := []Rename{} + + OUTERLOOP: for k, v := range source.Definitions { if usedNames[k] { v2, found := dest.Definitions[k] - // Reuse model iff they are exactly the same. + // Reuse model if they are exactly the same. if found && reflect.DeepEqual(v, v2) { continue } - i := 2 - newName := fmt.Sprintf("%s_v%d", k, i) + + // Reuse previously renamed model if one exists + var newName string + i := 1 + for found { + i++ + newName = fmt.Sprintf("%s_v%d", k, i) + v2, found = dest.Definitions[newName] + if found && reflect.DeepEqual(v, v2) { + renames = append(renames, Rename{from: k, to: newName}) + continue OUTERLOOP + } + } + _, foundInSource := source.Definitions[newName] for usedNames[newName] || foundInSource { i++ diff --git a/vendor/k8s.io/kube-openapi/pkg/generators/openapi.go b/vendor/k8s.io/kube-openapi/pkg/generators/openapi.go index d9b0980abb43a..73e367b3559f6 100644 --- a/vendor/k8s.io/kube-openapi/pkg/generators/openapi.go +++ b/vendor/k8s.io/kube-openapi/pkg/generators/openapi.go @@ -634,6 +634,8 @@ func (g openAPITypeWriter) generateSliceProperty(t *types.Type) error { return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType) case types.Struct: g.generateReferenceProperty(elemType) + case types.Slice, types.Array: + g.generateSliceProperty(elemType) default: return fmt.Errorf("slice Element kind %v is not supported in %v", elemType.Kind, t) } diff --git a/vendor/k8s.io/kube-openapi/pkg/handler/BUILD b/vendor/k8s.io/kube-openapi/pkg/handler/BUILD index 3e51d0c769409..072a64ec31097 100644 --- a/vendor/k8s.io/kube-openapi/pkg/handler/BUILD +++ b/vendor/k8s.io/kube-openapi/pkg/handler/BUILD @@ -6,6 +6,7 @@ go_library( importpath = "k8s.io/kube-openapi/pkg/handler", visibility = ["//visibility:public"], deps = [ + "//vendor/bitbucket.org/ww/goautoneg:go_default_library", "//vendor/github.com/NYTimes/gziphandler:go_default_library", "//vendor/github.com/emicklei/go-restful:go_default_library", "//vendor/github.com/go-openapi/spec:go_default_library", diff --git a/vendor/k8s.io/kube-openapi/pkg/handler/handler.go b/vendor/k8s.io/kube-openapi/pkg/handler/handler.go index 40f0fee3f1d7e..5a16cfcab4019 100644 --- a/vendor/k8s.io/kube-openapi/pkg/handler/handler.go +++ b/vendor/k8s.io/kube-openapi/pkg/handler/handler.go @@ -22,18 +22,21 @@ import ( "crypto/sha512" "encoding/json" "fmt" - "gopkg.in/yaml.v2" "mime" "net/http" "strings" "sync" "time" + "bitbucket.org/ww/goautoneg" + + yaml "gopkg.in/yaml.v2" + "github.com/NYTimes/gziphandler" - "github.com/emicklei/go-restful" + restful "github.com/emicklei/go-restful" "github.com/go-openapi/spec" "github.com/golang/protobuf/proto" - "github.com/googleapis/gnostic/OpenAPIv2" + openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2" "github.com/googleapis/gnostic/compiler" "k8s.io/kube-openapi/pkg/builder" @@ -77,6 +80,10 @@ func computeETag(data []byte) string { return fmt.Sprintf("\"%X\"", sha512.Sum512(data)) } +// NOTE: [DEPRECATION] We will announce deprecation for format-separated endpoints for OpenAPI spec, +// and switch to a single /openapi/v2 endpoint in Kubernetes 1.10. The design doc and deprecation process +// are tracked at: https://docs.google.com/document/d/19lEqE9lc4yHJ3WJAJxS_G7TcORIJXGHyq3wpwcH28nU. +// // BuildAndRegisterOpenAPIService builds the spec and registers a handler to provides access to it. // Use this method if your OpenAPI spec is static. If you want to update the spec, use BuildOpenAPISpec then RegisterOpenAPIService. func BuildAndRegisterOpenAPIService(servePath string, webServices []*restful.WebService, config *common.Config, handler common.PathHandler) (*OpenAPIService, error) { @@ -87,6 +94,10 @@ func BuildAndRegisterOpenAPIService(servePath string, webServices []*restful.Web return RegisterOpenAPIService(spec, servePath, handler) } +// NOTE: [DEPRECATION] We will announce deprecation for format-separated endpoints for OpenAPI spec, +// and switch to a single /openapi/v2 endpoint in Kubernetes 1.10. The design doc and deprecation process +// are tracked at: https://docs.google.com/document/d/19lEqE9lc4yHJ3WJAJxS_G7TcORIJXGHyq3wpwcH28nU. +// // RegisterOpenAPIService registers a handler to provides access to provided swagger spec. // Note: servePath should end with ".json" as the RegisterOpenAPIService assume it is serving a // json file and will also serve .pb and .gz files. @@ -202,3 +213,63 @@ func toGzip(data []byte) []byte { zw.Close() return buf.Bytes() } + +// RegisterOpenAPIVersionedService registers a handler to provides access to provided swagger spec. +func RegisterOpenAPIVersionedService(openapiSpec *spec.Swagger, servePath string, handler common.PathHandler) (*OpenAPIService, error) { + o := OpenAPIService{} + if err := o.UpdateSpec(openapiSpec); err != nil { + return nil, err + } + + accepted := []struct { + Type string + SubType string + GetDataAndETag func() ([]byte, string, time.Time) + }{ + {"application", "json", o.getSwaggerBytes}, + {"application", "com.github.proto-openapi.spec.v2@v1.0+protobuf", o.getSwaggerPbBytes}, + } + + handler.Handle(servePath, gziphandler.GzipHandler(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + decipherableFormats := r.Header.Get("Accept") + if decipherableFormats == "" { + decipherableFormats = "*/*" + } + clauses := goautoneg.ParseAccept(decipherableFormats) + w.Header().Add("Vary", "Accept") + for _, clause := range clauses { + for _, accepts := range accepted { + if clause.Type != accepts.Type && clause.Type != "*" { + continue + } + if clause.SubType != accepts.SubType && clause.SubType != "*" { + continue + } + + // serve the first matching media type in the sorted clause list + data, etag, lastModified := accepts.GetDataAndETag() + w.Header().Set("Etag", etag) + // ServeContent will take care of caching using eTag. + http.ServeContent(w, r, servePath, lastModified, bytes.NewReader(data)) + return + } + } + // Return 406 for not acceptable format + w.WriteHeader(406) + return + }), + )) + + return &o, nil +} + +// BuildAndRegisterOpenAPIVersionedService builds the spec and registers a handler to provides access to it. +// Use this method if your OpenAPI spec is static. If you want to update the spec, use BuildOpenAPISpec then RegisterOpenAPIVersionedService. +func BuildAndRegisterOpenAPIVersionedService(servePath string, webServices []*restful.WebService, config *common.Config, handler common.PathHandler) (*OpenAPIService, error) { + spec, err := builder.BuildOpenAPISpec(webServices, config) + if err != nil { + return nil, err + } + return RegisterOpenAPIVersionedService(spec, servePath, handler) +}