diff --git a/pkg/kubectl/cmd/BUILD b/pkg/kubectl/cmd/BUILD index c9d981029d..c85030783a 100644 --- a/pkg/kubectl/cmd/BUILD +++ b/pkg/kubectl/cmd/BUILD @@ -203,7 +203,6 @@ go_test( "//pkg/kubectl:go_default_library", "//pkg/kubectl/cmd/testing:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", - "//pkg/kubectl/cmd/util/openapi:go_default_library", "//pkg/kubectl/plugins:go_default_library", "//pkg/kubectl/resource:go_default_library", "//pkg/kubectl/util/i18n:go_default_library", @@ -237,6 +236,7 @@ go_test( "//vendor/k8s.io/client-go/rest/fake:go_default_library", "//vendor/k8s.io/client-go/rest/watch:go_default_library", "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", "//vendor/k8s.io/metrics/pkg/apis/metrics/v1alpha1:go_default_library", ], ) diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go index 91b8892743..5ee488f3ad 100644 --- a/pkg/kubectl/cmd/get.go +++ b/pkg/kubectl/cmd/get.go @@ -578,7 +578,7 @@ func outputOptsForMappingFromOpenAPI(f cmdutil.Factory, mapping *meta.RESTMappin return nil, false } // Found openapi metadata for this resource - schema := api.LookupResource(mapping.GroupVersionKind) + schema := api.LookupResource(mapping.GroupVersionKind.String()) if schema == nil { // Schema not found, return empty columns return nil, false diff --git a/pkg/kubectl/cmd/get_test.go b/pkg/kubectl/cmd/get_test.go index 129a0e08a7..ca7e751d0e 100644 --- a/pkg/kubectl/cmd/get_test.go +++ b/pkg/kubectl/cmd/get_test.go @@ -38,11 +38,11 @@ import ( restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" restclientwatch "k8s.io/client-go/rest/watch" + openapi "k8s.io/kube-openapi/pkg/util/proto" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" ) func testData() (*api.PodList, *api.ServiceList, *api.ReplicationControllerList) { @@ -217,10 +217,10 @@ func TestGetObjectsWithOpenAPIOutputFormatPresent(t *testing.T) { } type FakeResources struct { - resources map[schema.GroupVersionKind]openapi.Schema + resources map[string]openapi.Schema } -func (f FakeResources) LookupResource(s schema.GroupVersionKind) openapi.Schema { +func (f FakeResources) LookupResource(s string) openapi.Schema { return f.resources[s] } @@ -228,11 +228,11 @@ var _ openapi.Resources = &FakeResources{} func testOpenAPISchemaData() (openapi.Resources, error) { return &FakeResources{ - resources: map[schema.GroupVersionKind]openapi.Schema{ - { + resources: map[string]openapi.Schema{ + schema.GroupVersionKind{ Version: "v1", Kind: "Pod", - }: &openapi.Primitive{ + }.String(): &openapi.Primitive{ BaseSchema: openapi.BaseSchema{ Extensions: map[string]interface{}{ "x-kubernetes-print-columns": "custom-columns=NAME:.metadata.name,RSRC:.metadata.resourceVersion", diff --git a/pkg/kubectl/cmd/testing/BUILD b/pkg/kubectl/cmd/testing/BUILD index 722e7c4ca6..6f66e1e84a 100644 --- a/pkg/kubectl/cmd/testing/BUILD +++ b/pkg/kubectl/cmd/testing/BUILD @@ -19,7 +19,6 @@ go_library( "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/kubectl:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", - "//pkg/kubectl/cmd/util/openapi:go_default_library", "//pkg/kubectl/plugins:go_default_library", "//pkg/kubectl/resource:go_default_library", "//pkg/kubectl/validation:go_default_library", @@ -38,6 +37,7 @@ go_library( "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest/fake:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", ], ) diff --git a/pkg/kubectl/cmd/testing/fake.go b/pkg/kubectl/cmd/testing/fake.go index ce9c251c96..9306797453 100644 --- a/pkg/kubectl/cmd/testing/fake.go +++ b/pkg/kubectl/cmd/testing/fake.go @@ -37,13 +37,13 @@ import ( "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" + openapi "k8s.io/kube-openapi/pkg/util/proto" fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" "k8s.io/kubernetes/pkg/kubectl/plugins" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/validation" diff --git a/pkg/kubectl/cmd/util/BUILD b/pkg/kubectl/cmd/util/BUILD index 180988c990..c43d7e1df7 100644 --- a/pkg/kubectl/cmd/util/BUILD +++ b/pkg/kubectl/cmd/util/BUILD @@ -71,6 +71,7 @@ go_library( "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", "//vendor/k8s.io/client-go/util/homedir:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", ], ) diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index 992916dc5e..50fd5387a7 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -46,13 +46,13 @@ import ( "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + openapi "k8s.io/kube-openapi/pkg/util/proto" fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" "k8s.io/kubernetes/pkg/api" apiv1 "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/kubectl" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" "k8s.io/kubernetes/pkg/kubectl/plugins" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/validation" diff --git a/pkg/kubectl/cmd/util/factory_object_mapping.go b/pkg/kubectl/cmd/util/factory_object_mapping.go index 29f9bdb735..8f946304f2 100644 --- a/pkg/kubectl/cmd/util/factory_object_mapping.go +++ b/pkg/kubectl/cmd/util/factory_object_mapping.go @@ -39,6 +39,7 @@ import ( "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" restclient "k8s.io/client-go/rest" + oapi "k8s.io/kube-openapi/pkg/util/proto" "k8s.io/kubernetes/federation/apis/federation" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/apps" @@ -453,7 +454,7 @@ func (f *ring1Factory) SwaggerSchema(gvk schema.GroupVersionKind) (*swagger.ApiD } // OpenAPISchema returns metadata and structural information about Kubernetes object definitions. -func (f *ring1Factory) OpenAPISchema() (openapi.Resources, error) { +func (f *ring1Factory) OpenAPISchema() (oapi.Resources, error) { discovery, err := f.clientAccessFactory.DiscoveryClient() if err != nil { return nil, err diff --git a/pkg/kubectl/cmd/util/openapi/BUILD b/pkg/kubectl/cmd/util/openapi/BUILD index 1075879cd7..148053e57c 100644 --- a/pkg/kubectl/cmd/util/openapi/BUILD +++ b/pkg/kubectl/cmd/util/openapi/BUILD @@ -10,17 +10,16 @@ go_library( name = "go_default_library", srcs = [ "doc.go", - "document.go", "extensions.go", - "openapi.go", + "gvk.go", "openapi_getter.go", ], deps = [ "//vendor/github.com/go-openapi/spec: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/client-go/discovery:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", ], ) @@ -30,17 +29,16 @@ go_test( srcs = [ "openapi_getter_test.go", "openapi_suite_test.go", - "openapi_test.go", ], data = ["//api/openapi-spec:swagger-spec"], deps = [ ":go_default_library", - "//pkg/kubectl/cmd/util/openapi/testing: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/types:go_default_library", "//vendor/github.com/onsi/gomega:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto/testing:go_default_library", ], ) @@ -55,7 +53,6 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", - "//pkg/kubectl/cmd/util/openapi/testing:all-srcs", "//pkg/kubectl/cmd/util/openapi/validation:all-srcs", ], tags = ["automanaged"], diff --git a/pkg/kubectl/cmd/util/openapi/document.go b/pkg/kubectl/cmd/util/openapi/document.go deleted file mode 100644 index 6b2f6782b9..0000000000 --- a/pkg/kubectl/cmd/util/openapi/document.go +++ /dev/null @@ -1,330 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package openapi - -import ( - "fmt" - "strings" - - openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2" - yaml "gopkg.in/yaml.v2" - - "k8s.io/apimachinery/pkg/runtime/schema" -) - -func newSchemaError(path *Path, format string, a ...interface{}) error { - err := fmt.Sprintf(format, a...) - if path.Len() == 0 { - return fmt.Errorf("SchemaError: %v", err) - } - return fmt.Errorf("SchemaError(%v): %v", path, err) -} - -// groupVersionKindExtensionKey is the key used to lookup the -// GroupVersionKind value for an object definition from the -// definition's "extensions" map. -const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind" - -func vendorExtensionToMap(e []*openapi_v2.NamedAny) map[string]interface{} { - 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 - } - - values[na.GetName()] = value - } - - return values -} - -// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one. -func parseGroupVersionKind(s *openapi_v2.Schema) schema.GroupVersionKind { - extensionMap := vendorExtensionToMap(s.GetVendorExtension()) - - // Get the extensions - gvkExtension, ok := extensionMap[groupVersionKindExtensionKey] - if !ok { - return schema.GroupVersionKind{} - } - - // gvk extension must be a list of 1 element. - gvkList, ok := gvkExtension.([]interface{}) - if !ok { - return schema.GroupVersionKind{} - } - if len(gvkList) != 1 { - return schema.GroupVersionKind{} - - } - gvk := gvkList[0] - - // gvk extension list must be a map with group, version, and - // kind fields - gvkMap, ok := gvk.(map[interface{}]interface{}) - if !ok { - return schema.GroupVersionKind{} - } - group, ok := gvkMap["group"].(string) - if !ok { - return schema.GroupVersionKind{} - } - version, ok := gvkMap["version"].(string) - if !ok { - return schema.GroupVersionKind{} - } - kind, ok := gvkMap["kind"].(string) - if !ok { - return schema.GroupVersionKind{} - } - - return schema.GroupVersionKind{ - Group: group, - Version: version, - Kind: kind, - } -} - -// Definitions is an implementation of `Resources`. It looks for -// resources in an openapi Schema. -type Definitions struct { - models map[string]Schema - resources map[schema.GroupVersionKind]string -} - -var _ Resources = &Definitions{} - -// NewOpenAPIData creates a new `Resources` out of the openapi document. -func NewOpenAPIData(doc *openapi_v2.Document) (Resources, error) { - definitions := Definitions{ - models: map[string]Schema{}, - resources: map[schema.GroupVersionKind]string{}, - } - - // Save the list of all models first. This will allow us to - // validate that we don't have any dangling reference. - for _, namedSchema := range doc.GetDefinitions().GetAdditionalProperties() { - definitions.models[namedSchema.GetName()] = nil - } - - // Now, parse each model. We can validate that references exists. - for _, namedSchema := range doc.GetDefinitions().GetAdditionalProperties() { - path := NewPath(namedSchema.GetName()) - schema, err := definitions.ParseSchema(namedSchema.GetValue(), &path) - if err != nil { - return nil, err - } - definitions.models[namedSchema.GetName()] = schema - gvk := parseGroupVersionKind(namedSchema.GetValue()) - if len(gvk.Kind) > 0 { - definitions.resources[gvk] = namedSchema.GetName() - } - } - - return &definitions, nil -} - -// We believe the schema is a reference, verify that and returns a new -// Schema -func (d *Definitions) parseReference(s *openapi_v2.Schema, path *Path) (Schema, error) { - if len(s.GetProperties().GetAdditionalProperties()) > 0 { - return nil, newSchemaError(path, "unallowed embedded type definition") - } - if len(s.GetType().GetValue()) > 0 { - return nil, newSchemaError(path, "definition reference can't have a type") - } - - if !strings.HasPrefix(s.GetXRef(), "#/definitions/") { - return nil, newSchemaError(path, "unallowed reference to non-definition %q", s.GetXRef()) - } - reference := strings.TrimPrefix(s.GetXRef(), "#/definitions/") - if _, ok := d.models[reference]; !ok { - return nil, newSchemaError(path, "unknown model in reference: %q", reference) - } - return &Ref{ - BaseSchema: d.parseBaseSchema(s, path), - reference: reference, - definitions: d, - }, nil -} - -func (d *Definitions) parseBaseSchema(s *openapi_v2.Schema, path *Path) BaseSchema { - return BaseSchema{ - Description: s.GetDescription(), - Extensions: vendorExtensionToMap(s.GetVendorExtension()), - Path: *path, - } -} - -// We believe the schema is a map, verify and return a new schema -func (d *Definitions) parseMap(s *openapi_v2.Schema, path *Path) (Schema, error) { - if len(s.GetType().GetValue()) != 0 && s.GetType().GetValue()[0] != object { - return nil, newSchemaError(path, "invalid object type") - } - if s.GetAdditionalProperties().GetSchema() == nil { - return nil, newSchemaError(path, "invalid object doesn't have additional properties") - } - sub, err := d.ParseSchema(s.GetAdditionalProperties().GetSchema(), path) - if err != nil { - return nil, err - } - return &Map{ - BaseSchema: d.parseBaseSchema(s, path), - SubType: sub, - }, nil -} - -func (d *Definitions) parsePrimitive(s *openapi_v2.Schema, path *Path) (Schema, error) { - var t string - if len(s.GetType().GetValue()) > 1 { - return nil, newSchemaError(path, "primitive can't have more than 1 type") - } - if len(s.GetType().GetValue()) == 1 { - t = s.GetType().GetValue()[0] - } - switch t { - case String: - case Number: - case Integer: - case Boolean: - case "": // Some models are completely empty, and can be safely ignored. - // Do nothing - default: - return nil, newSchemaError(path, "Unknown primitive type: %q", t) - } - return &Primitive{ - BaseSchema: d.parseBaseSchema(s, path), - Type: t, - Format: s.GetFormat(), - }, nil -} - -func (d *Definitions) parseArray(s *openapi_v2.Schema, path *Path) (Schema, error) { - if len(s.GetType().GetValue()) != 1 { - return nil, newSchemaError(path, "array should have exactly one type") - } - if s.GetType().GetValue()[0] != array { - return nil, newSchemaError(path, `array should have type "array"`) - } - if len(s.GetItems().GetSchema()) != 1 { - return nil, newSchemaError(path, "array should have exactly one sub-item") - } - sub, err := d.ParseSchema(s.GetItems().GetSchema()[0], path) - if err != nil { - return nil, err - } - return &Array{ - BaseSchema: d.parseBaseSchema(s, path), - SubType: sub, - }, nil -} - -func (d *Definitions) parseKind(s *openapi_v2.Schema, path *Path) (Schema, error) { - if len(s.GetType().GetValue()) != 0 && s.GetType().GetValue()[0] != object { - return nil, newSchemaError(path, "invalid object type") - } - if s.GetProperties() == nil { - return nil, newSchemaError(path, "object doesn't have properties") - } - - fields := map[string]Schema{} - - for _, namedSchema := range s.GetProperties().GetAdditionalProperties() { - var err error - path := path.FieldPath(namedSchema.GetName()) - fields[namedSchema.GetName()], err = d.ParseSchema(namedSchema.GetValue(), &path) - if err != nil { - return nil, err - } - } - - return &Kind{ - BaseSchema: d.parseBaseSchema(s, path), - RequiredFields: s.GetRequired(), - Fields: fields, - }, nil -} - -// ParseSchema creates a walkable Schema from an openapi schema. While -// this function is public, it doesn't leak through the interface. -func (d *Definitions) ParseSchema(s *openapi_v2.Schema, path *Path) (Schema, error) { - if len(s.GetType().GetValue()) == 1 { - t := s.GetType().GetValue()[0] - switch t { - case object: - return d.parseMap(s, path) - case array: - return d.parseArray(s, path) - } - - } - if s.GetXRef() != "" { - return d.parseReference(s, path) - } - if s.GetProperties() != nil { - return d.parseKind(s, path) - } - return d.parsePrimitive(s, path) -} - -// LookupResource is public through the interface of Resources. It -// returns a visitable schema from the given group-version-kind. -func (d *Definitions) LookupResource(gvk schema.GroupVersionKind) Schema { - modelName, found := d.resources[gvk] - if !found { - return nil - } - model, found := d.models[modelName] - if !found { - return nil - } - return model -} - -type Ref struct { - BaseSchema - - reference string - definitions *Definitions -} - -var _ Reference = &Ref{} - -func (r *Ref) Reference() string { - return r.reference -} - -func (r *Ref) SubSchema() Schema { - return r.definitions.models[r.reference] -} - -func (r *Ref) Accept(v SchemaVisitor) { - v.VisitReference(r) -} - -func (r *Ref) GetName() string { - return fmt.Sprintf("Reference to %q", r.reference) -} diff --git a/pkg/kubectl/cmd/util/openapi/gvk.go b/pkg/kubectl/cmd/util/openapi/gvk.go new file mode 100644 index 0000000000..9c93403249 --- /dev/null +++ b/pkg/kubectl/cmd/util/openapi/gvk.go @@ -0,0 +1,73 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package openapi + +import ( + openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2" + + "k8s.io/apimachinery/pkg/runtime/schema" + openapi "k8s.io/kube-openapi/pkg/util/proto" +) + +const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind" + +// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one. +func ParseGroupVersionKind(s *openapi_v2.Schema) string { + extensionMap := openapi.VendorExtensionToMap(s.GetVendorExtension()) + + // Get the extensions + gvkExtension, ok := extensionMap[groupVersionKindExtensionKey] + if !ok { + return "" + } + + // gvk extension must be a list of 1 element. + gvkList, ok := gvkExtension.([]interface{}) + if !ok { + return "" + } + if len(gvkList) != 1 { + return "" + + } + gvk := gvkList[0] + + // gvk extension list must be a map with group, version, and + // kind fields + gvkMap, ok := gvk.(map[interface{}]interface{}) + if !ok { + return "" + } + group, ok := gvkMap["group"].(string) + if !ok { + return "" + } + version, ok := gvkMap["version"].(string) + if !ok { + return "" + } + kind, ok := gvkMap["kind"].(string) + if !ok { + return "" + } + + return schema.GroupVersionKind{ + Group: group, + Version: version, + Kind: kind, + }.String() +} diff --git a/pkg/kubectl/cmd/util/openapi/openapi.go b/pkg/kubectl/cmd/util/openapi/openapi.go deleted file mode 100644 index 8e6cf0639f..0000000000 --- a/pkg/kubectl/cmd/util/openapi/openapi.go +++ /dev/null @@ -1,231 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package openapi - -import ( - "fmt" - "strings" - - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// Defines openapi types. -const ( - Integer = "integer" - Number = "number" - String = "string" - Boolean = "boolean" - - // These types are private as they should never leak, and are - // represented by actual structs. - array = "array" - object = "object" -) - -// Resources interface describe a resources provider, that can give you -// resource based on group-version-kind. -type Resources interface { - LookupResource(gvk schema.GroupVersionKind) Schema -} - -// SchemaVisitor is an interface that you need to implement if you want -// to "visit" an openapi schema. A dispatch on the Schema type will call -// the appropriate function based on its actual type: -// - Array is a list of one and only one given subtype -// - Map is a map of string to one and only one given subtype -// - Primitive can be string, integer, number and boolean. -// - Kind is an object with specific fields mapping to specific types. -// - Reference is a link to another definition. -type SchemaVisitor interface { - VisitArray(*Array) - VisitMap(*Map) - VisitPrimitive(*Primitive) - VisitKind(*Kind) - VisitReference(Reference) -} - -// Schema is the base definition of an openapi type. -type Schema interface { - // Giving a visitor here will let you visit the actual type. - Accept(SchemaVisitor) - - // Pretty print the name of the type. - GetName() string - // Describes how to access this field. - GetPath() *Path - // Describes the field. - GetDescription() string - // Returns type extensions. - GetExtensions() map[string]interface{} -} - -// Path helps us keep track of type paths -type Path struct { - parent *Path - key string -} - -func NewPath(key string) Path { - return Path{key: key} -} - -func (p *Path) Get() []string { - if p == nil { - return []string{} - } - if p.key == "" { - return p.parent.Get() - } - return append(p.parent.Get(), p.key) -} - -func (p *Path) Len() int { - return len(p.Get()) -} - -func (p *Path) String() string { - return strings.Join(p.Get(), "") -} - -// ArrayPath appends an array index and creates a new path -func (p *Path) ArrayPath(i int) Path { - return Path{ - parent: p, - key: fmt.Sprintf("[%d]", i), - } -} - -// FieldPath appends a field name and creates a new path -func (p *Path) FieldPath(field string) Path { - return Path{ - parent: p, - key: fmt.Sprintf(".%s", field), - } -} - -// BaseSchema holds data used by each types of schema. -type BaseSchema struct { - Description string - Extensions map[string]interface{} - - Path Path -} - -func (b *BaseSchema) GetDescription() string { - return b.Description -} - -func (b *BaseSchema) GetExtensions() map[string]interface{} { - return b.Extensions -} - -func (b *BaseSchema) GetPath() *Path { - return &b.Path -} - -// Array must have all its element of the same `SubType`. -type Array struct { - BaseSchema - - SubType Schema -} - -var _ Schema = &Array{} - -func (a *Array) Accept(v SchemaVisitor) { - v.VisitArray(a) -} - -func (a *Array) GetName() string { - return fmt.Sprintf("Array of %s", a.SubType.GetName()) -} - -// Kind is a complex object. It can have multiple different -// subtypes for each field, as defined in the `Fields` field. Mandatory -// fields are listed in `RequiredFields`. The key of the object is -// always of type `string`. -type Kind struct { - BaseSchema - - // Lists names of required fields. - RequiredFields []string - // Maps field names to types. - Fields map[string]Schema -} - -var _ Schema = &Kind{} - -func (k *Kind) Accept(v SchemaVisitor) { - v.VisitKind(k) -} - -func (k *Kind) GetName() string { - properties := []string{} - for key := range k.Fields { - properties = append(properties, key) - } - return fmt.Sprintf("Kind(%v)", properties) -} - -// Map is an object who values must all be of the same `SubType`. -// The key of the object is always of type `string`. -type Map struct { - BaseSchema - - SubType Schema -} - -var _ Schema = &Map{} - -func (m *Map) Accept(v SchemaVisitor) { - v.VisitMap(m) -} - -func (m *Map) GetName() string { - return fmt.Sprintf("Map of %s", m.SubType.GetName()) -} - -// Primitive is a literal. There can be multiple types of primitives, -// and this subtype can be visited through the `subType` field. -type Primitive struct { - BaseSchema - - // Type of a primitive must be one of: integer, number, string, boolean. - Type string - Format string -} - -var _ Schema = &Primitive{} - -func (p *Primitive) Accept(v SchemaVisitor) { - v.VisitPrimitive(p) -} - -func (p *Primitive) GetName() string { - if p.Format == "" { - return p.Type - } - return fmt.Sprintf("%s (%s)", p.Type, p.Format) -} - -// Reference implementation depends on the type of document. -type Reference interface { - Schema - - Reference() string - SubSchema() Schema -} diff --git a/pkg/kubectl/cmd/util/openapi/openapi_getter.go b/pkg/kubectl/cmd/util/openapi/openapi_getter.go index d5c9476a02..4d5bccb4dd 100644 --- a/pkg/kubectl/cmd/util/openapi/openapi_getter.go +++ b/pkg/kubectl/cmd/util/openapi/openapi_getter.go @@ -20,13 +20,14 @@ import ( "sync" "k8s.io/client-go/discovery" + openapi "k8s.io/kube-openapi/pkg/util/proto" ) // synchronizedOpenAPIGetter fetches the openapi schema once and then caches it in memory type synchronizedOpenAPIGetter struct { // Cached results sync.Once - openAPISchema Resources + openAPISchema openapi.Resources err error openAPIClient discovery.OpenAPISchemaInterface @@ -37,7 +38,7 @@ var _ Getter = &synchronizedOpenAPIGetter{} // Getter is an interface for fetching openapi specs and parsing them into an Resources struct type Getter interface { // OpenAPIData returns the parsed OpenAPIData - Get() (Resources, error) + Get() (openapi.Resources, error) } // NewOpenAPIGetter returns an object to return OpenAPIDatas which reads @@ -49,7 +50,7 @@ func NewOpenAPIGetter(openAPIClient discovery.OpenAPISchemaInterface) Getter { } // Resources implements Getter -func (g *synchronizedOpenAPIGetter) Get() (Resources, error) { +func (g *synchronizedOpenAPIGetter) Get() (openapi.Resources, error) { g.Do(func() { s, err := g.openAPIClient.OpenAPISchema() if err != nil { @@ -57,7 +58,7 @@ func (g *synchronizedOpenAPIGetter) Get() (Resources, error) { return } - g.openAPISchema, g.err = NewOpenAPIData(s) + g.openAPISchema, g.err = openapi.NewOpenAPIData(s, ParseGroupVersionKind) }) // Return the save result diff --git a/pkg/kubectl/cmd/util/openapi/openapi_getter_test.go b/pkg/kubectl/cmd/util/openapi/openapi_getter_test.go index 004d27e1b2..6ba2ef3c85 100644 --- a/pkg/kubectl/cmd/util/openapi/openapi_getter_test.go +++ b/pkg/kubectl/cmd/util/openapi/openapi_getter_test.go @@ -18,17 +18,21 @@ package openapi_test import ( "fmt" + "path/filepath" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + oapi "k8s.io/kube-openapi/pkg/util/proto" + tst "k8s.io/kube-openapi/pkg/util/proto/testing" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" - tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing" ) +var fakeSchema = tst.Fake{Path: filepath.Join("..", "..", "..", "..", "..", "api", "openapi-spec", "swagger.json")} + var _ = Describe("Getting the Resources", func() { var client *tst.FakeClient - var expectedData openapi.Resources + var expectedData oapi.Resources var instance openapi.Getter BeforeEach(func() { @@ -36,7 +40,7 @@ var _ = Describe("Getting the Resources", func() { d, err := fakeSchema.OpenAPISchema() Expect(err).To(BeNil()) - expectedData, err = openapi.NewOpenAPIData(d) + expectedData, err = oapi.NewOpenAPIData(d, openapi.ParseGroupVersionKind) Expect(err).To(BeNil()) instance = openapi.NewOpenAPIGetter(client) diff --git a/pkg/kubectl/cmd/util/openapi/openapi_test.go b/pkg/kubectl/cmd/util/openapi/openapi_test.go deleted file mode 100644 index 93736c93ca..0000000000 --- a/pkg/kubectl/cmd/util/openapi/openapi_test.go +++ /dev/null @@ -1,218 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package openapi_test - -import ( - "path/filepath" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" - tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing" -) - -var fakeSchema = tst.Fake{Path: filepath.Join("..", "..", "..", "..", "..", "api", "openapi-spec", "swagger.json")} - -var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() { - var resources openapi.Resources - BeforeEach(func() { - s, err := fakeSchema.OpenAPISchema() - Expect(err).To(BeNil()) - resources, err = openapi.NewOpenAPIData(s) - Expect(err).To(BeNil()) - }) - - gvk := schema.GroupVersionKind{ - Kind: "Deployment", - Version: "v1beta1", - Group: "apps", - } - - var schema openapi.Schema - It("should lookup the Schema by its GroupVersionKind", func() { - schema = resources.LookupResource(gvk) - Expect(schema).ToNot(BeNil()) - }) - - var deployment *openapi.Kind - It("should be a Kind", func() { - deployment = schema.(*openapi.Kind) - Expect(deployment).ToNot(BeNil()) - }) - - It("should have a path", func() { - Expect(deployment.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.Deployment"})) - }) - - It("should have a kind key of type string", func() { - Expect(deployment.Fields).To(HaveKey("kind")) - key := deployment.Fields["kind"].(*openapi.Primitive) - Expect(key).ToNot(BeNil()) - Expect(key.Type).To(Equal("string")) - Expect(key.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.Deployment", ".kind"})) - }) - - It("should have a apiVersion key of type string", func() { - Expect(deployment.Fields).To(HaveKey("apiVersion")) - key := deployment.Fields["apiVersion"].(*openapi.Primitive) - Expect(key).ToNot(BeNil()) - Expect(key.Type).To(Equal("string")) - Expect(key.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.Deployment", ".apiVersion"})) - }) - - It("should have a metadata key of type Reference", func() { - Expect(deployment.Fields).To(HaveKey("metadata")) - key := deployment.Fields["metadata"].(openapi.Reference) - Expect(key).ToNot(BeNil()) - Expect(key.Reference()).To(Equal("io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta")) - subSchema := key.SubSchema().(*openapi.Kind) - Expect(subSchema).ToNot(BeNil()) - }) - - var status *openapi.Kind - It("should have a status key of type Reference", func() { - Expect(deployment.Fields).To(HaveKey("status")) - key := deployment.Fields["status"].(openapi.Reference) - Expect(key).ToNot(BeNil()) - Expect(key.Reference()).To(Equal("io.k8s.api.apps.v1beta1.DeploymentStatus")) - status = key.SubSchema().(*openapi.Kind) - Expect(status).ToNot(BeNil()) - }) - - It("should have a valid DeploymentStatus", func() { - By("having availableReplicas key") - Expect(status.Fields).To(HaveKey("availableReplicas")) - replicas := status.Fields["availableReplicas"].(*openapi.Primitive) - Expect(replicas).ToNot(BeNil()) - Expect(replicas.Type).To(Equal("integer")) - - By("having conditions key") - Expect(status.Fields).To(HaveKey("conditions")) - conditions := status.Fields["conditions"].(*openapi.Array) - Expect(conditions).ToNot(BeNil()) - Expect(conditions.GetName()).To(Equal(`Array of Reference to "io.k8s.api.apps.v1beta1.DeploymentCondition"`)) - Expect(conditions.GetExtensions()).To(Equal(map[string]interface{}{ - "x-kubernetes-patch-merge-key": "type", - "x-kubernetes-patch-strategy": "merge", - })) - condition := conditions.SubType.(openapi.Reference) - Expect(condition.Reference()).To(Equal("io.k8s.api.apps.v1beta1.DeploymentCondition")) - }) - - var spec *openapi.Kind - It("should have a spec key of type Reference", func() { - Expect(deployment.Fields).To(HaveKey("spec")) - key := deployment.Fields["spec"].(openapi.Reference) - Expect(key).ToNot(BeNil()) - Expect(key.Reference()).To(Equal("io.k8s.api.apps.v1beta1.DeploymentSpec")) - spec = key.SubSchema().(*openapi.Kind) - Expect(spec).ToNot(BeNil()) - }) - - It("should have a spec with no gvk", func() { - _, found := spec.GetExtensions()["x-kubernetes-group-version-kind"] - Expect(found).To(BeFalse()) - }) - - It("should have a spec with a PodTemplateSpec sub-field", func() { - Expect(spec.Fields).To(HaveKey("template")) - key := spec.Fields["template"].(openapi.Reference) - Expect(key).ToNot(BeNil()) - Expect(key.Reference()).To(Equal("io.k8s.api.core.v1.PodTemplateSpec")) - }) -}) - -var _ = Describe("Reading authorization.k8s.io/v1/SubjectAccessReview from openAPIData", func() { - var resources openapi.Resources - BeforeEach(func() { - s, err := fakeSchema.OpenAPISchema() - Expect(err).To(BeNil()) - resources, err = openapi.NewOpenAPIData(s) - Expect(err).To(BeNil()) - }) - - gvk := schema.GroupVersionKind{ - Kind: "SubjectAccessReview", - Version: "v1", - Group: "authorization.k8s.io", - } - - var schema openapi.Schema - It("should lookup the Schema by its GroupVersionKind", func() { - schema = resources.LookupResource(gvk) - Expect(schema).ToNot(BeNil()) - }) - - var sarspec *openapi.Kind - It("should be a Kind and have a spec", func() { - sar := schema.(*openapi.Kind) - Expect(sar).ToNot(BeNil()) - Expect(sar.Fields).To(HaveKey("spec")) - specRef := sar.Fields["spec"].(openapi.Reference) - Expect(specRef).ToNot(BeNil()) - Expect(specRef.Reference()).To(Equal("io.k8s.api.authorization.v1.SubjectAccessReviewSpec")) - sarspec = specRef.SubSchema().(*openapi.Kind) - Expect(sarspec).ToNot(BeNil()) - }) - - It("should have a valid SubjectAccessReviewSpec", func() { - Expect(sarspec.Fields).To(HaveKey("extra")) - extra := sarspec.Fields["extra"].(*openapi.Map) - Expect(extra).ToNot(BeNil()) - Expect(extra.GetName()).To(Equal("Map of Array of string")) - Expect(extra.GetPath().Get()).To(Equal([]string{"io.k8s.api.authorization.v1.SubjectAccessReviewSpec", ".extra"})) - array := extra.SubType.(*openapi.Array) - Expect(array).ToNot(BeNil()) - Expect(array.GetName()).To(Equal("Array of string")) - Expect(array.GetPath().Get()).To(Equal([]string{"io.k8s.api.authorization.v1.SubjectAccessReviewSpec", ".extra"})) - str := array.SubType.(*openapi.Primitive) - Expect(str).ToNot(BeNil()) - Expect(str.Type).To(Equal("string")) - Expect(str.GetName()).To(Equal("string")) - Expect(str.GetPath().Get()).To(Equal([]string{"io.k8s.api.authorization.v1.SubjectAccessReviewSpec", ".extra"})) - }) -}) - -var _ = Describe("Path", func() { - It("can be created by NewPath", func() { - path := openapi.NewPath("key") - Expect(path.String()).To(Equal("key")) - }) - It("can create and print complex paths", func() { - key := openapi.NewPath("key") - array := key.ArrayPath(12) - field := array.FieldPath("subKey") - - Expect(field.String()).To(Equal("key[12].subKey")) - }) - It("has a length", func() { - key := openapi.NewPath("key") - array := key.ArrayPath(12) - field := array.FieldPath("subKey") - - Expect(field.Len()).To(Equal(3)) - }) - It("can look like an array", func() { - key := openapi.NewPath("key") - array := key.ArrayPath(12) - field := array.FieldPath("subKey") - - Expect(field.Get()).To(Equal([]string{"key", "[12]", ".subKey"})) - }) -}) diff --git a/pkg/kubectl/cmd/util/openapi/testing/BUILD b/pkg/kubectl/cmd/util/openapi/testing/BUILD deleted file mode 100644 index 9fed9f861b..0000000000 --- a/pkg/kubectl/cmd/util/openapi/testing/BUILD +++ /dev/null @@ -1,32 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -licenses(["notice"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["openapi.go"], - tags = ["automanaged"], - deps = [ - "//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library", - "//vendor/github.com/googleapis/gnostic/compiler:go_default_library", - "//vendor/gopkg.in/yaml.v2:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/pkg/kubectl/cmd/util/openapi/testing/openapi.go b/pkg/kubectl/cmd/util/openapi/testing/openapi.go deleted file mode 100644 index 55554814b6..0000000000 --- a/pkg/kubectl/cmd/util/openapi/testing/openapi.go +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package testing - -import ( - "io/ioutil" - "os" - "sync" - - yaml "gopkg.in/yaml.v2" - - "github.com/googleapis/gnostic/OpenAPIv2" - "github.com/googleapis/gnostic/compiler" -) - -// Fake opens and returns a openapi swagger from a file Path. It will -// parse only once and then return the same copy everytime. -type Fake struct { - Path string - - once sync.Once - document *openapi_v2.Document - err error -} - -// OpenAPISchema returns the openapi document and a potential error. -func (f *Fake) OpenAPISchema() (*openapi_v2.Document, error) { - f.once.Do(func() { - _, err := os.Stat(f.Path) - if err != nil { - f.err = err - return - } - spec, err := ioutil.ReadFile(f.Path) - if err != nil { - f.err = err - return - } - var info yaml.MapSlice - err = yaml.Unmarshal(spec, &info) - if err != nil { - f.err = err - return - } - f.document, f.err = openapi_v2.NewDocument(info, compiler.NewContext("$root", nil)) - }) - return f.document, f.err -} - -// FakeClient implements a dummy OpenAPISchemaInterface that uses the -// fake OpenAPI schema given as a parameter, and count the number of -// call to the function. -type FakeClient struct { - Calls int - Err error - - fake *Fake -} - -// NewFakeClient creates a new FakeClient from the given Fake. -func NewFakeClient(f *Fake) *FakeClient { - return &FakeClient{fake: f} -} - -// OpenAPISchema returns a OpenAPI Document as returned by the fake, but -// it also counts the number of calls. -func (f *FakeClient) OpenAPISchema() (*openapi_v2.Document, error) { - f.Calls = f.Calls + 1 - - if f.Err != nil { - return nil, f.Err - } - - return f.fake.OpenAPISchema() -} diff --git a/pkg/kubectl/cmd/util/openapi/validation/BUILD b/pkg/kubectl/cmd/util/openapi/validation/BUILD index 77b98f39bb..f5d5231798 100644 --- a/pkg/kubectl/cmd/util/openapi/validation/BUILD +++ b/pkg/kubectl/cmd/util/openapi/validation/BUILD @@ -19,11 +19,11 @@ go_library( deps = [ "//pkg/api:go_default_library", "//pkg/api/util:go_default_library", - "//pkg/kubectl/cmd/util/openapi:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", ], ) @@ -39,12 +39,13 @@ go_test( ":go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/kubectl/cmd/util/openapi:go_default_library", - "//pkg/kubectl/cmd/util/openapi/testing: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/types:go_default_library", "//vendor/github.com/onsi/gomega:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto/testing:go_default_library", ], ) diff --git a/pkg/kubectl/cmd/util/openapi/validation/types.go b/pkg/kubectl/cmd/util/openapi/validation/types.go index 2c16c2b16f..b0949783fa 100644 --- a/pkg/kubectl/cmd/util/openapi/validation/types.go +++ b/pkg/kubectl/cmd/util/openapi/validation/types.go @@ -20,7 +20,7 @@ import ( "reflect" "sort" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" + openapi "k8s.io/kube-openapi/pkg/util/proto" ) type ValidationItem interface { diff --git a/pkg/kubectl/cmd/util/openapi/validation/validation.go b/pkg/kubectl/cmd/util/openapi/validation/validation.go index 08bc6a573b..3f5ee383ec 100644 --- a/pkg/kubectl/cmd/util/openapi/validation/validation.go +++ b/pkg/kubectl/cmd/util/openapi/validation/validation.go @@ -25,9 +25,9 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/yaml" + openapi "k8s.io/kube-openapi/pkg/util/proto" "k8s.io/kubernetes/pkg/api" apiutil "k8s.io/kubernetes/pkg/api/util" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" ) type SchemaValidation struct { @@ -82,7 +82,7 @@ func (v *SchemaValidation) validateResource(obj interface{}, gvk schema.GroupVer return nil } - resource := v.resources.LookupResource(gvk) + resource := v.resources.LookupResource(gvk.String()) if resource == nil { return []error{fmt.Errorf("unknown object type %#v", gvk)} } diff --git a/pkg/kubectl/cmd/util/openapi/validation/validation_test.go b/pkg/kubectl/cmd/util/openapi/validation/validation_test.go index 8454db4688..c4085d624c 100644 --- a/pkg/kubectl/cmd/util/openapi/validation/validation_test.go +++ b/pkg/kubectl/cmd/util/openapi/validation/validation_test.go @@ -24,9 +24,10 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" // This dependency is needed to register API types. + oapi "k8s.io/kube-openapi/pkg/util/proto" + tst "k8s.io/kube-openapi/pkg/util/proto/testing" _ "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" - tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation" ) @@ -37,7 +38,7 @@ var _ = Describe("resource validation using OpenAPI Schema", func() { BeforeEach(func() { s, err := fakeSchema.OpenAPISchema() Expect(err).To(BeNil()) - resources, err := openapi.NewOpenAPIData(s) + resources, err := oapi.NewOpenAPIData(s, openapi.ParseGroupVersionKind) Expect(err).To(BeNil()) validator = validation.NewSchemaValidation(resources) Expect(validator).ToNot(BeNil())