mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 04:33:26 +00:00
Merge pull request #49146 from apelisse/openapi-new-structure
Automatic merge from submit-queue (batch tested with PRs 49665, 49689, 49495, 49146, 48934) openapi: refactor into more generic structure **What this PR does / why we need it**: Refactor the openapi schema to be a more generic structure that can be "visited" to get more specific types. Will be used by validation. **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: #44589 **Special notes for your reviewer**: **Release note**: ```release-note NONE ```
This commit is contained in:
commit
bc3c5bc0d6
@ -217,7 +217,6 @@ go_test(
|
|||||||
"//pkg/printers/internalversion:go_default_library",
|
"//pkg/printers/internalversion:go_default_library",
|
||||||
"//pkg/util/i18n:go_default_library",
|
"//pkg/util/i18n:go_default_library",
|
||||||
"//pkg/util/strings:go_default_library",
|
"//pkg/util/strings:go_default_library",
|
||||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
|
||||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||||
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
||||||
|
@ -566,13 +566,13 @@ func outputOptsForMappingFromOpenAPI(f cmdutil.Factory, openAPIcacheDir string,
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
// Found openapi metadata for this resource
|
// Found openapi metadata for this resource
|
||||||
kind, found := api.LookupResource(mapping.GroupVersionKind)
|
schema := api.LookupResource(mapping.GroupVersionKind)
|
||||||
if !found {
|
if schema == nil {
|
||||||
// Kind not found, return empty columns
|
// Schema not found, return empty columns
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
columns, found := openapi.GetPrintColumns(kind.Extensions)
|
columns, found := openapi.GetPrintColumns(schema.GetExtensions())
|
||||||
if !found {
|
if !found {
|
||||||
// Extension not found, return empty columns
|
// Extension not found, return empty columns
|
||||||
return nil, false
|
return nil, false
|
||||||
|
@ -26,8 +26,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
|
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@ -218,20 +216,27 @@ func TestGetObjectsWithOpenAPIOutputFormatPresent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testOpenAPISchemaData() (*openapi.Resources, error) {
|
type FakeResources struct {
|
||||||
return &openapi.Resources{
|
resources map[schema.GroupVersionKind]openapi.Schema
|
||||||
GroupVersionKindToName: map[schema.GroupVersionKind]string{
|
}
|
||||||
|
|
||||||
|
func (f FakeResources) LookupResource(s schema.GroupVersionKind) openapi.Schema {
|
||||||
|
return f.resources[s]
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ openapi.Resources = &FakeResources{}
|
||||||
|
|
||||||
|
func testOpenAPISchemaData() (openapi.Resources, error) {
|
||||||
|
return &FakeResources{
|
||||||
|
resources: map[schema.GroupVersionKind]openapi.Schema{
|
||||||
{
|
{
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Pod",
|
Kind: "Pod",
|
||||||
}: "io.k8s.kubernetes.pkg.api.v1.Pod",
|
}: &openapi.Primitive{
|
||||||
},
|
BaseSchema: openapi.BaseSchema{
|
||||||
NameToDefinition: map[string]openapi.Kind{
|
Extensions: map[string]interface{}{
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Pod": {
|
"x-kubernetes-print-columns": "custom-columns=NAME:.metadata.name,RSRC:.metadata.resourceVersion",
|
||||||
Name: "io.k8s.kubernetes.pkg.api.v1.Pod",
|
},
|
||||||
IsResource: false,
|
|
||||||
Extensions: spec.Extensions{
|
|
||||||
"x-kubernetes-print-columns": "custom-columns=NAME:.metadata.name,RSRC:.metadata.resourceVersion",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -243,7 +243,7 @@ type TestFactory struct {
|
|||||||
|
|
||||||
ClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
|
ClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
|
||||||
UnstructuredClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
|
UnstructuredClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
|
||||||
OpenAPISchemaFunc func() (*openapi.Resources, error)
|
OpenAPISchemaFunc func() (openapi.Resources, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FakeFactory struct {
|
type FakeFactory struct {
|
||||||
@ -418,8 +418,8 @@ func (f *FakeFactory) SwaggerSchema(schema.GroupVersionKind) (*swagger.ApiDeclar
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeFactory) OpenAPISchema(cacheDir string) (*openapi.Resources, error) {
|
func (f *FakeFactory) OpenAPISchema(cacheDir string) (openapi.Resources, error) {
|
||||||
return &openapi.Resources{}, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeFactory) DefaultNamespace() (string, bool, error) {
|
func (f *FakeFactory) DefaultNamespace() (string, bool, error) {
|
||||||
@ -756,11 +756,11 @@ func (f *fakeAPIFactory) SwaggerSchema(schema.GroupVersionKind) (*swagger.ApiDec
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeAPIFactory) OpenAPISchema(cacheDir string) (*openapi.Resources, error) {
|
func (f *fakeAPIFactory) OpenAPISchema(cacheDir string) (openapi.Resources, error) {
|
||||||
if f.tf.OpenAPISchemaFunc != nil {
|
if f.tf.OpenAPISchemaFunc != nil {
|
||||||
return f.tf.OpenAPISchemaFunc()
|
return f.tf.OpenAPISchemaFunc()
|
||||||
}
|
}
|
||||||
return &openapi.Resources{}, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPIFactory() (cmdutil.Factory, *TestFactory, runtime.Codec, runtime.NegotiatedSerializer) {
|
func NewAPIFactory() (cmdutil.Factory, *TestFactory, runtime.Codec, runtime.NegotiatedSerializer) {
|
||||||
|
@ -224,7 +224,7 @@ type ObjectMappingFactory interface {
|
|||||||
// SwaggerSchema returns the schema declaration for the provided group version kind.
|
// SwaggerSchema returns the schema declaration for the provided group version kind.
|
||||||
SwaggerSchema(schema.GroupVersionKind) (*swagger.ApiDeclaration, error)
|
SwaggerSchema(schema.GroupVersionKind) (*swagger.ApiDeclaration, error)
|
||||||
// OpenAPISchema returns the schema openapi schema definiton
|
// OpenAPISchema returns the schema openapi schema definiton
|
||||||
OpenAPISchema(cacheDir string) (*openapi.Resources, error)
|
OpenAPISchema(cacheDir string) (openapi.Resources, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuilderFactory holds the second level of factory methods. These functions depend upon ObjectMappingFactory and ClientAccessFactory methods.
|
// BuilderFactory holds the second level of factory methods. These functions depend upon ObjectMappingFactory and ClientAccessFactory methods.
|
||||||
|
@ -445,7 +445,7 @@ func (f *ring1Factory) SwaggerSchema(gvk schema.GroupVersionKind) (*swagger.ApiD
|
|||||||
// schema will be cached separately for different client / server combinations.
|
// schema will be cached separately for different client / server combinations.
|
||||||
// Note, the cache will not be invalidated if the server changes its open API schema without
|
// Note, the cache will not be invalidated if the server changes its open API schema without
|
||||||
// changing the server version.
|
// changing the server version.
|
||||||
func (f *ring1Factory) OpenAPISchema(cacheDir string) (*openapi.Resources, error) {
|
func (f *ring1Factory) OpenAPISchema(cacheDir string) (openapi.Resources, error) {
|
||||||
discovery, err := f.clientAccessFactory.DiscoveryClient()
|
discovery, err := f.clientAccessFactory.DiscoveryClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -12,6 +12,7 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"doc.go",
|
"doc.go",
|
||||||
|
"document.go",
|
||||||
"extensions.go",
|
"extensions.go",
|
||||||
"openapi.go",
|
"openapi.go",
|
||||||
"openapi_cache.go",
|
"openapi_cache.go",
|
||||||
@ -22,10 +23,10 @@ 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/golang/protobuf/proto:go_default_library",
|
||||||
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
||||||
"//vendor/gopkg.in/yaml.v2: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/client-go/discovery:go_default_library",
|
"//vendor/k8s.io/client-go/discovery:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -43,7 +44,6 @@ 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/spec:go_default_library",
|
|
||||||
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
||||||
"//vendor/github.com/googleapis/gnostic/compiler: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",
|
||||||
|
338
pkg/kubectl/cmd/util/openapi/document.go
Normal file
338
pkg/kubectl/cmd/util/openapi/document.go
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
/*
|
||||||
|
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() {
|
||||||
|
schema, err := definitions.ParseSchema(namedSchema.GetValue(), &Path{key: namedSchema.GetName()})
|
||||||
|
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 &Reference{
|
||||||
|
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
|
||||||
|
fields[namedSchema.GetName()], err = d.ParseSchema(namedSchema.GetValue(), &Path{parent: path, key: namedSchema.GetName()})
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// SchemaReference doesn't match a specific type. It's mostly a
|
||||||
|
// pass-through type.
|
||||||
|
type Reference struct {
|
||||||
|
Reference string
|
||||||
|
|
||||||
|
definitions *Definitions
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Schema = &Reference{}
|
||||||
|
|
||||||
|
func (r *Reference) GetSubSchema() Schema {
|
||||||
|
return r.definitions.models[r.Reference]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reference) Accept(s SchemaVisitor) {
|
||||||
|
r.GetSubSchema().Accept(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reference) GetDescription() string {
|
||||||
|
return r.GetSubSchema().GetDescription()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reference) GetExtensions() map[string]interface{} {
|
||||||
|
return r.GetSubSchema().GetExtensions()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Reference) GetPath() *Path {
|
||||||
|
// Reference never has a path, because it can be referenced from
|
||||||
|
// multiple locations.
|
||||||
|
return &Path{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reference) GetName() string {
|
||||||
|
return r.Reference
|
||||||
|
}
|
@ -20,398 +20,182 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// groupVersionKindExtensionKey is the key used to lookup the GroupVersionKind value
|
// Defines openapi types.
|
||||||
// for an object definition from the definition's "extensions" map.
|
const (
|
||||||
const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind"
|
Integer = "integer"
|
||||||
|
Number = "number"
|
||||||
|
String = "string"
|
||||||
|
Boolean = "boolean"
|
||||||
|
|
||||||
// Integer is the name for integer types
|
// These types are private as they should never leak, and are
|
||||||
const Integer = "integer"
|
// represented by actual structs.
|
||||||
|
array = "array"
|
||||||
|
object = "object"
|
||||||
|
)
|
||||||
|
|
||||||
// String is the name for string types
|
// Resources interface describe a resources provider, that can give you
|
||||||
const String = "string"
|
// resource based on group-version-kind.
|
||||||
|
type Resources interface {
|
||||||
// Bool is the name for boolean types
|
LookupResource(gvk schema.GroupVersionKind) Schema
|
||||||
const Boolean = "boolean"
|
|
||||||
|
|
||||||
// Map is the name for map types
|
|
||||||
// types.go struct fields that are maps will have an open API type "object"
|
|
||||||
// types.go struct fields that are actual objects appearing as a struct
|
|
||||||
// in a types.go file will have no type defined
|
|
||||||
// and have a json pointer reference to the type definition
|
|
||||||
const Map = "object"
|
|
||||||
|
|
||||||
// Array is the name for array types
|
|
||||||
const Array = "array"
|
|
||||||
|
|
||||||
// Resources contains the object definitions for Kubernetes resource apis
|
|
||||||
// Fields are public for binary serialization (private fields don't get serialized)
|
|
||||||
type Resources struct {
|
|
||||||
// GroupVersionKindToName maps GroupVersionKinds to Type names
|
|
||||||
GroupVersionKindToName map[schema.GroupVersionKind]string
|
|
||||||
// NameToDefinition maps Type names to TypeDefinitions
|
|
||||||
NameToDefinition map[string]Kind
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupResource returns the Kind for the specified groupVersionKind
|
// SchemaVisitor is an interface that you need to implement if you want
|
||||||
func (r Resources) LookupResource(groupVersionKind schema.GroupVersionKind) (Kind, bool) {
|
// to "visit" an openapi schema. A dispatch on the Schema type will call
|
||||||
name, found := r.GroupVersionKindToName[groupVersionKind]
|
// the appropriate function based on its actual type:
|
||||||
if !found {
|
// - Array is a list of one and only one given subtype
|
||||||
return Kind{}, false
|
// - Map is a map of string to one and only one given subtype
|
||||||
}
|
// - Primitive can be string, integer, number and boolean.
|
||||||
def, found := r.NameToDefinition[name]
|
// - Kind is an object with specific fields mapping to specific types.
|
||||||
if !found {
|
type SchemaVisitor interface {
|
||||||
return Kind{}, false
|
VisitArray(*Array)
|
||||||
}
|
VisitMap(*Map)
|
||||||
return def, true
|
VisitPrimitive(*Primitive)
|
||||||
|
VisitKind(*Kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kind defines a Kubernetes object Kind
|
// 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 (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(), ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
type Kind struct {
|
||||||
// Name is the lookup key given to this Kind by the open API spec.
|
BaseSchema
|
||||||
// May not contain any semantic meaning or relation to the API definition,
|
|
||||||
// simply must be unique for each object definition in the Open API spec.
|
|
||||||
// e.g. io.k8s.api.apps.v1beta1.Deployment
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// IsResource is true if the Kind is a Resource (it has API endpoints)
|
// Lists names of required fields.
|
||||||
// e.g. Deployment is a Resource, DeploymentStatus is NOT a Resource
|
RequiredFields []string
|
||||||
IsResource bool
|
// Maps field names to types.
|
||||||
|
Fields map[string]Schema
|
||||||
// GroupVersionKind uniquely defines a resource type in the Kubernetes API
|
|
||||||
// and is present for all resources.
|
|
||||||
// Empty for non-resource Kinds (e.g. those without APIs).
|
|
||||||
// e.g. "Group": "apps", "Version": "v1beta1", "Kind": "Deployment"
|
|
||||||
GroupVersionKind schema.GroupVersionKind
|
|
||||||
|
|
||||||
// Present only for definitions that represent primitive types with additional
|
|
||||||
// semantic meaning beyond just string, integer, boolean - e.g.
|
|
||||||
// Fields with a PrimitiveType should follow the validation of the primitive type.
|
|
||||||
// io.k8s.apimachinery.pkg.apis.meta.v1.Time
|
|
||||||
// io.k8s.apimachinery.pkg.util.intstr.IntOrString
|
|
||||||
PrimitiveType string
|
|
||||||
|
|
||||||
// Extensions are openapi extensions for the object definition.
|
|
||||||
Extensions map[string]interface{}
|
|
||||||
|
|
||||||
// Fields are the fields defined for this Kind
|
|
||||||
Fields map[string]Type
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type defines a field type and are expected to be one of:
|
var _ Schema = &Kind{}
|
||||||
// - IsKind
|
|
||||||
// - IsMap
|
|
||||||
// - IsArray
|
|
||||||
// - IsPrimitive
|
|
||||||
type Type struct {
|
|
||||||
// Name is the name of the type
|
|
||||||
TypeName string
|
|
||||||
|
|
||||||
// IsKind is true if the definition represents a Kind
|
func (k *Kind) Accept(v SchemaVisitor) {
|
||||||
IsKind bool
|
v.VisitKind(k)
|
||||||
// IsPrimitive is true if the definition represents a primitive type - e.g. string, boolean, integer
|
|
||||||
IsPrimitive bool
|
|
||||||
// IsArray is true if the definition represents an array type
|
|
||||||
IsArray bool
|
|
||||||
// IsMap is true if the definition represents a map type
|
|
||||||
IsMap bool
|
|
||||||
|
|
||||||
// ElementType will be specified for arrays and maps
|
|
||||||
// if IsMap == true, then ElementType is the type of the value (key is always string)
|
|
||||||
// if IsArray == true, then ElementType is the type of the element
|
|
||||||
ElementType *Type
|
|
||||||
|
|
||||||
// Extensions are extensions for this field and may contain
|
|
||||||
// metadata from the types.go struct field tags.
|
|
||||||
// e.g. contains patchStrategy, patchMergeKey, etc
|
|
||||||
Extensions map[string]interface{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func vendorExtensionToMap(e []*openapi_v2.NamedAny) map[string]interface{} {
|
func (k *Kind) GetName() string {
|
||||||
var values map[string]interface{}
|
properties := []string{}
|
||||||
|
for key := range k.Fields {
|
||||||
for _, na := range e {
|
properties = append(properties, key)
|
||||||
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 fmt.Sprintf("Kind(%v)", properties)
|
||||||
return values
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOpenAPIData parses the resource definitions in openapi data by groupversionkind and name
|
// Map is an object who values must all be of the same `SubType`.
|
||||||
func NewOpenAPIData(doc *openapi_v2.Document) (*Resources, error) {
|
// The key of the object is always of type `string`.
|
||||||
o := &Resources{
|
type Map struct {
|
||||||
GroupVersionKindToName: map[schema.GroupVersionKind]string{},
|
BaseSchema
|
||||||
NameToDefinition: map[string]Kind{},
|
|
||||||
}
|
|
||||||
// Parse and index definitions by name
|
|
||||||
for _, ns := range doc.GetDefinitions().GetAdditionalProperties() {
|
|
||||||
definition := o.parseDefinition(ns.GetName(), ns.GetValue())
|
|
||||||
o.NameToDefinition[ns.GetName()] = definition
|
|
||||||
if len(definition.GroupVersionKind.Kind) > 0 {
|
|
||||||
o.GroupVersionKindToName[definition.GroupVersionKind] = ns.GetName()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := o.validate(); err != nil {
|
SubType Schema
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return o, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate makes sure the definition for each field type is found in the map
|
var _ Schema = &Map{}
|
||||||
func (o *Resources) validate() error {
|
|
||||||
types := sets.String{}
|
func (m *Map) Accept(v SchemaVisitor) {
|
||||||
for _, d := range o.NameToDefinition {
|
v.VisitMap(m)
|
||||||
for _, f := range d.Fields {
|
|
||||||
for _, t := range o.getTypeNames(f) {
|
|
||||||
types.Insert(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, n := range types.List() {
|
|
||||||
_, found := o.NameToDefinition[n]
|
|
||||||
if !found {
|
|
||||||
return fmt.Errorf("Unable to find definition for field of type %v", n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Resources) getTypeNames(elem Type) []string {
|
func (m *Map) GetName() string {
|
||||||
t := []string{}
|
return fmt.Sprintf("Map of %s", m.SubType.GetName())
|
||||||
if elem.IsKind {
|
|
||||||
t = append(t, elem.TypeName)
|
|
||||||
}
|
|
||||||
if elem.ElementType != nil && elem.ElementType.IsKind {
|
|
||||||
t = append(t, o.getTypeNames(*elem.ElementType)...)
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Resources) parseDefinition(name string, s *openapi_v2.Schema) Kind {
|
// Primitive is a literal. There can be multiple types of primitives,
|
||||||
gvk, err := o.getGroupVersionKind(s)
|
// and this subtype can be visited through the `subType` field.
|
||||||
value := Kind{
|
type Primitive struct {
|
||||||
Name: name,
|
BaseSchema
|
||||||
GroupVersionKind: gvk,
|
|
||||||
Extensions: vendorExtensionToMap(s.GetVendorExtension()),
|
|
||||||
Fields: map[string]Type{},
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
glog.V(2).Info(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Definition represents a primitive type - e.g.
|
// Type of a primitive must be one of: integer, number, string, boolean.
|
||||||
// io.k8s.apimachinery.pkg.util.intstr.IntOrString
|
Type string
|
||||||
if o.isPrimitive(s) {
|
Format string
|
||||||
value.PrimitiveType = o.getTypeNameForField(s)
|
|
||||||
}
|
|
||||||
for _, ns := range s.GetProperties().GetAdditionalProperties() {
|
|
||||||
value.Fields[ns.GetName()] = o.parseField(ns.GetValue())
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Resources) parseField(s *openapi_v2.Schema) Type {
|
var _ Schema = &Primitive{}
|
||||||
def := Type{
|
|
||||||
TypeName: o.getTypeNameForField(s),
|
|
||||||
IsPrimitive: o.isPrimitive(s),
|
|
||||||
IsArray: o.isArray(s),
|
|
||||||
IsMap: o.isMap(s),
|
|
||||||
IsKind: o.isDefinitionReference(s),
|
|
||||||
}
|
|
||||||
|
|
||||||
if elementType, arrayErr := o.getElementType(s); arrayErr == nil {
|
func (p *Primitive) Accept(v SchemaVisitor) {
|
||||||
d := o.parseField(elementType)
|
v.VisitPrimitive(p)
|
||||||
def.ElementType = &d
|
|
||||||
} else if valueType, mapErr := o.getValueType(s); mapErr == nil {
|
|
||||||
d := o.parseField(valueType)
|
|
||||||
def.ElementType = &d
|
|
||||||
}
|
|
||||||
|
|
||||||
def.Extensions = vendorExtensionToMap(s.GetVendorExtension())
|
|
||||||
|
|
||||||
return def
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// isArray returns true if s is an array type.
|
func (p *Primitive) GetName() string {
|
||||||
func (o *Resources) isArray(s *openapi_v2.Schema) bool {
|
if p.Format == "" {
|
||||||
if len(s.GetProperties().GetAdditionalProperties()) > 0 {
|
return p.Type
|
||||||
// Open API can have embedded type definitions, but Kubernetes doesn't generate these.
|
|
||||||
// This should just be a sanity check against changing the format.
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return o.getType(s) == Array
|
return fmt.Sprintf("%s (%s)", p.Type, p.Format)
|
||||||
}
|
|
||||||
|
|
||||||
// isMap returns true if s is a map type.
|
|
||||||
func (o *Resources) isMap(s *openapi_v2.Schema) bool {
|
|
||||||
if len(s.GetProperties().GetAdditionalProperties()) > 0 {
|
|
||||||
// Open API can have embedded type definitions, but Kubernetes doesn't generate these.
|
|
||||||
// This should just be a sanity check against changing the format.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return o.getType(s) == Map
|
|
||||||
}
|
|
||||||
|
|
||||||
// isPrimitive returns true if s is a primitive type
|
|
||||||
// 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".
|
|
||||||
func (o *Resources) isPrimitive(s *openapi_v2.Schema) bool {
|
|
||||||
if len(s.GetProperties().GetAdditionalProperties()) > 0 {
|
|
||||||
// Open API can have embedded type definitions, but Kubernetes doesn't generate these.
|
|
||||||
// This should just be a sanity check against changing the format.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
t := o.getType(s)
|
|
||||||
if t == Integer || t == Boolean || t == String {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Resources) getType(s *openapi_v2.Schema) string {
|
|
||||||
if len(s.GetType().GetValue()) != 1 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return strings.ToLower(s.GetType().GetValue()[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Resources) getTypeNameForField(s *openapi_v2.Schema) string {
|
|
||||||
// Get the reference for complex types
|
|
||||||
if o.isDefinitionReference(s) {
|
|
||||||
return o.nameForDefinitionField(s)
|
|
||||||
}
|
|
||||||
// Recurse if type is array
|
|
||||||
if o.isArray(s) {
|
|
||||||
return fmt.Sprintf("%s array", o.getTypeNameForField(s.GetItems().GetSchema()[0]))
|
|
||||||
}
|
|
||||||
if o.isMap(s) {
|
|
||||||
return fmt.Sprintf("%s map", o.getTypeNameForField(s.GetAdditionalProperties().GetSchema()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the value for primitive types
|
|
||||||
if o.isPrimitive(s) {
|
|
||||||
return fmt.Sprintf("%s", s.GetType().GetValue()[0])
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// isDefinitionReference returns true s is a complex type that should have a Kind.
|
|
||||||
func (o *Resources) isDefinitionReference(s *openapi_v2.Schema) bool {
|
|
||||||
if len(s.GetProperties().GetAdditionalProperties()) > 0 {
|
|
||||||
// Open API can have embedded type definitions, but Kubernetes doesn't generate these.
|
|
||||||
// This should just be a sanity check against changing the format.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(s.GetType().GetValue()) > 0 {
|
|
||||||
// Definition references won't have a type
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
p := s.GetXRef()
|
|
||||||
return len(p) > 0 && strings.HasPrefix(p, "#/definitions/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// getElementType returns the type of an element for arrays
|
|
||||||
// returns an error if s is not an array.
|
|
||||||
func (o *Resources) getElementType(s *openapi_v2.Schema) (*openapi_v2.Schema, error) {
|
|
||||||
if !o.isArray(s) {
|
|
||||||
return &openapi_v2.Schema{}, fmt.Errorf("%v is not an array type", o.getTypeNameForField(s))
|
|
||||||
}
|
|
||||||
return s.GetItems().GetSchema()[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getValueType returns the type of an element for maps
|
|
||||||
// returns an error if s is not a map.
|
|
||||||
func (o *Resources) getValueType(s *openapi_v2.Schema) (*openapi_v2.Schema, error) {
|
|
||||||
if !o.isMap(s) {
|
|
||||||
return &openapi_v2.Schema{}, fmt.Errorf("%v is not an map type", o.getTypeNameForField(s))
|
|
||||||
}
|
|
||||||
return s.GetAdditionalProperties().GetSchema(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// nameForDefinitionField returns the definition name for the schema (field) if it is a complex type
|
|
||||||
func (o *Resources) nameForDefinitionField(s *openapi_v2.Schema) string {
|
|
||||||
p := s.GetXRef()
|
|
||||||
if len(p) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip the "definitions/" pieces of the reference
|
|
||||||
return strings.Replace(p, "#/definitions/", "", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getGroupVersionKind implements OpenAPIData
|
|
||||||
// getGVK parses the gropuversionkind for a resource definition from the x-kubernetes
|
|
||||||
// extensions
|
|
||||||
// map[x-kubernetes-group-version-kind:[map[Group:authentication.k8s.io Version:v1 Kind:TokenReview]]]
|
|
||||||
func (o *Resources) getGroupVersionKind(s *openapi_v2.Schema) (schema.GroupVersionKind, error) {
|
|
||||||
empty := schema.GroupVersionKind{}
|
|
||||||
|
|
||||||
extensionMap := vendorExtensionToMap(s.GetVendorExtension())
|
|
||||||
// Get the extensions
|
|
||||||
extList, f := extensionMap[groupVersionKindExtensionKey]
|
|
||||||
if !f {
|
|
||||||
return empty, fmt.Errorf("No %s extension present in %v", groupVersionKindExtensionKey, extensionMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expect a empty of a list with 1 element
|
|
||||||
extListCasted, ok := extList.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return empty, fmt.Errorf("%s extension has unexpected type %T in %s", groupVersionKindExtensionKey, extListCasted, extensionMap)
|
|
||||||
}
|
|
||||||
if len(extListCasted) == 0 {
|
|
||||||
return empty, fmt.Errorf("No Group Version Kind found in %v", extListCasted)
|
|
||||||
}
|
|
||||||
if len(extListCasted) != 1 {
|
|
||||||
return empty, fmt.Errorf("Multiple Group Version gvkToName found in %v", extListCasted)
|
|
||||||
}
|
|
||||||
gvk := extListCasted[0]
|
|
||||||
|
|
||||||
// Expect a empty of a map with 3 entries
|
|
||||||
gvkMap, ok := gvk.(map[interface{}]interface{})
|
|
||||||
if !ok {
|
|
||||||
return empty, fmt.Errorf("%s extension has unexpected type %T in %s", groupVersionKindExtensionKey, gvk, extList)
|
|
||||||
}
|
|
||||||
group, ok := gvkMap["group"].(string)
|
|
||||||
if !ok {
|
|
||||||
return empty, fmt.Errorf("%s extension missing Group: %v", groupVersionKindExtensionKey, gvkMap)
|
|
||||||
}
|
|
||||||
version, ok := gvkMap["version"].(string)
|
|
||||||
if !ok {
|
|
||||||
return empty, fmt.Errorf("%s extension missing Version: %v", groupVersionKindExtensionKey, gvkMap)
|
|
||||||
}
|
|
||||||
kind, ok := gvkMap["kind"].(string)
|
|
||||||
if !ok {
|
|
||||||
return empty, fmt.Errorf("%s extension missing Kind: %v", groupVersionKindExtensionKey, gvkMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
return schema.GroupVersionKind{
|
|
||||||
Group: group,
|
|
||||||
Version: version,
|
|
||||||
Kind: kind,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ package openapi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/gob"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -26,15 +25,13 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
|
||||||
|
|
||||||
"k8s.io/client-go/discovery"
|
"k8s.io/client-go/discovery"
|
||||||
"k8s.io/kubernetes/pkg/version"
|
"k8s.io/kubernetes/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
registerBinaryEncodingTypes()
|
|
||||||
}
|
|
||||||
|
|
||||||
const openapiFileName = "openapi_cache"
|
const openapiFileName = "openapi_cache"
|
||||||
|
|
||||||
type CachingOpenAPIClient struct {
|
type CachingOpenAPIClient struct {
|
||||||
@ -61,12 +58,12 @@ func NewCachingOpenAPIClient(client discovery.OpenAPISchemaInterface, version, c
|
|||||||
// It will first attempt to read the spec from a local cache
|
// It will first attempt to read the spec from a local cache
|
||||||
// If it cannot read a local cache, it will read the file
|
// If it cannot read a local cache, it will read the file
|
||||||
// using the client and then write the cache.
|
// using the client and then write the cache.
|
||||||
func (c *CachingOpenAPIClient) OpenAPIData() (*Resources, error) {
|
func (c *CachingOpenAPIClient) OpenAPIData() (Resources, error) {
|
||||||
// Try to use the cached version
|
// Try to use the cached version
|
||||||
if c.useCache() {
|
if c.useCache() {
|
||||||
doc, err := c.readOpenAPICache()
|
doc, err := c.readOpenAPICache()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return doc, nil
|
return NewOpenAPIData(doc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +82,7 @@ func (c *CachingOpenAPIClient) OpenAPIData() (*Resources, error) {
|
|||||||
|
|
||||||
// Try to cache the openapi spec
|
// Try to cache the openapi spec
|
||||||
if c.useCache() {
|
if c.useCache() {
|
||||||
err = c.writeToCache(oa)
|
err = c.writeToCache(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Just log an message, no need to fail the command since we got the data we need
|
// Just log an message, no need to fail the command since we got the data we need
|
||||||
glog.V(2).Infof("Unable to cache openapi spec %v", err)
|
glog.V(2).Infof("Unable to cache openapi spec %v", err)
|
||||||
@ -102,7 +99,7 @@ func (c *CachingOpenAPIClient) useCache() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// readOpenAPICache tries to read the openapi spec from the local file cache
|
// readOpenAPICache tries to read the openapi spec from the local file cache
|
||||||
func (c *CachingOpenAPIClient) readOpenAPICache() (*Resources, error) {
|
func (c *CachingOpenAPIClient) readOpenAPICache() (*openapi_v2.Document, error) {
|
||||||
// Get the filename to read
|
// Get the filename to read
|
||||||
filename := c.openAPICacheFilename()
|
filename := c.openAPICacheFilename()
|
||||||
|
|
||||||
@ -112,38 +109,18 @@ func (c *CachingOpenAPIClient) readOpenAPICache() (*Resources, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the openapi spec
|
doc := &openapi_v2.Document{}
|
||||||
s, err := c.decodeSpec(data)
|
return doc, proto.Unmarshal(data, doc)
|
||||||
|
|
||||||
return s, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeSpec binary decodes the openapi spec
|
|
||||||
func (c *CachingOpenAPIClient) decodeSpec(data []byte) (*Resources, error) {
|
|
||||||
b := bytes.NewBuffer(data)
|
|
||||||
d := gob.NewDecoder(b)
|
|
||||||
parsed := &Resources{}
|
|
||||||
err := d.Decode(parsed)
|
|
||||||
return parsed, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodeSpec binary encodes the openapi spec
|
|
||||||
func (c *CachingOpenAPIClient) encodeSpec(parsed *Resources) ([]byte, error) {
|
|
||||||
b := &bytes.Buffer{}
|
|
||||||
e := gob.NewEncoder(b)
|
|
||||||
err := e.Encode(parsed)
|
|
||||||
return b.Bytes(), err
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeToCache tries to write the openapi spec to the local file cache.
|
// writeToCache tries to write the openapi spec to the local file cache.
|
||||||
// writes the data to a new tempfile, and then links the cache file and the tempfile
|
// writes the data to a new tempfile, and then links the cache file and the tempfile
|
||||||
func (c *CachingOpenAPIClient) writeToCache(parsed *Resources) error {
|
func (c *CachingOpenAPIClient) writeToCache(doc *openapi_v2.Document) error {
|
||||||
// Get the constant filename used to read the cache.
|
// Get the constant filename used to read the cache.
|
||||||
cacheFile := c.openAPICacheFilename()
|
cacheFile := c.openAPICacheFilename()
|
||||||
|
|
||||||
// Binary encode the spec. This is 10x as fast as using json encoding. (60ms vs 600ms)
|
// Binary encode the spec. This is 10x as fast as using json encoding. (60ms vs 600ms)
|
||||||
b, err := c.encodeSpec(parsed)
|
b, err := proto.Marshal(doc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Could not binary encode openapi spec: %v", err)
|
return fmt.Errorf("Could not binary encode openapi spec: %v", err)
|
||||||
}
|
}
|
||||||
@ -184,9 +161,3 @@ func linkFiles(old, new string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerBinaryEncodingTypes registers the types so they can be binary encoded by gob
|
|
||||||
func registerBinaryEncodingTypes() {
|
|
||||||
gob.Register(map[interface{}]interface{}{})
|
|
||||||
gob.Register([]interface{}{})
|
|
||||||
}
|
|
||||||
|
@ -38,7 +38,7 @@ var _ = Describe("When reading openAPIData", func() {
|
|||||||
var err error
|
var err error
|
||||||
var client *fakeOpenAPIClient
|
var client *fakeOpenAPIClient
|
||||||
var instance *openapi.CachingOpenAPIClient
|
var instance *openapi.CachingOpenAPIClient
|
||||||
var expectedData *openapi.Resources
|
var expectedData openapi.Resources
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
tmpDir, err = ioutil.TempDir("", "openapi_cache_test")
|
tmpDir, err = ioutil.TempDir("", "openapi_cache_test")
|
||||||
@ -61,7 +61,7 @@ var _ = Describe("When reading openAPIData", func() {
|
|||||||
By("getting the live openapi spec from the server")
|
By("getting the live openapi spec from the server")
|
||||||
result, err := instance.OpenAPIData()
|
result, err := instance.OpenAPIData()
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
expectEqual(result, expectedData)
|
Expect(result).To(Equal(expectedData))
|
||||||
Expect(client.calls).To(Equal(1))
|
Expect(client.calls).To(Equal(1))
|
||||||
|
|
||||||
By("writing the live openapi spec to a local cache file")
|
By("writing the live openapi spec to a local cache file")
|
||||||
@ -83,13 +83,13 @@ var _ = Describe("When reading openAPIData", func() {
|
|||||||
// First call should use the client
|
// First call should use the client
|
||||||
result, err := instance.OpenAPIData()
|
result, err := instance.OpenAPIData()
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
expectEqual(result, expectedData)
|
Expect(result).To(Equal(expectedData))
|
||||||
Expect(client.calls).To(Equal(1))
|
Expect(client.calls).To(Equal(1))
|
||||||
|
|
||||||
// Second call shouldn't use the client
|
// Second call shouldn't use the client
|
||||||
result, err = instance.OpenAPIData()
|
result, err = instance.OpenAPIData()
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
expectEqual(result, expectedData)
|
Expect(result).To(Equal(expectedData))
|
||||||
Expect(client.calls).To(Equal(1))
|
Expect(client.calls).To(Equal(1))
|
||||||
|
|
||||||
names, err := getFilenames(tmpDir)
|
names, err := getFilenames(tmpDir)
|
||||||
@ -153,7 +153,7 @@ var _ = Describe("Reading openAPIData", func() {
|
|||||||
By("getting the live openapi schema")
|
By("getting the live openapi schema")
|
||||||
result, err := instance.OpenAPIData()
|
result, err := instance.OpenAPIData()
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
expectEqual(result, expectedData)
|
Expect(result).To(Equal(expectedData))
|
||||||
Expect(client.calls).To(Equal(1))
|
Expect(client.calls).To(Equal(1))
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(tmpDir)
|
files, err := ioutil.ReadDir(tmpDir)
|
||||||
@ -181,7 +181,7 @@ var _ = Describe("Reading openAPIData", func() {
|
|||||||
By("getting the live openapi schema")
|
By("getting the live openapi schema")
|
||||||
result, err := instance.OpenAPIData()
|
result, err := instance.OpenAPIData()
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
expectEqual(result, expectedData)
|
Expect(result).To(Equal(expectedData))
|
||||||
Expect(client.calls).To(Equal(1))
|
Expect(client.calls).To(Equal(1))
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(tmpDir)
|
files, err := ioutil.ReadDir(tmpDir)
|
||||||
@ -204,19 +204,6 @@ func getFilenames(path string) ([]string, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func expectEqual(a *openapi.Resources, b *openapi.Resources) {
|
|
||||||
Expect(a.NameToDefinition).To(HaveLen(len(b.NameToDefinition)))
|
|
||||||
for k, v := range a.NameToDefinition {
|
|
||||||
Expect(v).To(Equal(b.NameToDefinition[k]),
|
|
||||||
fmt.Sprintf("Names for GVK do not match %v", k))
|
|
||||||
}
|
|
||||||
Expect(a.GroupVersionKindToName).To(HaveLen(len(b.GroupVersionKindToName)))
|
|
||||||
for k, v := range a.GroupVersionKindToName {
|
|
||||||
Expect(v).To(Equal(b.GroupVersionKindToName[k]),
|
|
||||||
fmt.Sprintf("Values for name do not match %v", k))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type fakeOpenAPIClient struct {
|
type fakeOpenAPIClient struct {
|
||||||
calls int
|
calls int
|
||||||
err error
|
err error
|
||||||
@ -276,5 +263,6 @@ func (d *apiData) OpenAPISchema() (*openapi_v2.Document, error) {
|
|||||||
}
|
}
|
||||||
d.data, d.err = openapi_v2.NewDocument(info, compiler.NewContext("$root", nil))
|
d.data, d.err = openapi_v2.NewDocument(info, compiler.NewContext("$root", nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
return d.data, d.err
|
return d.data, d.err
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
type synchronizedOpenAPIGetter struct {
|
type synchronizedOpenAPIGetter struct {
|
||||||
// Cached results
|
// Cached results
|
||||||
sync.Once
|
sync.Once
|
||||||
openAPISchema *Resources
|
openAPISchema Resources
|
||||||
err error
|
err error
|
||||||
|
|
||||||
serverVersion string
|
serverVersion string
|
||||||
@ -39,7 +39,7 @@ var _ Getter = &synchronizedOpenAPIGetter{}
|
|||||||
// Getter is an interface for fetching openapi specs and parsing them into an Resources struct
|
// Getter is an interface for fetching openapi specs and parsing them into an Resources struct
|
||||||
type Getter interface {
|
type Getter interface {
|
||||||
// OpenAPIData returns the parsed OpenAPIData
|
// OpenAPIData returns the parsed OpenAPIData
|
||||||
Get() (*Resources, error)
|
Get() (Resources, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOpenAPIGetter returns an object to return OpenAPIDatas which either read from a
|
// NewOpenAPIGetter returns an object to return OpenAPIDatas which either read from a
|
||||||
@ -53,7 +53,7 @@ func NewOpenAPIGetter(cacheDir, serverVersion string, openAPIClient discovery.Op
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resources implements Getter
|
// Resources implements Getter
|
||||||
func (g *synchronizedOpenAPIGetter) Get() (*Resources, error) {
|
func (g *synchronizedOpenAPIGetter) Get() (Resources, error) {
|
||||||
g.Do(func() {
|
g.Do(func() {
|
||||||
client := NewCachingOpenAPIClient(g.openAPIClient, g.serverVersion, g.cacheDir)
|
client := NewCachingOpenAPIClient(g.openAPIClient, g.serverVersion, g.cacheDir)
|
||||||
result, err := client.OpenAPIData()
|
result, err := client.OpenAPIData()
|
||||||
|
@ -27,7 +27,7 @@ import (
|
|||||||
|
|
||||||
var _ = Describe("Getting the Resources", func() {
|
var _ = Describe("Getting the Resources", func() {
|
||||||
var client *fakeOpenAPIClient
|
var client *fakeOpenAPIClient
|
||||||
var expectedData *openapi.Resources
|
var expectedData openapi.Resources
|
||||||
var instance openapi.Getter
|
var instance openapi.Getter
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
@ -47,12 +47,12 @@ var _ = Describe("Getting the Resources", func() {
|
|||||||
|
|
||||||
result, err := instance.Get()
|
result, err := instance.Get()
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
expectEqual(result, expectedData)
|
Expect(result).To(Equal(expectedData))
|
||||||
Expect(client.calls).To(Equal(1))
|
Expect(client.calls).To(Equal(1))
|
||||||
|
|
||||||
result, err = instance.Get()
|
result, err = instance.Get()
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
expectEqual(result, expectedData)
|
Expect(result).To(Equal(expectedData))
|
||||||
// No additional client calls expected
|
// No additional client calls expected
|
||||||
Expect(client.calls).To(Equal(1))
|
Expect(client.calls).To(Equal(1))
|
||||||
})
|
})
|
||||||
|
@ -17,9 +17,6 @@ limitations under the License.
|
|||||||
package openapi_test
|
package openapi_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
@ -28,395 +25,161 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() {
|
var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() {
|
||||||
var instance *openapi.Resources
|
var resources openapi.Resources
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
s, err := data.OpenAPISchema()
|
s, err := data.OpenAPISchema()
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
instance, err = openapi.NewOpenAPIData(s)
|
resources, err = openapi.NewOpenAPIData(s)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
fmt.Fprintf(GinkgoWriter, fmt.Sprintf("CHAO: instance.GroupVersionKindToName=%#v\n", instance.GroupVersionKindToName))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
deploymentName := "io.k8s.api.apps.v1beta1.Deployment"
|
|
||||||
gvk := schema.GroupVersionKind{
|
gvk := schema.GroupVersionKind{
|
||||||
Kind: "Deployment",
|
Kind: "Deployment",
|
||||||
Version: "v1beta1",
|
Version: "v1beta1",
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
}
|
}
|
||||||
|
|
||||||
It("should find the name by its GroupVersionKind", func() {
|
var schema openapi.Schema
|
||||||
name, found := instance.GroupVersionKindToName[gvk]
|
It("should lookup the Schema by its GroupVersionKind", func() {
|
||||||
fmt.Fprintf(GinkgoWriter, fmt.Sprintf("CHAO: instance.GroupVersionKindToName=%#v\n", instance.GroupVersionKindToName))
|
schema = resources.LookupResource(gvk)
|
||||||
Expect(found).To(BeTrue())
|
Expect(schema).ToNot(BeNil())
|
||||||
Expect(name).To(Equal(deploymentName))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
var definition openapi.Kind
|
var deployment *openapi.Kind
|
||||||
It("should find the definition by name", func() {
|
It("should be a Kind", func() {
|
||||||
var found bool
|
deployment = schema.(*openapi.Kind)
|
||||||
definition, found = instance.NameToDefinition[deploymentName]
|
Expect(deployment).ToNot(BeNil())
|
||||||
Expect(found).To(BeTrue())
|
|
||||||
Expect(definition.Name).To(Equal(deploymentName))
|
|
||||||
Expect(definition.PrimitiveType).To(BeEmpty())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should lookup the Kind by its GroupVersionKind", func() {
|
It("should have a path", func() {
|
||||||
d, found := instance.LookupResource(gvk)
|
Expect(deployment.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.Deployment"}))
|
||||||
Expect(found).To(BeTrue())
|
|
||||||
Expect(d).To(Equal(definition))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should find the definition GroupVersionKind", func() {
|
It("should have a kind key of type string", func() {
|
||||||
Expect(definition.GroupVersionKind).To(Equal(gvk))
|
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 find the definition GroupVersionKind extensions", func() {
|
It("should have a apiVersion key of type string", func() {
|
||||||
Expect(definition.Extensions).To(HaveKey("x-kubernetes-group-version-kind"))
|
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 find the definition fields", func() {
|
It("should have a metadata key of type Reference", func() {
|
||||||
By("for 'kind'")
|
Expect(deployment.Fields).To(HaveKey("metadata"))
|
||||||
Expect(definition.Fields).To(HaveKeyWithValue("kind", openapi.Type{
|
key := deployment.Fields["metadata"].(*openapi.Reference)
|
||||||
TypeName: "string",
|
Expect(key).ToNot(BeNil())
|
||||||
IsPrimitive: true,
|
Expect(key.Reference).To(Equal("io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"))
|
||||||
|
subSchema := key.GetSubSchema().(*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.GetSubSchema().(*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 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)
|
||||||
By("for 'apiVersion'")
|
Expect(condition.Reference).To(Equal("io.k8s.api.apps.v1beta1.DeploymentCondition"))
|
||||||
Expect(definition.Fields).To(HaveKeyWithValue("apiVersion", openapi.Type{
|
|
||||||
TypeName: "string",
|
|
||||||
IsPrimitive: true,
|
|
||||||
}))
|
|
||||||
|
|
||||||
By("for 'metadata'")
|
|
||||||
Expect(definition.Fields).To(HaveKeyWithValue("metadata", openapi.Type{
|
|
||||||
TypeName: "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta",
|
|
||||||
IsKind: true,
|
|
||||||
}))
|
|
||||||
|
|
||||||
By("for 'spec'")
|
|
||||||
Expect(definition.Fields).To(HaveKeyWithValue("spec", openapi.Type{
|
|
||||||
TypeName: "io.k8s.api.apps.v1beta1.DeploymentSpec",
|
|
||||||
IsKind: true,
|
|
||||||
}))
|
|
||||||
|
|
||||||
By("for 'status'")
|
|
||||||
Expect(definition.Fields).To(HaveKeyWithValue("status", openapi.Type{
|
|
||||||
TypeName: "io.k8s.api.apps.v1beta1.DeploymentStatus",
|
|
||||||
IsKind: true,
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
var _ = Describe("Reading apps/v1beta1/DeploymentStatus from openAPIData", func() {
|
|
||||||
var instance *openapi.Resources
|
|
||||||
BeforeEach(func() {
|
|
||||||
d, err := data.OpenAPISchema()
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
instance, err = openapi.NewOpenAPIData(d)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
deploymentStatusName := "io.k8s.api.apps.v1beta1.DeploymentStatus"
|
var spec *openapi.Kind
|
||||||
|
It("should have a spec key of type Reference", func() {
|
||||||
var definition openapi.Kind
|
Expect(deployment.Fields).To(HaveKey("spec"))
|
||||||
It("should find the definition by name", func() {
|
key := deployment.Fields["spec"].(*openapi.Reference)
|
||||||
var found bool
|
Expect(key).ToNot(BeNil())
|
||||||
definition, found = instance.NameToDefinition[deploymentStatusName]
|
Expect(key.Reference).To(Equal("io.k8s.api.apps.v1beta1.DeploymentSpec"))
|
||||||
Expect(found).To(BeTrue())
|
spec = key.GetSubSchema().(*openapi.Kind)
|
||||||
Expect(definition.Name).To(Equal(deploymentStatusName))
|
Expect(spec).ToNot(BeNil())
|
||||||
Expect(definition.PrimitiveType).To(BeEmpty())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should not find the definition GroupVersionKind", func() {
|
It("should have a spec with no gvk", func() {
|
||||||
Expect(definition.GroupVersionKind).To(Equal(schema.GroupVersionKind{}))
|
_, found := spec.GetExtensions()["x-kubernetes-group-version-kind"]
|
||||||
})
|
|
||||||
|
|
||||||
It("should not find the definition GroupVersionKind extensions", func() {
|
|
||||||
_, found := definition.Extensions["x-kubernetes-group-version-kind"]
|
|
||||||
Expect(found).To(BeFalse())
|
Expect(found).To(BeFalse())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should find the definition fields", func() {
|
It("should have a spec with a PodTemplateSpec sub-field", func() {
|
||||||
By("for 'availableReplicas'")
|
Expect(spec.Fields).To(HaveKey("template"))
|
||||||
Expect(definition.Fields).To(HaveKeyWithValue("availableReplicas", openapi.Type{
|
key := spec.Fields["template"].(*openapi.Reference)
|
||||||
TypeName: "integer",
|
Expect(key).ToNot(BeNil())
|
||||||
IsPrimitive: true,
|
Expect(key.Reference).To(Equal("io.k8s.api.core.v1.PodTemplateSpec"))
|
||||||
}))
|
|
||||||
|
|
||||||
By("for 'conditions'")
|
|
||||||
Expect(definition.Fields).To(HaveKeyWithValue("conditions", openapi.Type{
|
|
||||||
TypeName: "io.k8s.api.apps.v1beta1.DeploymentCondition array",
|
|
||||||
IsArray: true,
|
|
||||||
ElementType: &openapi.Type{
|
|
||||||
TypeName: "io.k8s.api.apps.v1beta1.DeploymentCondition",
|
|
||||||
IsKind: true,
|
|
||||||
},
|
|
||||||
Extensions: spec.Extensions{
|
|
||||||
"x-kubernetes-patch-merge-key": "type",
|
|
||||||
"x-kubernetes-patch-strategy": "merge",
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
var _ = Describe("Reading apps/v1beta1/DeploymentSpec from openAPIData", func() {
|
var _ = Describe("Reading authorization.k8s.io/v1/SubjectAccessReview from openAPIData", func() {
|
||||||
var instance *openapi.Resources
|
var resources openapi.Resources
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
d, err := data.OpenAPISchema()
|
s, err := data.OpenAPISchema()
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
instance, err = openapi.NewOpenAPIData(d)
|
resources, err = openapi.NewOpenAPIData(s)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
})
|
})
|
||||||
|
|
||||||
deploymentSpecName := "io.k8s.api.apps.v1beta1.DeploymentSpec"
|
gvk := schema.GroupVersionKind{
|
||||||
|
Kind: "SubjectAccessReview",
|
||||||
var definition openapi.Kind
|
Version: "v1",
|
||||||
It("should find the definition by name", func() {
|
Group: "authorization.k8s.io",
|
||||||
var found bool
|
|
||||||
definition, found = instance.NameToDefinition[deploymentSpecName]
|
|
||||||
Expect(found).To(BeTrue())
|
|
||||||
Expect(definition.Name).To(Equal(deploymentSpecName))
|
|
||||||
Expect(definition.PrimitiveType).To(BeEmpty())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should not find the definition GroupVersionKind", func() {
|
|
||||||
Expect(definition.GroupVersionKind).To(Equal(schema.GroupVersionKind{}))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should not find the definition GroupVersionKind extensions", func() {
|
|
||||||
_, found := definition.Extensions["x-kubernetes-group-version-kind"]
|
|
||||||
Expect(found).To(BeFalse())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should find the definition fields", func() {
|
|
||||||
By("for 'template'")
|
|
||||||
Expect(definition.Fields).To(HaveKeyWithValue("template", openapi.Type{
|
|
||||||
TypeName: "io.k8s.api.core.v1.PodTemplateSpec",
|
|
||||||
IsKind: true,
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
var _ = Describe("Reading v1/ObjectMeta from openAPIData", func() {
|
|
||||||
var instance *openapi.Resources
|
|
||||||
BeforeEach(func() {
|
|
||||||
d, err := data.OpenAPISchema()
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
instance, err = openapi.NewOpenAPIData(d)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
objectMetaName := "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
|
|
||||||
|
|
||||||
var definition openapi.Kind
|
|
||||||
It("should find the definition by name", func() {
|
|
||||||
var found bool
|
|
||||||
definition, found = instance.NameToDefinition[objectMetaName]
|
|
||||||
Expect(found).To(BeTrue())
|
|
||||||
Expect(definition.Name).To(Equal(objectMetaName))
|
|
||||||
Expect(definition.PrimitiveType).To(BeEmpty())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should not find the definition GroupVersionKind", func() {
|
|
||||||
Expect(definition.GroupVersionKind).To(Equal(schema.GroupVersionKind{}))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should not find the definition GroupVersionKind extensions", func() {
|
|
||||||
_, found := definition.Extensions["x-kubernetes-group-version-kind"]
|
|
||||||
Expect(found).To(BeFalse())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should find the definition fields", func() {
|
|
||||||
By("for 'finalizers'")
|
|
||||||
Expect(definition.Fields).To(HaveKeyWithValue("finalizers", openapi.Type{
|
|
||||||
TypeName: "string array",
|
|
||||||
IsArray: true,
|
|
||||||
ElementType: &openapi.Type{
|
|
||||||
TypeName: "string",
|
|
||||||
IsPrimitive: true,
|
|
||||||
},
|
|
||||||
Extensions: spec.Extensions{
|
|
||||||
"x-kubernetes-patch-strategy": "merge",
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
By("for 'ownerReferences'")
|
|
||||||
Expect(definition.Fields).To(HaveKeyWithValue("ownerReferences", openapi.Type{
|
|
||||||
TypeName: "io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference array",
|
|
||||||
IsArray: true,
|
|
||||||
ElementType: &openapi.Type{
|
|
||||||
TypeName: "io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference",
|
|
||||||
IsKind: true,
|
|
||||||
},
|
|
||||||
Extensions: spec.Extensions{
|
|
||||||
"x-kubernetes-patch-merge-key": "uid",
|
|
||||||
"x-kubernetes-patch-strategy": "merge",
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
By("for 'labels'")
|
|
||||||
Expect(definition.Fields).To(HaveKeyWithValue("labels", openapi.Type{
|
|
||||||
TypeName: "string map",
|
|
||||||
IsMap: true,
|
|
||||||
ElementType: &openapi.Type{
|
|
||||||
TypeName: "string",
|
|
||||||
IsPrimitive: true,
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
var _ = Describe("Reading v1/NodeStatus from openAPIData", func() {
|
|
||||||
var instance *openapi.Resources
|
|
||||||
BeforeEach(func() {
|
|
||||||
d, err := data.OpenAPISchema()
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
instance, err = openapi.NewOpenAPIData(d)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
nodeStatusName := "io.k8s.api.core.v1.NodeStatus"
|
|
||||||
|
|
||||||
var definition openapi.Kind
|
|
||||||
It("should find the definition by name", func() {
|
|
||||||
var found bool
|
|
||||||
definition, found = instance.NameToDefinition[nodeStatusName]
|
|
||||||
Expect(found).To(BeTrue())
|
|
||||||
Expect(definition.Name).To(Equal(nodeStatusName))
|
|
||||||
Expect(definition.PrimitiveType).To(BeEmpty())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should not find the definition GroupVersionKind", func() {
|
|
||||||
Expect(definition.GroupVersionKind).To(Equal(schema.GroupVersionKind{}))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should not find the definition GroupVersionKind extensions", func() {
|
|
||||||
_, found := definition.Extensions["x-kubernetes-group-version-kind"]
|
|
||||||
Expect(found).To(BeFalse())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should find the definition fields", func() {
|
|
||||||
By("for 'allocatable'")
|
|
||||||
Expect(definition.Fields).To(HaveKeyWithValue("allocatable", openapi.Type{
|
|
||||||
TypeName: "io.k8s.apimachinery.pkg.api.resource.Quantity map",
|
|
||||||
IsMap: true,
|
|
||||||
ElementType: &openapi.Type{
|
|
||||||
TypeName: "io.k8s.apimachinery.pkg.api.resource.Quantity",
|
|
||||||
IsKind: true,
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
var _ = Describe("Reading Utility Definitions from openAPIData", func() {
|
|
||||||
var instance *openapi.Resources
|
|
||||||
BeforeEach(func() {
|
|
||||||
d, err := data.OpenAPISchema()
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
instance, err = openapi.NewOpenAPIData(d)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("for util.intstr.IntOrString", func() {
|
|
||||||
var definition openapi.Kind
|
|
||||||
It("should find the definition by name", func() {
|
|
||||||
intOrStringName := "io.k8s.apimachinery.pkg.util.intstr.IntOrString"
|
|
||||||
var found bool
|
|
||||||
definition, found = instance.NameToDefinition[intOrStringName]
|
|
||||||
Expect(found).To(BeTrue())
|
|
||||||
Expect(definition.Name).To(Equal(intOrStringName))
|
|
||||||
Expect(definition.PrimitiveType).To(Equal("string"))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("for apis.meta.v1.Time", func() {
|
|
||||||
var definition openapi.Kind
|
|
||||||
It("should find the definition by name", func() {
|
|
||||||
intOrStringName := "io.k8s.apimachinery.pkg.apis.meta.v1.Time"
|
|
||||||
var found bool
|
|
||||||
definition, found = instance.NameToDefinition[intOrStringName]
|
|
||||||
Expect(found).To(BeTrue())
|
|
||||||
Expect(definition.Name).To(Equal(intOrStringName))
|
|
||||||
Expect(definition.PrimitiveType).To(Equal("string"))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
var _ = Describe("When parsing the openAPIData", func() {
|
|
||||||
var instance *openapi.Resources
|
|
||||||
BeforeEach(func() {
|
|
||||||
d, err := data.OpenAPISchema()
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
instance, err = openapi.NewOpenAPIData(d)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should result in each definition and field having a single type", func() {
|
|
||||||
for _, d := range instance.NameToDefinition {
|
|
||||||
Expect(d.Name).ToNot(BeEmpty())
|
|
||||||
for n, f := range d.Fields {
|
|
||||||
Expect(f.TypeName).ToNot(BeEmpty(),
|
|
||||||
fmt.Sprintf("TypeName for %v.%v is empty %+v", d.Name, n, f))
|
|
||||||
Expect(oneOf(f.IsArray, f.IsMap, f.IsPrimitive, f.IsKind)).To(BeTrue(),
|
|
||||||
fmt.Sprintf("%+v has multiple types", f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should find every GroupVersionKind by name", func() {
|
|
||||||
for _, name := range instance.GroupVersionKindToName {
|
|
||||||
_, found := instance.NameToDefinition[name]
|
|
||||||
Expect(found).To(BeTrue())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
var _ = Describe("Reading authorization/v1/SubjectAccessReviewSpec from openAPIData", func() {
|
|
||||||
var instance *openapi.Resources
|
|
||||||
BeforeEach(func() {
|
|
||||||
d, err := data.OpenAPISchema()
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
instance, err = openapi.NewOpenAPIData(d)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
subjectAccessReviewSpecName := "io.k8s.api.authorization.v1.SubjectAccessReviewSpec"
|
|
||||||
|
|
||||||
var definition openapi.Kind
|
|
||||||
It("should find the definition by name", func() {
|
|
||||||
var found bool
|
|
||||||
definition, found = instance.NameToDefinition[subjectAccessReviewSpecName]
|
|
||||||
Expect(found).To(BeTrue())
|
|
||||||
Expect(definition.Name).To(Equal(subjectAccessReviewSpecName))
|
|
||||||
Expect(definition.PrimitiveType).To(BeEmpty())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should find the definition fields", func() {
|
|
||||||
By("for 'allocatable'")
|
|
||||||
Expect(definition.Fields).To(HaveKeyWithValue("extra", openapi.Type{
|
|
||||||
TypeName: "string array map",
|
|
||||||
IsMap: true,
|
|
||||||
ElementType: &openapi.Type{
|
|
||||||
TypeName: "string array",
|
|
||||||
IsArray: true,
|
|
||||||
ElementType: &openapi.Type{
|
|
||||||
TypeName: "string",
|
|
||||||
IsPrimitive: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
func oneOf(values ...bool) bool {
|
|
||||||
found := false
|
|
||||||
for _, v := range values {
|
|
||||||
if v && found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return found
|
|
||||||
}
|
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.GetSubSchema().(*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"}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user