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:
Antoine Pelisse 2017-07-05 09:50:56 -07:00
parent eb735bfeb0
commit ba11c7370f
10 changed files with 838 additions and 8 deletions

View File

@ -64,6 +64,7 @@ filegroup(
srcs = [
":package-srcs",
"//pkg/kubectl/cmd/util/openapi/testing:all-srcs",
"//pkg/kubectl/cmd/util/openapi/validation:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -132,7 +132,8 @@ func NewOpenAPIData(doc *openapi_v2.Document) (Resources, error) {
// 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()})
path := NewPath(namedSchema.GetName())
schema, err := definitions.ParseSchema(namedSchema.GetValue(), &path)
if err != nil {
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() {
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 {
return nil, err
}

View File

@ -77,6 +77,10 @@ type Path struct {
key string
}
func NewPath(key string) Path {
return Path{key: key}
}
func (p *Path) Get() []string {
if p == nil {
return []string{}
@ -92,7 +96,23 @@ func (p *Path) Len() int {
}
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.

View File

@ -65,7 +65,7 @@ var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() {
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"}))
Expect(key.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.Deployment", ".kind"}))
})
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)
Expect(key).ToNot(BeNil())
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() {
@ -176,15 +176,43 @@ var _ = Describe("Reading authorization.k8s.io/v1/SubjectAccessReview from openA
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"}))
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"}))
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"}))
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"}))
})
})

View 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"],
)

View 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)
}

View 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()}
}

View 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
}

View File

@ -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") }

View 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",
},
})))
})
})