Merge pull request #77207 from sttts/sttts-structural-schema

apiextensions: implement structural schema condition
This commit is contained in:
Kubernetes Prow Robot
2019-05-09 15:48:10 -07:00
committed by GitHub
26 changed files with 2640 additions and 177 deletions

View File

@@ -30,6 +30,9 @@ API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiexten
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,JSON,Raw
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,JSONSchemaProps,Ref
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,JSONSchemaProps,Schema
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,JSONSchemaProps,XEmbeddedResource
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,JSONSchemaProps,XIntOrString
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,JSONSchemaProps,XPreserveUnknownFields
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,JSONSchemaPropsOrArray,JSONSchemas
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,JSONSchemaPropsOrArray,Schema
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,JSONSchemaPropsOrBool,Allows

View File

@@ -16808,6 +16808,18 @@
},
"uniqueItems": {
"type": "boolean"
},
"x-kubernetes-embedded-resource": {
"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.",
"type": "boolean"
}
},
"type": "object"

View File

@@ -47,6 +47,7 @@ filegroup(
"//staging/src/k8s.io/apiextensions-apiserver/pkg/cmd/server:all-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/controller/establish:all-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/controller/finalizer:all-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema:all-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi:all-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status:all-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/crdserverscheme:all-srcs",

View File

@@ -264,6 +264,22 @@ const (
// NamesAccepted means the names chosen for this CustomResourceDefinition do not conflict with others in
// the group and are therefore accepted.
NamesAccepted CustomResourceDefinitionConditionType = "NamesAccepted"
// NonStructuralSchema means that one or more OpenAPI schema is not structural.
//
// A schema is structural if it specifies types for all values, with the only exceptions of those with
// - x-kubernetes-int-or-string: true — for fields which can be integer or string
// - x-kubernetes-preserve-unknown-fields: true — for raw, unspecified JSON values
// and there is no type, additionalProperties, default, nullable or x-kubernetes-* vendor extenions
// specified under allOf, anyOf, oneOf or not.
//
// Non-structural schemas will not be allowed anymore in v1 API groups. Moreover, new features will not be
// available for non-structural CRDs:
// - pruning
// - defaulting
// - read-only
// - OpenAPI publishing
// - webhook conversion
NonStructuralSchema CustomResourceDefinitionConditionType = "NonStructuralSchema"
// Terminating means that the CustomResourceDefinition has been deleted and is cleaning up.
Terminating CustomResourceDefinitionConditionType = "Terminating"
)

View File

@@ -55,6 +55,36 @@ type JSONSchemaProps struct {
Definitions JSONSchemaDefinitions
ExternalDocs *ExternalDocumentation
Example *JSON
// 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.
XPreserveUnknownFields bool
// 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. Both ObjectMeta and TypeMeta
// are validated automatically. x-kubernetes-preserve-unknown-fields
// must be true.
XEmbeddedResource bool
// 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:
//
// 1) anyOf:
// - type: integer
// - type: string
// 2) allOf:
// - anyOf:
// - type: integer
// - type: string
// - ... zero or more
XIntOrString bool
}
// JSON represents any valid JSON value.

View File

@@ -1431,6 +1431,36 @@ func (m *JSONSchemaProps) MarshalTo(dAtA []byte) (int, error) {
dAtA[i] = 0
}
i++
dAtA[i] = 0xb0
i++
dAtA[i] = 0x2
i++
if m.XPreserveUnknownFields {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i++
dAtA[i] = 0xb8
i++
dAtA[i] = 0x2
i++
if m.XEmbeddedResource {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i++
dAtA[i] = 0xc0
i++
dAtA[i] = 0x2
i++
if m.XIntOrString {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i++
return i, nil
}
@@ -2080,6 +2110,9 @@ func (m *JSONSchemaProps) Size() (n int) {
n += 2 + l + sovGenerated(uint64(l))
}
n += 3
n += 3
n += 3
n += 3
return n
}
@@ -2478,6 +2511,9 @@ func (this *JSONSchemaProps) String() string {
`ExternalDocs:` + strings.Replace(fmt.Sprintf("%v", this.ExternalDocs), "ExternalDocumentation", "ExternalDocumentation", 1) + `,`,
`Example:` + strings.Replace(fmt.Sprintf("%v", this.Example), "JSON", "JSON", 1) + `,`,
`Nullable:` + fmt.Sprintf("%v", this.Nullable) + `,`,
`XPreserveUnknownFields:` + fmt.Sprintf("%v", this.XPreserveUnknownFields) + `,`,
`XEmbeddedResource:` + fmt.Sprintf("%v", this.XEmbeddedResource) + `,`,
`XIntOrString:` + fmt.Sprintf("%v", this.XIntOrString) + `,`,
`}`,
}, "")
return s
@@ -6603,6 +6639,66 @@ func (m *JSONSchemaProps) Unmarshal(dAtA []byte) error {
}
}
m.Nullable = bool(v != 0)
case 38:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field XPreserveUnknownFields", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
m.XPreserveUnknownFields = bool(v != 0)
case 39:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field XEmbeddedResource", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
m.XEmbeddedResource = bool(v != 0)
case 40:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field XIntOrString", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
m.XIntOrString = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
@@ -7365,180 +7461,186 @@ func init() {
}
var fileDescriptorGenerated = []byte{
// 2796 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5a, 0x4b, 0x6f, 0x23, 0xc7,
0xf1, 0xdf, 0x21, 0x45, 0x89, 0x6a, 0x49, 0x2b, 0xa9, 0xd7, 0x5a, 0xcf, 0xea, 0x2f, 0x93, 0x12,
0xfd, 0xb7, 0xa1, 0x38, 0xbb, 0x94, 0xbd, 0xb1, 0x63, 0xc7, 0x40, 0x0e, 0x22, 0x25, 0x1b, 0x72,
0x56, 0x8f, 0x34, 0x77, 0x6d, 0x27, 0x7e, 0xb6, 0x86, 0x4d, 0x6a, 0x56, 0xf3, 0xda, 0xe9, 0x19,
0x4a, 0x82, 0x93, 0x20, 0x0f, 0x18, 0x09, 0x82, 0x24, 0x0e, 0xe2, 0xbd, 0x04, 0x49, 0x0e, 0x4e,
0x90, 0x4b, 0x0e, 0xc9, 0x21, 0xb9, 0x25, 0x1f, 0x60, 0x8f, 0x46, 0x4e, 0x46, 0x10, 0x10, 0x59,
0xfa, 0x2b, 0x04, 0x08, 0xa0, 0x53, 0xd0, 0x8f, 0xe9, 0x19, 0x0e, 0xc9, 0x5d, 0xc1, 0x4b, 0x7a,
0x73, 0xd3, 0x54, 0x55, 0xd7, 0xaf, 0xba, 0xba, 0xaa, 0xba, 0xba, 0x28, 0xd0, 0x38, 0x7c, 0x81,
0x96, 0x4d, 0x77, 0xed, 0x30, 0xdc, 0x27, 0xbe, 0x43, 0x02, 0x42, 0xd7, 0x5a, 0xc4, 0xa9, 0xbb,
0xfe, 0x9a, 0x64, 0x60, 0xcf, 0x24, 0xc7, 0x01, 0x71, 0xa8, 0xe9, 0x3a, 0xf4, 0x0a, 0xf6, 0x4c,
0x4a, 0xfc, 0x16, 0xf1, 0xd7, 0xbc, 0xc3, 0x26, 0xe3, 0xd1, 0x6e, 0x81, 0xb5, 0xd6, 0x33, 0xfb,
0x24, 0xc0, 0xcf, 0xac, 0x35, 0x89, 0x43, 0x7c, 0x1c, 0x90, 0x7a, 0xd9, 0xf3, 0xdd, 0xc0, 0x85,
0x5f, 0x15, 0xea, 0xca, 0x5d, 0xd2, 0xef, 0x28, 0x75, 0x65, 0xef, 0xb0, 0xc9, 0x78, 0xb4, 0x5b,
0xa0, 0x2c, 0xd5, 0x2d, 0x5e, 0x69, 0x9a, 0xc1, 0x41, 0xb8, 0x5f, 0x36, 0x5c, 0x7b, 0xad, 0xe9,
0x36, 0xdd, 0x35, 0xae, 0x75, 0x3f, 0x6c, 0xf0, 0x2f, 0xfe, 0xc1, 0xff, 0x12, 0x68, 0x8b, 0xcf,
0xc6, 0xc6, 0xdb, 0xd8, 0x38, 0x30, 0x1d, 0xe2, 0x9f, 0xc4, 0x16, 0xdb, 0x24, 0xc0, 0x6b, 0xad,
0x1e, 0x1b, 0x17, 0xd7, 0x06, 0xad, 0xf2, 0x43, 0x27, 0x30, 0x6d, 0xd2, 0xb3, 0xe0, 0xcb, 0xf7,
0x5b, 0x40, 0x8d, 0x03, 0x62, 0xe3, 0xf4, 0xba, 0xd2, 0xa9, 0x06, 0xe6, 0xab, 0xae, 0xd3, 0x22,
0x3e, 0xdb, 0x25, 0x22, 0xb7, 0x42, 0x42, 0x03, 0x58, 0x01, 0xd9, 0xd0, 0xac, 0xeb, 0xda, 0xb2,
0xb6, 0x3a, 0x59, 0x79, 0xfa, 0x4e, 0xbb, 0x78, 0xae, 0xd3, 0x2e, 0x66, 0x6f, 0x6c, 0x6d, 0x9c,
0xb6, 0x8b, 0x2b, 0x83, 0x90, 0x82, 0x13, 0x8f, 0xd0, 0xf2, 0x8d, 0xad, 0x0d, 0xc4, 0x16, 0xc3,
0x97, 0xc1, 0x7c, 0x9d, 0x50, 0xd3, 0x27, 0xf5, 0xf5, 0xbd, 0xad, 0x57, 0x85, 0x7e, 0x3d, 0xc3,
0x35, 0x5e, 0x92, 0x1a, 0xe7, 0x37, 0xd2, 0x02, 0xa8, 0x77, 0x0d, 0x7c, 0x1d, 0x4c, 0xb8, 0xfb,
0x37, 0x89, 0x11, 0x50, 0x3d, 0xbb, 0x9c, 0x5d, 0x9d, 0xba, 0x7a, 0xa5, 0x1c, 0x9f, 0xa0, 0x32,
0x81, 0x1f, 0x9b, 0xdc, 0x6c, 0x19, 0xe1, 0xa3, 0xcd, 0xe8, 0xe4, 0x2a, 0xb3, 0x12, 0x6d, 0x62,
0x57, 0x68, 0x41, 0x91, 0xba, 0xd2, 0xef, 0x32, 0x00, 0x26, 0x37, 0x4f, 0x3d, 0xd7, 0xa1, 0x64,
0x28, 0xbb, 0xa7, 0x60, 0xce, 0xe0, 0x9a, 0x03, 0x52, 0x97, 0xb8, 0x7a, 0xe6, 0xb3, 0x58, 0xaf,
0x4b, 0xfc, 0xb9, 0x6a, 0x4a, 0x1d, 0xea, 0x01, 0x80, 0xd7, 0xc1, 0xb8, 0x4f, 0x68, 0x68, 0x05,
0x7a, 0x76, 0x59, 0x5b, 0x9d, 0xba, 0x7a, 0x79, 0x20, 0x14, 0x8f, 0x6f, 0x16, 0x7c, 0xe5, 0xd6,
0x33, 0xe5, 0x5a, 0x80, 0x83, 0x90, 0x56, 0xce, 0x4b, 0xa4, 0x71, 0xc4, 0x75, 0x20, 0xa9, 0xab,
0xf4, 0xa3, 0x0c, 0x98, 0x4b, 0x7a, 0xa9, 0x65, 0x92, 0x23, 0x78, 0x04, 0x26, 0x7c, 0x11, 0x2c,
0xdc, 0x4f, 0x53, 0x57, 0xf7, 0xca, 0x0f, 0x94, 0x56, 0xe5, 0x9e, 0x20, 0xac, 0x4c, 0xb1, 0x33,
0x93, 0x1f, 0x28, 0x42, 0x83, 0xef, 0x81, 0xbc, 0x2f, 0x0f, 0x8a, 0x47, 0xd3, 0xd4, 0xd5, 0xaf,
0x0f, 0x11, 0x59, 0x28, 0xae, 0x4c, 0x77, 0xda, 0xc5, 0x7c, 0xf4, 0x85, 0x14, 0x60, 0xe9, 0xc3,
0x0c, 0x28, 0x54, 0x43, 0x1a, 0xb8, 0x36, 0x22, 0xd4, 0x0d, 0x7d, 0x83, 0x54, 0x5d, 0x2b, 0xb4,
0x9d, 0x0d, 0xd2, 0x30, 0x1d, 0x33, 0x60, 0xd1, 0xba, 0x0c, 0xc6, 0x1c, 0x6c, 0x13, 0x19, 0x3d,
0xd3, 0xd2, 0xa7, 0x63, 0x3b, 0xd8, 0x26, 0x88, 0x73, 0x98, 0x04, 0x0b, 0x16, 0x99, 0x0b, 0x4a,
0xe2, 0xfa, 0x89, 0x47, 0x10, 0xe7, 0xc0, 0x27, 0xc1, 0x78, 0xc3, 0xf5, 0x6d, 0x2c, 0xce, 0x71,
0x32, 0x3e, 0x99, 0x97, 0x38, 0x15, 0x49, 0x2e, 0x7c, 0x0e, 0x4c, 0xd5, 0x09, 0x35, 0x7c, 0xd3,
0x63, 0xd0, 0xfa, 0x18, 0x17, 0xbe, 0x20, 0x85, 0xa7, 0x36, 0x62, 0x16, 0x4a, 0xca, 0xc1, 0xcb,
0x20, 0xef, 0xf9, 0xa6, 0xeb, 0x9b, 0xc1, 0x89, 0x9e, 0x5b, 0xd6, 0x56, 0x73, 0x95, 0x39, 0xb9,
0x26, 0xbf, 0x27, 0xe9, 0x48, 0x49, 0xc0, 0x65, 0x90, 0x7f, 0xa5, 0xb6, 0xbb, 0xb3, 0x87, 0x83,
0x03, 0x7d, 0x9c, 0x23, 0x8c, 0x31, 0x69, 0x94, 0xbf, 0x29, 0xa9, 0xa5, 0x7f, 0x66, 0x80, 0x9e,
0xf6, 0x4a, 0xe4, 0x52, 0xf8, 0x12, 0xc8, 0xd3, 0x80, 0x55, 0x9c, 0xe6, 0x89, 0xf4, 0xc9, 0x53,
0x11, 0x58, 0x4d, 0xd2, 0x4f, 0xdb, 0xc5, 0x8b, 0xf1, 0x8a, 0x88, 0xca, 0xfd, 0xa1, 0xd6, 0xc2,
0xdf, 0x68, 0xe0, 0xc2, 0x11, 0xd9, 0x3f, 0x70, 0xdd, 0xc3, 0xaa, 0x65, 0x12, 0x27, 0xa8, 0xba,
0x4e, 0xc3, 0x6c, 0xca, 0x18, 0x40, 0x0f, 0x18, 0x03, 0xaf, 0xf5, 0x6a, 0xae, 0x3c, 0xda, 0x69,
0x17, 0x2f, 0xf4, 0x61, 0xa0, 0x7e, 0x76, 0xc0, 0xd7, 0x81, 0x6e, 0xa4, 0x92, 0x44, 0x16, 0x30,
0x51, 0xb6, 0x26, 0x2b, 0x4b, 0x9d, 0x76, 0x51, 0xaf, 0x0e, 0x90, 0x41, 0x03, 0x57, 0x97, 0x7e,
0x90, 0x4d, 0xbb, 0x37, 0x11, 0x6e, 0xef, 0x82, 0x3c, 0x4b, 0xe3, 0x3a, 0x0e, 0xb0, 0x4c, 0xc4,
0xa7, 0xcf, 0x96, 0xf4, 0xa2, 0x66, 0x6c, 0x93, 0x00, 0x57, 0xa0, 0x3c, 0x10, 0x10, 0xd3, 0x90,
0xd2, 0x0a, 0xbf, 0x0d, 0xc6, 0xa8, 0x47, 0x0c, 0xe9, 0xe8, 0x37, 0x1e, 0x34, 0xd9, 0x06, 0x6c,
0xa4, 0xe6, 0x11, 0x23, 0xce, 0x05, 0xf6, 0x85, 0x38, 0x2c, 0x7c, 0x5f, 0x03, 0xe3, 0x94, 0x17,
0x28, 0x59, 0xd4, 0xde, 0x1a, 0x95, 0x05, 0xa9, 0x2a, 0x28, 0xbe, 0x91, 0x04, 0x2f, 0xfd, 0x3b,
0x03, 0x56, 0x06, 0x2d, 0xad, 0xba, 0x4e, 0x5d, 0x1c, 0xc7, 0x96, 0xcc, 0x6d, 0x11, 0xe9, 0xcf,
0x25, 0x73, 0xfb, 0xb4, 0x5d, 0x7c, 0xe2, 0xbe, 0x0a, 0x12, 0x45, 0xe0, 0x2b, 0x6a, 0xdf, 0xa2,
0x50, 0xac, 0x74, 0x1b, 0x76, 0xda, 0x2e, 0xce, 0xaa, 0x65, 0xdd, 0xb6, 0xc2, 0x16, 0x80, 0x16,
0xa6, 0xc1, 0x75, 0x1f, 0x3b, 0x54, 0xa8, 0x35, 0x6d, 0x22, 0xdd, 0xf7, 0xd4, 0xd9, 0xc2, 0x83,
0xad, 0xa8, 0x2c, 0x4a, 0x48, 0x78, 0xad, 0x47, 0x1b, 0xea, 0x83, 0xc0, 0xea, 0x96, 0x4f, 0x30,
0x55, 0xa5, 0x28, 0x71, 0xa3, 0x30, 0x2a, 0x92, 0x5c, 0xf8, 0x05, 0x30, 0x61, 0x13, 0x4a, 0x71,
0x93, 0xf0, 0xfa, 0x33, 0x19, 0x5f, 0xd1, 0xdb, 0x82, 0x8c, 0x22, 0x3e, 0xeb, 0x4f, 0x96, 0x06,
0x79, 0xed, 0x9a, 0x49, 0x03, 0xf8, 0x66, 0x4f, 0x02, 0x94, 0xcf, 0xb6, 0x43, 0xb6, 0x9a, 0x87,
0xbf, 0x2a, 0x7e, 0x11, 0x25, 0x11, 0xfc, 0xdf, 0x02, 0x39, 0x33, 0x20, 0x76, 0x74, 0x77, 0xbf,
0x36, 0xa2, 0xd8, 0xab, 0xcc, 0x48, 0x1b, 0x72, 0x5b, 0x0c, 0x0d, 0x09, 0xd0, 0xd2, 0xef, 0x33,
0xe0, 0xb1, 0x41, 0x4b, 0xd8, 0x85, 0x42, 0x99, 0xc7, 0x3d, 0x2b, 0xf4, 0xb1, 0x25, 0x23, 0x4e,
0x79, 0x7c, 0x8f, 0x53, 0x91, 0xe4, 0xb2, 0x92, 0x4f, 0x4d, 0xa7, 0x19, 0x5a, 0xd8, 0x97, 0xe1,
0xa4, 0x76, 0x5d, 0x93, 0x74, 0xa4, 0x24, 0x60, 0x19, 0x00, 0x7a, 0xe0, 0xfa, 0x01, 0xc7, 0x90,
0xd5, 0xeb, 0x3c, 0x2b, 0x10, 0x35, 0x45, 0x45, 0x09, 0x09, 0x76, 0xa3, 0x1d, 0x9a, 0x4e, 0x5d,
0x9e, 0xba, 0xca, 0xe2, 0xaf, 0x99, 0x4e, 0x1d, 0x71, 0x0e, 0xc3, 0xb7, 0x4c, 0x1a, 0x30, 0x8a,
0x3c, 0xf2, 0x2e, 0xaf, 0x73, 0x49, 0x25, 0xc1, 0xf0, 0x0d, 0x56, 0xf5, 0x5d, 0xdf, 0x24, 0x54,
0x1f, 0x8f, 0xf1, 0xab, 0x8a, 0x8a, 0x12, 0x12, 0xa5, 0x5f, 0xe5, 0x07, 0x07, 0x09, 0x2b, 0x25,
0xf0, 0x71, 0x90, 0x6b, 0xfa, 0x6e, 0xe8, 0x49, 0x2f, 0x29, 0x6f, 0xbf, 0xcc, 0x88, 0x48, 0xf0,
0x58, 0x54, 0xb6, 0xba, 0xda, 0x54, 0x15, 0x95, 0x51, 0x73, 0x1a, 0xf1, 0xe1, 0xf7, 0x34, 0x90,
0x73, 0xa4, 0x73, 0x58, 0xc8, 0xbd, 0x39, 0xa2, 0xb8, 0xe0, 0xee, 0x8d, 0xcd, 0x15, 0x9e, 0x17,
0xc8, 0xf0, 0x59, 0x90, 0xa3, 0x86, 0xeb, 0x11, 0xe9, 0xf5, 0x42, 0x24, 0x54, 0x63, 0xc4, 0xd3,
0x76, 0x71, 0x26, 0x52, 0xc7, 0x09, 0x48, 0x08, 0xc3, 0x1f, 0x6a, 0x00, 0xb4, 0xb0, 0x65, 0xd6,
0x31, 0x6f, 0x19, 0x72, 0xdc, 0xfc, 0xe1, 0x86, 0xf5, 0xab, 0x4a, 0xbd, 0x38, 0xb4, 0xf8, 0x1b,
0x25, 0xa0, 0xe1, 0x07, 0x1a, 0x98, 0xa6, 0xe1, 0xbe, 0x2f, 0x57, 0x51, 0xde, 0x5c, 0x4c, 0x5d,
0xfd, 0xc6, 0x50, 0x6d, 0xa9, 0x25, 0x00, 0x2a, 0x73, 0x9d, 0x76, 0x71, 0x3a, 0x49, 0x41, 0x5d,
0x06, 0xc0, 0x9f, 0x68, 0x20, 0xdf, 0x8a, 0xee, 0xec, 0x09, 0x9e, 0xf0, 0x6f, 0x8f, 0xe8, 0x60,
0x65, 0x44, 0xc5, 0x59, 0xa0, 0xfa, 0x00, 0x65, 0x01, 0xfc, 0xab, 0x06, 0x74, 0x5c, 0x17, 0x05,
0x1e, 0x5b, 0x7b, 0xbe, 0xe9, 0x04, 0xc4, 0x17, 0xfd, 0x26, 0xd5, 0xf3, 0xdc, 0xbc, 0xe1, 0xde,
0x85, 0xe9, 0x5e, 0xb6, 0xb2, 0x2c, 0xad, 0xd3, 0xd7, 0x07, 0x98, 0x81, 0x06, 0x1a, 0xc8, 0x03,
0x2d, 0x6e, 0x69, 0xf4, 0xc9, 0x11, 0x04, 0x5a, 0xdc, 0x4b, 0xc9, 0xea, 0x10, 0x77, 0x50, 0x09,
0xe8, 0xd2, 0x07, 0xd9, 0x74, 0xd3, 0x9e, 0xbe, 0xf4, 0xe1, 0x6d, 0x61, 0xac, 0xd8, 0x0a, 0xd5,
0x35, 0xee, 0xdc, 0x77, 0x47, 0x74, 0xf6, 0xea, 0xd6, 0x8e, 0x1b, 0x2f, 0x45, 0xa2, 0x28, 0x61,
0x07, 0xfc, 0xa5, 0x06, 0x66, 0xb0, 0x61, 0x10, 0x2f, 0x20, 0x75, 0x51, 0x8b, 0x33, 0x9f, 0x43,
0xb9, 0x59, 0x90, 0x56, 0xcd, 0xac, 0x27, 0xa1, 0x51, 0xb7, 0x25, 0xf0, 0x45, 0x70, 0x9e, 0x06,
0xae, 0x4f, 0xea, 0xa9, 0x2e, 0x17, 0x76, 0xda, 0xc5, 0xf3, 0xb5, 0x2e, 0x0e, 0x4a, 0x49, 0x96,
0x3e, 0x1d, 0x03, 0xc5, 0xfb, 0x64, 0xc6, 0x19, 0xde, 0x51, 0x4f, 0x82, 0x71, 0xbe, 0xdd, 0x3a,
0xf7, 0x4a, 0x3e, 0xd1, 0xb9, 0x71, 0x2a, 0x92, 0x5c, 0x56, 0xd7, 0x19, 0x3e, 0xeb, 0x36, 0xb2,
0x5c, 0x50, 0xd5, 0xf5, 0x9a, 0x20, 0xa3, 0x88, 0x0f, 0xdf, 0x03, 0xe3, 0x62, 0x4e, 0xc2, 0x8b,
0xea, 0x08, 0x0b, 0x23, 0xe0, 0x76, 0x72, 0x28, 0x24, 0x21, 0x7b, 0x0b, 0x62, 0xee, 0x61, 0x17,
0xc4, 0x7b, 0x56, 0xa0, 0xf1, 0xff, 0xf1, 0x0a, 0x54, 0xfa, 0x8f, 0x96, 0xce, 0xfb, 0xc4, 0x56,
0x6b, 0x06, 0xb6, 0x08, 0xdc, 0x00, 0x73, 0xec, 0x91, 0x81, 0x88, 0x67, 0x99, 0x06, 0xa6, 0xfc,
0x8d, 0x2b, 0x02, 0x4e, 0x8d, 0x5d, 0x6a, 0x29, 0x3e, 0xea, 0x59, 0x01, 0x5f, 0x01, 0x50, 0x34,
0xde, 0x5d, 0x7a, 0x44, 0x0f, 0xa1, 0x5a, 0xe8, 0x5a, 0x8f, 0x04, 0xea, 0xb3, 0x0a, 0x56, 0xc1,
0xbc, 0x85, 0xf7, 0x89, 0x55, 0x23, 0x16, 0x31, 0x02, 0xd7, 0xe7, 0xaa, 0xc4, 0x14, 0x60, 0xa1,
0xd3, 0x2e, 0xce, 0x5f, 0x4b, 0x33, 0x51, 0xaf, 0x7c, 0x69, 0x25, 0x9d, 0x5e, 0xc9, 0x8d, 0x8b,
0xe7, 0xcc, 0x47, 0x19, 0xb0, 0x38, 0x38, 0x32, 0xe0, 0xf7, 0xe3, 0x57, 0x97, 0x68, 0xaa, 0xdf,
0x1e, 0x55, 0x14, 0xca, 0x67, 0x17, 0xe8, 0x7d, 0x72, 0xc1, 0xef, 0xb0, 0x0e, 0x07, 0x5b, 0xd1,
0x9c, 0xe7, 0xad, 0x91, 0x99, 0xc0, 0x40, 0x2a, 0x93, 0xa2, 0x79, 0xc2, 0x16, 0xef, 0x95, 0xb0,
0x45, 0x4a, 0x7f, 0xd0, 0xd2, 0x0f, 0xef, 0x38, 0x83, 0xe1, 0x4f, 0x35, 0x30, 0xeb, 0x7a, 0xc4,
0x59, 0xdf, 0xdb, 0x7a, 0xf5, 0x4b, 0x22, 0x93, 0xa5, 0xab, 0x76, 0x1e, 0xd0, 0xce, 0x57, 0x6a,
0xbb, 0x3b, 0x42, 0xe1, 0x9e, 0xef, 0x7a, 0xb4, 0x72, 0xa1, 0xd3, 0x2e, 0xce, 0xee, 0x76, 0x43,
0xa1, 0x34, 0x76, 0xc9, 0x06, 0x0b, 0x9b, 0xc7, 0x01, 0xf1, 0x1d, 0x6c, 0x6d, 0xb8, 0x46, 0x68,
0x13, 0x27, 0x10, 0x86, 0xa6, 0x86, 0x44, 0xda, 0x19, 0x87, 0x44, 0x8f, 0x81, 0x6c, 0xe8, 0x5b,
0x32, 0x8a, 0xa7, 0xd4, 0x10, 0x14, 0x5d, 0x43, 0x8c, 0x5e, 0x5a, 0x01, 0x63, 0xcc, 0x4e, 0x78,
0x09, 0x64, 0x7d, 0x7c, 0xc4, 0xb5, 0x4e, 0x57, 0x26, 0x98, 0x08, 0xc2, 0x47, 0x88, 0xd1, 0x4a,
0xff, 0x58, 0x02, 0xb3, 0xa9, 0xbd, 0xc0, 0x45, 0x90, 0x51, 0x93, 0x55, 0x20, 0x95, 0x66, 0xb6,
0x36, 0x50, 0xc6, 0xac, 0xc3, 0xe7, 0x55, 0xf1, 0x15, 0xa0, 0x45, 0x55, 0xcf, 0x39, 0x95, 0xb5,
0xb4, 0xb1, 0x3a, 0x66, 0x48, 0x54, 0x38, 0x99, 0x0d, 0xa4, 0x21, 0xb3, 0x44, 0xd8, 0x40, 0x1a,
0x88, 0xd1, 0x3e, 0xeb, 0x84, 0x2c, 0x1a, 0xd1, 0xe5, 0xce, 0x30, 0xa2, 0x1b, 0xbf, 0xe7, 0x88,
0xee, 0x71, 0x90, 0x0b, 0xcc, 0xc0, 0x22, 0xfa, 0x44, 0xf7, 0xcb, 0xe3, 0x3a, 0x23, 0x22, 0xc1,
0x83, 0x37, 0xc1, 0x44, 0x9d, 0x34, 0x70, 0x68, 0x05, 0x7a, 0x9e, 0x87, 0x50, 0x75, 0x08, 0x21,
0x24, 0xe6, 0xa7, 0x1b, 0x42, 0x2f, 0x8a, 0x00, 0xe0, 0x13, 0x60, 0xc2, 0xc6, 0xc7, 0xa6, 0x1d,
0xda, 0xbc, 0x27, 0xd3, 0x84, 0xd8, 0xb6, 0x20, 0xa1, 0x88, 0xc7, 0x2a, 0x23, 0x39, 0x36, 0xac,
0x90, 0x9a, 0x2d, 0x22, 0x99, 0x3a, 0xe0, 0xb7, 0xa7, 0xaa, 0x8c, 0x9b, 0x29, 0x3e, 0xea, 0x59,
0xc1, 0xc1, 0x4c, 0x87, 0x2f, 0x9e, 0x4a, 0x80, 0x09, 0x12, 0x8a, 0x78, 0xdd, 0x60, 0x52, 0x7e,
0x7a, 0x10, 0x98, 0x5c, 0xdc, 0xb3, 0x02, 0x7e, 0x11, 0x4c, 0xda, 0xf8, 0xf8, 0x1a, 0x71, 0x9a,
0xc1, 0x81, 0x3e, 0xb3, 0xac, 0xad, 0x66, 0x2b, 0x33, 0x9d, 0x76, 0x71, 0x72, 0x3b, 0x22, 0xa2,
0x98, 0xcf, 0x85, 0x4d, 0x47, 0x0a, 0x9f, 0x4f, 0x08, 0x47, 0x44, 0x14, 0xf3, 0x59, 0x07, 0xe1,
0xe1, 0x80, 0x25, 0x97, 0x3e, 0xdb, 0xfd, 0x32, 0xdc, 0x13, 0x64, 0x14, 0xf1, 0xe1, 0x2a, 0xc8,
0xdb, 0xf8, 0x98, 0xbf, 0xe2, 0xf5, 0x39, 0xae, 0x96, 0xcf, 0x92, 0xb7, 0x25, 0x0d, 0x29, 0x2e,
0x97, 0x34, 0x1d, 0x21, 0x39, 0x9f, 0x90, 0x94, 0x34, 0xa4, 0xb8, 0x2c, 0x88, 0x43, 0xc7, 0xbc,
0x15, 0x12, 0x21, 0x0c, 0xb9, 0x67, 0x54, 0x10, 0xdf, 0x88, 0x59, 0x28, 0x29, 0xc7, 0x5e, 0xd1,
0x76, 0x68, 0x05, 0xa6, 0x67, 0x91, 0xdd, 0x86, 0x7e, 0x81, 0xfb, 0x9f, 0xf7, 0xc9, 0xdb, 0x8a,
0x8a, 0x12, 0x12, 0x90, 0x80, 0x31, 0xe2, 0x84, 0xb6, 0xfe, 0x08, 0xbf, 0xd8, 0x87, 0x12, 0x82,
0x2a, 0x73, 0x36, 0x9d, 0xd0, 0x46, 0x5c, 0x3d, 0x7c, 0x1e, 0xcc, 0xd8, 0xf8, 0x98, 0x95, 0x03,
0xe2, 0x07, 0xec, 0x7d, 0xbf, 0xc0, 0x37, 0x3f, 0xcf, 0x3a, 0xce, 0xed, 0x24, 0x03, 0x75, 0xcb,
0xf1, 0x85, 0xa6, 0x93, 0x58, 0x78, 0x31, 0xb1, 0x30, 0xc9, 0x40, 0xdd, 0x72, 0xcc, 0xd3, 0x3e,
0xb9, 0x15, 0x9a, 0x3e, 0xa9, 0xeb, 0x8f, 0xf2, 0x26, 0x55, 0xce, 0xf7, 0x05, 0x0d, 0x29, 0x2e,
0x6c, 0x45, 0xe3, 0x1e, 0x9d, 0xa7, 0xe1, 0x8d, 0xe1, 0x56, 0xf2, 0x5d, 0x7f, 0xdd, 0xf7, 0xf1,
0x89, 0xb8, 0x69, 0x92, 0x83, 0x1e, 0x48, 0x41, 0x0e, 0x5b, 0xd6, 0x6e, 0x43, 0xbf, 0xc4, 0x7d,
0x3f, 0xec, 0x1b, 0x44, 0x55, 0x9d, 0x75, 0x06, 0x82, 0x04, 0x16, 0x03, 0x75, 0x1d, 0x16, 0x1a,
0x8b, 0xa3, 0x05, 0xdd, 0x65, 0x20, 0x48, 0x60, 0xf1, 0x9d, 0x3a, 0x27, 0xbb, 0x0d, 0xfd, 0xff,
0x46, 0xbc, 0x53, 0x06, 0x82, 0x04, 0x16, 0x34, 0x41, 0xd6, 0x71, 0x03, 0x7d, 0x69, 0x24, 0xd7,
0x33, 0xbf, 0x70, 0x76, 0xdc, 0x00, 0x31, 0x0c, 0xf8, 0x0b, 0x0d, 0x00, 0x2f, 0x0e, 0xd1, 0xc7,
0x86, 0x32, 0x45, 0x48, 0x41, 0x96, 0xe3, 0xd8, 0xde, 0x74, 0x02, 0xff, 0x24, 0x7e, 0x47, 0x26,
0x72, 0x20, 0x61, 0x05, 0xfc, 0xad, 0x06, 0x1e, 0x49, 0xb6, 0xc9, 0xca, 0xbc, 0x02, 0xf7, 0xc8,
0xf5, 0x61, 0x87, 0x79, 0xc5, 0x75, 0xad, 0x8a, 0xde, 0x69, 0x17, 0x1f, 0x59, 0xef, 0x83, 0x8a,
0xfa, 0xda, 0x02, 0xff, 0xa8, 0x81, 0x79, 0x59, 0x45, 0x13, 0x16, 0x16, 0xb9, 0x03, 0xc9, 0xb0,
0x1d, 0x98, 0xc6, 0x11, 0x7e, 0x54, 0xbf, 0x4b, 0xf7, 0xf0, 0x51, 0xaf, 0x69, 0xf0, 0x2f, 0x1a,
0x98, 0xae, 0x13, 0x8f, 0x38, 0x75, 0xe2, 0x18, 0xcc, 0xd6, 0xe5, 0xa1, 0x8c, 0x0d, 0xd2, 0xb6,
0x6e, 0x24, 0x20, 0x84, 0x99, 0x65, 0x69, 0xe6, 0x74, 0x92, 0x75, 0xda, 0x2e, 0x5e, 0x8c, 0x97,
0x26, 0x39, 0xa8, 0xcb, 0x4a, 0xf8, 0xa1, 0x06, 0x66, 0xe3, 0x03, 0x10, 0x57, 0xca, 0xca, 0x08,
0xe3, 0x80, 0xb7, 0xaf, 0xeb, 0xdd, 0x80, 0x28, 0x6d, 0x01, 0xfc, 0x93, 0xc6, 0x3a, 0xb5, 0xe8,
0xdd, 0x47, 0xf5, 0x12, 0xf7, 0xe5, 0x3b, 0x43, 0xf7, 0xa5, 0x42, 0x10, 0xae, 0xbc, 0x1c, 0xb7,
0x82, 0x8a, 0x73, 0xda, 0x2e, 0x2e, 0x24, 0x3d, 0xa9, 0x18, 0x28, 0x69, 0x21, 0xfc, 0xb1, 0x06,
0xa6, 0x49, 0xdc, 0x71, 0x53, 0xfd, 0xf1, 0xa1, 0x38, 0xb1, 0x6f, 0x13, 0x2f, 0x5e, 0xea, 0x09,
0x16, 0x45, 0x5d, 0xd8, 0xac, 0x83, 0x24, 0xc7, 0xd8, 0xf6, 0x2c, 0xa2, 0xff, 0xff, 0x90, 0x3b,
0xc8, 0x4d, 0xa1, 0x17, 0x45, 0x00, 0xf0, 0x32, 0xc8, 0x3b, 0xa1, 0x65, 0xe1, 0x7d, 0x8b, 0xe8,
0x4f, 0xf0, 0x5e, 0x44, 0x4d, 0x31, 0x77, 0x24, 0x1d, 0x29, 0x89, 0x45, 0xf6, 0x4e, 0x4a, 0xe5,
0x19, 0x9c, 0x03, 0xd9, 0x43, 0x22, 0x7f, 0x0e, 0x46, 0xec, 0x4f, 0x58, 0x07, 0xb9, 0x16, 0xb6,
0xc2, 0xe8, 0xa9, 0x37, 0xe4, 0x1a, 0x8d, 0x84, 0xf2, 0x17, 0x33, 0x2f, 0x68, 0x8b, 0xb7, 0x35,
0x70, 0xb1, 0x7f, 0xfa, 0x3f, 0x54, 0xb3, 0x7e, 0xad, 0x81, 0xf9, 0x9e, 0x4c, 0xef, 0x63, 0xd1,
0xad, 0x6e, 0x8b, 0xde, 0x18, 0x76, 0xca, 0xd6, 0x02, 0xdf, 0x74, 0x9a, 0xbc, 0x4f, 0x49, 0x9a,
0xf7, 0x33, 0x0d, 0xcc, 0xa5, 0x93, 0xe7, 0x61, 0xfa, 0xab, 0x74, 0x3b, 0x03, 0x2e, 0xf6, 0x6f,
0xaf, 0xa0, 0xaf, 0xde, 0x91, 0xa3, 0x79, 0x8f, 0xf7, 0x9b, 0xdd, 0xbd, 0xaf, 0x81, 0xa9, 0x9b,
0x4a, 0x2e, 0xfa, 0xb9, 0x70, 0xe8, 0x93, 0x80, 0xa8, 0x5a, 0xc5, 0x0c, 0x8a, 0x92, 0xb8, 0xa5,
0x3f, 0x6b, 0x60, 0xa1, 0x6f, 0x19, 0x66, 0x0f, 0x56, 0x6c, 0x59, 0xee, 0x91, 0x18, 0xe8, 0x24,
0xa6, 0xa5, 0xeb, 0x9c, 0x8a, 0x24, 0x37, 0xe1, 0xbd, 0xcc, 0xe7, 0xe5, 0xbd, 0xd2, 0xdf, 0x34,
0xb0, 0x74, 0xaf, 0x48, 0x7c, 0x28, 0x47, 0xba, 0x0a, 0xf2, 0xb2, 0x85, 0x3a, 0xe1, 0xc7, 0x29,
0x5f, 0x0d, 0xb2, 0x68, 0xf0, 0xff, 0x90, 0x11, 0x7f, 0x95, 0x3e, 0xd2, 0xc0, 0x5c, 0x8d, 0xf8,
0x2d, 0xd3, 0x20, 0x88, 0x34, 0x88, 0x4f, 0x1c, 0x83, 0xc0, 0x35, 0x30, 0xc9, 0x7f, 0xa7, 0xf3,
0xb0, 0x11, 0x0d, 0xb1, 0xe7, 0xa5, 0xcb, 0x27, 0x77, 0x22, 0x06, 0x8a, 0x65, 0xd4, 0xc0, 0x3b,
0x33, 0x70, 0xe0, 0xbd, 0x04, 0xc6, 0xbc, 0x78, 0x1c, 0x98, 0x67, 0x5c, 0x3e, 0x01, 0xe4, 0x54,
0xce, 0x75, 0xfd, 0x80, 0xcf, 0x38, 0x72, 0x92, 0xeb, 0xfa, 0x01, 0xe2, 0xd4, 0xd2, 0xdf, 0x35,
0xd0, 0xef, 0x7f, 0x59, 0x60, 0x0b, 0x4c, 0x50, 0x61, 0xba, 0x74, 0xed, 0xee, 0x03, 0xba, 0x36,
0xed, 0x08, 0x71, 0x89, 0x44, 0xd4, 0x08, 0x8c, 0x79, 0xd7, 0xc0, 0x95, 0xd0, 0xa9, 0xcb, 0xf1,
0xde, 0xb4, 0xf0, 0x6e, 0x75, 0x5d, 0xd0, 0x90, 0xe2, 0xc2, 0x4b, 0x62, 0x10, 0x95, 0x98, 0xee,
0x44, 0x43, 0xa8, 0xca, 0x95, 0x3b, 0x77, 0x0b, 0xe7, 0x3e, 0xbe, 0x5b, 0x38, 0xf7, 0xc9, 0xdd,
0xc2, 0xb9, 0xef, 0x76, 0x0a, 0xda, 0x9d, 0x4e, 0x41, 0xfb, 0xb8, 0x53, 0xd0, 0x3e, 0xe9, 0x14,
0xb4, 0x7f, 0x75, 0x0a, 0xda, 0xcf, 0x3f, 0x2d, 0x9c, 0xfb, 0xe6, 0x84, 0x34, 0xed, 0xbf, 0x01,
0x00, 0x00, 0xff, 0xff, 0xd1, 0xda, 0xdd, 0xd0, 0x61, 0x2a, 0x00, 0x00,
// 2884 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5a, 0xcd, 0x73, 0x1c, 0x47,
0x15, 0xf7, 0xac, 0xb4, 0xd2, 0xaa, 0x25, 0x59, 0x52, 0xdb, 0x56, 0xc6, 0x8a, 0xb3, 0x2b, 0x6f,
0x48, 0x10, 0xc1, 0x5e, 0x25, 0x26, 0x21, 0x21, 0x55, 0x1c, 0xb4, 0x92, 0x92, 0x52, 0x62, 0x7d,
0xd0, 0x6b, 0x27, 0x86, 0x7c, 0xb6, 0x76, 0x5a, 0xab, 0xb1, 0xe6, 0xcb, 0xdd, 0x33, 0x2b, 0xa9,
0x02, 0x14, 0x1f, 0x95, 0x82, 0xa2, 0x80, 0x50, 0x24, 0x17, 0x0a, 0x38, 0x04, 0x8a, 0x0b, 0x07,
0x38, 0xc0, 0x0d, 0xfe, 0x80, 0x1c, 0x53, 0x9c, 0x72, 0xa0, 0xb6, 0xf0, 0xe6, 0x2f, 0xa0, 0x8a,
0x2a, 0xaa, 0x74, 0xa2, 0xfa, 0x63, 0x7a, 0x66, 0x67, 0x77, 0x6d, 0x57, 0xbc, 0x1b, 0x73, 0xd3,
0xbc, 0xaf, 0xdf, 0xeb, 0xd7, 0xaf, 0x5f, 0xbf, 0x7e, 0x2b, 0xb0, 0x77, 0xf0, 0x1c, 0xab, 0xd8,
0xfe, 0xf2, 0x41, 0xb4, 0x4b, 0xa8, 0x47, 0x42, 0xc2, 0x96, 0x9b, 0xc4, 0xb3, 0x7c, 0xba, 0xac,
0x18, 0x38, 0xb0, 0xc9, 0x51, 0x48, 0x3c, 0x66, 0xfb, 0x1e, 0xbb, 0x8c, 0x03, 0x9b, 0x11, 0xda,
0x24, 0x74, 0x39, 0x38, 0x68, 0x70, 0x1e, 0xeb, 0x14, 0x58, 0x6e, 0x3e, 0xb5, 0x4b, 0x42, 0xfc,
0xd4, 0x72, 0x83, 0x78, 0x84, 0xe2, 0x90, 0x58, 0x95, 0x80, 0xfa, 0xa1, 0x0f, 0xbf, 0x2e, 0xcd,
0x55, 0x3a, 0xa4, 0xdf, 0xd2, 0xe6, 0x2a, 0xc1, 0x41, 0x83, 0xf3, 0x58, 0xa7, 0x40, 0x45, 0x99,
0x5b, 0xb8, 0xdc, 0xb0, 0xc3, 0xfd, 0x68, 0xb7, 0x52, 0xf7, 0xdd, 0xe5, 0x86, 0xdf, 0xf0, 0x97,
0x85, 0xd5, 0xdd, 0x68, 0x4f, 0x7c, 0x89, 0x0f, 0xf1, 0x97, 0x44, 0x5b, 0x78, 0x3a, 0x71, 0xde,
0xc5, 0xf5, 0x7d, 0xdb, 0x23, 0xf4, 0x38, 0xf1, 0xd8, 0x25, 0x21, 0x5e, 0x6e, 0x76, 0xf9, 0xb8,
0xb0, 0xdc, 0x4f, 0x8b, 0x46, 0x5e, 0x68, 0xbb, 0xa4, 0x4b, 0xe1, 0xab, 0x77, 0x53, 0x60, 0xf5,
0x7d, 0xe2, 0xe2, 0xac, 0x5e, 0xf9, 0xc4, 0x00, 0x73, 0xab, 0xbe, 0xd7, 0x24, 0x94, 0xaf, 0x12,
0x91, 0x5b, 0x11, 0x61, 0x21, 0xac, 0x82, 0x91, 0xc8, 0xb6, 0x4c, 0x63, 0xd1, 0x58, 0x9a, 0xa8,
0x3e, 0xf9, 0x51, 0xab, 0x74, 0xaa, 0xdd, 0x2a, 0x8d, 0x5c, 0xdf, 0x58, 0x3b, 0x69, 0x95, 0x2e,
0xf6, 0x43, 0x0a, 0x8f, 0x03, 0xc2, 0x2a, 0xd7, 0x37, 0xd6, 0x10, 0x57, 0x86, 0x2f, 0x82, 0x39,
0x8b, 0x30, 0x9b, 0x12, 0x6b, 0x65, 0x67, 0xe3, 0x15, 0x69, 0xdf, 0xcc, 0x09, 0x8b, 0xe7, 0x95,
0xc5, 0xb9, 0xb5, 0xac, 0x00, 0xea, 0xd6, 0x81, 0x37, 0xc0, 0xb8, 0xbf, 0x7b, 0x93, 0xd4, 0x43,
0x66, 0x8e, 0x2c, 0x8e, 0x2c, 0x4d, 0x5e, 0xb9, 0x5c, 0x49, 0x76, 0x50, 0xbb, 0x20, 0xb6, 0x4d,
0x2d, 0xb6, 0x82, 0xf0, 0xe1, 0x7a, 0xbc, 0x73, 0xd5, 0x19, 0x85, 0x36, 0xbe, 0x2d, 0xad, 0xa0,
0xd8, 0x5c, 0xf9, 0xf7, 0x39, 0x00, 0xd3, 0x8b, 0x67, 0x81, 0xef, 0x31, 0x32, 0x90, 0xd5, 0x33,
0x30, 0x5b, 0x17, 0x96, 0x43, 0x62, 0x29, 0x5c, 0x33, 0xf7, 0x59, 0xbc, 0x37, 0x15, 0xfe, 0xec,
0x6a, 0xc6, 0x1c, 0xea, 0x02, 0x80, 0xd7, 0xc0, 0x18, 0x25, 0x2c, 0x72, 0x42, 0x73, 0x64, 0xd1,
0x58, 0x9a, 0xbc, 0x72, 0xa9, 0x2f, 0x94, 0xc8, 0x6f, 0x9e, 0x7c, 0x95, 0xe6, 0x53, 0x95, 0x5a,
0x88, 0xc3, 0x88, 0x55, 0x4f, 0x2b, 0xa4, 0x31, 0x24, 0x6c, 0x20, 0x65, 0xab, 0xfc, 0xe3, 0x1c,
0x98, 0x4d, 0x47, 0xa9, 0x69, 0x93, 0x43, 0x78, 0x08, 0xc6, 0xa9, 0x4c, 0x16, 0x11, 0xa7, 0xc9,
0x2b, 0x3b, 0x95, 0xfb, 0x3a, 0x56, 0x95, 0xae, 0x24, 0xac, 0x4e, 0xf2, 0x3d, 0x53, 0x1f, 0x28,
0x46, 0x83, 0xef, 0x80, 0x02, 0x55, 0x1b, 0x25, 0xb2, 0x69, 0xf2, 0xca, 0x37, 0x06, 0x88, 0x2c,
0x0d, 0x57, 0xa7, 0xda, 0xad, 0x52, 0x21, 0xfe, 0x42, 0x1a, 0xb0, 0xfc, 0x7e, 0x0e, 0x14, 0x57,
0x23, 0x16, 0xfa, 0x2e, 0x22, 0xcc, 0x8f, 0x68, 0x9d, 0xac, 0xfa, 0x4e, 0xe4, 0x7a, 0x6b, 0x64,
0xcf, 0xf6, 0xec, 0x90, 0x67, 0xeb, 0x22, 0x18, 0xf5, 0xb0, 0x4b, 0x54, 0xf6, 0x4c, 0xa9, 0x98,
0x8e, 0x6e, 0x61, 0x97, 0x20, 0xc1, 0xe1, 0x12, 0x3c, 0x59, 0xd4, 0x59, 0xd0, 0x12, 0xd7, 0x8e,
0x03, 0x82, 0x04, 0x07, 0x3e, 0x0e, 0xc6, 0xf6, 0x7c, 0xea, 0x62, 0xb9, 0x8f, 0x13, 0xc9, 0xce,
0xbc, 0x20, 0xa8, 0x48, 0x71, 0xe1, 0x33, 0x60, 0xd2, 0x22, 0xac, 0x4e, 0xed, 0x80, 0x43, 0x9b,
0xa3, 0x42, 0xf8, 0x8c, 0x12, 0x9e, 0x5c, 0x4b, 0x58, 0x28, 0x2d, 0x07, 0x2f, 0x81, 0x42, 0x40,
0x6d, 0x9f, 0xda, 0xe1, 0xb1, 0x99, 0x5f, 0x34, 0x96, 0xf2, 0xd5, 0x59, 0xa5, 0x53, 0xd8, 0x51,
0x74, 0xa4, 0x25, 0xe0, 0x22, 0x28, 0xbc, 0x54, 0xdb, 0xde, 0xda, 0xc1, 0xe1, 0xbe, 0x39, 0x26,
0x10, 0x46, 0xb9, 0x34, 0x2a, 0xdc, 0x54, 0xd4, 0xf2, 0x3f, 0x73, 0xc0, 0xcc, 0x46, 0x25, 0x0e,
0x29, 0x7c, 0x01, 0x14, 0x58, 0xc8, 0x2b, 0x4e, 0xe3, 0x58, 0xc5, 0xe4, 0x89, 0x18, 0xac, 0xa6,
0xe8, 0x27, 0xad, 0xd2, 0x7c, 0xa2, 0x11, 0x53, 0x45, 0x3c, 0xb4, 0x2e, 0xfc, 0xad, 0x01, 0xce,
0x1c, 0x92, 0xdd, 0x7d, 0xdf, 0x3f, 0x58, 0x75, 0x6c, 0xe2, 0x85, 0xab, 0xbe, 0xb7, 0x67, 0x37,
0x54, 0x0e, 0xa0, 0xfb, 0xcc, 0x81, 0x57, 0xbb, 0x2d, 0x57, 0x1f, 0x6a, 0xb7, 0x4a, 0x67, 0x7a,
0x30, 0x50, 0x2f, 0x3f, 0xe0, 0x0d, 0x60, 0xd6, 0x33, 0x87, 0x44, 0x15, 0x30, 0x59, 0xb6, 0x26,
0xaa, 0x17, 0xda, 0xad, 0x92, 0xb9, 0xda, 0x47, 0x06, 0xf5, 0xd5, 0x2e, 0xff, 0x70, 0x24, 0x1b,
0xde, 0x54, 0xba, 0xbd, 0x0d, 0x0a, 0xfc, 0x18, 0x5b, 0x38, 0xc4, 0xea, 0x20, 0x3e, 0x79, 0x6f,
0x87, 0x5e, 0xd6, 0x8c, 0x4d, 0x12, 0xe2, 0x2a, 0x54, 0x1b, 0x02, 0x12, 0x1a, 0xd2, 0x56, 0xe1,
0x77, 0xc0, 0x28, 0x0b, 0x48, 0x5d, 0x05, 0xfa, 0xb5, 0xfb, 0x3d, 0x6c, 0x7d, 0x16, 0x52, 0x0b,
0x48, 0x3d, 0x39, 0x0b, 0xfc, 0x0b, 0x09, 0x58, 0xf8, 0xae, 0x01, 0xc6, 0x98, 0x28, 0x50, 0xaa,
0xa8, 0xbd, 0x31, 0x2c, 0x0f, 0x32, 0x55, 0x50, 0x7e, 0x23, 0x05, 0x5e, 0xfe, 0x4f, 0x0e, 0x5c,
0xec, 0xa7, 0xba, 0xea, 0x7b, 0x96, 0xdc, 0x8e, 0x0d, 0x75, 0xb6, 0x65, 0xa6, 0x3f, 0x93, 0x3e,
0xdb, 0x27, 0xad, 0xd2, 0x63, 0x77, 0x35, 0x90, 0x2a, 0x02, 0x5f, 0xd3, 0xeb, 0x96, 0x85, 0xe2,
0x62, 0xa7, 0x63, 0x27, 0xad, 0xd2, 0x8c, 0x56, 0xeb, 0xf4, 0x15, 0x36, 0x01, 0x74, 0x30, 0x0b,
0xaf, 0x51, 0xec, 0x31, 0x69, 0xd6, 0x76, 0x89, 0x0a, 0xdf, 0x13, 0xf7, 0x96, 0x1e, 0x5c, 0xa3,
0xba, 0xa0, 0x20, 0xe1, 0xd5, 0x2e, 0x6b, 0xa8, 0x07, 0x02, 0xaf, 0x5b, 0x94, 0x60, 0xa6, 0x4b,
0x51, 0xea, 0x46, 0xe1, 0x54, 0xa4, 0xb8, 0xf0, 0x4b, 0x60, 0xdc, 0x25, 0x8c, 0xe1, 0x06, 0x11,
0xf5, 0x67, 0x22, 0xb9, 0xa2, 0x37, 0x25, 0x19, 0xc5, 0x7c, 0xde, 0x9f, 0x5c, 0xe8, 0x17, 0xb5,
0xab, 0x36, 0x0b, 0xe1, 0xeb, 0x5d, 0x07, 0xa0, 0x72, 0x6f, 0x2b, 0xe4, 0xda, 0x22, 0xfd, 0x75,
0xf1, 0x8b, 0x29, 0xa9, 0xe4, 0xff, 0x36, 0xc8, 0xdb, 0x21, 0x71, 0xe3, 0xbb, 0xfb, 0xd5, 0x21,
0xe5, 0x5e, 0x75, 0x5a, 0xf9, 0x90, 0xdf, 0xe0, 0x68, 0x48, 0x82, 0x96, 0xff, 0x90, 0x03, 0x8f,
0xf4, 0x53, 0xe1, 0x17, 0x0a, 0xe3, 0x11, 0x0f, 0x9c, 0x88, 0x62, 0x47, 0x65, 0x9c, 0x8e, 0xf8,
0x8e, 0xa0, 0x22, 0xc5, 0xe5, 0x25, 0x9f, 0xd9, 0x5e, 0x23, 0x72, 0x30, 0x55, 0xe9, 0xa4, 0x57,
0x5d, 0x53, 0x74, 0xa4, 0x25, 0x60, 0x05, 0x00, 0xb6, 0xef, 0xd3, 0x50, 0x60, 0xa8, 0xea, 0x75,
0x9a, 0x17, 0x88, 0x9a, 0xa6, 0xa2, 0x94, 0x04, 0xbf, 0xd1, 0x0e, 0x6c, 0xcf, 0x52, 0xbb, 0xae,
0x4f, 0xf1, 0xcb, 0xb6, 0x67, 0x21, 0xc1, 0xe1, 0xf8, 0x8e, 0xcd, 0x42, 0x4e, 0x51, 0x5b, 0xde,
0x11, 0x75, 0x21, 0xa9, 0x25, 0x38, 0x7e, 0x9d, 0x57, 0x7d, 0x9f, 0xda, 0x84, 0x99, 0x63, 0x09,
0xfe, 0xaa, 0xa6, 0xa2, 0x94, 0x44, 0xf9, 0xd7, 0x85, 0xfe, 0x49, 0xc2, 0x4b, 0x09, 0x7c, 0x14,
0xe4, 0x1b, 0xd4, 0x8f, 0x02, 0x15, 0x25, 0x1d, 0xed, 0x17, 0x39, 0x11, 0x49, 0x1e, 0xcf, 0xca,
0x66, 0x47, 0x9b, 0xaa, 0xb3, 0x32, 0x6e, 0x4e, 0x63, 0x3e, 0xfc, 0xbe, 0x01, 0xf2, 0x9e, 0x0a,
0x0e, 0x4f, 0xb9, 0xd7, 0x87, 0x94, 0x17, 0x22, 0xbc, 0x89, 0xbb, 0x32, 0xf2, 0x12, 0x19, 0x3e,
0x0d, 0xf2, 0xac, 0xee, 0x07, 0x44, 0x45, 0xbd, 0x18, 0x0b, 0xd5, 0x38, 0xf1, 0xa4, 0x55, 0x9a,
0x8e, 0xcd, 0x09, 0x02, 0x92, 0xc2, 0xf0, 0x47, 0x06, 0x00, 0x4d, 0xec, 0xd8, 0x16, 0x16, 0x2d,
0x43, 0x5e, 0xb8, 0x3f, 0xd8, 0xb4, 0x7e, 0x45, 0x9b, 0x97, 0x9b, 0x96, 0x7c, 0xa3, 0x14, 0x34,
0x7c, 0xcf, 0x00, 0x53, 0x2c, 0xda, 0xa5, 0x4a, 0x8b, 0x89, 0xe6, 0x62, 0xf2, 0xca, 0x37, 0x07,
0xea, 0x4b, 0x2d, 0x05, 0x50, 0x9d, 0x6d, 0xb7, 0x4a, 0x53, 0x69, 0x0a, 0xea, 0x70, 0x00, 0xfe,
0xd4, 0x00, 0x85, 0x66, 0x7c, 0x67, 0x8f, 0x8b, 0x03, 0xff, 0xe6, 0x90, 0x36, 0x56, 0x65, 0x54,
0x72, 0x0a, 0x74, 0x1f, 0xa0, 0x3d, 0x80, 0x7f, 0x33, 0x80, 0x89, 0x2d, 0x59, 0xe0, 0xb1, 0xb3,
0x43, 0x6d, 0x2f, 0x24, 0x54, 0xf6, 0x9b, 0xcc, 0x2c, 0x08, 0xf7, 0x06, 0x7b, 0x17, 0x66, 0x7b,
0xd9, 0xea, 0xa2, 0xf2, 0xce, 0x5c, 0xe9, 0xe3, 0x06, 0xea, 0xeb, 0xa0, 0x48, 0xb4, 0xa4, 0xa5,
0x31, 0x27, 0x86, 0x90, 0x68, 0x49, 0x2f, 0xa5, 0xaa, 0x43, 0xd2, 0x41, 0xa5, 0xa0, 0xcb, 0xef,
0x8d, 0x64, 0x9b, 0xf6, 0xec, 0xa5, 0x0f, 0x3f, 0x90, 0xce, 0xca, 0xa5, 0x30, 0xd3, 0x10, 0xc1,
0x7d, 0x7b, 0x48, 0x7b, 0xaf, 0x6f, 0xed, 0xa4, 0xf1, 0xd2, 0x24, 0x86, 0x52, 0x7e, 0xc0, 0x5f,
0x19, 0x60, 0x1a, 0xd7, 0xeb, 0x24, 0x08, 0x89, 0x25, 0x6b, 0x71, 0xee, 0x73, 0x28, 0x37, 0xe7,
0x94, 0x57, 0xd3, 0x2b, 0x69, 0x68, 0xd4, 0xe9, 0x09, 0x7c, 0x1e, 0x9c, 0x66, 0xa1, 0x4f, 0x89,
0x95, 0xe9, 0x72, 0x61, 0xbb, 0x55, 0x3a, 0x5d, 0xeb, 0xe0, 0xa0, 0x8c, 0x64, 0xf9, 0xd3, 0x51,
0x50, 0xba, 0xcb, 0xc9, 0xb8, 0x87, 0x77, 0xd4, 0xe3, 0x60, 0x4c, 0x2c, 0xd7, 0x12, 0x51, 0x29,
0xa4, 0x3a, 0x37, 0x41, 0x45, 0x8a, 0xcb, 0xeb, 0x3a, 0xc7, 0xe7, 0xdd, 0xc6, 0x88, 0x10, 0xd4,
0x75, 0xbd, 0x26, 0xc9, 0x28, 0xe6, 0xc3, 0x77, 0xc0, 0x98, 0x9c, 0x93, 0x88, 0xa2, 0x3a, 0xc4,
0xc2, 0x08, 0x84, 0x9f, 0x02, 0x0a, 0x29, 0xc8, 0xee, 0x82, 0x98, 0x7f, 0xd0, 0x05, 0xf1, 0x8e,
0x15, 0x68, 0xec, 0xff, 0xbc, 0x02, 0x95, 0xff, 0x6b, 0x64, 0xcf, 0x7d, 0x6a, 0xa9, 0xb5, 0x3a,
0x76, 0x08, 0x5c, 0x03, 0xb3, 0xfc, 0x91, 0x81, 0x48, 0xe0, 0xd8, 0x75, 0xcc, 0xc4, 0x1b, 0x57,
0x26, 0x9c, 0x1e, 0xbb, 0xd4, 0x32, 0x7c, 0xd4, 0xa5, 0x01, 0x5f, 0x02, 0x50, 0x36, 0xde, 0x1d,
0x76, 0x64, 0x0f, 0xa1, 0x5b, 0xe8, 0x5a, 0x97, 0x04, 0xea, 0xa1, 0x05, 0x57, 0xc1, 0x9c, 0x83,
0x77, 0x89, 0x53, 0x23, 0x0e, 0xa9, 0x87, 0x3e, 0x15, 0xa6, 0xe4, 0x14, 0xe0, 0x5c, 0xbb, 0x55,
0x9a, 0xbb, 0x9a, 0x65, 0xa2, 0x6e, 0xf9, 0xf2, 0xc5, 0xec, 0xf1, 0x4a, 0x2f, 0x5c, 0x3e, 0x67,
0x3e, 0xcc, 0x81, 0x85, 0xfe, 0x99, 0x01, 0x7f, 0x90, 0xbc, 0xba, 0x64, 0x53, 0xfd, 0xe6, 0xb0,
0xb2, 0x50, 0x3d, 0xbb, 0x40, 0xf7, 0x93, 0x0b, 0x7e, 0x97, 0x77, 0x38, 0xd8, 0x89, 0xe7, 0x3c,
0x6f, 0x0c, 0xcd, 0x05, 0x0e, 0x52, 0x9d, 0x90, 0xcd, 0x13, 0x76, 0x44, 0xaf, 0x84, 0x1d, 0x52,
0xfe, 0xa3, 0x91, 0x7d, 0x78, 0x27, 0x27, 0x18, 0xfe, 0xcc, 0x00, 0x33, 0x7e, 0x40, 0xbc, 0x95,
0x9d, 0x8d, 0x57, 0xbe, 0x22, 0x4f, 0xb2, 0x0a, 0xd5, 0xd6, 0x7d, 0xfa, 0xf9, 0x52, 0x6d, 0x7b,
0x4b, 0x1a, 0xdc, 0xa1, 0x7e, 0xc0, 0xaa, 0x67, 0xda, 0xad, 0xd2, 0xcc, 0x76, 0x27, 0x14, 0xca,
0x62, 0x97, 0x5d, 0x70, 0x6e, 0xfd, 0x28, 0x24, 0xd4, 0xc3, 0xce, 0x9a, 0x5f, 0x8f, 0x5c, 0xe2,
0x85, 0xd2, 0xd1, 0xcc, 0x90, 0xc8, 0xb8, 0xc7, 0x21, 0xd1, 0x23, 0x60, 0x24, 0xa2, 0x8e, 0xca,
0xe2, 0x49, 0x3d, 0x04, 0x45, 0x57, 0x11, 0xa7, 0x97, 0x2f, 0x82, 0x51, 0xee, 0x27, 0x3c, 0x0f,
0x46, 0x28, 0x3e, 0x14, 0x56, 0xa7, 0xaa, 0xe3, 0x5c, 0x04, 0xe1, 0x43, 0xc4, 0x69, 0xe5, 0x7f,
0x17, 0xc1, 0x4c, 0x66, 0x2d, 0x70, 0x01, 0xe4, 0xf4, 0x64, 0x15, 0x28, 0xa3, 0xb9, 0x8d, 0x35,
0x94, 0xb3, 0x2d, 0xf8, 0xac, 0x2e, 0xbe, 0x12, 0xb4, 0xa4, 0xeb, 0xb9, 0xa0, 0xf2, 0x96, 0x36,
0x31, 0xc7, 0x1d, 0x89, 0x0b, 0x27, 0xf7, 0x81, 0xec, 0xa9, 0x53, 0x22, 0x7d, 0x20, 0x7b, 0x88,
0xd3, 0x3e, 0xeb, 0x84, 0x2c, 0x1e, 0xd1, 0xe5, 0xef, 0x61, 0x44, 0x37, 0x76, 0xc7, 0x11, 0xdd,
0xa3, 0x20, 0x1f, 0xda, 0xa1, 0x43, 0xcc, 0xf1, 0xce, 0x97, 0xc7, 0x35, 0x4e, 0x44, 0x92, 0x07,
0x6f, 0x82, 0x71, 0x8b, 0xec, 0xe1, 0xc8, 0x09, 0xcd, 0x82, 0x48, 0xa1, 0xd5, 0x01, 0xa4, 0x90,
0x9c, 0x9f, 0xae, 0x49, 0xbb, 0x28, 0x06, 0x80, 0x8f, 0x81, 0x71, 0x17, 0x1f, 0xd9, 0x6e, 0xe4,
0x8a, 0x9e, 0xcc, 0x90, 0x62, 0x9b, 0x92, 0x84, 0x62, 0x1e, 0xaf, 0x8c, 0xe4, 0xa8, 0xee, 0x44,
0xcc, 0x6e, 0x12, 0xc5, 0x34, 0x81, 0xb8, 0x3d, 0x75, 0x65, 0x5c, 0xcf, 0xf0, 0x51, 0x97, 0x86,
0x00, 0xb3, 0x3d, 0xa1, 0x3c, 0x99, 0x02, 0x93, 0x24, 0x14, 0xf3, 0x3a, 0xc1, 0x94, 0xfc, 0x54,
0x3f, 0x30, 0xa5, 0xdc, 0xa5, 0x01, 0xbf, 0x0c, 0x26, 0x5c, 0x7c, 0x74, 0x95, 0x78, 0x8d, 0x70,
0xdf, 0x9c, 0x5e, 0x34, 0x96, 0x46, 0xaa, 0xd3, 0xed, 0x56, 0x69, 0x62, 0x33, 0x26, 0xa2, 0x84,
0x2f, 0x84, 0x6d, 0x4f, 0x09, 0x9f, 0x4e, 0x09, 0xc7, 0x44, 0x94, 0xf0, 0x79, 0x07, 0x11, 0xe0,
0x90, 0x1f, 0x2e, 0x73, 0xa6, 0xf3, 0x65, 0xb8, 0x23, 0xc9, 0x28, 0xe6, 0xc3, 0x25, 0x50, 0x70,
0xf1, 0x91, 0x78, 0xc5, 0x9b, 0xb3, 0xc2, 0xac, 0x98, 0x25, 0x6f, 0x2a, 0x1a, 0xd2, 0x5c, 0x21,
0x69, 0x7b, 0x52, 0x72, 0x2e, 0x25, 0xa9, 0x68, 0x48, 0x73, 0x79, 0x12, 0x47, 0x9e, 0x7d, 0x2b,
0x22, 0x52, 0x18, 0x8a, 0xc8, 0xe8, 0x24, 0xbe, 0x9e, 0xb0, 0x50, 0x5a, 0x8e, 0xbf, 0xa2, 0xdd,
0xc8, 0x09, 0xed, 0xc0, 0x21, 0xdb, 0x7b, 0xe6, 0x19, 0x11, 0x7f, 0xd1, 0x27, 0x6f, 0x6a, 0x2a,
0x4a, 0x49, 0x40, 0x02, 0x46, 0x89, 0x17, 0xb9, 0xe6, 0x59, 0x71, 0xb1, 0x0f, 0x24, 0x05, 0xf5,
0xc9, 0x59, 0xf7, 0x22, 0x17, 0x09, 0xf3, 0xf0, 0x59, 0x30, 0xed, 0xe2, 0x23, 0x5e, 0x0e, 0x08,
0x0d, 0xf9, 0xfb, 0xfe, 0x9c, 0x58, 0xfc, 0x1c, 0xef, 0x38, 0x37, 0xd3, 0x0c, 0xd4, 0x29, 0x27,
0x14, 0x6d, 0x2f, 0xa5, 0x38, 0x9f, 0x52, 0x4c, 0x33, 0x50, 0xa7, 0x1c, 0x8f, 0x34, 0x25, 0xb7,
0x22, 0x9b, 0x12, 0xcb, 0x7c, 0x48, 0x34, 0xa9, 0x6a, 0xbe, 0x2f, 0x69, 0x48, 0x73, 0x61, 0x33,
0x1e, 0xf7, 0x98, 0xe2, 0x18, 0x5e, 0x1f, 0x6c, 0x25, 0xdf, 0xa6, 0x2b, 0x94, 0xe2, 0x63, 0x79,
0xd3, 0xa4, 0x07, 0x3d, 0x90, 0x81, 0x3c, 0x76, 0x9c, 0xed, 0x3d, 0xf3, 0xbc, 0x88, 0xfd, 0xa0,
0x6f, 0x10, 0x5d, 0x75, 0x56, 0x38, 0x08, 0x92, 0x58, 0x1c, 0xd4, 0xf7, 0x78, 0x6a, 0x2c, 0x0c,
0x17, 0x74, 0x9b, 0x83, 0x20, 0x89, 0x25, 0x56, 0xea, 0x1d, 0x6f, 0xef, 0x99, 0x0f, 0x0f, 0x79,
0xa5, 0x1c, 0x04, 0x49, 0x2c, 0x68, 0x83, 0x11, 0xcf, 0x0f, 0xcd, 0x0b, 0x43, 0xb9, 0x9e, 0xc5,
0x85, 0xb3, 0xe5, 0x87, 0x88, 0x63, 0xc0, 0x5f, 0x1a, 0x00, 0x04, 0x49, 0x8a, 0x3e, 0x32, 0x90,
0x29, 0x42, 0x06, 0xb2, 0x92, 0xe4, 0xf6, 0xba, 0x17, 0xd2, 0xe3, 0xe4, 0x1d, 0x99, 0x3a, 0x03,
0x29, 0x2f, 0xe0, 0xef, 0x0c, 0x70, 0x36, 0xdd, 0x26, 0x6b, 0xf7, 0x8a, 0x22, 0x22, 0xd7, 0x06,
0x9d, 0xe6, 0x55, 0xdf, 0x77, 0xaa, 0x66, 0xbb, 0x55, 0x3a, 0xbb, 0xd2, 0x03, 0x15, 0xf5, 0xf4,
0x05, 0xfe, 0xc9, 0x00, 0x73, 0xaa, 0x8a, 0xa6, 0x3c, 0x2c, 0x89, 0x00, 0x92, 0x41, 0x07, 0x30,
0x8b, 0x23, 0xe3, 0xa8, 0x7f, 0x97, 0xee, 0xe2, 0xa3, 0x6e, 0xd7, 0xe0, 0x5f, 0x0d, 0x30, 0x65,
0x91, 0x80, 0x78, 0x16, 0xf1, 0xea, 0xdc, 0xd7, 0xc5, 0x81, 0x8c, 0x0d, 0xb2, 0xbe, 0xae, 0xa5,
0x20, 0xa4, 0x9b, 0x15, 0xe5, 0xe6, 0x54, 0x9a, 0x75, 0xd2, 0x2a, 0xcd, 0x27, 0xaa, 0x69, 0x0e,
0xea, 0xf0, 0x12, 0xbe, 0x6f, 0x80, 0x99, 0x64, 0x03, 0xe4, 0x95, 0x72, 0x71, 0x88, 0x79, 0x20,
0xda, 0xd7, 0x95, 0x4e, 0x40, 0x94, 0xf5, 0x00, 0xfe, 0xd9, 0xe0, 0x9d, 0x5a, 0xfc, 0xee, 0x63,
0x66, 0x59, 0xc4, 0xf2, 0xad, 0x81, 0xc7, 0x52, 0x23, 0xc8, 0x50, 0x5e, 0x4a, 0x5a, 0x41, 0xcd,
0x39, 0x69, 0x95, 0xce, 0xa5, 0x23, 0xa9, 0x19, 0x28, 0xed, 0x21, 0xfc, 0x89, 0x01, 0xa6, 0x48,
0xd2, 0x71, 0x33, 0xf3, 0xd1, 0x81, 0x04, 0xb1, 0x67, 0x13, 0x2f, 0x5f, 0xea, 0x29, 0x16, 0x43,
0x1d, 0xd8, 0xbc, 0x83, 0x24, 0x47, 0xd8, 0x0d, 0x1c, 0x62, 0x7e, 0x61, 0xc0, 0x1d, 0xe4, 0xba,
0xb4, 0x8b, 0x62, 0x00, 0x78, 0x09, 0x14, 0xbc, 0xc8, 0x71, 0xf0, 0xae, 0x43, 0xcc, 0xc7, 0x44,
0x2f, 0xa2, 0xa7, 0x98, 0x5b, 0x8a, 0x8e, 0xb4, 0x04, 0xbc, 0x09, 0x16, 0x8f, 0x5e, 0xd6, 0xff,
0xd1, 0xb3, 0x43, 0x89, 0xc0, 0xbf, 0xee, 0x1d, 0x78, 0xfe, 0xa1, 0xf7, 0x82, 0x4d, 0x1c, 0x8b,
0x99, 0x8f, 0x0b, 0x2b, 0xf1, 0x04, 0x7b, 0xfe, 0x46, 0x4f, 0x29, 0x74, 0x57, 0x3b, 0xf0, 0x35,
0xf0, 0x70, 0x4a, 0x66, 0xdd, 0xdd, 0x25, 0x96, 0x45, 0xac, 0xf8, 0xf1, 0x66, 0x7e, 0x51, 0xc0,
0xe8, 0x43, 0x7e, 0x23, 0x2b, 0x80, 0xee, 0xa4, 0x0d, 0xaf, 0x82, 0xf9, 0x14, 0x7b, 0xc3, 0x0b,
0xb7, 0x69, 0x2d, 0xa4, 0xb6, 0xd7, 0x30, 0x97, 0x84, 0xdd, 0xb3, 0xf1, 0xa9, 0xbc, 0x91, 0xe2,
0xa1, 0x3e, 0x3a, 0x0b, 0xfc, 0xf9, 0x98, 0x29, 0x3f, 0x70, 0x16, 0x8c, 0x1c, 0x10, 0xf5, 0x2b,
0x39, 0xe2, 0x7f, 0x42, 0x0b, 0xe4, 0x9b, 0xd8, 0x89, 0xe2, 0x17, 0xf0, 0x80, 0xaf, 0x2e, 0x24,
0x8d, 0x3f, 0x9f, 0x7b, 0xce, 0x58, 0xf8, 0xc0, 0x00, 0xf3, 0xbd, 0xab, 0xe2, 0x03, 0x75, 0xeb,
0x37, 0x06, 0x98, 0xeb, 0x2a, 0x80, 0x3d, 0x3c, 0xba, 0xd5, 0xe9, 0xd1, 0x6b, 0x83, 0xae, 0x64,
0x72, 0xd7, 0x44, 0xfb, 0x96, 0x76, 0xef, 0xe7, 0x06, 0x98, 0xcd, 0xd6, 0x94, 0x07, 0x19, 0xaf,
0xf2, 0x07, 0x39, 0x30, 0xdf, 0xbb, 0xeb, 0x84, 0x54, 0x3f, 0xaf, 0x87, 0x33, 0xa6, 0xe8, 0x35,
0xd2, 0x7c, 0xd7, 0x00, 0x93, 0x37, 0xb5, 0x5c, 0xfc, 0x2b, 0xea, 0xc0, 0x07, 0x24, 0x71, 0x11,
0x4f, 0x18, 0x0c, 0xa5, 0x71, 0xcb, 0x7f, 0x31, 0xc0, 0xb9, 0x9e, 0xb7, 0x13, 0x7f, 0xc7, 0x63,
0xc7, 0xf1, 0x0f, 0xe5, 0x9c, 0x2b, 0x35, 0x44, 0x5e, 0x11, 0x54, 0xa4, 0xb8, 0xa9, 0xe8, 0xe5,
0x3e, 0xaf, 0xe8, 0x95, 0xff, 0x6e, 0x80, 0x0b, 0x77, 0xca, 0xc4, 0x07, 0xb2, 0xa5, 0x4b, 0xa0,
0xa0, 0x3a, 0xcb, 0x63, 0xb1, 0x9d, 0xea, 0x31, 0xa5, 0x8a, 0x86, 0xf8, 0xc7, 0x21, 0xf9, 0x57,
0xf9, 0x43, 0x03, 0xcc, 0xd6, 0x08, 0x6d, 0xda, 0x75, 0x82, 0xc8, 0x1e, 0xa1, 0xc4, 0xab, 0x13,
0xb8, 0x0c, 0x26, 0xc4, 0xcf, 0x97, 0x01, 0xae, 0xc7, 0xb3, 0xfd, 0x39, 0x15, 0xf2, 0x89, 0xad,
0x98, 0x81, 0x12, 0x19, 0xfd, 0x3b, 0x40, 0xae, 0xef, 0xef, 0x00, 0x17, 0xc0, 0x68, 0x90, 0x4c,
0x49, 0x0b, 0x9c, 0x2b, 0x06, 0xa3, 0x82, 0x2a, 0xb8, 0x3e, 0x0d, 0xc5, 0xe8, 0x27, 0xaf, 0xb8,
0x3e, 0x0d, 0x91, 0xa0, 0x96, 0xff, 0x61, 0x80, 0x5e, 0xff, 0xe2, 0x03, 0x9b, 0x60, 0x9c, 0x49,
0xd7, 0x55, 0x68, 0xb7, 0xef, 0x33, 0xb4, 0xd9, 0x40, 0xc8, 0xbb, 0x35, 0xa6, 0xc6, 0x60, 0x3c,
0xba, 0x75, 0x5c, 0x8d, 0x3c, 0x4b, 0x4d, 0x3d, 0xa7, 0x64, 0x74, 0x57, 0x57, 0x24, 0x0d, 0x69,
0x2e, 0x3c, 0x2f, 0xe7, 0x73, 0xa9, 0xa1, 0x57, 0x3c, 0x9b, 0xab, 0x5e, 0xfe, 0xe8, 0x76, 0xf1,
0xd4, 0xc7, 0xb7, 0x8b, 0xa7, 0x3e, 0xb9, 0x5d, 0x3c, 0xf5, 0xbd, 0x76, 0xd1, 0xf8, 0xa8, 0x5d,
0x34, 0x3e, 0x6e, 0x17, 0x8d, 0x4f, 0xda, 0x45, 0xe3, 0x5f, 0xed, 0xa2, 0xf1, 0x8b, 0x4f, 0x8b,
0xa7, 0xbe, 0x35, 0xae, 0x5c, 0xfb, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x44, 0x57, 0xd1, 0xa5,
0x78, 0x2b, 0x00, 0x00,
}

