mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 05:57:25 +00:00
Merge pull request #59293 from roycaihw/openapi_endpoint
Automatic merge from submit-queue (batch tested with PRs 60011, 59256, 59293, 60328, 60367). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Serve OpenAPI spec with single /openapi/v2 endpoint **What this PR does / why we need it**: We are deprecating format-separated endpoints (`/swagger.json`, `/swagger-2.0.0.json`, `/swagger-2.0.0.pb-v1`, `/swagger-2.0.0.pb-v1.gz`) for OpenAPI spec, and switching 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 Requested format is specified by setting HTTP headers header | possible values -- | -- Accept | `application/json`, `application/com.github.proto-openapi.spec.v2@v1.0+protobuf` Accept-Encoding | `gzip` This PR changes dynamic_client (and kubectl as a result) to use the new endpoint. The old endpoints will remain in 1.10 and 1.11, and get removed in 1.12. **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: Fixes # **Special notes for your reviewer**: **Release note**: ```release-note action required: Deprecate format-separated endpoints for OpenAPI spec. Please use single `/openapi/v2` endpoint instead. ``` /sig api-machinery
This commit is contained in:
commit
d6153194d9
16
Godeps/Godeps.json
generated
16
Godeps/Godeps.json
generated
@ -3265,35 +3265,35 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/aggregator",
|
"ImportPath": "k8s.io/kube-openapi/pkg/aggregator",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/builder",
|
"ImportPath": "k8s.io/kube-openapi/pkg/builder",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/generators",
|
"ImportPath": "k8s.io/kube-openapi/pkg/generators",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/handler",
|
"ImportPath": "k8s.io/kube-openapi/pkg/handler",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/util",
|
"ImportPath": "k8s.io/kube-openapi/pkg/util",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto/validation",
|
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto/validation",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/utils/clock",
|
"ImportPath": "k8s.io/utils/clock",
|
||||||
|
@ -176,6 +176,7 @@ func ClusterRoles() []rbac.ClusterRole {
|
|||||||
// do not expand this pattern for openapi discovery docs
|
// do not expand this pattern for openapi discovery docs
|
||||||
// move to a single openapi endpoint that takes accept/accept-encoding headers
|
// move to a single openapi endpoint that takes accept/accept-encoding headers
|
||||||
"/swagger.json", "/swagger-2.0.0.pb-v1",
|
"/swagger.json", "/swagger-2.0.0.pb-v1",
|
||||||
|
"/openapi", "/openapi/*",
|
||||||
"/api", "/api/*",
|
"/api", "/api/*",
|
||||||
"/apis", "/apis/*",
|
"/apis", "/apis/*",
|
||||||
).RuleOrDie(),
|
).RuleOrDie(),
|
||||||
|
@ -635,6 +635,8 @@ items:
|
|||||||
- /apis
|
- /apis
|
||||||
- /apis/*
|
- /apis/*
|
||||||
- /healthz
|
- /healthz
|
||||||
|
- /openapi
|
||||||
|
- /openapi/*
|
||||||
- /swagger-2.0.0.pb-v1
|
- /swagger-2.0.0.pb-v1
|
||||||
- /swagger.json
|
- /swagger.json
|
||||||
- /swaggerapi
|
- /swaggerapi
|
||||||
|
@ -1984,23 +1984,23 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/builder",
|
"ImportPath": "k8s.io/kube-openapi/pkg/builder",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/handler",
|
"ImportPath": "k8s.io/kube-openapi/pkg/handler",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/util",
|
"ImportPath": "k8s.io/kube-openapi/pkg/util",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/api/equality",
|
"ImportPath": "k8s.io/apimachinery/pkg/api/equality",
|
||||||
|
@ -172,7 +172,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
10
staging/src/k8s.io/apiserver/Godeps/Godeps.json
generated
10
staging/src/k8s.io/apiserver/Godeps/Godeps.json
generated
@ -1720,23 +1720,23 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/builder",
|
"ImportPath": "k8s.io/kube-openapi/pkg/builder",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/handler",
|
"ImportPath": "k8s.io/kube-openapi/pkg/handler",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/util",
|
"ImportPath": "k8s.io/kube-openapi/pkg/util",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/client-go/discovery",
|
"ImportPath": "k8s.io/client-go/discovery",
|
||||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/emicklei/go-restful"
|
restful "github.com/emicklei/go-restful"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/server/mux"
|
"k8s.io/apiserver/pkg/server/mux"
|
||||||
@ -32,8 +32,15 @@ type OpenAPI struct {
|
|||||||
|
|
||||||
// Install adds the SwaggerUI webservice to the given mux.
|
// Install adds the SwaggerUI webservice to the given mux.
|
||||||
func (oa OpenAPI) Install(c *restful.Container, mux *mux.PathRecorderMux) {
|
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)
|
_, err := handler.BuildAndRegisterOpenAPIService("/swagger.json", c.RegisteredWebServices(), oa.Config, mux)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Fatalf("Failed to register open api spec for root: %v", err)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
2
staging/src/k8s.io/client-go/Godeps/Godeps.json
generated
2
staging/src/k8s.io/client-go/Godeps/Godeps.json
generated
@ -576,7 +576,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -36,8 +36,12 @@ import (
|
|||||||
restclient "k8s.io/client-go/rest"
|
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 (
|
||||||
const defaultRetries = 2
|
// 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,
|
// DiscoveryInterface holds the methods that discover server-supported API groups,
|
||||||
// versions and resources.
|
// 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.
|
// OpenAPISchema fetches the open api schema using a rest client and parses the proto.
|
||||||
func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
|
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 {
|
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{}
|
document := &openapi_v2.Document{}
|
||||||
err = proto.Unmarshal(data, document)
|
err = proto.Unmarshal(data, document)
|
||||||
|
@ -326,9 +326,14 @@ var returnedOpenAPI = openapi_v2.Document{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func openapiSchemaFakeServer() (*httptest.Server, error) {
|
func openapiSchemaDeprecatedFakeServer() (*httptest.Server, error) {
|
||||||
var sErr error
|
var sErr error
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
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" {
|
if req.URL.Path != "/swagger-2.0.0.pb-v1" {
|
||||||
sErr = fmt.Errorf("Unexpected url %v", req.URL)
|
sErr = fmt.Errorf("Unexpected url %v", req.URL)
|
||||||
}
|
}
|
||||||
@ -349,6 +354,33 @@ func openapiSchemaFakeServer() (*httptest.Server, error) {
|
|||||||
return server, sErr
|
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) {
|
func TestGetOpenAPISchema(t *testing.T) {
|
||||||
server, err := openapiSchemaFakeServer()
|
server, err := openapiSchemaFakeServer()
|
||||||
if err != nil {
|
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) {
|
func TestServerPreferredResources(t *testing.T) {
|
||||||
stable := metav1.APIResourceList{
|
stable := metav1.APIResourceList{
|
||||||
GroupVersion: "v1",
|
GroupVersion: "v1",
|
||||||
|
@ -260,11 +260,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/generators",
|
"ImportPath": "k8s.io/kube-openapi/pkg/generators",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1628,27 +1628,27 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/aggregator",
|
"ImportPath": "k8s.io/kube-openapi/pkg/aggregator",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/builder",
|
"ImportPath": "k8s.io/kube-openapi/pkg/builder",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/handler",
|
"ImportPath": "k8s.io/kube-openapi/pkg/handler",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/util",
|
"ImportPath": "k8s.io/kube-openapi/pkg/util",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
restful "github.com/emicklei/go-restful"
|
||||||
"github.com/go-openapi/spec"
|
"github.com/go-openapi/spec"
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/server"
|
"k8s.io/apiserver/pkg/server"
|
||||||
@ -51,7 +51,8 @@ type specAggregator struct {
|
|||||||
openAPISpecs map[string]*openAPISpecInfo
|
openAPISpecs map[string]*openAPISpecInfo
|
||||||
|
|
||||||
// provided for dynamic OpenAPI spec
|
// provided for dynamic OpenAPI spec
|
||||||
openAPIService *handler.OpenAPIService
|
openAPIService *handler.OpenAPIService
|
||||||
|
openAPIVersionedService *handler.OpenAPIService
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ AggregationManager = &specAggregator{}
|
var _ AggregationManager = &specAggregator{}
|
||||||
@ -109,11 +110,19 @@ func BuildAndRegisterAggregator(downloader *Downloader, delegationTarget server.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Install handler
|
// 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(
|
s.openAPIService, err = handler.RegisterOpenAPIService(
|
||||||
specToServe, "/swagger.json", pathHandler)
|
specToServe, "/swagger.json", pathHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
s.openAPIVersionedService, err = handler.RegisterOpenAPIVersionedService(
|
||||||
|
specToServe, "/openapi/v2", pathHandler)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return s, nil
|
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.
|
// updateOpenAPISpec aggregates all OpenAPI specs. It is not thread-safe. The caller is responsible to hold proper locks.
|
||||||
func (s *specAggregator) updateOpenAPISpec() error {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
specToServe, err := s.buildOpenAPISpec()
|
specToServe, err := s.buildOpenAPISpec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// tryUpdatingServiceSpecs tries updating openAPISpecs map with specified specInfo, and keeps the map intact
|
||||||
|
@ -93,6 +93,32 @@ func (h handlerTest) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write(h.data)
|
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,
|
func assertDownloadedSpec(actualSpec *spec.Swagger, actualEtag string, err error,
|
||||||
expectedSpecID string, expectedEtag string) error {
|
expectedSpecID string, expectedEtag string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -132,4 +158,9 @@ func TestDownloadOpenAPISpec(t *testing.T) {
|
|||||||
actualSpec, actualEtag, _, err = s.Download(
|
actualSpec, actualEtag, _, err = s.Download(
|
||||||
handlerTest{data: []byte("{\"id\": \"test\"}"), etag: "etag_test1"}, "etag_test2")
|
handlerTest{data: []byte("{\"id\": \"test\"}"), etag: "etag_test1"}, "etag_test2")
|
||||||
assert.NoError(t, assertDownloadedSpec(actualSpec, actualEtag, err, "test", "etag_test1"))
|
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\""))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -99,10 +99,11 @@ func (s *Downloader) Download(handler http.Handler, etag string) (returnSpec *sp
|
|||||||
handler = request.WithRequestContext(handler, s.contextMapper)
|
handler = request.WithRequestContext(handler, s.contextMapper)
|
||||||
handler = http.TimeoutHandler(handler, specDownloadTimeout, "request timed out")
|
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 {
|
if err != nil {
|
||||||
return nil, "", 0, err
|
return nil, "", 0, err
|
||||||
}
|
}
|
||||||
|
req.Header.Add("Accept", "application/json")
|
||||||
|
|
||||||
// Only pass eTag if it is not generated locally
|
// Only pass eTag if it is not generated locally
|
||||||
if len(etag) > 0 && !strings.HasPrefix(etag, locallyGeneratedEtagPrefix) {
|
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()
|
writer := newInMemoryResponseWriter()
|
||||||
handler.ServeHTTP(writer, req)
|
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 {
|
switch writer.respCode {
|
||||||
case http.StatusNotModified:
|
case http.StatusNotModified:
|
||||||
if len(etag) == 0 {
|
if len(etag) == 0 {
|
||||||
|
@ -1592,23 +1592,23 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/builder",
|
"ImportPath": "k8s.io/kube-openapi/pkg/builder",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/handler",
|
"ImportPath": "k8s.io/kube-openapi/pkg/handler",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/util",
|
"ImportPath": "k8s.io/kube-openapi/pkg/util",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -900,7 +900,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
||||||
"Rev": "a07b7bbb58e7fdc5144f8d7046331d29fc9ad3b3"
|
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
49
vendor/k8s.io/kube-openapi/pkg/aggregator/aggregator.go
generated
vendored
49
vendor/k8s.io/kube-openapi/pkg/aggregator/aggregator.go
generated
vendored
@ -58,8 +58,10 @@ func (s *referenceWalker) walkRef(ref spec.Ref) spec.Ref {
|
|||||||
// We do not support external references yet.
|
// We do not support external references yet.
|
||||||
if !s.alreadyVisited[refStr] && strings.HasPrefix(refStr, definitionPrefix) {
|
if !s.alreadyVisited[refStr] && strings.HasPrefix(refStr, definitionPrefix) {
|
||||||
s.alreadyVisited[refStr] = true
|
s.alreadyVisited[refStr] = true
|
||||||
def := s.root.Definitions[refStr[len(definitionPrefix):]]
|
k := refStr[len(definitionPrefix):]
|
||||||
|
def := s.root.Definitions[k]
|
||||||
s.walkSchema(&def)
|
s.walkSchema(&def)
|
||||||
|
s.root.Definitions[k] = def
|
||||||
}
|
}
|
||||||
return s.walkRefCallback(ref)
|
return s.walkRefCallback(ref)
|
||||||
}
|
}
|
||||||
@ -69,23 +71,26 @@ func (s *referenceWalker) walkSchema(schema *spec.Schema) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
schema.Ref = s.walkRef(schema.Ref)
|
schema.Ref = s.walkRef(schema.Ref)
|
||||||
for _, v := range schema.Definitions {
|
for k, v := range schema.Definitions {
|
||||||
s.walkSchema(&v)
|
s.walkSchema(&v)
|
||||||
|
schema.Definitions[k] = v
|
||||||
}
|
}
|
||||||
for _, v := range schema.Properties {
|
for k, v := range schema.Properties {
|
||||||
s.walkSchema(&v)
|
s.walkSchema(&v)
|
||||||
|
schema.Properties[k] = v
|
||||||
}
|
}
|
||||||
for _, v := range schema.PatternProperties {
|
for k, v := range schema.PatternProperties {
|
||||||
s.walkSchema(&v)
|
s.walkSchema(&v)
|
||||||
|
schema.PatternProperties[k] = v
|
||||||
}
|
}
|
||||||
for _, v := range schema.AllOf {
|
for i, _ := range schema.AllOf {
|
||||||
s.walkSchema(&v)
|
s.walkSchema(&schema.AllOf[i])
|
||||||
}
|
}
|
||||||
for _, v := range schema.AnyOf {
|
for i, _ := range schema.AnyOf {
|
||||||
s.walkSchema(&v)
|
s.walkSchema(&schema.AnyOf[i])
|
||||||
}
|
}
|
||||||
for _, v := range schema.OneOf {
|
for i, _ := range schema.OneOf {
|
||||||
s.walkSchema(&v)
|
s.walkSchema(&schema.OneOf[i])
|
||||||
}
|
}
|
||||||
if schema.Not != nil {
|
if schema.Not != nil {
|
||||||
s.walkSchema(schema.Not)
|
s.walkSchema(schema.Not)
|
||||||
@ -100,8 +105,8 @@ func (s *referenceWalker) walkSchema(schema *spec.Schema) {
|
|||||||
if schema.Items.Schema != nil {
|
if schema.Items.Schema != nil {
|
||||||
s.walkSchema(schema.Items.Schema)
|
s.walkSchema(schema.Items.Schema)
|
||||||
}
|
}
|
||||||
for _, v := range schema.Items.Schemas {
|
for i, _ := range schema.Items.Schemas {
|
||||||
s.walkSchema(&v)
|
s.walkSchema(&schema.Items.Schemas[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -291,15 +296,29 @@ func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConf
|
|||||||
from, to string
|
from, to string
|
||||||
}
|
}
|
||||||
renames := []Rename{}
|
renames := []Rename{}
|
||||||
|
|
||||||
|
OUTERLOOP:
|
||||||
for k, v := range source.Definitions {
|
for k, v := range source.Definitions {
|
||||||
if usedNames[k] {
|
if usedNames[k] {
|
||||||
v2, found := dest.Definitions[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) {
|
if found && reflect.DeepEqual(v, v2) {
|
||||||
continue
|
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]
|
_, foundInSource := source.Definitions[newName]
|
||||||
for usedNames[newName] || foundInSource {
|
for usedNames[newName] || foundInSource {
|
||||||
i++
|
i++
|
||||||
|
2
vendor/k8s.io/kube-openapi/pkg/generators/openapi.go
generated
vendored
2
vendor/k8s.io/kube-openapi/pkg/generators/openapi.go
generated
vendored
@ -634,6 +634,8 @@ func (g openAPITypeWriter) generateSliceProperty(t *types.Type) error {
|
|||||||
return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType)
|
return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType)
|
||||||
case types.Struct:
|
case types.Struct:
|
||||||
g.generateReferenceProperty(elemType)
|
g.generateReferenceProperty(elemType)
|
||||||
|
case types.Slice, types.Array:
|
||||||
|
g.generateSliceProperty(elemType)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("slice Element kind %v is not supported in %v", elemType.Kind, t)
|
return fmt.Errorf("slice Element kind %v is not supported in %v", elemType.Kind, t)
|
||||||
}
|
}
|
||||||
|
1
vendor/k8s.io/kube-openapi/pkg/handler/BUILD
generated
vendored
1
vendor/k8s.io/kube-openapi/pkg/handler/BUILD
generated
vendored
@ -6,6 +6,7 @@ go_library(
|
|||||||
importpath = "k8s.io/kube-openapi/pkg/handler",
|
importpath = "k8s.io/kube-openapi/pkg/handler",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//vendor/bitbucket.org/ww/goautoneg:go_default_library",
|
||||||
"//vendor/github.com/NYTimes/gziphandler:go_default_library",
|
"//vendor/github.com/NYTimes/gziphandler:go_default_library",
|
||||||
"//vendor/github.com/emicklei/go-restful:go_default_library",
|
"//vendor/github.com/emicklei/go-restful:go_default_library",
|
||||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
"//vendor/github.com/go-openapi/spec:go_default_library",
|
||||||
|
77
vendor/k8s.io/kube-openapi/pkg/handler/handler.go
generated
vendored
77
vendor/k8s.io/kube-openapi/pkg/handler/handler.go
generated
vendored
@ -22,18 +22,21 @@ import (
|
|||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"bitbucket.org/ww/goautoneg"
|
||||||
|
|
||||||
|
yaml "gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"github.com/NYTimes/gziphandler"
|
"github.com/NYTimes/gziphandler"
|
||||||
"github.com/emicklei/go-restful"
|
restful "github.com/emicklei/go-restful"
|
||||||
"github.com/go-openapi/spec"
|
"github.com/go-openapi/spec"
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/googleapis/gnostic/OpenAPIv2"
|
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
|
||||||
"github.com/googleapis/gnostic/compiler"
|
"github.com/googleapis/gnostic/compiler"
|
||||||
|
|
||||||
"k8s.io/kube-openapi/pkg/builder"
|
"k8s.io/kube-openapi/pkg/builder"
|
||||||
@ -77,6 +80,10 @@ func computeETag(data []byte) string {
|
|||||||
return fmt.Sprintf("\"%X\"", sha512.Sum512(data))
|
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.
|
// 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.
|
// 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) {
|
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)
|
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.
|
// 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
|
// Note: servePath should end with ".json" as the RegisterOpenAPIService assume it is serving a
|
||||||
// json file and will also serve .pb and .gz files.
|
// json file and will also serve .pb and .gz files.
|
||||||
@ -202,3 +213,63 @@ func toGzip(data []byte) []byte {
|
|||||||
zw.Close()
|
zw.Close()
|
||||||
return buf.Bytes()
|
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)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user