From 97a8b3b040ad4ef210ee71f7c464fba939e2d00b Mon Sep 17 00:00:00 2001 From: Phillip Wittrock Date: Thu, 13 Apr 2017 18:01:38 -0700 Subject: [PATCH] kubectl OpenAPI support Kubernetes-commit: 212c2a3a7217ff7e0a7c895582c7a25d03ac7f8c --- discovery/discovery_client.go | 25 ++++++++++ discovery/discovery_client_test.go | 77 ++++++++++++++++++++++++++++++ discovery/fake/discovery.go | 3 ++ discovery/restmapper_test.go | 5 ++ 4 files changed, 110 insertions(+) diff --git a/discovery/discovery_client.go b/discovery/discovery_client.go index ff4c57a4..f1afb651 100644 --- a/discovery/discovery_client.go +++ b/discovery/discovery_client.go @@ -25,6 +25,8 @@ import ( "github.com/emicklei/go-restful/swagger" + "github.com/go-openapi/loads" + "github.com/go-openapi/spec" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -47,6 +49,7 @@ type DiscoveryInterface interface { ServerResourcesInterface ServerVersionInterface SwaggerSchemaInterface + OpenAPISchemaInterface } // CachedDiscoveryInterface is a DiscoveryInterface with cache invalidation and freshness. @@ -91,6 +94,12 @@ type SwaggerSchemaInterface interface { SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error) } +// OpenAPISchemaInterface has a method to retrieve the open API schema. +type OpenAPISchemaInterface interface { + // OpenAPISchema retrieves and parses the swagger API schema the server supports. + OpenAPISchema() (*spec.Swagger, error) +} + // DiscoveryClient implements the functions that discover server-supported API groups, // versions and resources. type DiscoveryClient struct { @@ -332,6 +341,7 @@ func (d *DiscoveryClient) ServerVersion() (*version.Info, error) { } // SwaggerSchema retrieves and parses the swagger API schema the server supports. +// TODO: Replace usages with Open API. Tracked in https://github.com/kubernetes/kubernetes/issues/44589 func (d *DiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error) { if version.Empty() { return nil, fmt.Errorf("groupVersion cannot be empty") @@ -365,6 +375,21 @@ func (d *DiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swagger.A return &schema, nil } +// OpenAPISchema fetches the open api schema using a rest client and parses the json. +// Warning: this is very expensive (~1.2s) +func (d *DiscoveryClient) OpenAPISchema() (*spec.Swagger, error) { + data, err := d.restClient.Get().AbsPath("/swagger.json").Do().Raw() + if err != nil { + return nil, err + } + msg := json.RawMessage(data) + doc, err := loads.Analyzed(msg, "") + if err != nil { + return nil, err + } + return doc.Spec(), err +} + // withRetries retries the given recovery function in case the groups supported by the server change after ServerGroup() returns. func withRetries(maxRetries int, f func(failEarly bool) ([]*metav1.APIResourceList, error)) ([]*metav1.APIResourceList, error) { var result []*metav1.APIResourceList diff --git a/discovery/discovery_client_test.go b/discovery/discovery_client_test.go index 22f01e07..f519ffaf 100644 --- a/discovery/discovery_client_test.go +++ b/discovery/discovery_client_test.go @@ -18,6 +18,7 @@ package discovery_test import ( "encoding/json" + "fmt" "net/http" "net/http/httptest" "reflect" @@ -25,6 +26,7 @@ import ( "github.com/emicklei/go-restful/swagger" + "github.com/go-openapi/spec" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" @@ -325,6 +327,81 @@ func TestGetSwaggerSchemaFail(t *testing.T) { } } +var returnedOpenAPI = spec.Swagger{ + SwaggerProps: spec.SwaggerProps{ + Definitions: spec.Definitions{ + "fake.type.1": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Properties: map[string]spec.Schema{ + "count": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + }, + }, + }, + }, + }, + "fake.type.2": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Properties: map[string]spec.Schema{ + "count": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +} + +func openapiSchemaFakeServer() (*httptest.Server, error) { + var sErr error + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.URL.Path != "/swagger.json" { + sErr = fmt.Errorf("Unexpected url %v", req.URL) + } + if req.Method != "GET" { + sErr = fmt.Errorf("Unexpected method %v", req.Method) + } + + output, err := json.Marshal(returnedOpenAPI) + if err != nil { + sErr = err + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(output) + })) + return server, sErr +} + +func TestGetOpenAPISchema(t *testing.T) { + server, err := openapiSchemaFakeServer() + 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/discovery/fake/discovery.go b/discovery/fake/discovery.go index 8fbed28d..9a953efc 100644 --- a/discovery/fake/discovery.go +++ b/discovery/fake/discovery.go @@ -21,6 +21,7 @@ import ( "github.com/emicklei/go-restful/swagger" + "github.com/go-openapi/spec" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/version" @@ -92,6 +93,8 @@ func (c *FakeDiscovery) SwaggerSchema(version schema.GroupVersion) (*swagger.Api return &swagger.ApiDeclaration{}, nil } +func (c *FakeDiscovery) OpenAPISchema() (*spec.Swagger, error) { return &spec.Swagger{}, nil } + func (c *FakeDiscovery) RESTClient() restclient.Interface { return nil } diff --git a/discovery/restmapper_test.go b/discovery/restmapper_test.go index 523fdefd..917e677c 100644 --- a/discovery/restmapper_test.go +++ b/discovery/restmapper_test.go @@ -30,6 +30,7 @@ import ( "k8s.io/client-go/rest/fake" "github.com/emicklei/go-restful/swagger" + "github.com/go-openapi/spec" "github.com/stretchr/testify/assert" ) @@ -347,3 +348,7 @@ func (c *fakeCachedDiscoveryInterface) ServerVersion() (*version.Info, error) { func (c *fakeCachedDiscoveryInterface) SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error) { return &swagger.ApiDeclaration{}, nil } + +func (c *fakeCachedDiscoveryInterface) OpenAPISchema() (*spec.Swagger, error) { + return &spec.Swagger{}, nil +}