Merge pull request #46803 from apelisse/new-download-openapi

Automatic merge from submit-queue (batch tested with PRs 43558, 48261, 42376, 46803, 47058)

OpenAPI downloads protobuf rather than Json

**What this PR does / why we need it**: 
The current implementation of the OpenAPI getter fetches the swagger in a Json format from the apiserver. The Json file is big (~1.7mb), which means that it takes a long time to download, and then a long time to parse. Because that is going to be needed on each `kubectl` run later, we want this to be as fast as possible.

The apiserver has been modified to be able to return a protobuf version of the swagger, which this patch intends to use.

Note that there is currently no piece of code that exists that allows us to go from the protobuf version of the file, back into Json and/or `spec.Swagger`. Because the protobuf is not very different (but significantly different enough that it can't be translated), I've updated the code to use `openapi_v2.Document` (the protobuf type) everywhere rather than `spec.Swagger`. The behavior should be identical though.

There are more changes that are coming in follow-up pull-requests: using the gzip version (also provided by the new apiserver) to even further reduce the size of the downloaded content, and use the HTTP Etag cache mechanism to completely get rid of recurrent fetch requests. I'm currently working on these two features.

**Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: partly #38637

**Special notes for your reviewer**:

**Release note**:
```release-note
NONE
```
This commit is contained in:
Kubernetes Submit Queue 2017-06-30 16:28:48 -07:00 committed by GitHub
commit 9c74026ffc
18 changed files with 192 additions and 150 deletions

View File

@ -46,8 +46,8 @@ go_library(
"//pkg/version:go_default_library", "//pkg/version:go_default_library",
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library", "//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
"//vendor/github.com/evanphx/json-patch:go_default_library", "//vendor/github.com/evanphx/json-patch:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library",
@ -108,7 +108,7 @@ go_test(
"//pkg/kubectl/resource:go_default_library", "//pkg/kubectl/resource:go_default_library",
"//pkg/util/exec:go_default_library", "//pkg/util/exec:go_default_library",
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library", "//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library", "//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",

View File

@ -25,8 +25,8 @@ import (
"time" "time"
"github.com/emicklei/go-restful-swagger12" "github.com/emicklei/go-restful-swagger12"
"github.com/go-openapi/spec"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/googleapis/gnostic/OpenAPIv2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -237,7 +237,7 @@ func (d *CachedDiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swa
return d.delegate.SwaggerSchema(version) return d.delegate.SwaggerSchema(version)
} }
func (d *CachedDiscoveryClient) OpenAPISchema() (*spec.Swagger, error) { func (d *CachedDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
return d.delegate.OpenAPISchema() return d.delegate.OpenAPISchema()
} }

View File

@ -23,7 +23,7 @@ import (
"time" "time"
"github.com/emicklei/go-restful-swagger12" "github.com/emicklei/go-restful-swagger12"
"github.com/go-openapi/spec" "github.com/googleapis/gnostic/OpenAPIv2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
@ -171,7 +171,7 @@ func (c *fakeDiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swagg
return &swagger.ApiDeclaration{}, nil return &swagger.ApiDeclaration{}, nil
} }
func (c *fakeDiscoveryClient) OpenAPISchema() (*spec.Swagger, error) { func (c *fakeDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
c.openAPICalls = c.openAPICalls + 1 c.openAPICalls = c.openAPICalls + 1
return &spec.Swagger{}, nil return &openapi_v2.Document{}, nil
} }

View File

@ -22,6 +22,8 @@ go_library(
"//pkg/version:go_default_library", "//pkg/version:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library", "//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/client-go/discovery:go_default_library", "//vendor/k8s.io/client-go/discovery:go_default_library",
@ -41,12 +43,14 @@ go_test(
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//pkg/kubectl/cmd/util/openapi:go_default_library", "//pkg/kubectl/cmd/util/openapi:go_default_library",
"//vendor/github.com/go-openapi/loads:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library", "//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/github.com/googleapis/gnostic/compiler:go_default_library",
"//vendor/github.com/onsi/ginkgo:go_default_library", "//vendor/github.com/onsi/ginkgo:go_default_library",
"//vendor/github.com/onsi/ginkgo/config:go_default_library", "//vendor/github.com/onsi/ginkgo/config:go_default_library",
"//vendor/github.com/onsi/ginkgo/types:go_default_library", "//vendor/github.com/onsi/ginkgo/types:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library", "//vendor/github.com/onsi/gomega:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
], ],
) )

View File

@ -20,8 +20,10 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/go-openapi/spec" "gopkg.in/yaml.v2"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/googleapis/gnostic/OpenAPIv2"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
@ -98,7 +100,7 @@ type Kind struct {
PrimitiveType string PrimitiveType string
// Extensions are openapi extensions for the object definition. // Extensions are openapi extensions for the object definition.
Extensions spec.Extensions Extensions map[string]interface{}
// Fields are the fields defined for this Kind // Fields are the fields defined for this Kind
Fields map[string]Type Fields map[string]Type
@ -130,21 +132,45 @@ type Type struct {
// Extensions are extensions for this field and may contain // Extensions are extensions for this field and may contain
// metadata from the types.go struct field tags. // metadata from the types.go struct field tags.
// e.g. contains patchStrategy, patchMergeKey, etc // e.g. contains patchStrategy, patchMergeKey, etc
Extensions spec.Extensions Extensions map[string]interface{}
}
func vendorExtensionToMap(e []*openapi_v2.NamedAny) map[string]interface{} {
var values map[string]interface{}
for _, na := range e {
if na.GetName() == "" || na.GetValue() == nil {
continue
}
if na.GetValue().GetYaml() == "" {
continue
}
var value interface{}
err := yaml.Unmarshal([]byte(na.GetValue().GetYaml()), &value)
if err != nil {
continue
}
if values == nil {
values = make(map[string]interface{})
}
values[na.GetName()] = value
}
return values
} }
// NewOpenAPIData parses the resource definitions in openapi data by groupversionkind and name // NewOpenAPIData parses the resource definitions in openapi data by groupversionkind and name
func NewOpenAPIData(s *spec.Swagger) (*Resources, error) { func NewOpenAPIData(doc *openapi_v2.Document) (*Resources, error) {
o := &Resources{ o := &Resources{
GroupVersionKindToName: map[schema.GroupVersionKind]string{}, GroupVersionKindToName: map[schema.GroupVersionKind]string{},
NameToDefinition: map[string]Kind{}, NameToDefinition: map[string]Kind{},
} }
// Parse and index definitions by name // Parse and index definitions by name
for name, d := range s.Definitions { for _, ns := range doc.GetDefinitions().GetAdditionalProperties() {
definition := o.parseDefinition(name, d) definition := o.parseDefinition(ns.GetName(), ns.GetValue())
o.NameToDefinition[name] = definition o.NameToDefinition[ns.GetName()] = definition
if len(definition.GroupVersionKind.Kind) > 0 { if len(definition.GroupVersionKind.Kind) > 0 {
o.GroupVersionKindToName[definition.GroupVersionKind] = name o.GroupVersionKindToName[definition.GroupVersionKind] = ns.GetName()
} }
} }
@ -185,12 +211,12 @@ func (o *Resources) getTypeNames(elem Type) []string {
return t return t
} }
func (o *Resources) parseDefinition(name string, s spec.Schema) Kind { func (o *Resources) parseDefinition(name string, s *openapi_v2.Schema) Kind {
gvk, err := o.getGroupVersionKind(s) gvk, err := o.getGroupVersionKind(s)
value := Kind{ value := Kind{
Name: name, Name: name,
GroupVersionKind: gvk, GroupVersionKind: gvk,
Extensions: s.Extensions, Extensions: vendorExtensionToMap(s.GetVendorExtension()),
Fields: map[string]Type{}, Fields: map[string]Type{},
} }
if err != nil { if err != nil {
@ -202,13 +228,13 @@ func (o *Resources) parseDefinition(name string, s spec.Schema) Kind {
if o.isPrimitive(s) { if o.isPrimitive(s) {
value.PrimitiveType = o.getTypeNameForField(s) value.PrimitiveType = o.getTypeNameForField(s)
} }
for fieldname, property := range s.Properties { for _, ns := range s.GetProperties().GetAdditionalProperties() {
value.Fields[fieldname] = o.parseField(property) value.Fields[ns.GetName()] = o.parseField(ns.GetValue())
} }
return value return value
} }
func (o *Resources) parseField(s spec.Schema) Type { func (o *Resources) parseField(s *openapi_v2.Schema) Type {
def := Type{ def := Type{
TypeName: o.getTypeNameForField(s), TypeName: o.getTypeNameForField(s),
IsPrimitive: o.isPrimitive(s), IsPrimitive: o.isPrimitive(s),
@ -225,14 +251,14 @@ func (o *Resources) parseField(s spec.Schema) Type {
def.ElementType = &d def.ElementType = &d
} }
def.Extensions = s.Extensions def.Extensions = vendorExtensionToMap(s.GetVendorExtension())
return def return def
} }
// isArray returns true if s is an array type. // isArray returns true if s is an array type.
func (o *Resources) isArray(s spec.Schema) bool { func (o *Resources) isArray(s *openapi_v2.Schema) bool {
if len(s.Properties) > 0 { if len(s.GetProperties().GetAdditionalProperties()) > 0 {
// Open API can have embedded type definitions, but Kubernetes doesn't generate these. // Open API can have embedded type definitions, but Kubernetes doesn't generate these.
// This should just be a sanity check against changing the format. // This should just be a sanity check against changing the format.
return false return false
@ -241,8 +267,8 @@ func (o *Resources) isArray(s spec.Schema) bool {
} }
// isMap returns true if s is a map type. // isMap returns true if s is a map type.
func (o *Resources) isMap(s spec.Schema) bool { func (o *Resources) isMap(s *openapi_v2.Schema) bool {
if len(s.Properties) > 0 { if len(s.GetProperties().GetAdditionalProperties()) > 0 {
// Open API can have embedded type definitions, but Kubernetes doesn't generate these. // Open API can have embedded type definitions, but Kubernetes doesn't generate these.
// This should just be a sanity check against changing the format. // This should just be a sanity check against changing the format.
return false return false
@ -253,8 +279,8 @@ func (o *Resources) isMap(s spec.Schema) bool {
// isPrimitive returns true if s is a primitive type // isPrimitive returns true if s is a primitive type
// Note: For object references that represent primitive types - e.g. IntOrString - this will // Note: For object references that represent primitive types - e.g. IntOrString - this will
// be false, and the referenced Kind will have a non-empty "PrimitiveType". // be false, and the referenced Kind will have a non-empty "PrimitiveType".
func (o *Resources) isPrimitive(s spec.Schema) bool { func (o *Resources) isPrimitive(s *openapi_v2.Schema) bool {
if len(s.Properties) > 0 { if len(s.GetProperties().GetAdditionalProperties()) > 0 {
// Open API can have embedded type definitions, but Kubernetes doesn't generate these. // Open API can have embedded type definitions, but Kubernetes doesn't generate these.
// This should just be a sanity check against changing the format. // This should just be a sanity check against changing the format.
return false return false
@ -266,96 +292,96 @@ func (o *Resources) isPrimitive(s spec.Schema) bool {
return false return false
} }
func (*Resources) getType(s spec.Schema) string { func (*Resources) getType(s *openapi_v2.Schema) string {
if len(s.Type) != 1 { if len(s.GetType().GetValue()) != 1 {
return "" return ""
} }
return strings.ToLower(s.Type[0]) return strings.ToLower(s.GetType().GetValue()[0])
} }
func (o *Resources) getTypeNameForField(s spec.Schema) string { func (o *Resources) getTypeNameForField(s *openapi_v2.Schema) string {
// Get the reference for complex types // Get the reference for complex types
if o.isDefinitionReference(s) { if o.isDefinitionReference(s) {
return o.nameForDefinitionField(s) return o.nameForDefinitionField(s)
} }
// Recurse if type is array // Recurse if type is array
if o.isArray(s) { if o.isArray(s) {
return fmt.Sprintf("%s array", o.getTypeNameForField(*s.Items.Schema)) return fmt.Sprintf("%s array", o.getTypeNameForField(s.GetItems().GetSchema()[0]))
} }
if o.isMap(s) { if o.isMap(s) {
return fmt.Sprintf("%s map", o.getTypeNameForField(*s.AdditionalProperties.Schema)) return fmt.Sprintf("%s map", o.getTypeNameForField(s.GetAdditionalProperties().GetSchema()))
} }
// Get the value for primitive types // Get the value for primitive types
if o.isPrimitive(s) { if o.isPrimitive(s) {
return fmt.Sprintf("%s", s.Type[0]) return fmt.Sprintf("%s", s.GetType().GetValue()[0])
} }
return "" return ""
} }
// isDefinitionReference returns true s is a complex type that should have a Kind. // isDefinitionReference returns true s is a complex type that should have a Kind.
func (o *Resources) isDefinitionReference(s spec.Schema) bool { func (o *Resources) isDefinitionReference(s *openapi_v2.Schema) bool {
if len(s.Properties) > 0 { if len(s.GetProperties().GetAdditionalProperties()) > 0 {
// Open API can have embedded type definitions, but Kubernetes doesn't generate these. // Open API can have embedded type definitions, but Kubernetes doesn't generate these.
// This should just be a sanity check against changing the format. // This should just be a sanity check against changing the format.
return false return false
} }
if len(s.Type) > 0 { if len(s.GetType().GetValue()) > 0 {
// Definition references won't have a type // Definition references won't have a type
return false return false
} }
p := s.SchemaProps.Ref.GetPointer().String() p := s.GetXRef()
return len(p) > 0 && strings.HasPrefix(p, "/definitions/") return len(p) > 0 && strings.HasPrefix(p, "#/definitions/")
} }
// getElementType returns the type of an element for arrays // getElementType returns the type of an element for arrays
// returns an error if s is not an array. // returns an error if s is not an array.
func (o *Resources) getElementType(s spec.Schema) (spec.Schema, error) { func (o *Resources) getElementType(s *openapi_v2.Schema) (*openapi_v2.Schema, error) {
if !o.isArray(s) { if !o.isArray(s) {
return spec.Schema{}, fmt.Errorf("%v is not an array type", s.Type) return &openapi_v2.Schema{}, fmt.Errorf("%v is not an array type", o.getTypeNameForField(s))
} }
return *s.Items.Schema, nil return s.GetItems().GetSchema()[0], nil
} }
// getElementType returns the type of an element for maps // getValueType returns the type of an element for maps
// returns an error if s is not a map. // returns an error if s is not a map.
func (o *Resources) getValueType(s spec.Schema) (spec.Schema, error) { func (o *Resources) getValueType(s *openapi_v2.Schema) (*openapi_v2.Schema, error) {
if !o.isMap(s) { if !o.isMap(s) {
return spec.Schema{}, fmt.Errorf("%v is not an map type", s.Type) return &openapi_v2.Schema{}, fmt.Errorf("%v is not an map type", o.getTypeNameForField(s))
} }
return *s.AdditionalProperties.Schema, nil return s.GetAdditionalProperties().GetSchema(), nil
} }
// nameForDefinitionField returns the definition name for the schema (field) if it is a complex type // nameForDefinitionField returns the definition name for the schema (field) if it is a complex type
func (o *Resources) nameForDefinitionField(s spec.Schema) string { func (o *Resources) nameForDefinitionField(s *openapi_v2.Schema) string {
p := s.SchemaProps.Ref.GetPointer().String() p := s.GetXRef()
if len(p) == 0 { if len(p) == 0 {
return "" return ""
} }
// Strip the "definitions/" pieces of the reference // Strip the "definitions/" pieces of the reference
return strings.Replace(p, "/definitions/", "", -1) return strings.Replace(p, "#/definitions/", "", -1)
} }
// getGroupVersionKind implements OpenAPIData // getGroupVersionKind implements OpenAPIData
// getGVK parses the gropuversionkind for a resource definition from the x-kubernetes // getGVK parses the gropuversionkind for a resource definition from the x-kubernetes
// extensions // extensions
// Expected format for s.Extensions: map[string][]map[string]string
// map[x-kubernetes-group-version-kind:[map[Group:authentication.k8s.io Version:v1 Kind:TokenReview]]] // map[x-kubernetes-group-version-kind:[map[Group:authentication.k8s.io Version:v1 Kind:TokenReview]]]
func (o *Resources) getGroupVersionKind(s spec.Schema) (schema.GroupVersionKind, error) { func (o *Resources) getGroupVersionKind(s *openapi_v2.Schema) (schema.GroupVersionKind, error) {
empty := schema.GroupVersionKind{} empty := schema.GroupVersionKind{}
extensionMap := vendorExtensionToMap(s.GetVendorExtension())
// Get the extensions // Get the extensions
extList, f := s.Extensions[groupVersionKindExtensionKey] extList, f := extensionMap[groupVersionKindExtensionKey]
if !f { if !f {
return empty, fmt.Errorf("No %s extension present in %v", groupVersionKindExtensionKey, s.Extensions) return empty, fmt.Errorf("No %s extension present in %v", groupVersionKindExtensionKey, extensionMap)
} }
// Expect a empty of a list with 1 element // Expect a empty of a list with 1 element
extListCasted, ok := extList.([]interface{}) extListCasted, ok := extList.([]interface{})
if !ok { if !ok {
return empty, fmt.Errorf("%s extension has unexpected type %T in %s", groupVersionKindExtensionKey, extListCasted, s.Extensions) return empty, fmt.Errorf("%s extension has unexpected type %T in %s", groupVersionKindExtensionKey, extListCasted, extensionMap)
} }
if len(extListCasted) == 0 { if len(extListCasted) == 0 {
return empty, fmt.Errorf("No Group Version Kind found in %v", extListCasted) return empty, fmt.Errorf("No Group Version Kind found in %v", extListCasted)
@ -366,9 +392,9 @@ func (o *Resources) getGroupVersionKind(s spec.Schema) (schema.GroupVersionKind,
gvk := extListCasted[0] gvk := extListCasted[0]
// Expect a empty of a map with 3 entries // Expect a empty of a map with 3 entries
gvkMap, ok := gvk.(map[string]interface{}) gvkMap, ok := gvk.(map[interface{}]interface{})
if !ok { if !ok {
return empty, fmt.Errorf("%s extension has unexpected type %T in %s", groupVersionKindExtensionKey, gvk, s.Extensions) return empty, fmt.Errorf("%s extension has unexpected type %T in %s", groupVersionKindExtensionKey, gvk, extList)
} }
group, ok := gvkMap["group"].(string) group, ok := gvkMap["group"].(string)
if !ok { if !ok {

View File

@ -187,7 +187,6 @@ func linkFiles(old, new string) error {
// registerBinaryEncodingTypes registers the types so they can be binary encoded by gob // registerBinaryEncodingTypes registers the types so they can be binary encoded by gob
func registerBinaryEncodingTypes() { func registerBinaryEncodingTypes() {
gob.Register(map[string]interface{}{}) gob.Register(map[interface{}]interface{}{})
gob.Register([]interface{}{}) gob.Register([]interface{}{})
gob.Register(Resources{})
} }

View File

@ -23,8 +23,10 @@ import (
"path/filepath" "path/filepath"
"sync" "sync"
"github.com/go-openapi/loads" "gopkg.in/yaml.v2"
"github.com/go-openapi/spec"
"github.com/googleapis/gnostic/OpenAPIv2"
"github.com/googleapis/gnostic/compiler"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@ -220,7 +222,7 @@ type fakeOpenAPIClient struct {
err error err error
} }
func (f *fakeOpenAPIClient) OpenAPISchema() (*spec.Swagger, error) { func (f *fakeOpenAPIClient) OpenAPISchema() (*openapi_v2.Document, error) {
f.calls = f.calls + 1 f.calls = f.calls + 1
if f.err != nil { if f.err != nil {
@ -235,11 +237,11 @@ var data apiData
type apiData struct { type apiData struct {
sync.Once sync.Once
data *spec.Swagger data *openapi_v2.Document
err error err error
} }
func (d *apiData) OpenAPISchema() (*spec.Swagger, error) { func (d *apiData) OpenAPISchema() (*openapi_v2.Document, error) {
d.Do(func() { d.Do(func() {
// Get the path to the swagger.json file // Get the path to the swagger.json file
wd, err := os.Getwd() wd, err := os.Getwd()
@ -261,14 +263,18 @@ func (d *apiData) OpenAPISchema() (*spec.Swagger, error) {
d.err = err d.err = err
return return
} }
// Load the openapi document spec, err := ioutil.ReadFile(specpath)
doc, err := loads.Spec(specpath)
if err != nil { if err != nil {
d.err = err d.err = err
return return
} }
var info yaml.MapSlice
d.data = doc.Spec() err = yaml.Unmarshal(spec, &info)
if err != nil {
d.err = err
return
}
d.data, d.err = openapi_v2.NewDocument(info, compiler.NewContext("$root", nil))
}) })
return d.data, d.err return d.data, d.err
} }

View File

@ -106,10 +106,6 @@
"ImportPath": "github.com/ghodss/yaml", "ImportPath": "github.com/ghodss/yaml",
"Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee" "Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
}, },
{
"ImportPath": "github.com/go-openapi/analysis",
"Rev": "b44dc874b601d9e4e2f6e19140e794ba24bead3b"
},
{ {
"ImportPath": "github.com/go-openapi/jsonpointer", "ImportPath": "github.com/go-openapi/jsonpointer",
"Rev": "46af16f9f7b149af66e5d1bd010e3574dc06de98" "Rev": "46af16f9f7b149af66e5d1bd010e3574dc06de98"
@ -118,10 +114,6 @@
"ImportPath": "github.com/go-openapi/jsonreference", "ImportPath": "github.com/go-openapi/jsonreference",
"Rev": "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272" "Rev": "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272"
}, },
{
"ImportPath": "github.com/go-openapi/loads",
"Rev": "18441dfa706d924a39a030ee2c3b1d8d81917b38"
},
{ {
"ImportPath": "github.com/go-openapi/spec", "ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff" "Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"

View File

@ -326,10 +326,6 @@
"ImportPath": "github.com/ghodss/yaml", "ImportPath": "github.com/ghodss/yaml",
"Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee" "Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
}, },
{
"ImportPath": "github.com/go-openapi/analysis",
"Rev": "b44dc874b601d9e4e2f6e19140e794ba24bead3b"
},
{ {
"ImportPath": "github.com/go-openapi/jsonpointer", "ImportPath": "github.com/go-openapi/jsonpointer",
"Rev": "46af16f9f7b149af66e5d1bd010e3574dc06de98" "Rev": "46af16f9f7b149af66e5d1bd010e3574dc06de98"
@ -338,10 +334,6 @@
"ImportPath": "github.com/go-openapi/jsonreference", "ImportPath": "github.com/go-openapi/jsonreference",
"Rev": "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272" "Rev": "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272"
}, },
{
"ImportPath": "github.com/go-openapi/loads",
"Rev": "18441dfa706d924a39a030ee2c3b1d8d81917b38"
},
{ {
"ImportPath": "github.com/go-openapi/spec", "ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff" "Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"

View File

@ -98,10 +98,6 @@
"ImportPath": "github.com/ghodss/yaml", "ImportPath": "github.com/ghodss/yaml",
"Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee" "Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
}, },
{
"ImportPath": "github.com/go-openapi/analysis",
"Rev": "b44dc874b601d9e4e2f6e19140e794ba24bead3b"
},
{ {
"ImportPath": "github.com/go-openapi/jsonpointer", "ImportPath": "github.com/go-openapi/jsonpointer",
"Rev": "46af16f9f7b149af66e5d1bd010e3574dc06de98" "Rev": "46af16f9f7b149af66e5d1bd010e3574dc06de98"
@ -110,10 +106,6 @@
"ImportPath": "github.com/go-openapi/jsonreference", "ImportPath": "github.com/go-openapi/jsonreference",
"Rev": "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272" "Rev": "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272"
}, },
{
"ImportPath": "github.com/go-openapi/loads",
"Rev": "18441dfa706d924a39a030ee2c3b1d8d81917b38"
},
{ {
"ImportPath": "github.com/go-openapi/spec", "ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff" "Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"
@ -142,10 +134,38 @@
"ImportPath": "github.com/golang/protobuf/proto", "ImportPath": "github.com/golang/protobuf/proto",
"Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7"
}, },
{
"ImportPath": "github.com/golang/protobuf/ptypes",
"Rev": "4bd1920723d7b7c925de087aa32e2187708897f7"
},
{
"ImportPath": "github.com/golang/protobuf/ptypes/any",
"Rev": "4bd1920723d7b7c925de087aa32e2187708897f7"
},
{
"ImportPath": "github.com/golang/protobuf/ptypes/duration",
"Rev": "4bd1920723d7b7c925de087aa32e2187708897f7"
},
{
"ImportPath": "github.com/golang/protobuf/ptypes/timestamp",
"Rev": "4bd1920723d7b7c925de087aa32e2187708897f7"
},
{ {
"ImportPath": "github.com/google/gofuzz", "ImportPath": "github.com/google/gofuzz",
"Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c"
}, },
{
"ImportPath": "github.com/googleapis/gnostic/OpenAPIv2",
"Rev": "68f4ded48ba9414dab2ae69b3f0d69971da73aa5"
},
{
"ImportPath": "github.com/googleapis/gnostic/compiler",
"Rev": "68f4ded48ba9414dab2ae69b3f0d69971da73aa5"
},
{
"ImportPath": "github.com/googleapis/gnostic/extensions",
"Rev": "68f4ded48ba9414dab2ae69b3f0d69971da73aa5"
},
{ {
"ImportPath": "github.com/hashicorp/golang-lru", "ImportPath": "github.com/hashicorp/golang-lru",
"Rev": "a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4" "Rev": "a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4"

View File

@ -19,9 +19,9 @@ go_library(
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library", "//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
"//vendor/github.com/go-openapi/loads:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/golang/protobuf/proto:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
@ -46,7 +46,8 @@ go_test(
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library", "//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library", "//vendor/github.com/gogo/protobuf/proto:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",

View File

@ -24,9 +24,9 @@ import (
"strings" "strings"
"github.com/emicklei/go-restful-swagger12" "github.com/emicklei/go-restful-swagger12"
"github.com/golang/protobuf/proto"
"github.com/googleapis/gnostic/OpenAPIv2"
"github.com/go-openapi/loads"
"github.com/go-openapi/spec"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -97,7 +97,7 @@ type SwaggerSchemaInterface interface {
// OpenAPISchemaInterface has a method to retrieve the open API schema. // OpenAPISchemaInterface has a method to retrieve the open API schema.
type OpenAPISchemaInterface interface { type OpenAPISchemaInterface interface {
// OpenAPISchema retrieves and parses the swagger API schema the server supports. // OpenAPISchema retrieves and parses the swagger API schema the server supports.
OpenAPISchema() (*spec.Swagger, error) OpenAPISchema() (*openapi_v2.Document, error)
} }
// DiscoveryClient implements the functions that discover server-supported API groups, // DiscoveryClient implements the functions that discover server-supported API groups,
@ -375,19 +375,18 @@ func (d *DiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swagger.A
return &schema, nil return &schema, nil
} }
// OpenAPISchema fetches the open api schema using a rest client and parses the json. // OpenAPISchema fetches the open api schema using a rest client and parses the proto.
// Warning: this is very expensive (~1.2s) func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
func (d *DiscoveryClient) OpenAPISchema() (*spec.Swagger, error) { data, err := d.restClient.Get().AbsPath("/swagger-2.0.0.pb-v1").Do().Raw()
data, err := d.restClient.Get().AbsPath("/swagger.json").Do().Raw()
if err != nil { if err != nil {
return nil, err return nil, err
} }
msg := json.RawMessage(data) document := &openapi_v2.Document{}
doc, err := loads.Analyzed(msg, "") err = proto.Unmarshal(data, document)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return doc.Spec(), err return document, nil
} }
// withRetries retries the given recovery function in case the groups supported by the server change after ServerGroup() returns. // withRetries retries the given recovery function in case the groups supported by the server change after ServerGroup() returns.

View File

@ -19,14 +19,16 @@ package discovery_test
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"mime"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect" "reflect"
"testing" "testing"
"github.com/emicklei/go-restful-swagger12" "github.com/emicklei/go-restful-swagger12"
"github.com/gogo/protobuf/proto"
"github.com/googleapis/gnostic/OpenAPIv2"
"github.com/go-openapi/spec"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
@ -327,30 +329,44 @@ func TestGetSwaggerSchemaFail(t *testing.T) {
} }
} }
var returnedOpenAPI = spec.Swagger{ var returnedOpenAPI = openapi_v2.Document{
SwaggerProps: spec.SwaggerProps{ Definitions: &openapi_v2.Definitions{
Definitions: spec.Definitions{ AdditionalProperties: []*openapi_v2.NamedSchema{
"fake.type.1": spec.Schema{ {
SchemaProps: spec.SchemaProps{ Name: "fake.type.1",
Properties: map[string]spec.Schema{ Value: &openapi_v2.Schema{
"count": { Properties: &openapi_v2.Properties{
SchemaProps: spec.SchemaProps{ AdditionalProperties: []*openapi_v2.NamedSchema{
Type: []string{"integer"}, {
Name: "count",
Value: &openapi_v2.Schema{
Type: &openapi_v2.TypeItem{
Value: []string{"integer"},
},
},
}, },
}, },
}, },
}, },
}, },
"fake.type.2": spec.Schema{ {
SchemaProps: spec.SchemaProps{ Name: "fake.type.2",
Properties: map[string]spec.Schema{ Value: &openapi_v2.Schema{
"count": { Properties: &openapi_v2.Properties{
SchemaProps: spec.SchemaProps{ AdditionalProperties: []*openapi_v2.NamedSchema{
Type: []string{"array"}, {
Items: &spec.SchemaOrArray{ Name: "count",
Schema: &spec.Schema{ Value: &openapi_v2.Schema{
SchemaProps: spec.SchemaProps{ Type: &openapi_v2.TypeItem{
Type: []string{"string"}, Value: []string{"array"},
},
Items: &openapi_v2.ItemsItem{
Schema: []*openapi_v2.Schema{
{
Type: &openapi_v2.TypeItem{
Value: []string{"string"},
},
},
}, },
}, },
}, },
@ -366,19 +382,20 @@ var returnedOpenAPI = spec.Swagger{
func openapiSchemaFakeServer() (*httptest.Server, error) { func openapiSchemaFakeServer() (*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) {
if req.URL.Path != "/swagger.json" { 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)
} }
if req.Method != "GET" { if req.Method != "GET" {
sErr = fmt.Errorf("Unexpected method %v", req.Method) sErr = fmt.Errorf("Unexpected method %v", req.Method)
} }
output, err := json.Marshal(returnedOpenAPI) mime.AddExtensionType(".pb-v1", "application/com.github.googleapis.gnostic.OpenAPIv2@68f4ded+protobuf")
output, err := proto.Marshal(&returnedOpenAPI)
if err != nil { if err != nil {
sErr = err sErr = err
return return
} }
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Write(output) w.Write(output)
})) }))

View File

@ -13,7 +13,7 @@ go_library(
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library", "//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library", "//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",

View File

@ -20,8 +20,8 @@ import (
"fmt" "fmt"
"github.com/emicklei/go-restful-swagger12" "github.com/emicklei/go-restful-swagger12"
"github.com/googleapis/gnostic/OpenAPIv2"
"github.com/go-openapi/spec"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
@ -93,7 +93,9 @@ func (c *FakeDiscovery) SwaggerSchema(version schema.GroupVersion) (*swagger.Api
return &swagger.ApiDeclaration{}, nil return &swagger.ApiDeclaration{}, nil
} }
func (c *FakeDiscovery) OpenAPISchema() (*spec.Swagger, error) { return &spec.Swagger{}, nil } func (c *FakeDiscovery) OpenAPISchema() (*openapi_v2.Document, error) {
return &openapi_v2.Document{}, nil
}
func (c *FakeDiscovery) RESTClient() restclient.Interface { func (c *FakeDiscovery) RESTClient() restclient.Interface {
return nil return nil

View File

@ -29,7 +29,7 @@ import (
"k8s.io/client-go/rest/fake" "k8s.io/client-go/rest/fake"
"github.com/emicklei/go-restful-swagger12" "github.com/emicklei/go-restful-swagger12"
"github.com/go-openapi/spec" "github.com/googleapis/gnostic/OpenAPIv2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -348,6 +348,6 @@ func (c *fakeCachedDiscoveryInterface) SwaggerSchema(version schema.GroupVersion
return &swagger.ApiDeclaration{}, nil return &swagger.ApiDeclaration{}, nil
} }
func (c *fakeCachedDiscoveryInterface) OpenAPISchema() (*spec.Swagger, error) { func (c *fakeCachedDiscoveryInterface) OpenAPISchema() (*openapi_v2.Document, error) {
return &spec.Swagger{}, nil return &openapi_v2.Document{}, nil
} }

View File

@ -114,10 +114,6 @@
"ImportPath": "github.com/ghodss/yaml", "ImportPath": "github.com/ghodss/yaml",
"Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee" "Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
}, },
{
"ImportPath": "github.com/go-openapi/analysis",
"Rev": "b44dc874b601d9e4e2f6e19140e794ba24bead3b"
},
{ {
"ImportPath": "github.com/go-openapi/jsonpointer", "ImportPath": "github.com/go-openapi/jsonpointer",
"Rev": "46af16f9f7b149af66e5d1bd010e3574dc06de98" "Rev": "46af16f9f7b149af66e5d1bd010e3574dc06de98"
@ -126,10 +122,6 @@
"ImportPath": "github.com/go-openapi/jsonreference", "ImportPath": "github.com/go-openapi/jsonreference",
"Rev": "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272" "Rev": "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272"
}, },
{
"ImportPath": "github.com/go-openapi/loads",
"Rev": "18441dfa706d924a39a030ee2c3b1d8d81917b38"
},
{ {
"ImportPath": "github.com/go-openapi/spec", "ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff" "Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"

View File

@ -106,10 +106,6 @@
"ImportPath": "github.com/ghodss/yaml", "ImportPath": "github.com/ghodss/yaml",
"Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee" "Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
}, },
{
"ImportPath": "github.com/go-openapi/analysis",
"Rev": "b44dc874b601d9e4e2f6e19140e794ba24bead3b"
},
{ {
"ImportPath": "github.com/go-openapi/jsonpointer", "ImportPath": "github.com/go-openapi/jsonpointer",
"Rev": "46af16f9f7b149af66e5d1bd010e3574dc06de98" "Rev": "46af16f9f7b149af66e5d1bd010e3574dc06de98"
@ -118,10 +114,6 @@
"ImportPath": "github.com/go-openapi/jsonreference", "ImportPath": "github.com/go-openapi/jsonreference",
"Rev": "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272" "Rev": "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272"
}, },
{
"ImportPath": "github.com/go-openapi/loads",
"Rev": "18441dfa706d924a39a030ee2c3b1d8d81917b38"
},
{ {
"ImportPath": "github.com/go-openapi/spec", "ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff" "Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"