View File

@@ -443,6 +443,37 @@ message JSONSchemaProps {
optional JSON example = 36;
optional bool nullable = 37;
// 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.
optional bool xKubernetesPreserveUnknownFields = 38;
// 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).
optional bool xKubernetesEmbeddedResource = 39;
// 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:
//
// 1) anyOf:
// - type: integer
// - type: string
// 2) allOf:
// - anyOf:
// - type: integer
// - type: string
// - ... zero or more
optional bool xKubernetesIntOrString = 40;
}
// JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps

View File

@@ -279,6 +279,22 @@ const (
// NamesAccepted means the names chosen for this CustomResourceDefinition do not conflict with others in
// the group and are therefore accepted.
NamesAccepted CustomResourceDefinitionConditionType = "NamesAccepted"
// NonStructuralSchema means that one or more OpenAPI schema is not structural.
//
// A schema is structural if it specifies types for all values, with the only exceptions of those with
// - x-kubernetes-int-or-string: true — for fields which can be integer or string
// - x-kubernetes-preserve-unknown-fields: true — for raw, unspecified JSON values
// and there is no type, additionalProperties, default, nullable or x-kubernetes-* vendor extenions
// specified under allOf, anyOf, oneOf or not.
//
// Non-structural schemas will not be allowed anymore in v1 API groups. Moreover, new features will not be
// available for non-structural CRDs:
// - pruning
// - defaulting
// - read-only
// - OpenAPI publishing
// - webhook conversion
NonStructuralSchema CustomResourceDefinitionConditionType = "NonStructuralSchema"
// Terminating means that the CustomResourceDefinition has been deleted and is cleaning up.
Terminating CustomResourceDefinitionConditionType = "Terminating"
)

