openapi: Add validation logic
This allows validation of a yaml/json object against an openapi schema. A lot more testing would be needed to validate the logic, and also this is not plumbed in, so it can't be used by kubectl yet.
This commit is contained in:
		@@ -64,6 +64,7 @@ filegroup(
 | 
				
			|||||||
    srcs = [
 | 
					    srcs = [
 | 
				
			||||||
        ":package-srcs",
 | 
					        ":package-srcs",
 | 
				
			||||||
        "//pkg/kubectl/cmd/util/openapi/testing:all-srcs",
 | 
					        "//pkg/kubectl/cmd/util/openapi/testing:all-srcs",
 | 
				
			||||||
 | 
					        "//pkg/kubectl/cmd/util/openapi/validation:all-srcs",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    tags = ["automanaged"],
 | 
					    tags = ["automanaged"],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -132,7 +132,8 @@ func NewOpenAPIData(doc *openapi_v2.Document) (Resources, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Now, parse each model. We can validate that references exists.
 | 
						// Now, parse each model. We can validate that references exists.
 | 
				
			||||||
	for _, namedSchema := range doc.GetDefinitions().GetAdditionalProperties() {
 | 
						for _, namedSchema := range doc.GetDefinitions().GetAdditionalProperties() {
 | 
				
			||||||
		schema, err := definitions.ParseSchema(namedSchema.GetValue(), &Path{key: namedSchema.GetName()})
 | 
							path := NewPath(namedSchema.GetName())
 | 
				
			||||||
 | 
							schema, err := definitions.ParseSchema(namedSchema.GetValue(), &path)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -252,7 +253,8 @@ func (d *Definitions) parseKind(s *openapi_v2.Schema, path *Path) (Schema, error
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for _, namedSchema := range s.GetProperties().GetAdditionalProperties() {
 | 
						for _, namedSchema := range s.GetProperties().GetAdditionalProperties() {
 | 
				
			||||||
		var err error
 | 
							var err error
 | 
				
			||||||
		fields[namedSchema.GetName()], err = d.ParseSchema(namedSchema.GetValue(), &Path{parent: path, key: namedSchema.GetName()})
 | 
							path := path.FieldPath(namedSchema.GetName())
 | 
				
			||||||
 | 
							fields[namedSchema.GetName()], err = d.ParseSchema(namedSchema.GetValue(), &path)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -77,6 +77,10 @@ type Path struct {
 | 
				
			|||||||
	key    string
 | 
						key    string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewPath(key string) Path {
 | 
				
			||||||
 | 
						return Path{key: key}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (p *Path) Get() []string {
 | 
					func (p *Path) Get() []string {
 | 
				
			||||||
	if p == nil {
 | 
						if p == nil {
 | 
				
			||||||
		return []string{}
 | 
							return []string{}
 | 
				
			||||||
@@ -92,7 +96,23 @@ func (p *Path) Len() int {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (p *Path) String() string {
 | 
					func (p *Path) String() string {
 | 
				
			||||||
	return strings.Join(p.Get(), ".")
 | 
						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.
 | 
					// BaseSchema holds data used by each types of schema.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -65,7 +65,7 @@ var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() {
 | 
				
			|||||||
		key := deployment.Fields["kind"].(*openapi.Primitive)
 | 
							key := deployment.Fields["kind"].(*openapi.Primitive)
 | 
				
			||||||
		Expect(key).ToNot(BeNil())
 | 
							Expect(key).ToNot(BeNil())
 | 
				
			||||||
		Expect(key.Type).To(Equal("string"))
 | 
							Expect(key.Type).To(Equal("string"))
 | 
				
			||||||
		Expect(key.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.Deployment", "kind"}))
 | 
							Expect(key.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.Deployment", ".kind"}))
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	It("should have a apiVersion key of type string", func() {
 | 
						It("should have a apiVersion key of type string", func() {
 | 
				
			||||||
@@ -73,7 +73,7 @@ var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() {
 | 
				
			|||||||
		key := deployment.Fields["apiVersion"].(*openapi.Primitive)
 | 
							key := deployment.Fields["apiVersion"].(*openapi.Primitive)
 | 
				
			||||||
		Expect(key).ToNot(BeNil())
 | 
							Expect(key).ToNot(BeNil())
 | 
				
			||||||
		Expect(key.Type).To(Equal("string"))
 | 
							Expect(key.Type).To(Equal("string"))
 | 
				
			||||||
		Expect(key.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.Deployment", "apiVersion"}))
 | 
							Expect(key.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.Deployment", ".apiVersion"}))
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	It("should have a metadata key of type Reference", func() {
 | 
						It("should have a metadata key of type Reference", func() {
 | 
				
			||||||
@@ -176,15 +176,43 @@ var _ = Describe("Reading authorization.k8s.io/v1/SubjectAccessReview from openA
 | 
				
			|||||||
		extra := sarspec.Fields["extra"].(*openapi.Map)
 | 
							extra := sarspec.Fields["extra"].(*openapi.Map)
 | 
				
			||||||
		Expect(extra).ToNot(BeNil())
 | 
							Expect(extra).ToNot(BeNil())
 | 
				
			||||||
		Expect(extra.GetName()).To(Equal("Map of Array of string"))
 | 
							Expect(extra.GetName()).To(Equal("Map of Array of string"))
 | 
				
			||||||
		Expect(extra.GetPath().Get()).To(Equal([]string{"io.k8s.api.authorization.v1.SubjectAccessReviewSpec", "extra"}))
 | 
							Expect(extra.GetPath().Get()).To(Equal([]string{"io.k8s.api.authorization.v1.SubjectAccessReviewSpec", ".extra"}))
 | 
				
			||||||
		array := extra.SubType.(*openapi.Array)
 | 
							array := extra.SubType.(*openapi.Array)
 | 
				
			||||||
		Expect(array).ToNot(BeNil())
 | 
							Expect(array).ToNot(BeNil())
 | 
				
			||||||
		Expect(array.GetName()).To(Equal("Array of string"))
 | 
							Expect(array.GetName()).To(Equal("Array of string"))
 | 
				
			||||||
		Expect(array.GetPath().Get()).To(Equal([]string{"io.k8s.api.authorization.v1.SubjectAccessReviewSpec", "extra"}))
 | 
							Expect(array.GetPath().Get()).To(Equal([]string{"io.k8s.api.authorization.v1.SubjectAccessReviewSpec", ".extra"}))
 | 
				
			||||||
		str := array.SubType.(*openapi.Primitive)
 | 
							str := array.SubType.(*openapi.Primitive)
 | 
				
			||||||
		Expect(str).ToNot(BeNil())
 | 
							Expect(str).ToNot(BeNil())
 | 
				
			||||||
		Expect(str.Type).To(Equal("string"))
 | 
							Expect(str.Type).To(Equal("string"))
 | 
				
			||||||
		Expect(str.GetName()).To(Equal("string"))
 | 
							Expect(str.GetName()).To(Equal("string"))
 | 
				
			||||||
		Expect(str.GetPath().Get()).To(Equal([]string{"io.k8s.api.authorization.v1.SubjectAccessReviewSpec", "extra"}))
 | 
							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"}))
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										60
									
								
								pkg/kubectl/cmd/util/openapi/validation/BUILD
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								pkg/kubectl/cmd/util/openapi/validation/BUILD
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					package(default_visibility = ["//visibility:public"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					licenses(["notice"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					load(
 | 
				
			||||||
 | 
					    "@io_bazel_rules_go//go:def.bzl",
 | 
				
			||||||
 | 
					    "go_library",
 | 
				
			||||||
 | 
					    "go_test",
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go_library(
 | 
				
			||||||
 | 
					    name = "go_default_library",
 | 
				
			||||||
 | 
					    srcs = [
 | 
				
			||||||
 | 
					        "errors.go",
 | 
				
			||||||
 | 
					        "types.go",
 | 
				
			||||||
 | 
					        "validation.go",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    tags = ["automanaged"],
 | 
				
			||||||
 | 
					    deps = [
 | 
				
			||||||
 | 
					        "//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",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go_test(
 | 
				
			||||||
 | 
					    name = "go_default_xtest",
 | 
				
			||||||
 | 
					    srcs = [
 | 
				
			||||||
 | 
					        "validation_suite_test.go",
 | 
				
			||||||
 | 
					        "validation_test.go",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    data = ["//api/openapi-spec:swagger-spec"],
 | 
				
			||||||
 | 
					    tags = ["automanaged"],
 | 
				
			||||||
 | 
					    deps = [
 | 
				
			||||||
 | 
					        ":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",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					filegroup(
 | 
				
			||||||
 | 
					    name = "package-srcs",
 | 
				
			||||||
 | 
					    srcs = glob(["**"]),
 | 
				
			||||||
 | 
					    tags = ["automanaged"],
 | 
				
			||||||
 | 
					    visibility = ["//visibility:private"],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					filegroup(
 | 
				
			||||||
 | 
					    name = "all-srcs",
 | 
				
			||||||
 | 
					    srcs = [":package-srcs"],
 | 
				
			||||||
 | 
					    tags = ["automanaged"],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										79
									
								
								pkg/kubectl/cmd/util/openapi/validation/errors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								pkg/kubectl/cmd/util/openapi/validation/errors.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					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 validation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Errors struct {
 | 
				
			||||||
 | 
						errors []error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e *Errors) Errors() []error {
 | 
				
			||||||
 | 
						return e.errors
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e *Errors) AppendErrors(err ...error) {
 | 
				
			||||||
 | 
						e.errors = append(e.errors, err...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ValidationError struct {
 | 
				
			||||||
 | 
						Path string
 | 
				
			||||||
 | 
						Err  error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e ValidationError) Error() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("ValidationError(%s): %v", e.Path, e.Err)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type InvalidTypeError struct {
 | 
				
			||||||
 | 
						Path     string
 | 
				
			||||||
 | 
						Expected string
 | 
				
			||||||
 | 
						Actual   string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e InvalidTypeError) Error() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("invalid type for %s: got %q, expected %q", e.Path, e.Actual, e.Expected)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MissingRequiredFieldError struct {
 | 
				
			||||||
 | 
						Path  string
 | 
				
			||||||
 | 
						Field string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e MissingRequiredFieldError) Error() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("missing required field %q in %s", e.Field, e.Path)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type UnknownFieldError struct {
 | 
				
			||||||
 | 
						Path  string
 | 
				
			||||||
 | 
						Field string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e UnknownFieldError) Error() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("unknown field %q in %s", e.Field, e.Path)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type InvalidObjectTypeError struct {
 | 
				
			||||||
 | 
						Path string
 | 
				
			||||||
 | 
						Type string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e InvalidObjectTypeError) Error() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("unknown object type %q in %s", e.Type, e.Path)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										266
									
								
								pkg/kubectl/cmd/util/openapi/validation/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								pkg/kubectl/cmd/util/openapi/validation/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,266 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					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 validation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"sort"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ValidationItem interface {
 | 
				
			||||||
 | 
						openapi.SchemaVisitor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Errors() []error
 | 
				
			||||||
 | 
						Path() *openapi.Path
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type baseItem struct {
 | 
				
			||||||
 | 
						errors Errors
 | 
				
			||||||
 | 
						path   openapi.Path
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Errors returns the list of errors found for this item.
 | 
				
			||||||
 | 
					func (item *baseItem) Errors() []error {
 | 
				
			||||||
 | 
						return item.errors.Errors()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AddValidationError wraps the given error into a ValidationError and
 | 
				
			||||||
 | 
					// attaches it to this item.
 | 
				
			||||||
 | 
					func (item *baseItem) AddValidationError(err error) {
 | 
				
			||||||
 | 
						item.errors.AppendErrors(ValidationError{Path: item.path.String(), Err: err})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AddError adds a regular (non-validation related) error to the list.
 | 
				
			||||||
 | 
					func (item *baseItem) AddError(err error) {
 | 
				
			||||||
 | 
						item.errors.AppendErrors(err)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CopyErrors adds a list of errors to this item. This is useful to copy
 | 
				
			||||||
 | 
					// errors from subitems.
 | 
				
			||||||
 | 
					func (item *baseItem) CopyErrors(errs []error) {
 | 
				
			||||||
 | 
						item.errors.AppendErrors(errs...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Path returns the path of this item, helps print useful errors.
 | 
				
			||||||
 | 
					func (item *baseItem) Path() *openapi.Path {
 | 
				
			||||||
 | 
						return &item.path
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// mapItem represents a map entry in the yaml.
 | 
				
			||||||
 | 
					type mapItem struct {
 | 
				
			||||||
 | 
						baseItem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Map map[string]interface{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (item *mapItem) sortedKeys() []string {
 | 
				
			||||||
 | 
						sortedKeys := []string{}
 | 
				
			||||||
 | 
						for key := range item.Map {
 | 
				
			||||||
 | 
							sortedKeys = append(sortedKeys, key)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sort.Strings(sortedKeys)
 | 
				
			||||||
 | 
						return sortedKeys
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ ValidationItem = &mapItem{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (item *mapItem) VisitPrimitive(schema *openapi.Primitive) {
 | 
				
			||||||
 | 
						item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: "map"})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (item *mapItem) VisitArray(schema *openapi.Array) {
 | 
				
			||||||
 | 
						item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: "map"})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (item *mapItem) VisitMap(schema *openapi.Map) {
 | 
				
			||||||
 | 
						for _, key := range item.sortedKeys() {
 | 
				
			||||||
 | 
							subItem, err := itemFactory(item.Path().FieldPath(key), item.Map[key])
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								item.AddError(err)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							schema.SubType.Accept(subItem)
 | 
				
			||||||
 | 
							item.CopyErrors(subItem.Errors())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (item *mapItem) VisitKind(schema *openapi.Kind) {
 | 
				
			||||||
 | 
						// Verify each sub-field.
 | 
				
			||||||
 | 
						for _, key := range item.sortedKeys() {
 | 
				
			||||||
 | 
							subItem, err := itemFactory(item.Path().FieldPath(key), item.Map[key])
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								item.AddError(err)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if _, ok := schema.Fields[key]; !ok {
 | 
				
			||||||
 | 
								item.AddValidationError(UnknownFieldError{Path: schema.GetPath().String(), Field: key})
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							schema.Fields[key].Accept(subItem)
 | 
				
			||||||
 | 
							item.CopyErrors(subItem.Errors())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Verify that all required fields are present.
 | 
				
			||||||
 | 
						for _, required := range schema.RequiredFields {
 | 
				
			||||||
 | 
							if _, ok := item.Map[required]; !ok {
 | 
				
			||||||
 | 
								item.AddValidationError(MissingRequiredFieldError{Path: schema.GetPath().String(), Field: required})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// arrayItem represents a yaml array.
 | 
				
			||||||
 | 
					type arrayItem struct {
 | 
				
			||||||
 | 
						baseItem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Array []interface{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ ValidationItem = &arrayItem{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (item *arrayItem) VisitPrimitive(schema *openapi.Primitive) {
 | 
				
			||||||
 | 
						item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: "array"})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (item *arrayItem) VisitArray(schema *openapi.Array) {
 | 
				
			||||||
 | 
						for i, v := range item.Array {
 | 
				
			||||||
 | 
							subItem, err := itemFactory(item.Path().ArrayPath(i), v)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								item.AddError(err)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							schema.SubType.Accept(subItem)
 | 
				
			||||||
 | 
							item.CopyErrors(subItem.Errors())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (item *arrayItem) VisitMap(schema *openapi.Map) {
 | 
				
			||||||
 | 
						item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: "map"})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (item *arrayItem) VisitKind(schema *openapi.Kind) {
 | 
				
			||||||
 | 
						item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: "map"})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// primitiveItem represents a yaml value.
 | 
				
			||||||
 | 
					type primitiveItem struct {
 | 
				
			||||||
 | 
						baseItem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Value interface{}
 | 
				
			||||||
 | 
						Kind  string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ ValidationItem = &primitiveItem{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (item *primitiveItem) VisitPrimitive(schema *openapi.Primitive) {
 | 
				
			||||||
 | 
						// Some types of primitives can match more than one (a number
 | 
				
			||||||
 | 
						// can be a string, but not the other way around). Return from
 | 
				
			||||||
 | 
						// the switch if we have a valid possible type conversion
 | 
				
			||||||
 | 
						// NOTE(apelisse): This logic is blindly copied from the
 | 
				
			||||||
 | 
						// existing swagger logic, and I'm not sure I agree with it.
 | 
				
			||||||
 | 
						switch schema.Type {
 | 
				
			||||||
 | 
						case openapi.Boolean:
 | 
				
			||||||
 | 
							switch item.Kind {
 | 
				
			||||||
 | 
							case openapi.Boolean:
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case openapi.Integer:
 | 
				
			||||||
 | 
							switch item.Kind {
 | 
				
			||||||
 | 
							case openapi.Integer, openapi.Number:
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case openapi.Number:
 | 
				
			||||||
 | 
							switch item.Kind {
 | 
				
			||||||
 | 
							case openapi.Number:
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case openapi.String:
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: item.Kind})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (item *primitiveItem) VisitArray(schema *openapi.Array) {
 | 
				
			||||||
 | 
						item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: item.Kind})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (item *primitiveItem) VisitMap(schema *openapi.Map) {
 | 
				
			||||||
 | 
						item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: item.Kind})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (item *primitiveItem) VisitKind(schema *openapi.Kind) {
 | 
				
			||||||
 | 
						item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: item.Kind})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// itemFactory creates the relevant item type/visitor based on the current yaml type.
 | 
				
			||||||
 | 
					func itemFactory(path openapi.Path, v interface{}) (ValidationItem, error) {
 | 
				
			||||||
 | 
						// We need to special case for no-type fields in yaml (e.g. empty item in list)
 | 
				
			||||||
 | 
						if v == nil {
 | 
				
			||||||
 | 
							return nil, InvalidObjectTypeError{Type: "nil", Path: path.String()}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						kind := reflect.TypeOf(v).Kind()
 | 
				
			||||||
 | 
						switch kind {
 | 
				
			||||||
 | 
						case reflect.Bool:
 | 
				
			||||||
 | 
							return &primitiveItem{
 | 
				
			||||||
 | 
								baseItem: baseItem{path: path},
 | 
				
			||||||
 | 
								Value:    v,
 | 
				
			||||||
 | 
								Kind:     openapi.Boolean,
 | 
				
			||||||
 | 
							}, nil
 | 
				
			||||||
 | 
						case reflect.Int,
 | 
				
			||||||
 | 
							reflect.Int8,
 | 
				
			||||||
 | 
							reflect.Int16,
 | 
				
			||||||
 | 
							reflect.Int32,
 | 
				
			||||||
 | 
							reflect.Int64,
 | 
				
			||||||
 | 
							reflect.Uint,
 | 
				
			||||||
 | 
							reflect.Uint8,
 | 
				
			||||||
 | 
							reflect.Uint16,
 | 
				
			||||||
 | 
							reflect.Uint32,
 | 
				
			||||||
 | 
							reflect.Uint64:
 | 
				
			||||||
 | 
							return &primitiveItem{
 | 
				
			||||||
 | 
								baseItem: baseItem{path: path},
 | 
				
			||||||
 | 
								Value:    v,
 | 
				
			||||||
 | 
								Kind:     openapi.Integer,
 | 
				
			||||||
 | 
							}, nil
 | 
				
			||||||
 | 
						case reflect.Float32,
 | 
				
			||||||
 | 
							reflect.Float64:
 | 
				
			||||||
 | 
							return &primitiveItem{
 | 
				
			||||||
 | 
								baseItem: baseItem{path: path},
 | 
				
			||||||
 | 
								Value:    v,
 | 
				
			||||||
 | 
								Kind:     openapi.Number,
 | 
				
			||||||
 | 
							}, nil
 | 
				
			||||||
 | 
						case reflect.String:
 | 
				
			||||||
 | 
							return &primitiveItem{
 | 
				
			||||||
 | 
								baseItem: baseItem{path: path},
 | 
				
			||||||
 | 
								Value:    v,
 | 
				
			||||||
 | 
								Kind:     openapi.String,
 | 
				
			||||||
 | 
							}, nil
 | 
				
			||||||
 | 
						case reflect.Array,
 | 
				
			||||||
 | 
							reflect.Slice:
 | 
				
			||||||
 | 
							return &arrayItem{
 | 
				
			||||||
 | 
								baseItem: baseItem{path: path},
 | 
				
			||||||
 | 
								Array:    v.([]interface{}),
 | 
				
			||||||
 | 
							}, nil
 | 
				
			||||||
 | 
						case reflect.Map:
 | 
				
			||||||
 | 
							return &mapItem{
 | 
				
			||||||
 | 
								baseItem: baseItem{path: path},
 | 
				
			||||||
 | 
								Map:      v.(map[string]interface{}),
 | 
				
			||||||
 | 
							}, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, InvalidObjectTypeError{Type: kind.String(), Path: path.String()}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										103
									
								
								pkg/kubectl/cmd/util/openapi/validation/validation.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								pkg/kubectl/cmd/util/openapi/validation/validation.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					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 validation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/runtime/schema"
 | 
				
			||||||
 | 
						utilerrors "k8s.io/apimachinery/pkg/util/errors"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/json"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/yaml"
 | 
				
			||||||
 | 
						apiutil "k8s.io/kubernetes/pkg/api/util"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SchemaValidation struct {
 | 
				
			||||||
 | 
						resources openapi.Resources
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewSchemaValidation(resources openapi.Resources) *SchemaValidation {
 | 
				
			||||||
 | 
						return &SchemaValidation{
 | 
				
			||||||
 | 
							resources: resources,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (v *SchemaValidation) Validate(data []byte) error {
 | 
				
			||||||
 | 
						obj, err := parse(data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gvk, err := getObjectKind(obj)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						resource := v.resources.LookupResource(gvk)
 | 
				
			||||||
 | 
						if resource == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("unknown object type %q", gvk)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rootValidation, err := itemFactory(openapi.NewPath(gvk.Kind), obj)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						resource.Accept(rootValidation)
 | 
				
			||||||
 | 
						errs := rootValidation.Errors()
 | 
				
			||||||
 | 
						if errs != nil {
 | 
				
			||||||
 | 
							return utilerrors.NewAggregate(errs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func parse(data []byte) (interface{}, error) {
 | 
				
			||||||
 | 
						var obj interface{}
 | 
				
			||||||
 | 
						out, err := yaml.ToJSON(data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := json.Unmarshal(out, &obj); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return obj, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getObjectKind(object interface{}) (schema.GroupVersionKind, error) {
 | 
				
			||||||
 | 
						fields := object.(map[string]interface{})
 | 
				
			||||||
 | 
						if fields == nil {
 | 
				
			||||||
 | 
							return schema.GroupVersionKind{}, errors.New("invalid object to validate")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						apiVersion := fields["apiVersion"]
 | 
				
			||||||
 | 
						if apiVersion == nil {
 | 
				
			||||||
 | 
							return schema.GroupVersionKind{}, errors.New("apiVersion not set")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if _, ok := apiVersion.(string); !ok {
 | 
				
			||||||
 | 
							return schema.GroupVersionKind{}, errors.New("apiVersion isn't string type")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						version := apiutil.GetVersion(apiVersion.(string))
 | 
				
			||||||
 | 
						kind := fields["kind"]
 | 
				
			||||||
 | 
						if kind == nil {
 | 
				
			||||||
 | 
							return schema.GroupVersionKind{}, errors.New("kind not set")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if _, ok := kind.(string); !ok {
 | 
				
			||||||
 | 
							return schema.GroupVersionKind{}, errors.New("kind isn't string type")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return schema.GroupVersionKind{Kind: kind.(string), Version: version}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					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 validation_test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						. "github.com/onsi/ginkgo"
 | 
				
			||||||
 | 
						. "github.com/onsi/ginkgo/config"
 | 
				
			||||||
 | 
						. "github.com/onsi/ginkgo/types"
 | 
				
			||||||
 | 
						. "github.com/onsi/gomega"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestOpenapi(t *testing.T) {
 | 
				
			||||||
 | 
						RegisterFailHandler(Fail)
 | 
				
			||||||
 | 
						RunSpecsWithDefaultAndCustomReporters(t, "Openapi Suite", []Reporter{newlineReporter{}})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Print a newline after the default newlineReporter due to issue
 | 
				
			||||||
 | 
					// https://github.com/jstemmer/go-junit-report/issues/31
 | 
				
			||||||
 | 
					type newlineReporter struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (newlineReporter) SpecSuiteWillBegin(config GinkgoConfigType, summary *SuiteSummary) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (newlineReporter) BeforeSuiteDidRun(setupSummary *SetupSummary) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (newlineReporter) AfterSuiteDidRun(setupSummary *SetupSummary) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (newlineReporter) SpecWillRun(specSummary *SpecSummary) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (newlineReporter) SpecDidComplete(specSummary *SpecSummary) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SpecSuiteDidEnd Prints a newline between "35 Passed | 0 Failed | 0 Pending | 0 Skipped" and "--- PASS:"
 | 
				
			||||||
 | 
					func (newlineReporter) SpecSuiteDidEnd(summary *SuiteSummary) { fmt.Printf("\n") }
 | 
				
			||||||
							
								
								
									
										222
									
								
								pkg/kubectl/cmd/util/openapi/validation/validation_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								pkg/kubectl/cmd/util/openapi/validation/validation_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,222 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					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 validation_test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						. "github.com/onsi/ginkgo"
 | 
				
			||||||
 | 
						. "github.com/onsi/gomega"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						utilerrors "k8s.io/apimachinery/pkg/util/errors"
 | 
				
			||||||
 | 
						"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"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var fakeSchema = tst.Fake{Path: filepath.Join("..", "..", "..", "..", "..", "..", "api", "openapi-spec", "swagger.json")}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ = Describe("resource validation using OpenAPI Schema", func() {
 | 
				
			||||||
 | 
						var validator *validation.SchemaValidation
 | 
				
			||||||
 | 
						BeforeEach(func() {
 | 
				
			||||||
 | 
							s, err := fakeSchema.OpenAPISchema()
 | 
				
			||||||
 | 
							Expect(err).To(BeNil())
 | 
				
			||||||
 | 
							resources, err := openapi.NewOpenAPIData(s)
 | 
				
			||||||
 | 
							Expect(err).To(BeNil())
 | 
				
			||||||
 | 
							validator = validation.NewSchemaValidation(resources)
 | 
				
			||||||
 | 
							Expect(validator).ToNot(BeNil())
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						It("validates a valid pod", func() {
 | 
				
			||||||
 | 
							err := validator.Validate([]byte(`
 | 
				
			||||||
 | 
					apiVersion: v1
 | 
				
			||||||
 | 
					kind: Pod
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  labels:
 | 
				
			||||||
 | 
					    name: redis-master
 | 
				
			||||||
 | 
					  name: name
 | 
				
			||||||
 | 
					spec:
 | 
				
			||||||
 | 
					  containers:
 | 
				
			||||||
 | 
					  - args:
 | 
				
			||||||
 | 
					    - this
 | 
				
			||||||
 | 
					    - is
 | 
				
			||||||
 | 
					    - an
 | 
				
			||||||
 | 
					    - ok
 | 
				
			||||||
 | 
					    - command
 | 
				
			||||||
 | 
					    image: gcr.io/fake_project/fake_image:fake_tag
 | 
				
			||||||
 | 
					    name: master
 | 
				
			||||||
 | 
					`))
 | 
				
			||||||
 | 
							Expect(err).To(BeNil())
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						It("finds invalid command (string instead of []string) in Json Pod", func() {
 | 
				
			||||||
 | 
							err := validator.Validate([]byte(`
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "kind": "Pod",
 | 
				
			||||||
 | 
					  "apiVersion": "v1",
 | 
				
			||||||
 | 
					  "metadata": {
 | 
				
			||||||
 | 
					    "name": "name",
 | 
				
			||||||
 | 
					    "labels": {
 | 
				
			||||||
 | 
					      "name": "redis-master"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "spec": {
 | 
				
			||||||
 | 
					    "containers": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "name": "master",
 | 
				
			||||||
 | 
						"image": "gcr.io/fake_project/fake_image:fake_tag",
 | 
				
			||||||
 | 
					        "args": "this is a bad command"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					`))
 | 
				
			||||||
 | 
							Expect(err).To(Equal(utilerrors.NewAggregate([]error{
 | 
				
			||||||
 | 
								validation.ValidationError{
 | 
				
			||||||
 | 
									Path: "Pod.spec.containers[0].args",
 | 
				
			||||||
 | 
									Err: validation.InvalidTypeError{
 | 
				
			||||||
 | 
										Path:     "io.k8s.api.core.v1.Container.args",
 | 
				
			||||||
 | 
										Expected: "array",
 | 
				
			||||||
 | 
										Actual:   "string",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							})))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						It("fails because hostPort is string instead of int", func() {
 | 
				
			||||||
 | 
							err := validator.Validate([]byte(`
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "kind": "Pod",
 | 
				
			||||||
 | 
					  "apiVersion": "v1",
 | 
				
			||||||
 | 
					  "metadata": {
 | 
				
			||||||
 | 
					    "name": "apache-php",
 | 
				
			||||||
 | 
					    "labels": {
 | 
				
			||||||
 | 
					      "name": "apache-php"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "spec": {
 | 
				
			||||||
 | 
					    "volumes": [{
 | 
				
			||||||
 | 
					        "name": "shared-disk"
 | 
				
			||||||
 | 
					    }],
 | 
				
			||||||
 | 
					    "containers": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "name": "apache-php",
 | 
				
			||||||
 | 
					        "image": "gcr.io/fake_project/fake_image:fake_tag",
 | 
				
			||||||
 | 
					        "ports": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "name": "apache",
 | 
				
			||||||
 | 
					            "hostPort": "13380",
 | 
				
			||||||
 | 
					            "containerPort": 80,
 | 
				
			||||||
 | 
					            "protocol": "TCP"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "volumeMounts": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "name": "shared-disk",
 | 
				
			||||||
 | 
					            "mountPath": "/var/www/html"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					`))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Expect(err).To(Equal(utilerrors.NewAggregate([]error{
 | 
				
			||||||
 | 
								validation.ValidationError{
 | 
				
			||||||
 | 
									Path: "Pod.spec.containers[0].ports[0].hostPort",
 | 
				
			||||||
 | 
									Err: validation.InvalidTypeError{
 | 
				
			||||||
 | 
										Path:     "io.k8s.api.core.v1.ContainerPort.hostPort",
 | 
				
			||||||
 | 
										Expected: "integer",
 | 
				
			||||||
 | 
										Actual:   "string",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							})))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						It("fails because volume is not an array of object", func() {
 | 
				
			||||||
 | 
							err := validator.Validate([]byte(`
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "kind": "Pod",
 | 
				
			||||||
 | 
					  "apiVersion": "v1",
 | 
				
			||||||
 | 
					  "metadata": {
 | 
				
			||||||
 | 
					    "name": "apache-php",
 | 
				
			||||||
 | 
					    "labels": {
 | 
				
			||||||
 | 
					      "name": "apache-php"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "spec": {
 | 
				
			||||||
 | 
					    "volumes": [
 | 
				
			||||||
 | 
					        "name": "shared-disk"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "containers": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "name": "apache-php",
 | 
				
			||||||
 | 
						"image": "gcr.io/fake_project/fake_image:fake_tag",
 | 
				
			||||||
 | 
					        "ports": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "name": "apache",
 | 
				
			||||||
 | 
					            "hostPort": 13380,
 | 
				
			||||||
 | 
					            "containerPort": 80,
 | 
				
			||||||
 | 
					            "protocol": "TCP"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "volumeMounts": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "name": "shared-disk",
 | 
				
			||||||
 | 
					            "mountPath": "/var/www/html"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					`))
 | 
				
			||||||
 | 
							Expect(err.Error()).To(Equal("invalid character ':' after array element"))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						It("fails because some string lists have empty strings", func() {
 | 
				
			||||||
 | 
							err := validator.Validate([]byte(`
 | 
				
			||||||
 | 
					apiVersion: v1
 | 
				
			||||||
 | 
					kind: Pod
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  labels:
 | 
				
			||||||
 | 
					    name: redis-master
 | 
				
			||||||
 | 
					  name: name
 | 
				
			||||||
 | 
					spec:
 | 
				
			||||||
 | 
					  containers:
 | 
				
			||||||
 | 
					  - image: gcr.io/fake_project/fake_image:fake_tag
 | 
				
			||||||
 | 
					    name: master
 | 
				
			||||||
 | 
					    args:
 | 
				
			||||||
 | 
					    -
 | 
				
			||||||
 | 
					    command:
 | 
				
			||||||
 | 
					    -
 | 
				
			||||||
 | 
					`))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Expect(err).To(Equal(utilerrors.NewAggregate([]error{
 | 
				
			||||||
 | 
								validation.InvalidObjectTypeError{
 | 
				
			||||||
 | 
									Path: "Pod.spec.containers[0].args[0]",
 | 
				
			||||||
 | 
									Type: "nil",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								validation.InvalidObjectTypeError{
 | 
				
			||||||
 | 
									Path: "Pod.spec.containers[0].command[0]",
 | 
				
			||||||
 | 
									Type: "nil",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							})))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
		Reference in New Issue
	
	Block a user