"description":"x-kubernetes-embedded-resource defines that the value is an embedded Kubernetes runtime.Object, with TypeMeta and ObjectMeta. The type must be object. It is allowed to further restrict the embedded object. kind, apiVersion and metadata are validated automatically. x-kubernetes-preserve-unknown-fields is allowed to be true, but does not have to be if the object is fully specified (up to kind, apiVersion, metadata).",
"type":"boolean"
},
"x-kubernetes-int-or-string":{
"description":"x-kubernetes-int-or-string specifies that this value is either an integer or a string. If this is true, an empty type is allowed and type as child of anyOf is permitted if following one of the following patterns:\n\n1) anyOf:\n - type: integer\n - type: string\n2) allOf:\n - anyOf:\n - type: integer\n - type: string\n - ... zero or more",
"type":"boolean"
},
"x-kubernetes-preserve-unknown-fields":{
"description":"x-kubernetes-preserve-unknown-fields stops the API server decoding step from pruning fields which are not specified in the validation schema. This affects fields recursively, but switches back to normal pruning behaviour if nested properties or additionalProperties are specified in the schema.",
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.
*/
packageschema
import(
"reflect"
"k8s.io/apimachinery/pkg/util/validation/field"
)
varintOrStringAnyOf=[]NestedValueValidation{
{ForbiddenGenerics:Generic{
Type:"integer",
}},
{ForbiddenGenerics:Generic{
Type:"string",
}},
}
typelevelint
const(
rootLevellevel=iota
itemLevel
fieldLevel
)
// ValidateStructural checks that s is a structural schema with the invariants:
//
// * structurality: both `ForbiddenGenerics` and `ForbiddenExtensions` only have zero values, with the two exceptions for IntOrString.
// * RawExtension: for every schema with `x-kubernetes-embedded-resource: true`, `x-kubernetes-preserve-unknown-fields: true` and `type: object` are set
// * IntOrString: for `x-kubernetes-int-or-string: true` either `type` is empty under `anyOf` and `allOf` or the schema structure is one of these:
//
// 1) anyOf:
// - type: integer
// - type: string
// 2) allOf:
// - anyOf:
// - type: integer
// - type: string
// - ... zero or more
//
// * every specified field or array in s is also specified outside of value validation.
// * additionalProperties at the root is not allowed.
allErrs=append(allErrs,field.Required(fldPath.Child("properties"),"must not be empty if x-kubernetes-embedded-resource is true without x-kubernetes-preserve-unknown-fields"))
}
returnallErrs
}
// validateGeneric checks the generic fields of a structural schema.
allErrs=append(allErrs,field.Invalid(fldPath.Child("x-kubernetes-preserve-unknown-fields"),x.XPreserveUnknownFields,"must be false if x-kubernetes-int-or-string is true"))
}
ifx.XIntOrString&&x.XEmbeddedResource{
allErrs=append(allErrs,field.Invalid(fldPath.Child("x-kubernetes-embedded-resource"),x.XEmbeddedResource,"must be false if x-kubernetes-int-or-string is true"))
}
returnallErrs
}
// validateValueValidation checks the value validation in a structural schema.
t.Fatalf("unexpected error waiting for NonStructuralSchema condition: %v",cond)
}
ifv:="spec.validation.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields";!strings.Contains(cond.Message,v){
t.Fatalf("expected violation %q, but got: %v",v,cond.Message)
t.Fatalf("unexpected error waiting for NonStructuralSchema condition: %v",cond)
}
ifv:="spec.validation.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields";!strings.Contains(cond.Message,v){
t.Fatalf("expected violation %q, but got: %v",v,cond.Message)
desc:"int-or-string and preserve-unknown-fields true",
globalSchema:`
x-kubernetes-preserve-unknown-fields: true
x-kubernetes-int-or-string: true
`,
expectedViolations:[]string{
"spec.validation.openAPIV3Schema.x-kubernetes-preserve-unknown-fields: Invalid value: true: must be false if x-kubernetes-int-or-string is true",
},
},
{
desc:"int-or-string and embedded-resource true",
globalSchema:`
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-int-or-string: true
`,
expectedViolations:[]string{
"spec.validation.openAPIV3Schema.x-kubernetes-embedded-resource: Invalid value: true: must be false if x-kubernetes-int-or-string is true",
},
},
{
desc:"embedded-resource without preserve-unknown-fields",
globalSchema:`
type: object
x-kubernetes-embedded-resource: true
`,
expectedViolations:[]string{
"spec.validation.openAPIV3Schema.properties: Required value: must not be empty if x-kubernetes-embedded-resource is true without x-kubernetes-preserve-unknown-fields",
},
},
{
desc:"embedded-resource without preserve-unknown-fields, but properties",
globalSchema:`
type: object
x-kubernetes-embedded-resource: true
properties:
apiVersion:
type: string
kind:
type: string
metadata:
type: object
`,
expectedViolations:[]string{},
},
{
desc:"embedded-resource with preserve-unknown-fields",
globalSchema:`
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
`,
expectedViolations:[]string{},
},
{
desc:"embedded-resource with wrong type",
globalSchema:`
type: array
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
`,
expectedViolations:[]string{
"spec.validation.openAPIV3Schema.type: Invalid value: \"array\": must be object if x-kubernetes-embedded-resource is true",
},
},
{
desc:"embedded-resource with empty type",
globalSchema:`
type: ""
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
`,
expectedViolations:[]string{
"spec.validation.openAPIV3Schema.type: Required value: must be object if x-kubernetes-embedded-resource is true",
},
},
{
desc:"no top-level type",
globalSchema:`
type: ""
`,
expectedViolations:[]string{
"spec.validation.openAPIV3Schema.type: Required value: must not be empty at the root",
},
},
{
desc:"non-object top-level type",
globalSchema:`
type: "integer"
`,
expectedViolations:[]string{
"spec.validation.openAPIV3Schema.type: Invalid value: \"integer\": must be object at the root",
},
},
{
desc:"forbidden in nested value validation",
globalSchema:`
type: object
properties:
foo:
type: string
not:
type: string
additionalProperties: true
title: hello
description: world
nullable: true
allOf:
- properties:
foo:
type: string
additionalProperties: true
title: hello
description: world
nullable: true
anyOf:
- items:
type: string
additionalProperties: true
title: hello
description: world
nullable: true
oneOf:
- properties:
foo:
type: string
additionalProperties: true
title: hello
description: world
nullable: true
`,
expectedViolations:[]string{
"spec.validation.openAPIV3Schema.anyOf[0].items.type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.anyOf[0].items.additionalProperties: Forbidden: must be undefined to be structural",
"spec.validation.openAPIV3Schema.anyOf[0].items.title: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.anyOf[0].items.description: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.anyOf[0].items.nullable: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.allOf[0].properties[foo].type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.allOf[0].properties[foo].additionalProperties: Forbidden: must be undefined to be structural",
"spec.validation.openAPIV3Schema.allOf[0].properties[foo].title: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.allOf[0].properties[foo].description: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.allOf[0].properties[foo].nullable: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.oneOf[0].properties[foo].type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.oneOf[0].properties[foo].additionalProperties: Forbidden: must be undefined to be structural",
"spec.validation.openAPIV3Schema.oneOf[0].properties[foo].title: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.oneOf[0].properties[foo].description: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.oneOf[0].properties[foo].nullable: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.not.type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.not.additionalProperties: Forbidden: must be undefined to be structural",
"spec.validation.openAPIV3Schema.not.title: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.not.description: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.not.nullable: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.items: Required value: because it is defined in spec.validation.openAPIV3Schema.anyOf[0].items",
},
unexpectedViolations:[]string{
"spec.validation.openAPIV3Schema.not.default",
},
},
{
desc:"forbidden vendor extensions in nested value validation",
globalSchema:`
type: object
properties:
int-or-string:
x-kubernetes-int-or-string: true
embedded-resource:
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
not:
properties:
int-or-string:
x-kubernetes-int-or-string: true
embedded-resource:
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
allOf:
- properties:
int-or-string:
x-kubernetes-int-or-string: true
embedded-resource:
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
anyOf:
- properties:
int-or-string:
x-kubernetes-int-or-string: true
embedded-resource:
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
oneOf:
- properties:
int-or-string:
x-kubernetes-int-or-string: true
embedded-resource:
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
`,
expectedViolations:[]string{
"spec.validation.openAPIV3Schema.allOf[0].properties[embedded-resource].x-kubernetes-preserve-unknown-fields: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.allOf[0].properties[embedded-resource].x-kubernetes-embedded-resource: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.allOf[0].properties[int-or-string].x-kubernetes-int-or-string: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.anyOf[0].properties[embedded-resource].x-kubernetes-preserve-unknown-fields: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.anyOf[0].properties[embedded-resource].x-kubernetes-embedded-resource: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.anyOf[0].properties[int-or-string].x-kubernetes-int-or-string: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.oneOf[0].properties[embedded-resource].x-kubernetes-preserve-unknown-fields: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.oneOf[0].properties[embedded-resource].x-kubernetes-embedded-resource: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.oneOf[0].properties[int-or-string].x-kubernetes-int-or-string: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.not.properties[embedded-resource].x-kubernetes-preserve-unknown-fields: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.not.properties[embedded-resource].x-kubernetes-embedded-resource: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.not.properties[int-or-string].x-kubernetes-int-or-string: Forbidden: must be false to be structural",
},
},
{
desc:"missing types",
globalSchema:`
properties:
foo:
properties:
a: {}
bar:
items:
additionalProperties:
properties:
a: {}
items: {}
abc:
additionalProperties:
properties:
a:
items:
additionalProperties:
items:
json:
x-kubernetes-preserve-unknown-fields: true
properties:
a: {}
int-or-string:
x-kubernetes-int-or-string: true
properties:
a: {}
`,
expectedViolations:[]string{
"spec.validation.openAPIV3Schema.properties[foo].properties[a].type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[foo].type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[int-or-string].properties[a].type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[json].properties[a].type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[abc].additionalProperties.properties[a].items.additionalProperties.type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[abc].additionalProperties.properties[a].items.type: Required value: must not be empty for specified array items",
"spec.validation.openAPIV3Schema.properties[abc].additionalProperties.properties[a].type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[abc].additionalProperties.type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[abc].type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[bar].items.additionalProperties.items.type: Required value: must not be empty for specified array items",
"spec.validation.openAPIV3Schema.properties[bar].items.additionalProperties.properties[a].type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[bar].items.additionalProperties.type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[bar].items.type: Required value: must not be empty for specified array items",
"spec.validation.openAPIV3Schema.properties[bar].type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.type: Required value: must not be empty at the root",
},
},
{
desc:"int-or-string variants",
globalSchema:`
type: object
properties:
a:
x-kubernetes-int-or-string: true
b:
x-kubernetes-int-or-string: true
anyOf:
- type: integer
- type: string
allOf:
- pattern: abc
c:
x-kubernetes-int-or-string: true
allOf:
- anyOf:
- type: integer
- type: string
- pattern: abc
- pattern: abc
d:
x-kubernetes-int-or-string: true
anyOf:
- type: integer
- type: string
pattern: abc
e:
x-kubernetes-int-or-string: true
allOf:
- anyOf:
- type: integer
- type: string
pattern: abc
- pattern: abc
f:
x-kubernetes-int-or-string: true
anyOf:
- type: integer
- type: string
- pattern: abc
g:
x-kubernetes-int-or-string: true
anyOf:
- type: string
- type: integer
`,
expectedViolations:[]string{
"spec.validation.openAPIV3Schema.properties[d].anyOf[0].type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.properties[d].anyOf[1].type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.properties[e].allOf[0].anyOf[0].type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.properties[e].allOf[0].anyOf[1].type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.properties[f].anyOf[0].type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.properties[f].anyOf[1].type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.properties[g].anyOf[0].type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.properties[g].anyOf[1].type: Forbidden: must be empty to be structural",
},
unexpectedViolations:[]string{
"spec.validation.openAPIV3Schema.properties[a]",
"spec.validation.openAPIV3Schema.properties[b]",
"spec.validation.openAPIV3Schema.properties[c]",
},
},
{
desc:"forbidden additionalProperties at the root",
globalSchema:`
type: object
additionalProperties: false
`,
expectedViolations:[]string{
"spec.validation.openAPIV3Schema.additionalProperties: Forbidden: must not be used at the root",
},
},
{
desc:"structural incomplete",
globalSchema:`
type: object
properties:
b:
type: object
properties:
b:
type: array
c:
type: array
items:
type: object
d:
type: array
not:
properties:
a: {}
b:
not:
properties:
a: {}
b:
items: {}
c:
items:
not:
items:
properties:
a: {}
d:
items: {}
allOf:
- properties:
e: {}
anyOf:
- properties:
f: {}
oneOf:
- properties:
g: {}
`,
expectedViolations:[]string{
"spec.validation.openAPIV3Schema.properties[d].items: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[d].items",
"spec.validation.openAPIV3Schema.properties[a]: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[a]",
"spec.validation.openAPIV3Schema.properties[b].properties[a]: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[b].not.properties[a]",
"spec.validation.openAPIV3Schema.properties[b].properties[b].items: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[b].not.properties[b].items",
"spec.validation.openAPIV3Schema.properties[c].items.items: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[c].items.not.items",
"spec.validation.openAPIV3Schema.properties[e]: Required value: because it is defined in spec.validation.openAPIV3Schema.allOf[0].properties[e]",
"spec.validation.openAPIV3Schema.properties[f]: Required value: because it is defined in spec.validation.openAPIV3Schema.anyOf[0].properties[f]",
"spec.validation.openAPIV3Schema.properties[g]: Required value: because it is defined in spec.validation.openAPIV3Schema.oneOf[0].properties[g]",
},
},
{
desc:"structural complete",
globalSchema:`
type: object
properties:
a:
type: string
b:
type: object
properties:
a:
type: string
b:
type: array
items:
type: string
c:
type: array
items:
type: array
items:
type: object
properties:
a:
type: string
d:
type: array
items:
type: string
e:
type: string
f:
type: string
g:
type: string
not:
properties:
a: {}
b:
not:
properties:
a: {}
b:
items: {}
c:
items:
not:
items:
properties:
a: {}
d:
items: {}
allOf:
- properties:
e: {}
anyOf:
- properties:
f: {}
oneOf:
- properties:
g: {}
`,
expectedViolations:nil,
},
{
desc:"invalid v1beta1 schema",
v1beta1Schema:`
type: object
properties:
a: {}
not:
properties:
b: {}
`,
v1Schema:`
type: object
properties:
a:
type: string
`,
expectedViolations:[]string{
"spec.version[v1beta1].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields",
"spec.version[v1beta1].schema.openAPIV3Schema.properties[b]: Required value: because it is defined in spec.version[v1beta1].schema.openAPIV3Schema.not.properties[b]",
},
},
{
desc:"invalid v1beta1 and v1 schemas",
v1beta1Schema:`
type: object
properties:
a: {}
not:
properties:
b: {}
`,
v1Schema:`
type: object
properties:
c: {}
not:
properties:
d: {}
`,
expectedViolations:[]string{
"spec.version[v1beta1].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields",
"spec.version[v1beta1].schema.openAPIV3Schema.properties[b]: Required value: because it is defined in spec.version[v1beta1].schema.openAPIV3Schema.not.properties[b]",
"spec.version[v1].schema.openAPIV3Schema.properties[c].type: Required value: must not be empty for specified object fields",
"spec.version[v1].schema.openAPIV3Schema.properties[d]: Required value: because it is defined in spec.version[v1].schema.openAPIV3Schema.not.properties[d]",