View File

@@ -55,6 +55,37 @@ type JSONSchemaProps struct {
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty" protobuf:"bytes,35,opt,name=externalDocs"`
Example *JSON `json:"example,omitempty" protobuf:"bytes,36,opt,name=example"`
Nullable bool `json:"nullable,omitempty" protobuf:"bytes,37,opt,name=nullable"`
// 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.
XPreserveUnknownFields bool `json:"x-kubernetes-preserve-unknown-fields,omitempty" protobuf:"bytes,38,opt,name=xKubernetesPreserveUnknownFields"`
// 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).
XEmbeddedResource bool `json:"x-kubernetes-embedded-resource,omitempty" protobuf:"bytes,39,opt,name=xKubernetesEmbeddedResource"`
// 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:
//
// 1) anyOf:
// - type: integer
// - type: string
// 2) allOf:
// - anyOf:
// - type: integer
// - type: string
// - ... zero or more
XIntOrString bool `json:"x-kubernetes-int-or-string,omitempty" protobuf:"bytes,40,opt,name=xKubernetesIntOrString"`
}
// JSON represents any valid JSON value.

View File

@@ -938,6 +938,9 @@ func autoConvert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(in *JS
out.Example = nil
}
out.Nullable = in.Nullable
out.XPreserveUnknownFields = in.XPreserveUnknownFields
out.XEmbeddedResource = in.XEmbeddedResource
out.XIntOrString = in.XIntOrString
return nil
}
@@ -1120,6 +1123,9 @@ func autoConvert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(in *ap
} else {
out.Example = nil
}
out.XPreserveUnknownFields = in.XPreserveUnknownFields
out.XEmbeddedResource = in.XEmbeddedResource
out.XIntOrString = in.XIntOrString
return nil
}

