diff --git a/discovery/discovery_client.go b/discovery/discovery_client.go index 24c11f33..3c685a95 100644 --- a/discovery/discovery_client.go +++ b/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/discovery/discovery_client_test.go b/discovery/discovery_client_test.go index 64249c88..ad855139 100644 --- a/discovery/discovery_client_test.go +++ b/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",