View File

@@ -686,6 +686,10 @@ func (v *specStandardValidatorV3) validate(schema *apiextensions.JSONSchemaProps
return allErrs
}
//
// WARNING: if anything new is allowed below, NewStructural must be adapted to support it.
//
if schema.Default != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("default"), "default is not supported"))
}
@@ -786,7 +790,7 @@ func validateSimpleJSONPath(s string, fldPath *field.Path) field.ErrorList {
return allErrs
}
var allowedFieldsAtRootSchema = []string{"Description", "Type", "Format", "Title", "Maximum", "ExclusiveMaximum", "Minimum", "ExclusiveMinimum", "MaxLength", "MinLength", "Pattern", "MaxItems", "MinItems", "UniqueItems", "MultipleOf", "Required", "Items", "Properties", "ExternalDocs", "Example"}
var allowedFieldsAtRootSchema = []string{"Description", "Type", "Format", "Title", "Maximum", "ExclusiveMaximum", "Minimum", "ExclusiveMinimum", "MaxLength", "MinLength", "Pattern", "MaxItems", "MinItems", "UniqueItems", "MultipleOf", "Required", "Items", "Properties", "ExternalDocs", "Example", "XPreserveUnknownFields"}
func allowedAtRootSchema(field string) bool {
for _, v := range allowedFieldsAtRootSchema {

View File

@@ -32,6 +32,7 @@ go_library(
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/controller/establish:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/controller/finalizer:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/crdserverscheme:go_default_library",
@@ -119,6 +120,7 @@ filegroup(
srcs = [
":package-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion:all-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema:all-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:all-srcs",
],
tags = ["automanaged"],

View File

@@ -30,6 +30,7 @@ import (
internalinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion"
"k8s.io/apiextensions-apiserver/pkg/controller/establish"
"k8s.io/apiextensions-apiserver/pkg/controller/finalizer"
"k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema"
openapicontroller "k8s.io/apiextensions-apiserver/pkg/controller/openapi"
"k8s.io/apiextensions-apiserver/pkg/controller/status"
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
@@ -195,6 +196,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
crdController := NewDiscoveryController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), versionDiscoveryHandler, groupDiscoveryHandler)
namingController := status.NewNamingConditionController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions())
nonStructuralSchemaController := nonstructuralschema.NewConditionController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions())
finalizingController := finalizer.NewCRDFinalizer(
s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(),
crdClient.Apiextensions(),
@@ -217,6 +219,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
go crdController.Run(context.StopCh)
go namingController.Run(context.StopCh)
go establishingController.Run(context.StopCh)
go nonStructuralSchemaController.Run(5, context.StopCh)
go finalizingController.Run(5, context.StopCh)
return nil
})

View File

@@ -0,0 +1,44 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"complete.go",
"convert.go",
"structural.go",
"validation.go",
"zz_generated.deepcopy.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema",
importpath = "k8s.io/apiextensions-apiserver/pkg/apiserver/schema",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["validation_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/util/rand:go_default_library",
"//vendor/github.com/google/gofuzz:go_default_library",
],
)

View File

@@ -0,0 +1,82 @@
/*
Copyright 2019 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 schema
import (
"fmt"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// validateStructuralCompleteness checks that every specified field or array in s is also specified
// outside of value validation.
func validateStructuralCompleteness(s *Structural, fldPath *field.Path) field.ErrorList {
if s == nil {
return nil
}
return validateValueValidationCompleteness(s.ValueValidation, s, fldPath, fldPath)
}
func validateValueValidationCompleteness(v *ValueValidation, s *Structural, sPath, vPath *field.Path) field.ErrorList {
if v == nil {
return nil
}
if s == nil {
return field.ErrorList{field.Required(sPath, fmt.Sprintf("because it is defined in %s", vPath.String()))}
}
allErrs := field.ErrorList{}
allErrs = append(allErrs, validateNestedValueValidationCompleteness(v.Not, s, sPath, vPath.Child("not"))...)
for i := range v.AllOf {
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.AllOf[i], s, sPath, vPath.Child("allOf").Index(i))...)
}
for i := range v.AnyOf {
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.AnyOf[i], s, sPath, vPath.Child("anyOf").Index(i))...)
}
for i := range v.OneOf {
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.OneOf[i], s, sPath, vPath.Child("oneOf").Index(i))...)
}
return allErrs
}
func validateNestedValueValidationCompleteness(v *NestedValueValidation, s *Structural, sPath, vPath *field.Path) field.ErrorList {
if v == nil {
return nil
}
if s == nil {
return field.ErrorList{field.Required(sPath, fmt.Sprintf("because it is defined in %s", vPath.String()))}
}
allErrs := field.ErrorList{}
allErrs = append(allErrs, validateValueValidationCompleteness(&v.ValueValidation, s, sPath, vPath)...)
allErrs = append(allErrs, validateNestedValueValidationCompleteness(v.Items, s.Items, sPath.Child("items"), vPath.Child("items"))...)
for k, vFld := range v.Properties {
if sFld, ok := s.Properties[k]; !ok {
allErrs = append(allErrs, field.Required(sPath.Child("properties").Key(k), fmt.Sprintf("because it is defined in %s", vPath.Child("properties").Key(k))))
} else {
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&vFld, &sFld, sPath.Child("properties").Key(k), vPath.Child("properties").Key(k))...)
}
}
// don't check additionalProperties as this is not allowed (and checked during validation)
return allErrs
}

View File

@@ -0,0 +1,276 @@
/*
Copyright 2019 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 schema
import (
"fmt"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
)
// NewStructural converts an OpenAPI v3 schema into a structural schema. A pre-validated JSONSchemaProps will
// not fail on NewStructural. This means that we require that:
//
// - items is not an array of schemas
// - the following fields are not set:
// - id
// - schema
// - $ref
// - patternProperties
// - dependencies
// - additionalItems
// - definitions.
//
// The follow fields are not preserved:
// - externalDocs
// - example.
func NewStructural(s *apiextensions.JSONSchemaProps) (*Structural, error) {
if s == nil {
return nil, nil
}
if err := validateUnsupportedFields(s); err != nil {
return nil, err
}
vv, err := newValueValidation(s)
if err != nil {
return nil, err
}
g, err := newGenerics(s)
if err != nil {
return nil, err
}
x, err := newExtensions(s)
if err != nil {
return nil, err
}
ss := &Structural{
Generic: *g,
Extensions: *x,
ValueValidation: vv,
}
if s.Items != nil {
if len(s.Items.JSONSchemas) > 0 {
// we validate that it is not an array
return nil, fmt.Errorf("OpenAPIV3Schema 'items' must be a schema, but is an array")
}
item, err := NewStructural(s.Items.Schema)
if err != nil {
return nil, err
}
ss.Items = item
}
if len(s.Properties) > 0 {
ss.Properties = make(map[string]Structural, len(s.Properties))
for k, x := range s.Properties {
fld, err := NewStructural(&x)
if err != nil {
return nil, err
}
ss.Properties[k] = *fld
}
}
return ss, nil
}
func newGenerics(s *apiextensions.JSONSchemaProps) (*Generic, error) {
if s == nil {
return nil, nil
}
g := &Generic{
Type: s.Type,
Description: s.Description,
Title: s.Title,
Nullable: s.Nullable,
}
if s.Default != nil {
g.Default = JSON{interface{}(*s.Default)}
}
if s.AdditionalProperties != nil {
if s.AdditionalProperties.Schema != nil {
ss, err := NewStructural(s.AdditionalProperties.Schema)
if err != nil {
return nil, err
}
g.AdditionalProperties = &StructuralOrBool{Structural: ss}
} else {
g.AdditionalProperties = &StructuralOrBool{Bool: s.AdditionalProperties.Allows}
}
}
return g, nil
}
func newValueValidation(s *apiextensions.JSONSchemaProps) (*ValueValidation, error) {
if s == nil {
return nil, nil
}
not, err := newNestedValueValidation(s.Not)
if err != nil {
return nil, err
}
v := &ValueValidation{
Format: s.Format,
Maximum: s.Maximum,
ExclusiveMaximum: s.ExclusiveMaximum,
Minimum: s.Minimum,
ExclusiveMinimum: s.ExclusiveMinimum,
MaxLength: s.MaxLength,
MinLength: s.MinLength,
Pattern: s.Pattern,
MaxItems: s.MaxItems,
MinItems: s.MinItems,
UniqueItems: s.UniqueItems,
MultipleOf: s.MultipleOf,
MaxProperties: s.MaxProperties,
MinProperties: s.MinProperties,
Required: s.Required,
Not: not,
}
for _, e := range s.Enum {
v.Enum = append(v.Enum, JSON{e})
}
for _, x := range s.AllOf {
clause, err := newNestedValueValidation(&x)
if err != nil {
return nil, err
}
v.AllOf = append(v.AllOf, *clause)
}
for _, x := range s.AnyOf {
clause, err := newNestedValueValidation(&x)
if err != nil {
return nil, err
}
v.AnyOf = append(v.AnyOf, *clause)
}
for _, x := range s.OneOf {
clause, err := newNestedValueValidation(&x)
if err != nil {
return nil, err
}
v.OneOf = append(v.OneOf, *clause)
}
return v, nil
}
func newNestedValueValidation(s *apiextensions.JSONSchemaProps) (*NestedValueValidation, error) {
if s == nil {
return nil, nil
}
if err := validateUnsupportedFields(s); err != nil {
return nil, err
}
vv, err := newValueValidation(s)
if err != nil {
return nil, err
}
g, err := newGenerics(s)
if err != nil {
return nil, err
}
x, err := newExtensions(s)
if err != nil {
return nil, err
}
v := &NestedValueValidation{
ValueValidation: *vv,
ForbiddenGenerics: *g,
ForbiddenExtensions: *x,
}
if s.Items != nil {
if len(s.Items.JSONSchemas) > 0 {
// we validate that it is not an array
return nil, fmt.Errorf("OpenAPIV3Schema 'items' must be a schema, but is an array")
}
nvv, err := newNestedValueValidation(s.Items.Schema)
if err != nil {
return nil, err
}
v.Items = nvv
}
if s.Properties != nil {
v.Properties = make(map[string]NestedValueValidation, len(s.Properties))
for k, x := range s.Properties {
nvv, err := newNestedValueValidation(&x)
if err != nil {
return nil, err
}
v.Properties[k] = *nvv
}
}
return v, nil
}
func newExtensions(s *apiextensions.JSONSchemaProps) (*Extensions, error) {
if s == nil {
return nil, nil
}
return &Extensions{
XPreserveUnknownFields: s.XPreserveUnknownFields,
XEmbeddedResource: s.XEmbeddedResource,
XIntOrString: s.XIntOrString,
}, nil
}
// validateUnsupportedFields checks that those fields rejected by validation are actually unset.
func validateUnsupportedFields(s *apiextensions.JSONSchemaProps) error {
if len(s.ID) > 0 {
return fmt.Errorf("OpenAPIV3Schema 'id' is not supported")
}
if len(s.Schema) > 0 {
return fmt.Errorf("OpenAPIV3Schema 'schema' is not supported")
}
if s.Ref != nil && len(*s.Ref) > 0 {
return fmt.Errorf("OpenAPIV3Schema '$ref' is not supported")
}
if len(s.PatternProperties) > 0 {
return fmt.Errorf("OpenAPIV3Schema 'patternProperties' is not supported")
}
if len(s.Dependencies) > 0 {
return fmt.Errorf("OpenAPIV3Schema 'dependencies' is not supported")
}
if s.AdditionalItems != nil {
return fmt.Errorf("OpenAPIV3Schema 'additionalItems' is not supported")
}
if len(s.Definitions) > 0 {
return fmt.Errorf("OpenAPIV3Schema 'definitions' is not supported")
}
return nil
}

View File

@@ -0,0 +1,160 @@
/*
Copyright 2019 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 schema
import (
"k8s.io/apimachinery/pkg/runtime"
)
// +k8s:deepcopy-gen=true
// Structural represents a structural schema.
type Structural struct {
Items *Structural
Properties map[string]Structural
Generic
Extensions
*ValueValidation
}
// +k8s:deepcopy-gen=true
// StructuralOrBool is either a structural schema or a boolean.
type StructuralOrBool struct {
Structural *Structural
Bool bool
}
// +k8s:deepcopy-gen=true
// Generic contains the generic schema fields not allowed in value validation.
type Generic struct {
Description string
// type specifies the type of a value.
// It can be object, array, number, integer, boolean, string.
// It is optional only if x-kubernetes-preserve-unknown-fields
// or x-kubernetes-int-or-string is true.
Type string
Title string
Default JSON
AdditionalProperties *StructuralOrBool
Nullable bool
}
// +k8s:deepcopy-gen=true
// Extensions contains the Kubernetes OpenAPI v3 vendor extensions.
type Extensions struct {
// 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.
XPreserveUnknownFields bool
// 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. Both ObjectMeta and TypeMeta
// are validated automatically. x-kubernetes-preserve-unknown-fields
// must be true.
XEmbeddedResource bool
// 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:
//
// 1) anyOf:
// - type: integer
// - type: string
// 2) allOf:
// - anyOf:
// - type: integer
// - type: string
// - ... zero or more
XIntOrString bool
}
// +k8s:deepcopy-gen=true
// ValueValidation contains all schema fields not contributing to the structure of the schema.
type ValueValidation struct {
Format string
Maximum *float64
ExclusiveMaximum bool
Minimum *float64
ExclusiveMinimum bool
MaxLength *int64
MinLength *int64
Pattern string
MaxItems *int64
MinItems *int64
UniqueItems bool
MultipleOf *float64
Enum []JSON
MaxProperties *int64
MinProperties *int64
Required []string
AllOf []NestedValueValidation
OneOf []NestedValueValidation
AnyOf []NestedValueValidation
Not *NestedValueValidation
}
// +k8s:deepcopy-gen=true
// NestedValueValidation contains value validations, items and properties usable when nested
// under a logical junctor, and catch all structs for generic and vendor extensions schema fields.
type NestedValueValidation struct {
ValueValidation
Items *NestedValueValidation
Properties map[string]NestedValueValidation
// Anything set in the following will make the scheme
// non-structural, with the exception of these two patterns if
// x-kubernetes-int-or-string is true:
//
// 1) anyOf:
// - type: integer
// - type: string
// 2) allOf:
// - anyOf:
// - type: integer
// - type: string
// - ... zero or more
ForbiddenGenerics Generic
ForbiddenExtensions Extensions
}
// JSON wraps an arbitrary JSON value to be able to implement deepcopy.
type JSON struct {
Object interface{}
}
// DeepCopy creates a deep copy of the wrapped JSON value.
func (j JSON) DeepCopy() JSON {
return JSON{runtime.DeepCopyJSONValue(j.Object)}
}
// DeepCopyInto creates a deep copy of the wrapped JSON value and stores it in into.
func (j JSON) DeepCopyInto(into *JSON) {
into.Object = runtime.DeepCopyJSONValue(j.Object)
}

View File

@@ -0,0 +1,242 @@
/*
Copyright 2019 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 schema
import (
"reflect"
"k8s.io/apimachinery/pkg/util/validation/field"
)
var intOrStringAnyOf = []NestedValueValidation{
{ForbiddenGenerics: Generic{
Type: "integer",
}},
{ForbiddenGenerics: Generic{
Type: "string",
}},
}
type level int
const (
rootLevel level = 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.
func ValidateStructural(s *Structural, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, validateStructuralInvariants(s, rootLevel, fldPath)...)
allErrs = append(allErrs, validateStructuralCompleteness(s, fldPath)...)
return allErrs
}
// validateStructuralInvariants checks the invariants of a structural schema.
func validateStructuralInvariants(s *Structural, lvl level, fldPath *field.Path) field.ErrorList {
if s == nil {
return nil
}
allErrs := field.ErrorList{}
allErrs = append(allErrs, validateStructuralInvariants(s.Items, itemLevel, fldPath.Child("items"))...)
for k, v := range s.Properties {
allErrs = append(allErrs, validateStructuralInvariants(&v, fieldLevel, fldPath.Child("properties").Key(k))...)
}
allErrs = append(allErrs, validateGeneric(&s.Generic, lvl, fldPath)...)
allErrs = append(allErrs, validateExtensions(&s.Extensions, fldPath)...)
// detect the two IntOrString exceptions:
// 1) anyOf:
// - type: integer
// - type: string
// 2) allOf:
// - anyOf:
// - type: integer
// - type: string
// - ... zero or more
skipAnyOf := false
skipFirstAllOfAnyOf := false
if s.XIntOrString && s.ValueValidation != nil {
if len(s.ValueValidation.AnyOf) == 2 && reflect.DeepEqual(s.ValueValidation.AnyOf, intOrStringAnyOf) {
skipAnyOf = true
} else if len(s.ValueValidation.AllOf) >= 1 && len(s.ValueValidation.AllOf[0].AnyOf) == 2 && reflect.DeepEqual(s.ValueValidation.AllOf[0].AnyOf, intOrStringAnyOf) {
skipFirstAllOfAnyOf = true
}
}
allErrs = append(allErrs, validateValueValidation(s.ValueValidation, skipAnyOf, skipFirstAllOfAnyOf, fldPath)...)
if s.XEmbeddedResource && s.Type != "object" {
if len(s.Type) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must be object if x-kubernetes-embedded-resource is true"))
} else {
allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), s.Type, "must be object if x-kubernetes-embedded-resource is true"))
}
} else if len(s.Type) == 0 && !s.Extensions.XIntOrString && !s.Extensions.XPreserveUnknownFields {
switch lvl {
case rootLevel:
allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must not be empty at the root"))
case itemLevel:
allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must not be empty for specified array items"))
case fieldLevel:
allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must not be empty for specified object fields"))
}
}
if lvl == rootLevel && len(s.Type) > 0 && s.Type != "object" {
allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), s.Type, "must be object at the root"))
}
if s.XEmbeddedResource && !s.XPreserveUnknownFields && s.Properties == nil {
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"))
}
return allErrs
}
// validateGeneric checks the generic fields of a structural schema.
func validateGeneric(g *Generic, lvl level, fldPath *field.Path) field.ErrorList {
if g == nil {
return nil
}
allErrs := field.ErrorList{}
if g.AdditionalProperties != nil {
if lvl == rootLevel {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must not be used at the root"))
}
if g.AdditionalProperties.Structural != nil {
allErrs = append(allErrs, validateStructuralInvariants(g.AdditionalProperties.Structural, fieldLevel, fldPath.Child("additionalProperties"))...)
}
}
return allErrs
}
// validateExtensions checks Kubernetes vendor extensions of a structural schema.
func validateExtensions(x *Extensions, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if x.XIntOrString && x.XPreserveUnknownFields {
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"))
}
if x.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"))
}
return allErrs
}
// validateValueValidation checks the value validation in a structural schema.
func validateValueValidation(v *ValueValidation, skipAnyOf, skipFirstAllOfAnyOf bool, fldPath *field.Path) field.ErrorList {
if v == nil {
return nil
}
allErrs := field.ErrorList{}
if !skipAnyOf {
for i := range v.AnyOf {
allErrs = append(allErrs, validateNestedValueValidation(&v.AnyOf[i], false, false, fldPath.Child("anyOf").Index(i))...)
}
}
for i := range v.AllOf {
skipAnyOf := false
if skipFirstAllOfAnyOf && i == 0 {
skipAnyOf = true
}
allErrs = append(allErrs, validateNestedValueValidation(&v.AllOf[i], skipAnyOf, false, fldPath.Child("allOf").Index(i))...)
}
for i := range v.OneOf {
allErrs = append(allErrs, validateNestedValueValidation(&v.OneOf[i], false, false, fldPath.Child("oneOf").Index(i))...)
}
allErrs = append(allErrs, validateNestedValueValidation(v.Not, false, false, fldPath.Child("not"))...)
return allErrs
}
// validateNestedValueValidation checks the nested value validation under a logic junctor in a structural schema.
func validateNestedValueValidation(v *NestedValueValidation, skipAnyOf, skipAllOfAnyOf bool, fldPath *field.Path) field.ErrorList {
if v == nil {
return nil
}
allErrs := field.ErrorList{}
allErrs = append(allErrs, validateValueValidation(&v.ValueValidation, skipAnyOf, skipAllOfAnyOf, fldPath)...)
allErrs = append(allErrs, validateNestedValueValidation(v.Items, false, false, fldPath.Child("items"))...)
for k, fld := range v.Properties {
allErrs = append(allErrs, validateNestedValueValidation(&fld, false, false, fldPath.Child("properties").Key(k))...)
}
if len(v.ForbiddenGenerics.Type) > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("type"), "must be empty to be structural"))
}
if v.ForbiddenGenerics.AdditionalProperties != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must be undefined to be structural"))
}
if v.ForbiddenGenerics.Default.Object != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("default"), "must be undefined to be structural"))
}
if len(v.ForbiddenGenerics.Title) > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("title"), "must be empty to be structural"))
}
if len(v.ForbiddenGenerics.Description) > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("description"), "must be empty to be structural"))
}
if v.ForbiddenGenerics.Nullable {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("nullable"), "must be false to be structural"))
}
if v.ForbiddenExtensions.XPreserveUnknownFields {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-preserve-unknown-fields"), "must be false to be structural"))
}
if v.ForbiddenExtensions.XEmbeddedResource {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-embedded-resource"), "must be false to be structural"))
}
if v.ForbiddenExtensions.XIntOrString {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-int-or-string"), "must be false to be structural"))
}
return allErrs
}

View File

@@ -0,0 +1,71 @@
/*
Copyright 2019 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 schema
import (
"reflect"
"testing"
fuzz "github.com/google/gofuzz"
"k8s.io/apimachinery/pkg/util/rand"
)
func TestValidateNestedValueValidationComplete(t *testing.T) {
fuzzer := fuzz.New()
fuzzer.Funcs(
func(s *JSON, c fuzz.Continue) {
if c.RandBool() {
s.Object = float64(42.0)
}
},
func(s **StructuralOrBool, c fuzz.Continue) {
if c.RandBool() {
*s = &StructuralOrBool{}
}
},
)
fuzzer.NilChance(0)
// check that we didn't forget to check any forbidden generic field
tt := reflect.TypeOf(Generic{})
for i := 0; i < tt.NumField(); i++ {
vv := &NestedValueValidation{}
x := reflect.ValueOf(&vv.ForbiddenGenerics).Elem()
i := rand.Intn(x.NumField())
fuzzer.Fuzz(x.Field(i).Addr().Interface())
errs := validateNestedValueValidation(vv, false, false, nil)
if len(errs) == 0 && !reflect.DeepEqual(vv.ForbiddenGenerics, Generic{}) {
t.Errorf("expected ForbiddenGenerics validation errors for: %#v", vv)
}
}
// check that we didn't forget to check any forbidden extension field
tt = reflect.TypeOf(Extensions{})
for i := 0; i < tt.NumField(); i++ {
vv := &NestedValueValidation{}
x := reflect.ValueOf(&vv.ForbiddenExtensions).Elem()
i := rand.Intn(x.NumField())
fuzzer.Fuzz(x.Field(i).Addr().Interface())
errs := validateNestedValueValidation(vv, false, false, nil)
if len(errs) == 0 && !reflect.DeepEqual(vv.ForbiddenExtensions, Extensions{}) {
t.Errorf("expected ForbiddenExtensions validation errors for: %#v", vv)
}
}
}

View File

@@ -0,0 +1,245 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package schema
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Extensions) DeepCopyInto(out *Extensions) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Extensions.
func (in *Extensions) DeepCopy() *Extensions {
if in == nil {
return nil
}
out := new(Extensions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Generic) DeepCopyInto(out *Generic) {
*out = *in
out.Default = in.Default.DeepCopy()
if in.AdditionalProperties != nil {
in, out := &in.AdditionalProperties, &out.AdditionalProperties
*out = new(StructuralOrBool)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Generic.
func (in *Generic) DeepCopy() *Generic {
if in == nil {
return nil
}
out := new(Generic)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NestedValueValidation) DeepCopyInto(out *NestedValueValidation) {
*out = *in
in.ValueValidation.DeepCopyInto(&out.ValueValidation)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = new(NestedValueValidation)
(*in).DeepCopyInto(*out)
}
if in.Properties != nil {
in, out := &in.Properties, &out.Properties
*out = make(map[string]NestedValueValidation, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
in.ForbiddenGenerics.DeepCopyInto(&out.ForbiddenGenerics)
out.ForbiddenExtensions = in.ForbiddenExtensions
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NestedValueValidation.
func (in *NestedValueValidation) DeepCopy() *NestedValueValidation {
if in == nil {
return nil
}
out := new(NestedValueValidation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Structural) DeepCopyInto(out *Structural) {
*out = *in
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = new(Structural)
(*in).DeepCopyInto(*out)
}
if in.Properties != nil {
in, out := &in.Properties, &out.Properties
*out = make(map[string]Structural, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
in.Generic.DeepCopyInto(&out.Generic)
out.Extensions = in.Extensions
if in.ValueValidation != nil {
in, out := &in.ValueValidation, &out.ValueValidation
*out = new(ValueValidation)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Structural.
func (in *Structural) DeepCopy() *Structural {
if in == nil {
return nil
}
out := new(Structural)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StructuralOrBool) DeepCopyInto(out *StructuralOrBool) {
*out = *in
if in.Structural != nil {
in, out := &in.Structural, &out.Structural
*out = new(Structural)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructuralOrBool.
func (in *StructuralOrBool) DeepCopy() *StructuralOrBool {
if in == nil {
return nil
}
out := new(StructuralOrBool)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ValueValidation) DeepCopyInto(out *ValueValidation) {
*out = *in
if in.Maximum != nil {
in, out := &in.Maximum, &out.Maximum
*out = new(float64)
**out = **in
}
if in.Minimum != nil {
in, out := &in.Minimum, &out.Minimum
*out = new(float64)
**out = **in
}
if in.MaxLength != nil {
in, out := &in.MaxLength, &out.MaxLength
*out = new(int64)
**out = **in
}
if in.MinLength != nil {
in, out := &in.MinLength, &out.MinLength
*out = new(int64)
**out = **in
}
if in.MaxItems != nil {
in, out := &in.MaxItems, &out.MaxItems
*out = new(int64)
**out = **in
}
if in.MinItems != nil {
in, out := &in.MinItems, &out.MinItems
*out = new(int64)
**out = **in
}
if in.MultipleOf != nil {
in, out := &in.MultipleOf, &out.MultipleOf
*out = new(float64)
**out = **in
}
if in.Enum != nil {
in, out := &in.Enum, &out.Enum
*out = make([]JSON, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.MaxProperties != nil {
in, out := &in.MaxProperties, &out.MaxProperties
*out = new(int64)
**out = **in
}
if in.MinProperties != nil {
in, out := &in.MinProperties, &out.MinProperties
*out = new(int64)
**out = **in
}
if in.Required != nil {
in, out := &in.Required, &out.Required
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AllOf != nil {
in, out := &in.AllOf, &out.AllOf
*out = make([]NestedValueValidation, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.OneOf != nil {
in, out := &in.OneOf, &out.OneOf
*out = make([]NestedValueValidation, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AnyOf != nil {
in, out := &in.AnyOf, &out.AnyOf
*out = make([]NestedValueValidation, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Not != nil {
in, out := &in.Not, &out.Not
*out = new(NestedValueValidation)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValueValidation.
func (in *ValueValidation) DeepCopy() *ValueValidation {
if in == nil {
return nil
}
out := new(ValueValidation)
in.DeepCopyInto(out)
return out
}

View File

@@ -194,6 +194,16 @@ func ConvertJSONSchemaPropsWithPostProcess(in *apiextensions.JSONSchemaProps, ou
}
}
if in.XPreserveUnknownFields {
out.VendorExtensible.AddExtension("x-kubernetes-preserve-unknown-fields", true)
}
if in.XEmbeddedResource {
out.VendorExtensible.AddExtension("x-kubernetes-embedded-resource", true)
}
if in.XIntOrString {
out.VendorExtensible.AddExtension("x-kubernetes-int-or-string", true)
}
return nil
}

View File

@@ -0,0 +1,38 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["nonstructuralschema_controller.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema",
importpath = "k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
"//staging/src/k8s.io/client-go/util/workqueue:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,236 @@
/*
Copyright 2019 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 nonstructuralschema
import (
"fmt"
"sort"
"time"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
"k8s.io/klog"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
client "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
)
// ConditionController is maintaining the NonStructuralSchema condition.
type ConditionController struct {
crdClient client.CustomResourceDefinitionsGetter
crdLister listers.CustomResourceDefinitionLister
crdSynced cache.InformerSynced
// To allow injection for testing.
syncFn func(key string) error
queue workqueue.RateLimitingInterface
}
// NewConditionController constructs a non-structural schema condition controller.
func NewConditionController(
crdInformer informers.CustomResourceDefinitionInformer,
crdClient client.CustomResourceDefinitionsGetter,
) *ConditionController {
c := &ConditionController{
crdClient: crdClient,
crdLister: crdInformer.Lister(),
crdSynced: crdInformer.Informer().HasSynced,
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "non_structural_schema_condition_controller"),
}
crdInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: c.addCustomResourceDefinition,
UpdateFunc: c.updateCustomResourceDefinition,
DeleteFunc: nil,
})
c.syncFn = c.sync
return c
}
func calculateCondition(in *apiextensions.CustomResourceDefinition) *apiextensions.CustomResourceDefinitionCondition {
cond := &apiextensions.CustomResourceDefinitionCondition{
Type: apiextensions.NonStructuralSchema,
Status: apiextensions.ConditionUnknown,
}
allErrs := field.ErrorList{}
if in.Spec.Validation != nil && in.Spec.Validation.OpenAPIV3Schema != nil {
s, err := schema.NewStructural(in.Spec.Validation.OpenAPIV3Schema)
if err != nil {
cond.Reason = "StructuralError"
cond.Message = fmt.Sprintf("failed to check global validation schema: %v", err)
return cond
}
pth := field.NewPath("spec", "validation", "openAPIV3Schema")
allErrs = append(allErrs, schema.ValidateStructural(s, pth)...)
}
for _, v := range in.Spec.Versions {
if v.Schema == nil || v.Schema.OpenAPIV3Schema == nil {
continue
}
s, err := schema.NewStructural(v.Schema.OpenAPIV3Schema)
if err != nil {
cond.Reason = "StructuralError"
cond.Message = fmt.Sprintf("failed to check validation schema for version %s: %v", v.Name, err)
return cond
}
pth := field.NewPath("spec", "version").Key(v.Name).Child("schema", "openAPIV3Schema")
allErrs = append(allErrs, schema.ValidateStructural(s, pth)...)
}
if len(allErrs) == 0 {
return nil
}
// sort error messages. Otherwise, the condition message will change every sync due to
// randomized map iteration.
sort.Slice(allErrs, func(i, j int) bool {
return allErrs[i].Error() < allErrs[j].Error()
})
cond.Status = apiextensions.ConditionTrue
cond.Reason = "Violations"
cond.Message = allErrs.ToAggregate().Error()
return cond
}
func (c *ConditionController) sync(key string) error {
inCustomResourceDefinition, err := c.crdLister.Get(key)
if apierrors.IsNotFound(err) {
return nil
}
if err != nil {
return err
}
// check old condition
cond := calculateCondition(inCustomResourceDefinition)
old := apiextensions.FindCRDCondition(inCustomResourceDefinition, apiextensions.NonStructuralSchema)
if cond == nil && old == nil {
return nil
}
if cond != nil && old != nil && old.Status == cond.Status && old.Reason == cond.Reason && old.Message == cond.Message {
return nil
}
// update condition
crd := inCustomResourceDefinition.DeepCopy()
if cond == nil {
apiextensions.RemoveCRDCondition(crd, apiextensions.NonStructuralSchema)
} else {
cond.LastTransitionTime = metav1.NewTime(time.Now())
apiextensions.SetCRDCondition(crd, *cond)
}
_, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
if apierrors.IsNotFound(err) || apierrors.IsConflict(err) {
// deleted or changed in the meantime, we'll get called again
return nil
}
if err != nil {
return err
}
return nil
}
// Run starts the controller.
func (c *ConditionController) Run(threadiness int, stopCh <-chan struct{}) {
defer utilruntime.HandleCrash()
defer c.queue.ShutDown()
klog.Infof("Starting NonStructuralSchemaConditionController")
defer klog.Infof("Shutting down NonStructuralSchemaConditionController")
if !cache.WaitForCacheSync(stopCh, c.crdSynced) {
return
}
for i := 0; i < threadiness; i++ {
go wait.Until(c.runWorker, time.Second, stopCh)
}
<-stopCh
}
func (c *ConditionController) runWorker() {
for c.processNextWorkItem() {
}
}
// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit.
func (c *ConditionController) processNextWorkItem() bool {
key, quit := c.queue.Get()
if quit {
return false
}
defer c.queue.Done(key)
err := c.syncFn(key.(string))
if err == nil {
c.queue.Forget(key)
return true
}
utilruntime.HandleError(fmt.Errorf("%v failed with: %v", key, err))
c.queue.AddRateLimited(key)
return true
}
func (c *ConditionController) enqueue(obj *apiextensions.CustomResourceDefinition) {
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
if err != nil {
utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %#v: %v", obj, err))
return
}
c.queue.Add(key)
}
func (c *ConditionController) addCustomResourceDefinition(obj interface{}) {
castObj := obj.(*apiextensions.CustomResourceDefinition)
klog.V(4).Infof("Adding %s", castObj.Name)
c.enqueue(castObj)
}
func (c *ConditionController) updateCustomResourceDefinition(obj, _ interface{}) {
castObj := obj.(*apiextensions.CustomResourceDefinition)
klog.V(4).Infof("Updating %s", castObj.Name)
c.enqueue(castObj)
}

View File

@@ -26,6 +26,7 @@ go_test(
"//staging/src/k8s.io/api/autoscaling/v1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/cmd/server/options:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/test/integration/fixtures:go_default_library",
@@ -40,6 +41,7 @@ go_test(
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/features:go_default_library",

View File

@@ -22,10 +22,12 @@ import (
"testing"
"time"
clientschema "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/util/yaml"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
@@ -539,6 +541,802 @@ func TestForbiddenFieldsInSchema(t *testing.T) {
}
}
func TestNonStructuralSchemaConditionUpdate(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceWebhookConversion, true)()
tearDown, apiExtensionClient, _, err := fixtures.StartDefaultServerWithClients(t)
if err != nil {
t.Fatal(err)
}
defer tearDown()
manifest := `
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: foos.tests.apiextensions.k8s.io
spec:
group: tests.apiextensions.k8s.io
version: v1beta1
names:
plural: foos
singular: foo
kind: Foo
listKind: Foolist
scope: Namespaced
validation:
openAPIV3Schema:
type: object
properties:
a: {}
versions:
- name: v1beta1
served: true
storage: true
`
// decode CRD manifest
obj, _, err := clientschema.Codecs.UniversalDeserializer().Decode([]byte(manifest), nil, nil)
if err != nil {
t.Fatalf("failed decoding of: %v\n\n%s", err, manifest)
}
crd := obj.(*apiextensionsv1beta1.CustomResourceDefinition)
name := crd.Name
// save schema for later
origSchema := crd.Spec.Validation.OpenAPIV3Schema
// create CRDs
t.Logf("Creating CRD %s", crd.Name)
crd, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd)
if err != nil {
t.Fatalf("unexpected create error: %v", err)
}
// wait for condition with violations
t.Log("Waiting for NonStructuralSchema condition")
var cond *apiextensionsv1beta1.CustomResourceDefinitionCondition
err = wait.PollImmediate(100*time.Millisecond, 5*time.Second, func() (bool, error) {
obj, err := apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(name, metav1.GetOptions{})
if err != nil {
return false, err
}
cond = findCRDCondition(obj, apiextensionsv1beta1.NonStructuralSchema)
return cond != nil, nil
})
if err != nil {
t.Fatalf("unexpected error waiting for NonStructuralSchema condition: %v", cond)
}
if v := "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)
}
// remove schema
t.Log("Remove schema")
for retry := 0; retry < 5; retry++ {
crd, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(name, metav1.GetOptions{})
if err != nil {
t.Fatalf("unexpected get error: %v", err)
}
crd.Spec.Validation = nil
crd, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Update(crd)
if apierrors.IsConflict(err) {
continue
}
if err != nil {
t.Fatalf("unexpected update error: %v", err)
}
}
if err != nil {
t.Fatalf("unexpected update error: %v", err)
}
// wait for condition to go away
t.Log("Wait for condition to disappear")
err = wait.PollImmediate(100*time.Millisecond, 5*time.Second, func() (bool, error) {
obj, err := apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(name, metav1.GetOptions{})
if err != nil {
return false, err
}
cond = findCRDCondition(obj, apiextensionsv1beta1.NonStructuralSchema)
return cond == nil, nil
})
if err != nil {
t.Fatalf("unexpected error waiting for NonStructuralSchema condition: %v", cond)
}
// readd schema
t.Log("Readd schema")
for retry := 0; retry < 5; retry++ {
crd, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(name, metav1.GetOptions{})
if err != nil {
t.Fatalf("unexpected get error: %v", err)
}
crd.Spec.Validation = &apiextensionsv1beta1.CustomResourceValidation{OpenAPIV3Schema: origSchema}
crd, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Update(crd)
if apierrors.IsConflict(err) {
continue
}
if err != nil {
t.Fatalf("unexpected update error: %v", err)
}
}
if err != nil {
t.Fatalf("unexpected update error: %v", err)
}
// wait for condition with violations
t.Log("Wait for condition to reappear")
err = wait.PollImmediate(100*time.Millisecond, 5*time.Second, func() (bool, error) {
obj, err := apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(name, metav1.GetOptions{})
if err != nil {
return false, err
}
cond = findCRDCondition(obj, apiextensionsv1beta1.NonStructuralSchema)
return cond != nil, nil
})
if err != nil {
t.Fatalf("unexpected error waiting for NonStructuralSchema condition: %v", cond)
}
if v := "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)
}
}
func TestNonStructuralSchemaCondition(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceWebhookConversion, true)()
tearDown, apiExtensionClient, _, err := fixtures.StartDefaultServerWithClients(t)
if err != nil {
t.Fatal(err)
}
defer tearDown()
tmpl := `
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
spec:
version: v1beta1
names:
plural: foos
singular: foo
kind: Foo
listKind: Foolist
scope: Namespaced
validation: GLOBAL_SCHEMA
versions:
- name: v1beta1
served: true
storage: true
schema: V1BETA1_SCHEMA
- name: v1
served: true
schema: V1_SCHEMA
`
type Test struct {
desc string
globalSchema, v1Schema, v1beta1Schema string
expectedCreateError bool
expectedViolations []string
unexpectedViolations []string
}
tests := []Test{
{"empty", "", "", "", false, nil, nil},
{
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]",
},
},
}
for i := range tests {
tst := tests[i]
t.Run(tst.desc, func(t *testing.T) {
// plug in schemas
manifest := strings.NewReplacer(
"GLOBAL_SCHEMA", toValidationJSON(tst.globalSchema),
"V1BETA1_SCHEMA", toValidationJSON(tst.v1beta1Schema),
"V1_SCHEMA", toValidationJSON(tst.v1Schema),
).Replace(tmpl)
// decode CRD manifest
obj, _, err := clientschema.Codecs.UniversalDeserializer().Decode([]byte(manifest), nil, nil)
if err != nil {
t.Fatalf("failed decoding of: %v\n\n%s", err, manifest)
}
crd := obj.(*apiextensionsv1beta1.CustomResourceDefinition)
crd.Spec.Group = fmt.Sprintf("tests-%d.apiextension.k8s.io", i)
crd.Name = fmt.Sprintf("foos.%s", crd.Spec.Group)
// create CRDs
crd, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd)
if tst.expectedCreateError && err == nil {
t.Fatalf("expected error, got none")
} else if !tst.expectedCreateError && err != nil {
t.Fatalf("unexpected create error: %v", err)
}
if err != nil {
return
}
if len(tst.expectedViolations) == 0 {
// wait for condition to not appear
var cond *apiextensionsv1beta1.CustomResourceDefinitionCondition
err := wait.PollImmediate(100*time.Millisecond, 5*time.Second, func() (bool, error) {
obj, err := apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(crd.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
cond = findCRDCondition(obj, apiextensionsv1beta1.NonStructuralSchema)
if cond == nil {
return false, nil
}
return true, nil
})
if err != wait.ErrWaitTimeout {
t.Fatalf("expected no NonStructuralSchema condition, but got one: %v", cond)
}
return
}
// wait for condition to appear with the given violations
var cond *apiextensionsv1beta1.CustomResourceDefinitionCondition
err = wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
obj, err := apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(crd.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
cond = findCRDCondition(obj, apiextensionsv1beta1.NonStructuralSchema)
if cond != nil {
return true, nil
}
return false, nil
})
if err != nil {
t.Fatalf("unexpected error waiting for violations in NonStructuralSchema condition: %v", err)
}
// check that the condition looks good
if cond.Reason != "Violations" {
t.Errorf("expected reason Violations, got: %v", cond.Reason)
}
if cond.Status != apiextensionsv1beta1.ConditionTrue {
t.Errorf("expected reason True, got: %v", cond.Status)
}
// check that we got all violations
t.Logf("Got violations: %q", cond.Message)
for _, v := range tst.expectedViolations {
if strings.Index(cond.Message, v) == -1 {
t.Errorf("expected violation %q, but didn't get it", v)
}
}
for _, v := range tst.unexpectedViolations {
if strings.Index(cond.Message, v) != -1 {
t.Errorf("unexpected violation %q", v)
}
}
})
}
}
// findCRDCondition returns the condition you're looking for or nil.
func findCRDCondition(crd *apiextensionsv1beta1.CustomResourceDefinition, conditionType apiextensionsv1beta1.CustomResourceDefinitionConditionType) *apiextensionsv1beta1.CustomResourceDefinitionCondition {
for i := range crd.Status.Conditions {
if crd.Status.Conditions[i].Type == conditionType {
return &crd.Status.Conditions[i]
}
}
return nil
}
func toValidationJSON(yml string) string {
if len(yml) == 0 {
return "null"
}
bs, err := yaml.ToJSON([]byte(yml))
if err != nil {
panic(err)
}
return fmt.Sprintf("{\"openAPIV3Schema\": %s}", string(bs))
}
func float64Ptr(f float64) *float64 {
return &f
}

2
vendor/modules.txt vendored
View File

@@ -1066,6 +1066,7 @@ k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1
k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation
k8s.io/apiextensions-apiserver/pkg/apiserver
k8s.io/apiextensions-apiserver/pkg/apiserver/conversion
k8s.io/apiextensions-apiserver/pkg/apiserver/schema
k8s.io/apiextensions-apiserver/pkg/apiserver/validation
k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset
k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme
@@ -1087,6 +1088,7 @@ k8s.io/apiextensions-apiserver/pkg/cmd/server/options
k8s.io/apiextensions-apiserver/pkg/cmd/server/testing
k8s.io/apiextensions-apiserver/pkg/controller/establish
k8s.io/apiextensions-apiserver/pkg/controller/finalizer
k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema
k8s.io/apiextensions-apiserver/pkg/controller/openapi
k8s.io/apiextensions-apiserver/pkg/controller/status
k8s.io/apiextensions-apiserver/pkg/crdserverscheme