Merge pull request #106051 from jpbetz/cel-port
Feature implementation: Validation rules for Custom Resource Definitions using the CEL expression language
This commit is contained in:
commit
c84da4e3e6
@ -419,6 +419,7 @@ API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiexten
|
||||
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XListType
|
||||
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XMapType
|
||||
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XPreserveUnknownFields
|
||||
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XValidations
|
||||
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaPropsOrArray,JSONSchemas
|
||||
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaPropsOrArray,Schema
|
||||
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaPropsOrBool,Allows
|
||||
@ -435,6 +436,7 @@ 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,JSONSchemaProps,XListType
|
||||
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,JSONSchemaProps,XMapType
|
||||
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,JSONSchemaProps,XValidations
|
||||
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
|
||||
|
30
api/openapi-spec/swagger.json
generated
30
api/openapi-spec/swagger.json
generated
@ -15625,6 +15625,19 @@
|
||||
"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. This can either be true or undefined. False is forbidden.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"x-kubernetes-validations": {
|
||||
"description": "x-kubernetes-validations describes a list of validation rules written in the CEL expression language. This field is an alpha-level. Using this field requires the feature gate `CustomResourceValidationExpressions` to be enabled.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.ValidationRule"
|
||||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-map-keys": [
|
||||
"rule"
|
||||
],
|
||||
"x-kubernetes-list-type": "map",
|
||||
"x-kubernetes-patch-merge-key": "rule",
|
||||
"x-kubernetes-patch-strategy": "merge"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@ -15665,6 +15678,23 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.ValidationRule": {
|
||||
"description": "ValidationRule describes a validation rule written in the CEL expression language.",
|
||||
"properties": {
|
||||
"message": {
|
||||
"description": "Message represents the message displayed when validation fails. The message is required if the Rule contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\"",
|
||||
"type": "string"
|
||||
},
|
||||
"rule": {
|
||||
"description": "Rule represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec The Rule is scoped to the location of the x-kubernetes-validations extension in the schema. The `self` variable in the CEL expression is bound to the scoped value. Example: - Rule scoped to the root of a resource with a status subresource: {\"rule\": \"self.status.actual <= self.spec.maxDesired\"}\n\nIf the Rule is scoped to an object with properties, the accessible properties of the object are field selectable via `self.field` and field presence can be checked via `has(self.field)`. Null valued fields are treated as absent fields in CEL expressions. If the Rule is scoped to an object with additionalProperties (i.e. a map) the value of the map are accessible via `self[mapKey]`, map containment can be checked via `mapKey in self` and all entries of the map are accessible via CEL macros and functions such as `self.all(...)`. If the Rule is scoped to an array, the elements of the array are accessible via `self[i]` and also by macros and functions. If the Rule is scoped to a scalar, `self` is bound to the scalar value. Examples: - Rule scoped to a map of objects: {\"rule\": \"self.components['Widget'].priority < 10\"} - Rule scoped to a list of integers: {\"rule\": \"self.values.all(value, value >= 0 && value < 100)\"} - Rule scoped to a string value: {\"rule\": \"self.startsWith('kube')\"}\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object and from any x-kubernetes-embedded-resource annotated objects. No other metadata properties are accessible.\n\nUnknown data preserved in custom resources via x-kubernetes-preserve-unknown-fields is not accessible in CEL expressions. This includes: - Unknown field values that are preserved by object schemas with x-kubernetes-preserve-unknown-fields. - Object properties where the property schema is of an \"unknown type\". An \"unknown type\" is recursively defined as:\n - A schema with no type and x-kubernetes-preserve-unknown-fields set to true\n - An array where the items schema is of an \"unknown type\"\n - An object where the additionalProperties schema is of an \"unknown type\"\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Rule accessing a property named \"namespace\": {\"rule\": \"self.__namespace__ > 0\"}\n - Rule accessing a property named \"x-prop\": {\"rule\": \"self.x__dash__prop > 0\"}\n - Rule accessing a property named \"redact__d\": {\"rule\": \"self.redact__underscores__d > 0\"}\n\nEquality on arrays with x-kubernetes-list-type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"rule"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.WebhookClientConfig": {
|
||||
"description": "WebhookClientConfig contains the information to make a TLS connection with the webhook.",
|
||||
"properties": {
|
||||
|
@ -122,6 +122,80 @@ type JSONSchemaProps struct {
|
||||
// Atomic maps will be entirely replaced when updated.
|
||||
// +optional
|
||||
XMapType *string
|
||||
|
||||
// x-kubernetes-validations -kubernetes-validations describes a list of validation rules written in the CEL expression language.
|
||||
// This field is an alpha-level. Using this field requires the feature gate `CustomResourceValidationExpressions` to be enabled.
|
||||
// +patchMergeKey=rule
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=rule
|
||||
XValidations ValidationRules
|
||||
}
|
||||
|
||||
// ValidationRules describes a list of validation rules written in the CEL expression language.
|
||||
type ValidationRules []ValidationRule
|
||||
|
||||
// ValidationRule describes a validation rule written in the CEL expression language.
|
||||
type ValidationRule struct {
|
||||
// Rule represents the expression which will be evaluated by CEL.
|
||||
// ref: https://github.com/google/cel-spec
|
||||
// The Rule is scoped to the location of the x-kubernetes-validations extension in the schema.
|
||||
// The `self` variable in the CEL expression is bound to the scoped value.
|
||||
// Example:
|
||||
// - Rule scoped to the root of a resource with a status subresource: {"rule": "self.status.actual <= self.spec.maxDesired"}
|
||||
//
|
||||
// If the Rule is scoped to an object with properties, the accessible properties of the object are field selectable
|
||||
// via `self.field` and field presence can be checked via `has(self.field)`. Null valued fields are treated as
|
||||
// absent fields in CEL expressions.
|
||||
// If the Rule is scoped to an object with additionalProperties (i.e. a map) the value of the map
|
||||
// are accessible via `self[mapKey]`, map containment can be checked via `mapKey in self` and all entries of the map
|
||||
// are accessible via CEL macros and functions such as `self.all(...)`.
|
||||
// If the Rule is scoped to an array, the elements of the array are accessible via `self[i]` and also by macros and
|
||||
// functions.
|
||||
// If the Rule is scoped to a scalar, `self` is bound to the scalar value.
|
||||
// Examples:
|
||||
// - Rule scoped to a map of objects: {"rule": "self.components['Widget'].priority < 10"}
|
||||
// - Rule scoped to a list of integers: {"rule": "self.values.all(value, value >= 0 && value < 100)"}
|
||||
// - Rule scoped to a string value: {"rule": "self.startsWith('kube')"}
|
||||
//
|
||||
// The `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the
|
||||
// object and from any x-kubernetes-embedded-resource annotated objects. No other metadata properties are accessible.
|
||||
//
|
||||
// Unknown data preserved in custom resources via x-kubernetes-preserve-unknown-fields is not accessible in CEL
|
||||
// expressions. This includes:
|
||||
// - Unknown field values that are preserved by object schemas with x-kubernetes-preserve-unknown-fields.
|
||||
// - Object properties where the property schema is of an "unknown type". An "unknown type" is recursively defined as:
|
||||
// - A schema with no type and x-kubernetes-preserve-unknown-fields set to true
|
||||
// - An array where the items schema is of an "unknown type"
|
||||
// - An object where the additionalProperties schema is of an "unknown type"
|
||||
//
|
||||
// Only property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible.
|
||||
// Accessible property names are escaped according to the following rules when accessed in the expression:
|
||||
// - '__' escapes to '__underscores__'
|
||||
// - '.' escapes to '__dot__'
|
||||
// - '-' escapes to '__dash__'
|
||||
// - '/' escapes to '__slash__'
|
||||
// - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:
|
||||
// "true", "false", "null", "in", "as", "break", "const", "continue", "else", "for", "function", "if",
|
||||
// "import", "let", "loop", "package", "namespace", "return".
|
||||
// Examples:
|
||||
// - Rule accessing a property named "namespace": {"rule": "self.__namespace__ > 0"}
|
||||
// - Rule accessing a property named "x-prop": {"rule": "self.x__dash__prop > 0"}
|
||||
// - Rule accessing a property named "redact__d": {"rule": "self.redact__underscores__d > 0"}
|
||||
//
|
||||
// Equality on arrays with x-kubernetes-list-type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1].
|
||||
// Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:
|
||||
// - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and
|
||||
// non-intersecting elements in `Y` are appended, retaining their partial order.
|
||||
// - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values
|
||||
// are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with
|
||||
// non-intersecting keys are appended, retaining their partial order.
|
||||
Rule string
|
||||
// Message represents the message displayed when validation fails. The message is required if the Rule contains
|
||||
// line breaks. The message must not contain line breaks.
|
||||
// If unset, the message is "failed rule: {Rule}".
|
||||
// e.g. "must be a URL with the host matching spec.host"
|
||||
Message string
|
||||
}
|
||||
|
||||
// JSON represents any valid JSON value.
|
||||
|
@ -692,10 +692,38 @@ func (m *ServiceReference) XXX_DiscardUnknown() {
|
||||
|
||||
var xxx_messageInfo_ServiceReference proto.InternalMessageInfo
|
||||
|
||||
func (m *ValidationRule) Reset() { *m = ValidationRule{} }
|
||||
func (*ValidationRule) ProtoMessage() {}
|
||||
func (*ValidationRule) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_f5a35c9667703937, []int{23}
|
||||
}
|
||||
func (m *ValidationRule) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *ValidationRule) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
func (m *ValidationRule) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ValidationRule.Merge(m, src)
|
||||
}
|
||||
func (m *ValidationRule) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *ValidationRule) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ValidationRule.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ValidationRule proto.InternalMessageInfo
|
||||
|
||||
func (m *WebhookClientConfig) Reset() { *m = WebhookClientConfig{} }
|
||||
func (*WebhookClientConfig) ProtoMessage() {}
|
||||
func (*WebhookClientConfig) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_f5a35c9667703937, []int{23}
|
||||
return fileDescriptor_f5a35c9667703937, []int{24}
|
||||
}
|
||||
func (m *WebhookClientConfig) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -723,7 +751,7 @@ var xxx_messageInfo_WebhookClientConfig proto.InternalMessageInfo
|
||||
func (m *WebhookConversion) Reset() { *m = WebhookConversion{} }
|
||||
func (*WebhookConversion) ProtoMessage() {}
|
||||
func (*WebhookConversion) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_f5a35c9667703937, []int{24}
|
||||
return fileDescriptor_f5a35c9667703937, []int{25}
|
||||
}
|
||||
func (m *WebhookConversion) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -776,6 +804,7 @@ func init() {
|
||||
proto.RegisterType((*JSONSchemaPropsOrBool)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1.JSONSchemaPropsOrBool")
|
||||
proto.RegisterType((*JSONSchemaPropsOrStringArray)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1.JSONSchemaPropsOrStringArray")
|
||||
proto.RegisterType((*ServiceReference)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1.ServiceReference")
|
||||
proto.RegisterType((*ValidationRule)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1.ValidationRule")
|
||||
proto.RegisterType((*WebhookClientConfig)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1.WebhookClientConfig")
|
||||
proto.RegisterType((*WebhookConversion)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1.WebhookConversion")
|
||||
}
|
||||
@ -785,194 +814,198 @@ func init() {
|
||||
}
|
||||
|
||||
var fileDescriptor_f5a35c9667703937 = []byte{
|
||||
// 2989 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x5a, 0xdf, 0x6f, 0x5c, 0x47,
|
||||
0xf5, 0xcf, 0x5d, 0x7b, 0xed, 0xf5, 0xd8, 0x8e, 0xed, 0x49, 0xec, 0xef, 0x8d, 0x9b, 0x78, 0x9d,
|
||||
0xed, 0xb7, 0xc1, 0x6d, 0xd3, 0x75, 0x63, 0x5a, 0x5a, 0x2a, 0x04, 0xf2, 0xda, 0x4e, 0x71, 0x63,
|
||||
0xc7, 0xd6, 0x6c, 0x92, 0xba, 0x2d, 0x52, 0x3b, 0xde, 0x3b, 0x5e, 0xdf, 0xfa, 0xfe, 0xca, 0x9d,
|
||||
0x7b, 0xd7, 0xb6, 0x04, 0x52, 0x05, 0xaa, 0x80, 0x4a, 0x50, 0x1e, 0x10, 0x3c, 0x21, 0x84, 0x50,
|
||||
0x1f, 0xe0, 0x01, 0xde, 0xe0, 0x5f, 0xe8, 0x0b, 0x52, 0x9f, 0xa0, 0x12, 0xd2, 0x8a, 0x2e, 0x7f,
|
||||
0x02, 0x20, 0x84, 0x1f, 0x10, 0x9a, 0x1f, 0x77, 0xee, 0xec, 0xdd, 0xdd, 0x24, 0x8a, 0xd7, 0xed,
|
||||
0x9b, 0xf7, 0xfc, 0xfa, 0x9c, 0x39, 0x73, 0xe6, 0xcc, 0x99, 0x73, 0x0d, 0xf0, 0xc1, 0xcb, 0xb4,
|
||||
0x6c, 0xfb, 0x8b, 0x07, 0xf1, 0x2e, 0x09, 0x3d, 0x12, 0x11, 0xba, 0xd8, 0x20, 0x9e, 0xe5, 0x87,
|
||||
0x8b, 0x92, 0x81, 0x03, 0x9b, 0x1c, 0x45, 0xc4, 0xa3, 0xb6, 0xef, 0xd1, 0xe7, 0x70, 0x60, 0x53,
|
||||
0x12, 0x36, 0x48, 0xb8, 0x18, 0x1c, 0xd4, 0x19, 0x8f, 0xb6, 0x0b, 0x2c, 0x36, 0x6e, 0x2c, 0xd6,
|
||||
0x89, 0x47, 0x42, 0x1c, 0x11, 0xab, 0x1c, 0x84, 0x7e, 0xe4, 0xc3, 0x97, 0x85, 0xa5, 0x72, 0x9b,
|
||||
0xe0, 0xdb, 0xca, 0x52, 0x39, 0x38, 0xa8, 0x33, 0x1e, 0x6d, 0x17, 0x28, 0x37, 0x6e, 0xcc, 0x3e,
|
||||
0x57, 0xb7, 0xa3, 0xfd, 0x78, 0xb7, 0x5c, 0xf3, 0xdd, 0xc5, 0xba, 0x5f, 0xf7, 0x17, 0xb9, 0xc1,
|
||||
0xdd, 0x78, 0x8f, 0xff, 0xe2, 0x3f, 0xf8, 0x5f, 0x02, 0x68, 0xf6, 0x85, 0xd4, 0x65, 0x17, 0xd7,
|
||||
0xf6, 0x6d, 0x8f, 0x84, 0xc7, 0xa9, 0x9f, 0x2e, 0x89, 0x70, 0x17, 0xf7, 0x66, 0x17, 0x7b, 0x69,
|
||||
0x85, 0xb1, 0x17, 0xd9, 0x2e, 0xe9, 0x50, 0xf8, 0xca, 0xc3, 0x14, 0x68, 0x6d, 0x9f, 0xb8, 0x38,
|
||||
0xab, 0x57, 0x3a, 0x31, 0xc0, 0xd4, 0x8a, 0xef, 0x35, 0x48, 0xc8, 0x16, 0x88, 0xc8, 0xfd, 0x98,
|
||||
0xd0, 0x08, 0x56, 0xc0, 0x40, 0x6c, 0x5b, 0xa6, 0x31, 0x6f, 0x2c, 0x8c, 0x54, 0x9e, 0xff, 0xb8,
|
||||
0x59, 0x3c, 0xd7, 0x6a, 0x16, 0x07, 0xee, 0xae, 0xaf, 0x9e, 0x34, 0x8b, 0x57, 0x7b, 0x21, 0x45,
|
||||
0xc7, 0x01, 0xa1, 0xe5, 0xbb, 0xeb, 0xab, 0x88, 0x29, 0xc3, 0x57, 0xc1, 0x94, 0x45, 0xa8, 0x1d,
|
||||
0x12, 0x6b, 0x79, 0x7b, 0xfd, 0x9e, 0xb0, 0x6f, 0xe6, 0xb8, 0xc5, 0x4b, 0xd2, 0xe2, 0xd4, 0x6a,
|
||||
0x56, 0x00, 0x75, 0xea, 0xc0, 0x1d, 0x30, 0xec, 0xef, 0xbe, 0x4b, 0x6a, 0x11, 0x35, 0x07, 0xe6,
|
||||
0x07, 0x16, 0x46, 0x97, 0x9e, 0x2b, 0xa7, 0x9b, 0xa7, 0x5c, 0xe0, 0x3b, 0x26, 0x17, 0x5b, 0x46,
|
||||
0xf8, 0x70, 0x2d, 0xd9, 0xb4, 0xca, 0x84, 0x44, 0x1b, 0xde, 0x12, 0x56, 0x50, 0x62, 0xae, 0xf4,
|
||||
0xeb, 0x1c, 0x80, 0xfa, 0xe2, 0x69, 0xe0, 0x7b, 0x94, 0xf4, 0x65, 0xf5, 0x14, 0x4c, 0xd6, 0xb8,
|
||||
0xe5, 0x88, 0x58, 0x12, 0xd7, 0xcc, 0x3d, 0x8e, 0xf7, 0xa6, 0xc4, 0x9f, 0x5c, 0xc9, 0x98, 0x43,
|
||||
0x1d, 0x00, 0xf0, 0x0e, 0x18, 0x0a, 0x09, 0x8d, 0x9d, 0xc8, 0x1c, 0x98, 0x37, 0x16, 0x46, 0x97,
|
||||
0xae, 0xf7, 0x84, 0xe2, 0xa9, 0xcd, 0x92, 0xaf, 0xdc, 0xb8, 0x51, 0xae, 0x46, 0x38, 0x8a, 0x69,
|
||||
0xe5, 0xbc, 0x44, 0x1a, 0x42, 0xdc, 0x06, 0x92, 0xb6, 0x4a, 0xff, 0x35, 0xc0, 0xa4, 0x1e, 0xa5,
|
||||
0x86, 0x4d, 0x0e, 0x61, 0x08, 0x86, 0x43, 0x91, 0x2c, 0x3c, 0x4e, 0xa3, 0x4b, 0xb7, 0xca, 0x8f,
|
||||
0x7b, 0xa2, 0xca, 0x1d, 0xf9, 0x57, 0x19, 0x65, 0xdb, 0x25, 0x7f, 0xa0, 0x04, 0x08, 0x36, 0x40,
|
||||
0x21, 0x94, 0x7b, 0xc4, 0x13, 0x69, 0x74, 0x69, 0xa3, 0x3f, 0xa0, 0xc2, 0x66, 0x65, 0xac, 0xd5,
|
||||
0x2c, 0x16, 0x92, 0x5f, 0x48, 0x61, 0x95, 0x7e, 0x99, 0x03, 0x73, 0x2b, 0x31, 0x8d, 0x7c, 0x17,
|
||||
0x11, 0xea, 0xc7, 0x61, 0x8d, 0xac, 0xf8, 0x4e, 0xec, 0x7a, 0xab, 0x64, 0xcf, 0xf6, 0xec, 0x88,
|
||||
0xe5, 0xe8, 0x3c, 0x18, 0xf4, 0xb0, 0x4b, 0x64, 0xce, 0x8c, 0xc9, 0x48, 0x0e, 0xde, 0xc6, 0x2e,
|
||||
0x41, 0x9c, 0xc3, 0x24, 0x58, 0x8a, 0xc8, 0x13, 0xa0, 0x24, 0xee, 0x1c, 0x07, 0x04, 0x71, 0x0e,
|
||||
0xbc, 0x06, 0x86, 0xf6, 0xfc, 0xd0, 0xc5, 0x62, 0xf7, 0x46, 0xd2, 0xfd, 0xb8, 0xc9, 0xa9, 0x48,
|
||||
0x72, 0xe1, 0x8b, 0x60, 0xd4, 0x22, 0xb4, 0x16, 0xda, 0x01, 0x83, 0x36, 0x07, 0xb9, 0xf0, 0x05,
|
||||
0x29, 0x3c, 0xba, 0x9a, 0xb2, 0x90, 0x2e, 0x07, 0xaf, 0x83, 0x42, 0x10, 0xda, 0x7e, 0x68, 0x47,
|
||||
0xc7, 0x66, 0x7e, 0xde, 0x58, 0xc8, 0x57, 0x26, 0xa5, 0x4e, 0x61, 0x5b, 0xd2, 0x91, 0x92, 0x60,
|
||||
0xd2, 0xef, 0x52, 0xdf, 0xdb, 0xc6, 0xd1, 0xbe, 0x39, 0xc4, 0x11, 0x94, 0xf4, 0x6b, 0xd5, 0xad,
|
||||
0xdb, 0x8c, 0x8e, 0x94, 0x44, 0xe9, 0xcf, 0x06, 0x30, 0xb3, 0x11, 0x4a, 0xc2, 0x0b, 0x6f, 0x82,
|
||||
0x02, 0x8d, 0x58, 0xcd, 0xa9, 0x1f, 0xcb, 0xf8, 0x3c, 0x93, 0x98, 0xaa, 0x4a, 0xfa, 0x49, 0xb3,
|
||||
0x38, 0x93, 0x6a, 0x24, 0x54, 0x1e, 0x1b, 0xa5, 0xcb, 0x52, 0xee, 0x90, 0xec, 0xee, 0xfb, 0xfe,
|
||||
0x81, 0xdc, 0xfd, 0x53, 0xa4, 0xdc, 0xeb, 0xc2, 0x50, 0x8a, 0x29, 0x52, 0x4e, 0x92, 0x51, 0x02,
|
||||
0x54, 0xfa, 0x4f, 0x2e, 0xbb, 0x30, 0x6d, 0xd3, 0xdf, 0x01, 0x05, 0x76, 0x84, 0x2c, 0x1c, 0x61,
|
||||
0x79, 0x08, 0x9e, 0x7f, 0xb4, 0x03, 0x27, 0xce, 0xeb, 0x26, 0x89, 0x70, 0x05, 0xca, 0x50, 0x80,
|
||||
0x94, 0x86, 0x94, 0x55, 0x78, 0x04, 0x06, 0x69, 0x40, 0x6a, 0x72, 0xbd, 0xf7, 0x4e, 0x91, 0xed,
|
||||
0x3d, 0xd6, 0x50, 0x0d, 0x48, 0x2d, 0x4d, 0x46, 0xf6, 0x0b, 0x71, 0x44, 0xf8, 0x9e, 0x01, 0x86,
|
||||
0x28, 0xaf, 0x0b, 0xb2, 0x96, 0xec, 0x9c, 0x01, 0x78, 0xa6, 0xee, 0x88, 0xdf, 0x48, 0xe2, 0x96,
|
||||
0xfe, 0x99, 0x03, 0x57, 0x7b, 0xa9, 0xae, 0xf8, 0x9e, 0x25, 0x36, 0x61, 0x5d, 0x9e, 0x2b, 0x91,
|
||||
0x59, 0x2f, 0xea, 0xe7, 0xea, 0xa4, 0x59, 0x7c, 0xea, 0xa1, 0x06, 0xb4, 0x03, 0xf8, 0x55, 0xb5,
|
||||
0x64, 0x71, 0x48, 0xaf, 0xb6, 0x3b, 0x76, 0xd2, 0x2c, 0x4e, 0x28, 0xb5, 0x76, 0x5f, 0x61, 0x03,
|
||||
0x40, 0x07, 0xd3, 0xe8, 0x4e, 0x88, 0x3d, 0x2a, 0xcc, 0xda, 0x2e, 0x91, 0x91, 0x7b, 0xe6, 0xd1,
|
||||
0x92, 0x82, 0x69, 0x54, 0x66, 0x25, 0x24, 0xdc, 0xe8, 0xb0, 0x86, 0xba, 0x20, 0xb0, 0x9a, 0x11,
|
||||
0x12, 0x4c, 0x55, 0x19, 0xd0, 0x6a, 0x38, 0xa3, 0x22, 0xc9, 0x85, 0x4f, 0x83, 0x61, 0x97, 0x50,
|
||||
0x8a, 0xeb, 0x84, 0x9f, 0xfd, 0x91, 0xf4, 0x52, 0xdc, 0x14, 0x64, 0x94, 0xf0, 0x4b, 0xff, 0x32,
|
||||
0xc0, 0xe5, 0x5e, 0x51, 0xdb, 0xb0, 0x69, 0x04, 0xbf, 0xd5, 0x91, 0xf6, 0xe5, 0x47, 0x5b, 0x21,
|
||||
0xd3, 0xe6, 0x49, 0xaf, 0x4a, 0x49, 0x42, 0xd1, 0x52, 0xfe, 0x10, 0xe4, 0xed, 0x88, 0xb8, 0xc9,
|
||||
0x6d, 0x89, 0xfa, 0x9f, 0x76, 0x95, 0x71, 0x09, 0x9f, 0x5f, 0x67, 0x40, 0x48, 0xe0, 0x95, 0x3e,
|
||||
0xca, 0x81, 0x2b, 0xbd, 0x54, 0x58, 0x1d, 0xa7, 0x2c, 0xd8, 0x81, 0x13, 0x87, 0xd8, 0x91, 0xc9,
|
||||
0xa6, 0x82, 0xbd, 0xcd, 0xa9, 0x48, 0x72, 0x59, 0xed, 0xa4, 0xb6, 0x57, 0x8f, 0x1d, 0x1c, 0xca,
|
||||
0x4c, 0x52, 0x0b, 0xae, 0x4a, 0x3a, 0x52, 0x12, 0xb0, 0x0c, 0x00, 0xdd, 0xf7, 0xc3, 0x88, 0x63,
|
||||
0xf0, 0x0e, 0x67, 0xa4, 0x72, 0x9e, 0x55, 0x84, 0xaa, 0xa2, 0x22, 0x4d, 0x82, 0x5d, 0x24, 0x07,
|
||||
0xb6, 0x67, 0xc9, 0x0d, 0x57, 0x67, 0xf7, 0x96, 0xed, 0x59, 0x88, 0x73, 0x18, 0xbe, 0x63, 0xd3,
|
||||
0x88, 0x51, 0xe4, 0x6e, 0xb7, 0x05, 0x9c, 0x4b, 0x2a, 0x09, 0x86, 0x5f, 0x63, 0x05, 0xd6, 0x0f,
|
||||
0x6d, 0x42, 0xcd, 0xa1, 0x14, 0x7f, 0x45, 0x51, 0x91, 0x26, 0x51, 0xfa, 0xeb, 0x60, 0xef, 0xfc,
|
||||
0x60, 0x05, 0x04, 0x3e, 0x09, 0xf2, 0xf5, 0xd0, 0x8f, 0x03, 0x19, 0x25, 0x15, 0xed, 0x57, 0x19,
|
||||
0x11, 0x09, 0x1e, 0xfc, 0x36, 0xc8, 0x7b, 0x72, 0xc1, 0x2c, 0x83, 0x5e, 0xef, 0xff, 0x36, 0xf3,
|
||||
0x68, 0xa5, 0xe8, 0x22, 0x90, 0x02, 0x14, 0xbe, 0x00, 0xf2, 0xb4, 0xe6, 0x07, 0x44, 0x06, 0x71,
|
||||
0x2e, 0x11, 0xaa, 0x32, 0xe2, 0x49, 0xb3, 0x38, 0x9e, 0x98, 0xe3, 0x04, 0x24, 0x84, 0xe1, 0xf7,
|
||||
0x0d, 0x50, 0x90, 0xd7, 0x05, 0x35, 0x87, 0x79, 0x7a, 0xbe, 0xd1, 0x7f, 0xbf, 0x65, 0xdb, 0x9b,
|
||||
0xee, 0x99, 0x24, 0x50, 0xa4, 0xc0, 0xe1, 0x77, 0x0d, 0x00, 0x6a, 0xea, 0xee, 0x32, 0x47, 0x78,
|
||||
0x0c, 0xfb, 0x76, 0x54, 0xb4, 0x5b, 0x51, 0x24, 0x42, 0xda, 0x2a, 0x69, 0xa8, 0xb0, 0x0a, 0xa6,
|
||||
0x83, 0x90, 0x70, 0xdb, 0x77, 0xbd, 0x03, 0xcf, 0x3f, 0xf4, 0x6e, 0xda, 0xc4, 0xb1, 0xa8, 0x09,
|
||||
0xe6, 0x8d, 0x85, 0x42, 0xe5, 0x8a, 0xf4, 0x7f, 0x7a, 0xbb, 0x9b, 0x10, 0xea, 0xae, 0x5b, 0x7a,
|
||||
0x7f, 0x20, 0xdb, 0x6b, 0x65, 0xef, 0x0b, 0xf8, 0xa1, 0x58, 0xbc, 0xa8, 0xc3, 0xd4, 0x34, 0xf8,
|
||||
0x46, 0xbc, 0xd5, 0xff, 0x8d, 0x50, 0xb5, 0x3e, 0xbd, 0xa4, 0x15, 0x89, 0x22, 0xcd, 0x05, 0xf8,
|
||||
0x53, 0x03, 0x8c, 0xe3, 0x5a, 0x8d, 0x04, 0x11, 0xb1, 0xc4, 0x31, 0xce, 0x9d, 0x6d, 0x56, 0x4f,
|
||||
0x4b, 0x87, 0xc6, 0x97, 0x75, 0x54, 0xd4, 0xee, 0x04, 0x7c, 0x05, 0x9c, 0xa7, 0x91, 0x1f, 0x12,
|
||||
0x2b, 0xc9, 0x20, 0x59, 0x5d, 0x60, 0xab, 0x59, 0x3c, 0x5f, 0x6d, 0xe3, 0xa0, 0x8c, 0x64, 0xe9,
|
||||
0x93, 0x3c, 0x28, 0x3e, 0x24, 0x43, 0x1f, 0xa1, 0xe9, 0xbd, 0x06, 0x86, 0xf8, 0x4a, 0x2d, 0x1e,
|
||||
0x90, 0x82, 0x76, 0xd5, 0x73, 0x2a, 0x92, 0x5c, 0x76, 0x3d, 0x31, 0x7c, 0x76, 0x3d, 0x0d, 0x70,
|
||||
0x41, 0x75, 0x3d, 0x55, 0x05, 0x19, 0x25, 0x7c, 0xb8, 0x04, 0x80, 0x45, 0x82, 0x90, 0xb0, 0x8a,
|
||||
0x64, 0x99, 0xc3, 0x5c, 0x5a, 0xed, 0xcf, 0xaa, 0xe2, 0x20, 0x4d, 0x0a, 0xde, 0x04, 0x30, 0xf9,
|
||||
0x65, 0xfb, 0xde, 0xeb, 0x38, 0xf4, 0x6c, 0xaf, 0x6e, 0x16, 0xb8, 0xdb, 0x33, 0xec, 0xb6, 0x5d,
|
||||
0xed, 0xe0, 0xa2, 0x2e, 0x1a, 0xb0, 0x01, 0x86, 0xc4, 0x33, 0x9a, 0xd7, 0x8d, 0x3e, 0x9e, 0xb8,
|
||||
0x7b, 0xd8, 0xb1, 0x2d, 0x0e, 0x55, 0x01, 0x3c, 0x3c, 0x1c, 0x05, 0x49, 0x34, 0xf8, 0x81, 0x01,
|
||||
0xc6, 0x68, 0xbc, 0x1b, 0x4a, 0x69, 0xca, 0xab, 0xfa, 0xe8, 0xd2, 0x9d, 0x7e, 0xc1, 0x57, 0x35,
|
||||
0xdb, 0x95, 0xc9, 0x56, 0xb3, 0x38, 0xa6, 0x53, 0x50, 0x1b, 0x36, 0xfc, 0x83, 0x01, 0x4c, 0x6c,
|
||||
0x89, 0xd4, 0xc7, 0xce, 0x76, 0x68, 0x7b, 0x11, 0x09, 0xc5, 0x83, 0x48, 0x5c, 0x1f, 0x7d, 0xec,
|
||||
0x15, 0xb3, 0xef, 0xac, 0xca, 0xbc, 0xdc, 0x69, 0x73, 0xb9, 0x87, 0x07, 0xa8, 0xa7, 0x6f, 0xa5,
|
||||
0x7f, 0x1b, 0xd9, 0xd2, 0xa2, 0xad, 0xb2, 0x5a, 0xc3, 0x0e, 0x81, 0xab, 0x60, 0x92, 0x75, 0xbf,
|
||||
0x88, 0x04, 0x8e, 0x5d, 0xc3, 0x94, 0xbf, 0x7e, 0x44, 0x76, 0xab, 0x67, 0x78, 0x35, 0xc3, 0x47,
|
||||
0x1d, 0x1a, 0xf0, 0x35, 0x00, 0x45, 0x5b, 0xd8, 0x66, 0x47, 0x74, 0x02, 0xaa, 0xc1, 0xab, 0x76,
|
||||
0x48, 0xa0, 0x2e, 0x5a, 0x70, 0x05, 0x4c, 0x39, 0x78, 0x97, 0x38, 0x55, 0xe2, 0x90, 0x5a, 0xe4,
|
||||
0x87, 0xdc, 0x94, 0x78, 0x1f, 0x4e, 0xb7, 0x9a, 0xc5, 0xa9, 0x8d, 0x2c, 0x13, 0x75, 0xca, 0x97,
|
||||
0xae, 0x66, 0xcf, 0xb2, 0xbe, 0x70, 0xd1, 0x6c, 0xff, 0x2c, 0x07, 0x66, 0x7b, 0x27, 0x05, 0xfc,
|
||||
0x8e, 0x6a, 0x8d, 0x45, 0xc7, 0xf7, 0xc6, 0x19, 0xa4, 0x9e, 0x7c, 0x0e, 0x80, 0xce, 0xa7, 0x00,
|
||||
0x3c, 0x66, 0xf7, 0x35, 0x76, 0x92, 0x67, 0xff, 0xce, 0x59, 0xa0, 0x33, 0xfb, 0x95, 0x11, 0xd1,
|
||||
0x05, 0x60, 0x87, 0x5f, 0xfa, 0xd8, 0x21, 0xa5, 0x8f, 0x3a, 0x9e, 0xb6, 0xe9, 0x61, 0x85, 0x3f,
|
||||
0x30, 0xc0, 0x84, 0x1f, 0x10, 0x6f, 0x79, 0x7b, 0xfd, 0xde, 0x97, 0xc5, 0xa1, 0x95, 0x01, 0x5a,
|
||||
0x7f, 0x7c, 0x17, 0xd9, 0xfb, 0x5a, 0xd8, 0xda, 0x0e, 0xfd, 0x80, 0x56, 0x2e, 0xb4, 0x9a, 0xc5,
|
||||
0x89, 0xad, 0x76, 0x14, 0x94, 0x85, 0x2d, 0xb9, 0x60, 0x7a, 0xed, 0x28, 0x22, 0xa1, 0x87, 0x9d,
|
||||
0x55, 0xbf, 0x16, 0xbb, 0xc4, 0x8b, 0x84, 0x8f, 0x99, 0x71, 0x81, 0xf1, 0x88, 0xe3, 0x82, 0x2b,
|
||||
0x60, 0x20, 0x0e, 0x1d, 0x99, 0xb5, 0xa3, 0x6a, 0x08, 0x86, 0x36, 0x10, 0xa3, 0x97, 0xae, 0x82,
|
||||
0x41, 0xe6, 0x27, 0xbc, 0x04, 0x06, 0x42, 0x7c, 0xc8, 0xad, 0x8e, 0x55, 0x86, 0x99, 0x08, 0xc2,
|
||||
0x87, 0x88, 0xd1, 0x4a, 0x7f, 0x29, 0x82, 0x89, 0xcc, 0x5a, 0xe0, 0x2c, 0xc8, 0xa9, 0xc9, 0x1a,
|
||||
0x90, 0x46, 0x73, 0xeb, 0xab, 0x28, 0x67, 0x5b, 0xf0, 0x25, 0x55, 0x5d, 0x05, 0x68, 0x51, 0x5d,
|
||||
0x16, 0x9c, 0xca, 0xda, 0xb2, 0xd4, 0x1c, 0x73, 0x24, 0x29, 0x8f, 0xcc, 0x07, 0xb2, 0x27, 0x4f,
|
||||
0x85, 0xf0, 0x81, 0xec, 0x21, 0x46, 0x7b, 0xdc, 0x59, 0x49, 0x32, 0xac, 0xc9, 0x3f, 0xc2, 0xb0,
|
||||
0x66, 0xe8, 0x81, 0xc3, 0x9a, 0x27, 0x41, 0x3e, 0xb2, 0x23, 0x87, 0xf0, 0x9b, 0x4a, 0x6b, 0x86,
|
||||
0xef, 0x30, 0x22, 0x12, 0x3c, 0x48, 0xc0, 0xb0, 0x45, 0xf6, 0x70, 0xec, 0x44, 0xfc, 0x52, 0x1a,
|
||||
0x5d, 0xfa, 0xfa, 0xe9, 0xb2, 0x47, 0x0c, 0x33, 0x56, 0x85, 0x49, 0x94, 0xd8, 0x86, 0x4f, 0x81,
|
||||
0x61, 0x17, 0x1f, 0xd9, 0x6e, 0xec, 0xf2, 0x8e, 0xd1, 0x10, 0x62, 0x9b, 0x82, 0x84, 0x12, 0x1e,
|
||||
0x2b, 0x82, 0xe4, 0xa8, 0xe6, 0xc4, 0xd4, 0x6e, 0x10, 0xc9, 0x94, 0x2d, 0x9d, 0x2a, 0x82, 0x6b,
|
||||
0x19, 0x3e, 0xea, 0xd0, 0xe0, 0x60, 0xb6, 0xc7, 0x95, 0x47, 0x35, 0x30, 0x41, 0x42, 0x09, 0xaf,
|
||||
0x1d, 0x4c, 0xca, 0x8f, 0xf5, 0x02, 0x93, 0xca, 0x1d, 0x1a, 0xf0, 0x59, 0x30, 0xe2, 0xe2, 0xa3,
|
||||
0x0d, 0xe2, 0xd5, 0xa3, 0x7d, 0x73, 0x7c, 0xde, 0x58, 0x18, 0xa8, 0x8c, 0xb7, 0x9a, 0xc5, 0x91,
|
||||
0xcd, 0x84, 0x88, 0x52, 0x3e, 0x17, 0xb6, 0x3d, 0x29, 0x7c, 0x5e, 0x13, 0x4e, 0x88, 0x28, 0xe5,
|
||||
0xb3, 0xce, 0x24, 0xc0, 0x11, 0x3b, 0x57, 0xe6, 0x44, 0xfb, 0xc3, 0x79, 0x5b, 0x90, 0x51, 0xc2,
|
||||
0x87, 0x0b, 0xa0, 0xe0, 0xe2, 0x23, 0xfe, 0xa6, 0x34, 0x27, 0xb9, 0x59, 0x3e, 0x50, 0xdc, 0x94,
|
||||
0x34, 0xa4, 0xb8, 0x5c, 0xd2, 0xf6, 0x84, 0xe4, 0x94, 0x26, 0x29, 0x69, 0x48, 0x71, 0x59, 0xfe,
|
||||
0xc6, 0x9e, 0x7d, 0x3f, 0x26, 0x42, 0x18, 0xf2, 0xc8, 0xa8, 0xfc, 0xbd, 0x9b, 0xb2, 0x90, 0x2e,
|
||||
0xc7, 0xde, 0x74, 0x6e, 0xec, 0x44, 0x76, 0xe0, 0x90, 0xad, 0x3d, 0xf3, 0x02, 0x8f, 0x3f, 0x6f,
|
||||
0xe5, 0x37, 0x15, 0x15, 0x69, 0x12, 0xf0, 0x1d, 0x30, 0x48, 0xbc, 0xd8, 0x35, 0x2f, 0xf2, 0xeb,
|
||||
0xfb, 0xb4, 0xd9, 0xa7, 0xce, 0xcb, 0x9a, 0x17, 0xbb, 0x88, 0x5b, 0x86, 0x2f, 0x81, 0x71, 0x17,
|
||||
0x1f, 0xb1, 0x22, 0x40, 0xc2, 0x88, 0x3d, 0x34, 0xa7, 0xf9, 0xba, 0xa7, 0x58, 0x13, 0xbb, 0xa9,
|
||||
0x33, 0x50, 0xbb, 0x1c, 0x57, 0xb4, 0x3d, 0x4d, 0x71, 0x46, 0x53, 0xd4, 0x19, 0xa8, 0x5d, 0x8e,
|
||||
0x05, 0x39, 0x24, 0xf7, 0x63, 0x3b, 0x24, 0x96, 0xf9, 0x7f, 0xbc, 0xef, 0x95, 0xf3, 0x5d, 0x41,
|
||||
0x43, 0x8a, 0x0b, 0xef, 0x27, 0x23, 0x07, 0x93, 0x1f, 0xbe, 0xed, 0xbe, 0x95, 0xee, 0xad, 0x70,
|
||||
0x39, 0x0c, 0xf1, 0xb1, 0xb8, 0x55, 0xf4, 0x61, 0x03, 0xf4, 0x40, 0x1e, 0x3b, 0xce, 0xd6, 0x9e,
|
||||
0x79, 0x89, 0x47, 0xbc, 0x8f, 0xb7, 0x85, 0xaa, 0x30, 0xcb, 0xcc, 0x3e, 0x12, 0x30, 0x0c, 0xcf,
|
||||
0xf7, 0x58, 0x2e, 0xcc, 0x9e, 0x19, 0xde, 0x16, 0xb3, 0x8f, 0x04, 0x0c, 0x5f, 0x9f, 0x77, 0xbc,
|
||||
0xb5, 0x67, 0x3e, 0x71, 0x76, 0xeb, 0x63, 0xf6, 0x91, 0x80, 0x81, 0x16, 0x18, 0xf0, 0xfc, 0xc8,
|
||||
0xbc, 0xdc, 0xef, 0xbb, 0x97, 0xdf, 0x26, 0xb7, 0xfd, 0x08, 0x31, 0xf3, 0xf0, 0x47, 0x06, 0x00,
|
||||
0x41, 0x9a, 0x89, 0x57, 0x4e, 0x3b, 0x02, 0xc8, 0xa0, 0x95, 0xd3, 0xec, 0x5d, 0xf3, 0xa2, 0xf0,
|
||||
0x38, 0x7d, 0xd7, 0x68, 0x59, 0xae, 0x39, 0x00, 0x7f, 0x61, 0x80, 0x8b, 0x7a, 0xbb, 0xab, 0x3c,
|
||||
0x9b, 0xe3, 0x71, 0xd8, 0xea, 0x63, 0x22, 0x57, 0x7c, 0xdf, 0xa9, 0x98, 0xad, 0x66, 0xf1, 0xe2,
|
||||
0x72, 0x17, 0x40, 0xd4, 0xd5, 0x0d, 0xf8, 0x1b, 0x03, 0x4c, 0xc9, 0xea, 0xa8, 0x39, 0x57, 0xe4,
|
||||
0x61, 0x7b, 0xa7, 0x8f, 0x61, 0xcb, 0x42, 0x88, 0xe8, 0xa9, 0xaf, 0x8c, 0x1d, 0x7c, 0xd4, 0xe9,
|
||||
0x15, 0xfc, 0xbd, 0x01, 0xc6, 0x2c, 0x12, 0x10, 0xcf, 0x22, 0x5e, 0x8d, 0xb9, 0x39, 0x7f, 0xda,
|
||||
0xb9, 0x42, 0xd6, 0xcd, 0x55, 0xcd, 0xba, 0xf0, 0xb0, 0x2c, 0x3d, 0x1c, 0xd3, 0x59, 0x27, 0xcd,
|
||||
0xe2, 0x4c, 0xaa, 0xaa, 0x73, 0x50, 0x9b, 0x83, 0xf0, 0xc7, 0x06, 0x98, 0x48, 0xc3, 0x2e, 0x2e,
|
||||
0x88, 0xab, 0x67, 0xb3, 0xf1, 0xbc, 0x05, 0x5d, 0x6e, 0xc7, 0x42, 0x59, 0x70, 0xf8, 0x5b, 0x83,
|
||||
0x75, 0x5b, 0xc9, 0x5b, 0x8d, 0x9a, 0x25, 0x1e, 0xc1, 0x37, 0xfb, 0x19, 0x41, 0x65, 0x5c, 0x04,
|
||||
0xf0, 0x7a, 0xda, 0xc9, 0x29, 0xce, 0x49, 0xb3, 0x38, 0xad, 0xc7, 0x4f, 0x31, 0x90, 0xee, 0x1c,
|
||||
0x7c, 0xdf, 0x00, 0x63, 0x24, 0x6d, 0x98, 0xa9, 0xf9, 0xe4, 0x69, 0x43, 0xd7, 0xb5, 0xfd, 0x16,
|
||||
0xcf, 0x69, 0x8d, 0x45, 0x51, 0x1b, 0x2c, 0xeb, 0xfd, 0xc8, 0x11, 0x76, 0x03, 0x87, 0x98, 0xff,
|
||||
0xdf, 0xbf, 0xde, 0x6f, 0x4d, 0x98, 0x44, 0x89, 0x6d, 0x78, 0x1d, 0x14, 0xbc, 0xd8, 0x71, 0xf0,
|
||||
0xae, 0x43, 0xcc, 0xa7, 0x78, 0x17, 0xa1, 0xe6, 0x8b, 0xb7, 0x25, 0x1d, 0x29, 0x09, 0xb8, 0x07,
|
||||
0xe6, 0x8f, 0x6e, 0xa9, 0x7f, 0xbe, 0xe8, 0x3a, 0xc0, 0x33, 0xaf, 0x71, 0x2b, 0xb3, 0xad, 0x66,
|
||||
0x71, 0x66, 0xa7, 0xfb, 0x88, 0xef, 0xa1, 0x36, 0xe0, 0x5b, 0xe0, 0x09, 0x4d, 0x66, 0xcd, 0xdd,
|
||||
0x25, 0x96, 0x45, 0xac, 0xe4, 0xa1, 0x65, 0x7e, 0x89, 0x43, 0xa8, 0x73, 0xbc, 0x93, 0x15, 0x40,
|
||||
0x0f, 0xd2, 0x86, 0x1b, 0x60, 0x46, 0x63, 0xaf, 0x7b, 0xd1, 0x56, 0x58, 0x8d, 0x42, 0xdb, 0xab,
|
||||
0x9b, 0x0b, 0xdc, 0xee, 0xc5, 0xe4, 0xf4, 0xed, 0x68, 0x3c, 0xd4, 0x43, 0x07, 0x7e, 0xb3, 0xcd,
|
||||
0x1a, 0xff, 0x70, 0x81, 0x83, 0x5b, 0xe4, 0x98, 0x9a, 0x4f, 0xf3, 0xe6, 0x82, 0xef, 0xf3, 0x8e,
|
||||
0x46, 0x47, 0x3d, 0xe4, 0xe1, 0x37, 0xc0, 0x85, 0x0c, 0x87, 0xbd, 0x2b, 0xcc, 0x67, 0xc4, 0x03,
|
||||
0x81, 0x75, 0xa2, 0x3b, 0x09, 0x11, 0x75, 0x93, 0x84, 0x5f, 0x03, 0x50, 0x23, 0x6f, 0xe2, 0x80,
|
||||
0xeb, 0x3f, 0x2b, 0xde, 0x2a, 0x6c, 0x47, 0x77, 0x24, 0x0d, 0x75, 0x91, 0x9b, 0x65, 0x6f, 0xd6,
|
||||
0x4c, 0xa9, 0x84, 0x93, 0x60, 0xe0, 0x80, 0xc8, 0xaf, 0xb3, 0x88, 0xfd, 0x09, 0xdf, 0x06, 0xf9,
|
||||
0x06, 0x76, 0xe2, 0xe4, 0xc5, 0xdd, 0xbf, 0x2b, 0x15, 0x09, 0xbb, 0xaf, 0xe4, 0x5e, 0x36, 0x66,
|
||||
0x3f, 0x34, 0xc0, 0x4c, 0xf7, 0xe2, 0xfd, 0x45, 0x79, 0xf4, 0x73, 0x03, 0x4c, 0x75, 0xd4, 0xe9,
|
||||
0x2e, 0xce, 0x38, 0xed, 0xce, 0xdc, 0xeb, 0x63, 0xc1, 0x15, 0xf9, 0xc6, 0x1b, 0x47, 0xdd, 0xb3,
|
||||
0x1f, 0x1a, 0x60, 0x32, 0x5b, 0xff, 0xbe, 0xa0, 0x28, 0x95, 0x3e, 0xc8, 0x81, 0x99, 0xee, 0xad,
|
||||
0x2e, 0x74, 0xd5, 0x23, 0xbe, 0xef, 0x73, 0x90, 0x6e, 0x93, 0xd1, 0xf7, 0x0c, 0x30, 0xfa, 0xae,
|
||||
0x92, 0x4b, 0x3e, 0x1a, 0xf6, 0x73, 0xf8, 0x92, 0xdc, 0x30, 0x29, 0x83, 0x22, 0x1d, 0xb2, 0xf4,
|
||||
0x3b, 0x03, 0x4c, 0x77, 0xbd, 0x35, 0xe1, 0x35, 0x30, 0x84, 0x1d, 0xc7, 0x3f, 0x14, 0x43, 0x33,
|
||||
0x6d, 0xfa, 0xbd, 0xcc, 0xa9, 0x48, 0x72, 0xb5, 0x98, 0xe5, 0x3e, 0x87, 0x98, 0x95, 0xfe, 0x68,
|
||||
0x80, 0xcb, 0x0f, 0xca, 0xba, 0xcf, 0x7b, 0x0f, 0x17, 0x40, 0x41, 0xf6, 0xb4, 0xc7, 0x7c, 0xff,
|
||||
0x64, 0x11, 0x93, 0x15, 0x81, 0xff, 0x53, 0x8a, 0xf8, 0xab, 0xf4, 0x2b, 0x03, 0x4c, 0x56, 0x49,
|
||||
0xd8, 0xb0, 0x6b, 0x04, 0x91, 0x3d, 0x12, 0x12, 0xaf, 0x46, 0xe0, 0x22, 0x18, 0xe1, 0x1f, 0xf5,
|
||||
0x02, 0x5c, 0x4b, 0x3e, 0x45, 0x4c, 0xc9, 0x40, 0x8f, 0xdc, 0x4e, 0x18, 0x28, 0x95, 0x51, 0x9f,
|
||||
0x2d, 0x72, 0x3d, 0x3f, 0x5b, 0x5c, 0x06, 0x83, 0x41, 0x3a, 0x67, 0x2d, 0x30, 0x2e, 0x1f, 0xad,
|
||||
0x72, 0x2a, 0xe7, 0xfa, 0x61, 0xc4, 0x87, 0x49, 0x79, 0xc9, 0xf5, 0xc3, 0x08, 0x71, 0x6a, 0xe9,
|
||||
0x4f, 0x06, 0xb8, 0x90, 0xfc, 0x77, 0x89, 0x63, 0x13, 0x2f, 0x5a, 0xf1, 0xbd, 0x3d, 0xbb, 0x0e,
|
||||
0x2f, 0x89, 0x79, 0x9a, 0x36, 0xa4, 0x4a, 0x66, 0x69, 0xf0, 0x3e, 0x18, 0xa6, 0x62, 0x55, 0x32,
|
||||
0xe0, 0xaf, 0x3d, 0x7e, 0xc0, 0xb3, 0xe1, 0x11, 0xed, 0x40, 0x42, 0x4d, 0x70, 0x58, 0xcc, 0x6b,
|
||||
0xb8, 0x12, 0x7b, 0x96, 0x9c, 0xa9, 0x8e, 0x89, 0x98, 0xaf, 0x2c, 0x0b, 0x1a, 0x52, 0xdc, 0xd2,
|
||||
0x3f, 0x0c, 0x30, 0xd5, 0xf1, 0xdf, 0x32, 0xf0, 0x7b, 0x06, 0x18, 0xab, 0x69, 0xcb, 0x93, 0x99,
|
||||
0xbb, 0x79, 0xfa, 0xff, 0xc8, 0xd1, 0x8c, 0x8a, 0x3b, 0x55, 0xa7, 0xa0, 0x36, 0x50, 0xb8, 0x03,
|
||||
0xcc, 0x5a, 0xe6, 0x1f, 0xd3, 0x32, 0x9f, 0xba, 0x2e, 0xb7, 0x9a, 0x45, 0x73, 0xa5, 0x87, 0x0c,
|
||||
0xea, 0xa9, 0x5d, 0x59, 0xf8, 0xf8, 0xb3, 0xb9, 0x73, 0x9f, 0x7c, 0x36, 0x77, 0xee, 0xd3, 0xcf,
|
||||
0xe6, 0xce, 0xbd, 0xd7, 0x9a, 0x33, 0x3e, 0x6e, 0xcd, 0x19, 0x9f, 0xb4, 0xe6, 0x8c, 0x4f, 0x5b,
|
||||
0x73, 0xc6, 0xdf, 0x5a, 0x73, 0xc6, 0x4f, 0xfe, 0x3e, 0x77, 0xee, 0xcd, 0x5c, 0xe3, 0xc6, 0xff,
|
||||
0x02, 0x00, 0x00, 0xff, 0xff, 0x59, 0xc7, 0x8d, 0x55, 0xac, 0x2a, 0x00, 0x00,
|
||||
// 3046 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x5a, 0xdf, 0x6f, 0x24, 0x47,
|
||||
0xf1, 0xbf, 0x59, 0x7b, 0xed, 0x75, 0xdb, 0x3e, 0xdb, 0x7d, 0x67, 0x7f, 0xe7, 0x9c, 0x3b, 0xaf,
|
||||
0x6f, 0xf3, 0xcd, 0x7d, 0x9d, 0xe4, 0xb2, 0xce, 0xf9, 0x9b, 0x90, 0x10, 0x21, 0x90, 0xd7, 0xf6,
|
||||
0x25, 0xce, 0xd9, 0x67, 0xab, 0xf7, 0xee, 0xe2, 0x24, 0xa0, 0x64, 0xbc, 0xd3, 0x5e, 0x4f, 0x3c,
|
||||
0xbf, 0xae, 0x7b, 0x66, 0x6d, 0x4b, 0x20, 0x45, 0xa0, 0x08, 0x88, 0x04, 0xe1, 0x01, 0x85, 0x27,
|
||||
0x84, 0x10, 0xca, 0x03, 0x3c, 0xc0, 0x1b, 0xfc, 0x0b, 0x79, 0x41, 0xca, 0x13, 0x8a, 0x84, 0xb4,
|
||||
0x22, 0xcb, 0x3f, 0x80, 0x04, 0x08, 0xe1, 0x07, 0x84, 0xfa, 0xc7, 0xf4, 0xf4, 0xce, 0xee, 0xe6,
|
||||
0x4e, 0xe7, 0x75, 0xf2, 0x66, 0x57, 0x55, 0xd7, 0xa7, 0xba, 0xba, 0xba, 0xaa, 0xba, 0x66, 0x81,
|
||||
0x75, 0xf0, 0x22, 0x2d, 0x3b, 0xc1, 0xe2, 0x41, 0xbc, 0x8b, 0x89, 0x8f, 0x23, 0x4c, 0x17, 0x1b,
|
||||
0xd8, 0xb7, 0x03, 0xb2, 0x28, 0x19, 0x56, 0xe8, 0xe0, 0xa3, 0x08, 0xfb, 0xd4, 0x09, 0x7c, 0xfa,
|
||||
0x8c, 0x15, 0x3a, 0x14, 0x93, 0x06, 0x26, 0x8b, 0xe1, 0x41, 0x9d, 0xf1, 0x68, 0xbb, 0xc0, 0x62,
|
||||
0xe3, 0xc6, 0x62, 0x1d, 0xfb, 0x98, 0x58, 0x11, 0xb6, 0xcb, 0x21, 0x09, 0xa2, 0x00, 0xbe, 0x28,
|
||||
0x34, 0x95, 0xdb, 0x04, 0xdf, 0x52, 0x9a, 0xca, 0xe1, 0x41, 0x9d, 0xf1, 0x68, 0xbb, 0x40, 0xb9,
|
||||
0x71, 0x63, 0xf6, 0x99, 0xba, 0x13, 0xed, 0xc7, 0xbb, 0xe5, 0x5a, 0xe0, 0x2d, 0xd6, 0x83, 0x7a,
|
||||
0xb0, 0xc8, 0x15, 0xee, 0xc6, 0x7b, 0xfc, 0x3f, 0xfe, 0x0f, 0xff, 0x4b, 0x00, 0xcd, 0x3e, 0x97,
|
||||
0x9a, 0xec, 0x59, 0xb5, 0x7d, 0xc7, 0xc7, 0xe4, 0x38, 0xb5, 0xd3, 0xc3, 0x91, 0xd5, 0xc5, 0xbc,
|
||||
0xd9, 0xc5, 0x5e, 0xab, 0x48, 0xec, 0x47, 0x8e, 0x87, 0x3b, 0x16, 0x7c, 0xe5, 0x41, 0x0b, 0x68,
|
||||
0x6d, 0x1f, 0x7b, 0x56, 0x76, 0x5d, 0xe9, 0xc4, 0x00, 0x53, 0x2b, 0x81, 0xdf, 0xc0, 0x84, 0x6d,
|
||||
0x10, 0xe1, 0xfb, 0x31, 0xa6, 0x11, 0xac, 0x80, 0x81, 0xd8, 0xb1, 0x4d, 0x63, 0xde, 0x58, 0x18,
|
||||
0xa9, 0x3c, 0xfb, 0x71, 0xb3, 0x78, 0xae, 0xd5, 0x2c, 0x0e, 0xdc, 0x5d, 0x5f, 0x3d, 0x69, 0x16,
|
||||
0xaf, 0xf6, 0x42, 0x8a, 0x8e, 0x43, 0x4c, 0xcb, 0x77, 0xd7, 0x57, 0x11, 0x5b, 0x0c, 0x5f, 0x06,
|
||||
0x53, 0x36, 0xa6, 0x0e, 0xc1, 0xf6, 0xf2, 0xf6, 0xfa, 0x3d, 0xa1, 0xdf, 0xcc, 0x71, 0x8d, 0x97,
|
||||
0xa4, 0xc6, 0xa9, 0xd5, 0xac, 0x00, 0xea, 0x5c, 0x03, 0x77, 0xc0, 0x70, 0xb0, 0xfb, 0x0e, 0xae,
|
||||
0x45, 0xd4, 0x1c, 0x98, 0x1f, 0x58, 0x18, 0x5d, 0x7a, 0xa6, 0x9c, 0x1e, 0x9e, 0x32, 0x81, 0x9f,
|
||||
0x98, 0xdc, 0x6c, 0x19, 0x59, 0x87, 0x6b, 0xc9, 0xa1, 0x55, 0x26, 0x24, 0xda, 0xf0, 0x96, 0xd0,
|
||||
0x82, 0x12, 0x75, 0xa5, 0x5f, 0xe5, 0x00, 0xd4, 0x37, 0x4f, 0xc3, 0xc0, 0xa7, 0xb8, 0x2f, 0xbb,
|
||||
0xa7, 0x60, 0xb2, 0xc6, 0x35, 0x47, 0xd8, 0x96, 0xb8, 0x66, 0xee, 0x51, 0xac, 0x37, 0x25, 0xfe,
|
||||
0xe4, 0x4a, 0x46, 0x1d, 0xea, 0x00, 0x80, 0x77, 0xc0, 0x10, 0xc1, 0x34, 0x76, 0x23, 0x73, 0x60,
|
||||
0xde, 0x58, 0x18, 0x5d, 0xba, 0xde, 0x13, 0x8a, 0x87, 0x36, 0x0b, 0xbe, 0x72, 0xe3, 0x46, 0xb9,
|
||||
0x1a, 0x59, 0x51, 0x4c, 0x2b, 0xe7, 0x25, 0xd2, 0x10, 0xe2, 0x3a, 0x90, 0xd4, 0x55, 0xfa, 0x8f,
|
||||
0x01, 0x26, 0x75, 0x2f, 0x35, 0x1c, 0x7c, 0x08, 0x09, 0x18, 0x26, 0x22, 0x58, 0xb8, 0x9f, 0x46,
|
||||
0x97, 0x6e, 0x95, 0x1f, 0xf5, 0x46, 0x95, 0x3b, 0xe2, 0xaf, 0x32, 0xca, 0x8e, 0x4b, 0xfe, 0x83,
|
||||
0x12, 0x20, 0xd8, 0x00, 0x05, 0x22, 0xcf, 0x88, 0x07, 0xd2, 0xe8, 0xd2, 0x46, 0x7f, 0x40, 0x85,
|
||||
0xce, 0xca, 0x58, 0xab, 0x59, 0x2c, 0x24, 0xff, 0x21, 0x85, 0x55, 0xfa, 0x45, 0x0e, 0xcc, 0xad,
|
||||
0xc4, 0x34, 0x0a, 0x3c, 0x84, 0x69, 0x10, 0x93, 0x1a, 0x5e, 0x09, 0xdc, 0xd8, 0xf3, 0x57, 0xf1,
|
||||
0x9e, 0xe3, 0x3b, 0x11, 0x8b, 0xd1, 0x79, 0x30, 0xe8, 0x5b, 0x1e, 0x96, 0x31, 0x33, 0x26, 0x3d,
|
||||
0x39, 0x78, 0xdb, 0xf2, 0x30, 0xe2, 0x1c, 0x26, 0xc1, 0x42, 0x44, 0xde, 0x00, 0x25, 0x71, 0xe7,
|
||||
0x38, 0xc4, 0x88, 0x73, 0xe0, 0x35, 0x30, 0xb4, 0x17, 0x10, 0xcf, 0x12, 0xa7, 0x37, 0x92, 0x9e,
|
||||
0xc7, 0x4d, 0x4e, 0x45, 0x92, 0x0b, 0x9f, 0x07, 0xa3, 0x36, 0xa6, 0x35, 0xe2, 0x84, 0x0c, 0xda,
|
||||
0x1c, 0xe4, 0xc2, 0x17, 0xa4, 0xf0, 0xe8, 0x6a, 0xca, 0x42, 0xba, 0x1c, 0xbc, 0x0e, 0x0a, 0x21,
|
||||
0x71, 0x02, 0xe2, 0x44, 0xc7, 0x66, 0x7e, 0xde, 0x58, 0xc8, 0x57, 0x26, 0xe5, 0x9a, 0xc2, 0xb6,
|
||||
0xa4, 0x23, 0x25, 0xc1, 0xa4, 0xdf, 0xa1, 0x81, 0xbf, 0x6d, 0x45, 0xfb, 0xe6, 0x10, 0x47, 0x50,
|
||||
0xd2, 0xaf, 0x56, 0xb7, 0x6e, 0x33, 0x3a, 0x52, 0x12, 0xa5, 0x3f, 0x19, 0xc0, 0xcc, 0x7a, 0x28,
|
||||
0x71, 0x2f, 0xbc, 0x09, 0x0a, 0x34, 0x62, 0x39, 0xa7, 0x7e, 0x2c, 0xfd, 0xf3, 0x54, 0xa2, 0xaa,
|
||||
0x2a, 0xe9, 0x27, 0xcd, 0xe2, 0x4c, 0xba, 0x22, 0xa1, 0x72, 0xdf, 0xa8, 0xb5, 0x2c, 0xe4, 0x0e,
|
||||
0xf1, 0xee, 0x7e, 0x10, 0x1c, 0xc8, 0xd3, 0x3f, 0x45, 0xc8, 0xbd, 0x26, 0x14, 0xa5, 0x98, 0x22,
|
||||
0xe4, 0x24, 0x19, 0x25, 0x40, 0xa5, 0x7f, 0xe7, 0xb2, 0x1b, 0xd3, 0x0e, 0xfd, 0x6d, 0x50, 0x60,
|
||||
0x57, 0xc8, 0xb6, 0x22, 0x4b, 0x5e, 0x82, 0x67, 0x1f, 0xee, 0xc2, 0x89, 0xfb, 0xba, 0x89, 0x23,
|
||||
0xab, 0x02, 0xa5, 0x2b, 0x40, 0x4a, 0x43, 0x4a, 0x2b, 0x3c, 0x02, 0x83, 0x34, 0xc4, 0x35, 0xb9,
|
||||
0xdf, 0x7b, 0xa7, 0x88, 0xf6, 0x1e, 0x7b, 0xa8, 0x86, 0xb8, 0x96, 0x06, 0x23, 0xfb, 0x0f, 0x71,
|
||||
0x44, 0xf8, 0xae, 0x01, 0x86, 0x28, 0xcf, 0x0b, 0x32, 0x97, 0xec, 0x9c, 0x01, 0x78, 0x26, 0xef,
|
||||
0x88, 0xff, 0x91, 0xc4, 0x2d, 0xfd, 0x23, 0x07, 0xae, 0xf6, 0x5a, 0xba, 0x12, 0xf8, 0xb6, 0x38,
|
||||
0x84, 0x75, 0x79, 0xaf, 0x44, 0x64, 0x3d, 0xaf, 0xdf, 0xab, 0x93, 0x66, 0xf1, 0x89, 0x07, 0x2a,
|
||||
0xd0, 0x2e, 0xe0, 0x57, 0xd5, 0x96, 0xc5, 0x25, 0xbd, 0xda, 0x6e, 0xd8, 0x49, 0xb3, 0x38, 0xa1,
|
||||
0x96, 0xb5, 0xdb, 0x0a, 0x1b, 0x00, 0xba, 0x16, 0x8d, 0xee, 0x10, 0xcb, 0xa7, 0x42, 0xad, 0xe3,
|
||||
0x61, 0xe9, 0xb9, 0xa7, 0x1e, 0x2e, 0x28, 0xd8, 0x8a, 0xca, 0xac, 0x84, 0x84, 0x1b, 0x1d, 0xda,
|
||||
0x50, 0x17, 0x04, 0x96, 0x33, 0x08, 0xb6, 0xa8, 0x4a, 0x03, 0x5a, 0x0e, 0x67, 0x54, 0x24, 0xb9,
|
||||
0xf0, 0x49, 0x30, 0xec, 0x61, 0x4a, 0xad, 0x3a, 0xe6, 0x77, 0x7f, 0x24, 0x2d, 0x8a, 0x9b, 0x82,
|
||||
0x8c, 0x12, 0x7e, 0xe9, 0x9f, 0x06, 0xb8, 0xdc, 0xcb, 0x6b, 0x1b, 0x0e, 0x8d, 0xe0, 0x37, 0x3b,
|
||||
0xc2, 0xbe, 0xfc, 0x70, 0x3b, 0x64, 0xab, 0x79, 0xd0, 0xab, 0x54, 0x92, 0x50, 0xb4, 0x90, 0x3f,
|
||||
0x04, 0x79, 0x27, 0xc2, 0x5e, 0x52, 0x2d, 0x51, 0xff, 0xc3, 0xae, 0x32, 0x2e, 0xe1, 0xf3, 0xeb,
|
||||
0x0c, 0x08, 0x09, 0xbc, 0xd2, 0x47, 0x39, 0x70, 0xa5, 0xd7, 0x12, 0x96, 0xc7, 0x29, 0x73, 0x76,
|
||||
0xe8, 0xc6, 0xc4, 0x72, 0x65, 0xb0, 0x29, 0x67, 0x6f, 0x73, 0x2a, 0x92, 0x5c, 0x96, 0x3b, 0xa9,
|
||||
0xe3, 0xd7, 0x63, 0xd7, 0x22, 0x32, 0x92, 0xd4, 0x86, 0xab, 0x92, 0x8e, 0x94, 0x04, 0x2c, 0x03,
|
||||
0x40, 0xf7, 0x03, 0x12, 0x71, 0x0c, 0xde, 0xe1, 0x8c, 0x54, 0xce, 0xb3, 0x8c, 0x50, 0x55, 0x54,
|
||||
0xa4, 0x49, 0xb0, 0x42, 0x72, 0xe0, 0xf8, 0xb6, 0x3c, 0x70, 0x75, 0x77, 0x6f, 0x39, 0xbe, 0x8d,
|
||||
0x38, 0x87, 0xe1, 0xbb, 0x0e, 0x8d, 0x18, 0x45, 0x9e, 0x76, 0x9b, 0xc3, 0xb9, 0xa4, 0x92, 0x60,
|
||||
0xf8, 0x35, 0x96, 0x60, 0x03, 0xe2, 0x60, 0x6a, 0x0e, 0xa5, 0xf8, 0x2b, 0x8a, 0x8a, 0x34, 0x89,
|
||||
0xd2, 0x9f, 0x07, 0x7b, 0xc7, 0x07, 0x4b, 0x20, 0xf0, 0x71, 0x90, 0xaf, 0x93, 0x20, 0x0e, 0xa5,
|
||||
0x97, 0x94, 0xb7, 0x5f, 0x66, 0x44, 0x24, 0x78, 0xf0, 0xdb, 0x20, 0xef, 0xcb, 0x0d, 0xb3, 0x08,
|
||||
0x7a, 0xad, 0xff, 0xc7, 0xcc, 0xbd, 0x95, 0xa2, 0x0b, 0x47, 0x0a, 0x50, 0xf8, 0x1c, 0xc8, 0xd3,
|
||||
0x5a, 0x10, 0x62, 0xe9, 0xc4, 0xb9, 0x44, 0xa8, 0xca, 0x88, 0x27, 0xcd, 0xe2, 0x78, 0xa2, 0x8e,
|
||||
0x13, 0x90, 0x10, 0x86, 0xdf, 0x37, 0x40, 0x41, 0x96, 0x0b, 0x6a, 0x0e, 0xf3, 0xf0, 0x7c, 0xbd,
|
||||
0xff, 0x76, 0xcb, 0xb6, 0x37, 0x3d, 0x33, 0x49, 0xa0, 0x48, 0x81, 0xc3, 0xef, 0x1a, 0x00, 0xd4,
|
||||
0x54, 0xed, 0x32, 0x47, 0xb8, 0x0f, 0xfb, 0x76, 0x55, 0xb4, 0xaa, 0x28, 0x02, 0x21, 0x6d, 0x95,
|
||||
0x34, 0x54, 0x58, 0x05, 0xd3, 0x21, 0xc1, 0x5c, 0xf7, 0x5d, 0xff, 0xc0, 0x0f, 0x0e, 0xfd, 0x9b,
|
||||
0x0e, 0x76, 0x6d, 0x6a, 0x82, 0x79, 0x63, 0xa1, 0x50, 0xb9, 0x22, 0xed, 0x9f, 0xde, 0xee, 0x26,
|
||||
0x84, 0xba, 0xaf, 0x2d, 0xbd, 0x37, 0x90, 0xed, 0xb5, 0xb2, 0xf5, 0x02, 0x7e, 0x20, 0x36, 0x2f,
|
||||
0xf2, 0x30, 0x35, 0x0d, 0x7e, 0x10, 0x6f, 0xf6, 0xff, 0x20, 0x54, 0xae, 0x4f, 0x8b, 0xb4, 0x22,
|
||||
0x51, 0xa4, 0x99, 0x00, 0x7f, 0x6a, 0x80, 0x71, 0xab, 0x56, 0xc3, 0x61, 0x84, 0x6d, 0x71, 0x8d,
|
||||
0x73, 0x67, 0x1b, 0xd5, 0xd3, 0xd2, 0xa0, 0xf1, 0x65, 0x1d, 0x15, 0xb5, 0x1b, 0x01, 0x5f, 0x02,
|
||||
0xe7, 0x69, 0x14, 0x10, 0x6c, 0x27, 0x11, 0x24, 0xb3, 0x0b, 0x6c, 0x35, 0x8b, 0xe7, 0xab, 0x6d,
|
||||
0x1c, 0x94, 0x91, 0x2c, 0x7d, 0x92, 0x07, 0xc5, 0x07, 0x44, 0xe8, 0x43, 0x34, 0xbd, 0xd7, 0xc0,
|
||||
0x10, 0xdf, 0xa9, 0xcd, 0x1d, 0x52, 0xd0, 0x4a, 0x3d, 0xa7, 0x22, 0xc9, 0x65, 0xe5, 0x89, 0xe1,
|
||||
0xb3, 0xf2, 0x34, 0xc0, 0x05, 0x55, 0x79, 0xaa, 0x0a, 0x32, 0x4a, 0xf8, 0x70, 0x09, 0x00, 0x1b,
|
||||
0x87, 0x04, 0xb3, 0x8c, 0x64, 0x9b, 0xc3, 0x5c, 0x5a, 0x9d, 0xcf, 0xaa, 0xe2, 0x20, 0x4d, 0x0a,
|
||||
0xde, 0x04, 0x30, 0xf9, 0xcf, 0x09, 0xfc, 0xd7, 0x2c, 0xe2, 0x3b, 0x7e, 0xdd, 0x2c, 0x70, 0xb3,
|
||||
0x67, 0x58, 0xb5, 0x5d, 0xed, 0xe0, 0xa2, 0x2e, 0x2b, 0x60, 0x03, 0x0c, 0x89, 0x67, 0x34, 0xcf,
|
||||
0x1b, 0x7d, 0xbc, 0x71, 0xf7, 0x2c, 0xd7, 0xb1, 0x39, 0x54, 0x05, 0x70, 0xf7, 0x70, 0x14, 0x24,
|
||||
0xd1, 0xe0, 0xfb, 0x06, 0x18, 0xa3, 0xf1, 0x2e, 0x91, 0xd2, 0x94, 0x67, 0xf5, 0xd1, 0xa5, 0x3b,
|
||||
0xfd, 0x82, 0xaf, 0x6a, 0xba, 0x2b, 0x93, 0xad, 0x66, 0x71, 0x4c, 0xa7, 0xa0, 0x36, 0x6c, 0xf8,
|
||||
0x7b, 0x03, 0x98, 0x96, 0x2d, 0x42, 0xdf, 0x72, 0xb7, 0x89, 0xe3, 0x47, 0x98, 0x88, 0x07, 0x91,
|
||||
0x28, 0x1f, 0x7d, 0xec, 0x15, 0xb3, 0xef, 0xac, 0xca, 0xbc, 0x3c, 0x69, 0x73, 0xb9, 0x87, 0x05,
|
||||
0xa8, 0xa7, 0x6d, 0xa5, 0x7f, 0x19, 0xd9, 0xd4, 0xa2, 0xed, 0xb2, 0x5a, 0xb3, 0x5c, 0x0c, 0x57,
|
||||
0xc1, 0x24, 0xeb, 0x7e, 0x11, 0x0e, 0x5d, 0xa7, 0x66, 0x51, 0xfe, 0xfa, 0x11, 0xd1, 0xad, 0x9e,
|
||||
0xe1, 0xd5, 0x0c, 0x1f, 0x75, 0xac, 0x80, 0xaf, 0x02, 0x28, 0xda, 0xc2, 0x36, 0x3d, 0xa2, 0x13,
|
||||
0x50, 0x0d, 0x5e, 0xb5, 0x43, 0x02, 0x75, 0x59, 0x05, 0x57, 0xc0, 0x94, 0x6b, 0xed, 0x62, 0xb7,
|
||||
0x8a, 0x5d, 0x5c, 0x8b, 0x02, 0xc2, 0x55, 0x89, 0xf7, 0xe1, 0x74, 0xab, 0x59, 0x9c, 0xda, 0xc8,
|
||||
0x32, 0x51, 0xa7, 0x7c, 0xe9, 0x6a, 0xf6, 0x2e, 0xeb, 0x1b, 0x17, 0xcd, 0xf6, 0x87, 0x39, 0x30,
|
||||
0xdb, 0x3b, 0x28, 0xe0, 0x77, 0x54, 0x6b, 0x2c, 0x3a, 0xbe, 0xd7, 0xcf, 0x20, 0xf4, 0xe4, 0x73,
|
||||
0x00, 0x74, 0x3e, 0x05, 0xe0, 0x31, 0xab, 0xd7, 0x96, 0x9b, 0x3c, 0xfb, 0x77, 0xce, 0x02, 0x9d,
|
||||
0xe9, 0xaf, 0x8c, 0x88, 0x2e, 0xc0, 0x72, 0x79, 0xd1, 0xb7, 0x5c, 0x5c, 0xfa, 0xa8, 0xe3, 0x69,
|
||||
0x9b, 0x5e, 0x56, 0xf8, 0x03, 0x03, 0x4c, 0x04, 0x21, 0xf6, 0x97, 0xb7, 0xd7, 0xef, 0xfd, 0xbf,
|
||||
0xb8, 0xb4, 0xd2, 0x41, 0xeb, 0x8f, 0x6e, 0x22, 0x7b, 0x5f, 0x0b, 0x5d, 0xdb, 0x24, 0x08, 0x69,
|
||||
0xe5, 0x42, 0xab, 0x59, 0x9c, 0xd8, 0x6a, 0x47, 0x41, 0x59, 0xd8, 0x92, 0x07, 0xa6, 0xd7, 0x8e,
|
||||
0x22, 0x4c, 0x7c, 0xcb, 0x5d, 0x0d, 0x6a, 0xb1, 0x87, 0xfd, 0x48, 0xd8, 0x98, 0x19, 0x17, 0x18,
|
||||
0x0f, 0x39, 0x2e, 0xb8, 0x02, 0x06, 0x62, 0xe2, 0xca, 0xa8, 0x1d, 0x55, 0x43, 0x30, 0xb4, 0x81,
|
||||
0x18, 0xbd, 0x74, 0x15, 0x0c, 0x32, 0x3b, 0xe1, 0x25, 0x30, 0x40, 0xac, 0x43, 0xae, 0x75, 0xac,
|
||||
0x32, 0xcc, 0x44, 0x90, 0x75, 0x88, 0x18, 0xad, 0xf4, 0xb7, 0x79, 0x30, 0x91, 0xd9, 0x0b, 0x9c,
|
||||
0x05, 0x39, 0x35, 0x59, 0x03, 0x52, 0x69, 0x6e, 0x7d, 0x15, 0xe5, 0x1c, 0x1b, 0xbe, 0xa0, 0xb2,
|
||||
0xab, 0x00, 0x2d, 0xaa, 0x62, 0xc1, 0xa9, 0xac, 0x2d, 0x4b, 0xd5, 0x31, 0x43, 0x92, 0xf4, 0xc8,
|
||||
0x6c, 0xc0, 0x7b, 0xf2, 0x56, 0x08, 0x1b, 0xf0, 0x1e, 0x62, 0xb4, 0x47, 0x9d, 0x95, 0x24, 0xc3,
|
||||
0x9a, 0xfc, 0x43, 0x0c, 0x6b, 0x86, 0x3e, 0x77, 0x58, 0xf3, 0x38, 0xc8, 0x47, 0x4e, 0xe4, 0x62,
|
||||
0x5e, 0xa9, 0xb4, 0x66, 0xf8, 0x0e, 0x23, 0x22, 0xc1, 0x83, 0x18, 0x0c, 0xdb, 0x78, 0xcf, 0x8a,
|
||||
0xdd, 0x88, 0x17, 0xa5, 0xd1, 0xa5, 0xaf, 0x9f, 0x2e, 0x7a, 0xc4, 0x30, 0x63, 0x55, 0xa8, 0x44,
|
||||
0x89, 0x6e, 0xf8, 0x04, 0x18, 0xf6, 0xac, 0x23, 0xc7, 0x8b, 0x3d, 0xde, 0x31, 0x1a, 0x42, 0x6c,
|
||||
0x53, 0x90, 0x50, 0xc2, 0x63, 0x49, 0x10, 0x1f, 0xd5, 0xdc, 0x98, 0x3a, 0x0d, 0x2c, 0x99, 0xb2,
|
||||
0xa5, 0x53, 0x49, 0x70, 0x2d, 0xc3, 0x47, 0x1d, 0x2b, 0x38, 0x98, 0xe3, 0xf3, 0xc5, 0xa3, 0x1a,
|
||||
0x98, 0x20, 0xa1, 0x84, 0xd7, 0x0e, 0x26, 0xe5, 0xc7, 0x7a, 0x81, 0xc9, 0xc5, 0x1d, 0x2b, 0xe0,
|
||||
0xd3, 0x60, 0xc4, 0xb3, 0x8e, 0x36, 0xb0, 0x5f, 0x8f, 0xf6, 0xcd, 0xf1, 0x79, 0x63, 0x61, 0xa0,
|
||||
0x32, 0xde, 0x6a, 0x16, 0x47, 0x36, 0x13, 0x22, 0x4a, 0xf9, 0x5c, 0xd8, 0xf1, 0xa5, 0xf0, 0x79,
|
||||
0x4d, 0x38, 0x21, 0xa2, 0x94, 0xcf, 0x3a, 0x93, 0xd0, 0x8a, 0xd8, 0xbd, 0x32, 0x27, 0xda, 0x1f,
|
||||
0xce, 0xdb, 0x82, 0x8c, 0x12, 0x3e, 0x5c, 0x00, 0x05, 0xcf, 0x3a, 0xe2, 0x6f, 0x4a, 0x73, 0x92,
|
||||
0xab, 0xe5, 0x03, 0xc5, 0x4d, 0x49, 0x43, 0x8a, 0xcb, 0x25, 0x1d, 0x5f, 0x48, 0x4e, 0x69, 0x92,
|
||||
0x92, 0x86, 0x14, 0x97, 0xc5, 0x6f, 0xec, 0x3b, 0xf7, 0x63, 0x2c, 0x84, 0x21, 0xf7, 0x8c, 0x8a,
|
||||
0xdf, 0xbb, 0x29, 0x0b, 0xe9, 0x72, 0xec, 0x4d, 0xe7, 0xc5, 0x6e, 0xe4, 0x84, 0x2e, 0xde, 0xda,
|
||||
0x33, 0x2f, 0x70, 0xff, 0xf3, 0x56, 0x7e, 0x53, 0x51, 0x91, 0x26, 0x01, 0xdf, 0x06, 0x83, 0xd8,
|
||||
0x8f, 0x3d, 0xf3, 0x22, 0x2f, 0xdf, 0xa7, 0x8d, 0x3e, 0x75, 0x5f, 0xd6, 0xfc, 0xd8, 0x43, 0x5c,
|
||||
0x33, 0x7c, 0x01, 0x8c, 0x7b, 0xd6, 0x11, 0x4b, 0x02, 0x98, 0x44, 0xec, 0xa1, 0x39, 0xcd, 0xf7,
|
||||
0x3d, 0xc5, 0x9a, 0xd8, 0x4d, 0x9d, 0x81, 0xda, 0xe5, 0xf8, 0x42, 0xc7, 0xd7, 0x16, 0xce, 0x68,
|
||||
0x0b, 0x75, 0x06, 0x6a, 0x97, 0x63, 0x4e, 0x26, 0xf8, 0x7e, 0xec, 0x10, 0x6c, 0x9b, 0xff, 0xc3,
|
||||
0xfb, 0x5e, 0x39, 0xdf, 0x15, 0x34, 0xa4, 0xb8, 0xf0, 0x7e, 0x32, 0x72, 0x30, 0xf9, 0xe5, 0xdb,
|
||||
0xee, 0x5b, 0xea, 0xde, 0x22, 0xcb, 0x84, 0x58, 0xc7, 0xa2, 0xaa, 0xe8, 0xc3, 0x06, 0xe8, 0x83,
|
||||
0xbc, 0xe5, 0xba, 0x5b, 0x7b, 0xe6, 0x25, 0xee, 0xf1, 0x3e, 0x56, 0x0b, 0x95, 0x61, 0x96, 0x99,
|
||||
0x7e, 0x24, 0x60, 0x18, 0x5e, 0xe0, 0xb3, 0x58, 0x98, 0x3d, 0x33, 0xbc, 0x2d, 0xa6, 0x1f, 0x09,
|
||||
0x18, 0xbe, 0x3f, 0xff, 0x78, 0x6b, 0xcf, 0x7c, 0xec, 0xec, 0xf6, 0xc7, 0xf4, 0x23, 0x01, 0x03,
|
||||
0x6d, 0x30, 0xe0, 0x07, 0x91, 0x79, 0xb9, 0xdf, 0xb5, 0x97, 0x57, 0x93, 0xdb, 0x41, 0x84, 0x98,
|
||||
0x7a, 0xf8, 0x23, 0x03, 0x80, 0x30, 0x8d, 0xc4, 0x2b, 0xa7, 0x1d, 0x01, 0x64, 0xd0, 0xca, 0x69,
|
||||
0xf4, 0xae, 0xf9, 0x11, 0x39, 0x4e, 0xdf, 0x35, 0x5a, 0x94, 0x6b, 0x06, 0xc0, 0x9f, 0x1b, 0xe0,
|
||||
0xa2, 0xde, 0xee, 0x2a, 0xcb, 0xe6, 0xb8, 0x1f, 0xb6, 0xfa, 0x18, 0xc8, 0x95, 0x20, 0x70, 0x2b,
|
||||
0x66, 0xab, 0x59, 0xbc, 0xb8, 0xdc, 0x05, 0x10, 0x75, 0x35, 0x03, 0xfe, 0xda, 0x00, 0x53, 0x32,
|
||||
0x3b, 0x6a, 0xc6, 0x15, 0xb9, 0xdb, 0xde, 0xee, 0xa3, 0xdb, 0xb2, 0x10, 0xc2, 0x7b, 0xea, 0x2b,
|
||||
0x63, 0x07, 0x1f, 0x75, 0x5a, 0x05, 0x7f, 0x67, 0x80, 0x31, 0x1b, 0x87, 0xd8, 0xb7, 0xb1, 0x5f,
|
||||
0x63, 0x66, 0xce, 0x9f, 0x76, 0xae, 0x90, 0x35, 0x73, 0x55, 0xd3, 0x2e, 0x2c, 0x2c, 0x4b, 0x0b,
|
||||
0xc7, 0x74, 0xd6, 0x49, 0xb3, 0x38, 0x93, 0x2e, 0xd5, 0x39, 0xa8, 0xcd, 0x40, 0xf8, 0x63, 0x03,
|
||||
0x4c, 0xa4, 0x6e, 0x17, 0x05, 0xe2, 0xea, 0xd9, 0x1c, 0x3c, 0x6f, 0x41, 0x97, 0xdb, 0xb1, 0x50,
|
||||
0x16, 0x1c, 0xfe, 0xc6, 0x60, 0xdd, 0x56, 0xf2, 0x56, 0xa3, 0x66, 0x89, 0x7b, 0xf0, 0x8d, 0x7e,
|
||||
0x7a, 0x50, 0x29, 0x17, 0x0e, 0xbc, 0x9e, 0x76, 0x72, 0x8a, 0x73, 0xd2, 0x2c, 0x4e, 0xeb, 0xfe,
|
||||
0x53, 0x0c, 0xa4, 0x1b, 0x07, 0xdf, 0x33, 0xc0, 0x18, 0x4e, 0x1b, 0x66, 0x6a, 0x3e, 0x7e, 0x5a,
|
||||
0xd7, 0x75, 0x6d, 0xbf, 0xc5, 0x73, 0x5a, 0x63, 0x51, 0xd4, 0x06, 0xcb, 0x7a, 0x3f, 0x7c, 0x64,
|
||||
0x79, 0xa1, 0x8b, 0xcd, 0xff, 0xed, 0x5f, 0xef, 0xb7, 0x26, 0x54, 0xa2, 0x44, 0x37, 0xbc, 0x0e,
|
||||
0x0a, 0x7e, 0xec, 0xba, 0xd6, 0xae, 0x8b, 0xcd, 0x27, 0x78, 0x17, 0xa1, 0xe6, 0x8b, 0xb7, 0x25,
|
||||
0x1d, 0x29, 0x09, 0xb8, 0x07, 0xe6, 0x8f, 0x6e, 0xa9, 0x1f, 0x5f, 0x74, 0x1d, 0xe0, 0x99, 0xd7,
|
||||
0xb8, 0x96, 0xd9, 0x56, 0xb3, 0x38, 0xb3, 0xd3, 0x7d, 0xc4, 0xf7, 0x40, 0x1d, 0xf0, 0x4d, 0xf0,
|
||||
0x98, 0x26, 0xb3, 0xe6, 0xed, 0x62, 0xdb, 0xc6, 0x76, 0xf2, 0xd0, 0x32, 0xff, 0x8f, 0x43, 0xa8,
|
||||
0x7b, 0xbc, 0x93, 0x15, 0x40, 0x9f, 0xb7, 0x1a, 0x6e, 0x80, 0x19, 0x8d, 0xbd, 0xee, 0x47, 0x5b,
|
||||
0xa4, 0x1a, 0x11, 0xc7, 0xaf, 0x9b, 0x0b, 0x5c, 0xef, 0xc5, 0xe4, 0xf6, 0xed, 0x68, 0x3c, 0xd4,
|
||||
0x63, 0x0d, 0x7c, 0xa5, 0x4d, 0x1b, 0xff, 0x70, 0x61, 0x85, 0xb7, 0xf0, 0x31, 0x35, 0x9f, 0xe4,
|
||||
0xcd, 0x05, 0x3f, 0xe7, 0x1d, 0x8d, 0x8e, 0x7a, 0xc8, 0xc3, 0x6f, 0x80, 0x0b, 0x19, 0x0e, 0x7b,
|
||||
0x57, 0x98, 0x4f, 0x89, 0x07, 0x02, 0xeb, 0x44, 0x77, 0x12, 0x22, 0xea, 0x26, 0x09, 0xbf, 0x06,
|
||||
0xa0, 0x46, 0xde, 0xb4, 0x42, 0xbe, 0xfe, 0x69, 0xf1, 0x56, 0x61, 0x27, 0xba, 0x23, 0x69, 0xa8,
|
||||
0x8b, 0x1c, 0xfc, 0xd0, 0x68, 0xdb, 0x49, 0xfa, 0x9a, 0xa5, 0xe6, 0x75, 0x7e, 0x61, 0x5f, 0x79,
|
||||
0xf4, 0x00, 0x4c, 0x95, 0xa1, 0xd8, 0xc5, 0x9a, 0x87, 0x35, 0x14, 0xd4, 0x03, 0x7d, 0x96, 0x3d,
|
||||
0xa6, 0x33, 0x39, 0x1c, 0x4e, 0x82, 0x81, 0x03, 0x2c, 0x3f, 0x1b, 0x23, 0xf6, 0x27, 0x7c, 0x0b,
|
||||
0xe4, 0x1b, 0x96, 0x1b, 0x27, 0xa3, 0x80, 0xfe, 0xd5, 0x7a, 0x24, 0xf4, 0xbe, 0x94, 0x7b, 0xd1,
|
||||
0x98, 0xfd, 0xc0, 0x00, 0x33, 0xdd, 0xab, 0xca, 0x97, 0x65, 0xd1, 0xcf, 0x0c, 0x30, 0xd5, 0x51,
|
||||
0x40, 0xba, 0x18, 0xe3, 0xb6, 0x1b, 0x73, 0xaf, 0x8f, 0x95, 0x40, 0x5c, 0x04, 0xde, 0xd1, 0xea,
|
||||
0x96, 0xfd, 0xd0, 0x00, 0x93, 0xd9, 0xc4, 0xfc, 0x25, 0x79, 0xa9, 0xf4, 0x7e, 0x0e, 0xcc, 0x74,
|
||||
0xef, 0xc1, 0xa1, 0xa7, 0xa6, 0x0b, 0x7d, 0x1f, 0xd0, 0x74, 0x1b, 0xd9, 0xbe, 0x6b, 0x80, 0xd1,
|
||||
0x77, 0x94, 0x5c, 0xf2, 0x35, 0xb3, 0x9f, 0x53, 0xa1, 0xa4, 0xf4, 0xa5, 0x0c, 0x8a, 0x74, 0xc8,
|
||||
0xd2, 0x6f, 0x0d, 0x30, 0xdd, 0xb5, 0x9c, 0xc3, 0x6b, 0x60, 0xc8, 0x72, 0xdd, 0xe0, 0x50, 0x4c,
|
||||
0xf3, 0xb4, 0xb1, 0xfc, 0x32, 0xa7, 0x22, 0xc9, 0xd5, 0x7c, 0x96, 0xfb, 0x02, 0x7c, 0x56, 0xfa,
|
||||
0x83, 0x01, 0x2e, 0x7f, 0x5e, 0xd4, 0x7d, 0xd1, 0x67, 0xb8, 0x00, 0x0a, 0xb2, 0xd9, 0x3e, 0xe6,
|
||||
0xe7, 0x27, 0xb3, 0xab, 0xcc, 0x08, 0xfc, 0xd7, 0x32, 0xe2, 0xaf, 0xd2, 0x2f, 0x0d, 0x30, 0x59,
|
||||
0xc5, 0xa4, 0xe1, 0xd4, 0x30, 0xc2, 0x7b, 0x98, 0x60, 0xbf, 0x86, 0xe1, 0x22, 0x18, 0xe1, 0x5f,
|
||||
0x1b, 0x43, 0xab, 0x96, 0x7c, 0x23, 0x99, 0x92, 0x8e, 0x1e, 0xb9, 0x9d, 0x30, 0x50, 0x2a, 0xa3,
|
||||
0xbe, 0xa7, 0xe4, 0x7a, 0x7e, 0x4f, 0xb9, 0x0c, 0x06, 0xc3, 0x74, 0x00, 0x5c, 0x60, 0x5c, 0x3e,
|
||||
0xf3, 0xe5, 0x54, 0xce, 0x0d, 0x48, 0xc4, 0xa7, 0x5c, 0x79, 0xc9, 0x0d, 0x48, 0x84, 0x38, 0xb5,
|
||||
0xf4, 0x2d, 0x70, 0xbe, 0x3d, 0x3d, 0x33, 0x3c, 0x12, 0xbb, 0x1d, 0xdf, 0x6f, 0x18, 0x0f, 0x71,
|
||||
0x8e, 0xfe, 0xb3, 0x81, 0xdc, 0x03, 0x7e, 0x36, 0xf0, 0x47, 0x03, 0x5c, 0x48, 0x7e, 0x55, 0xe3,
|
||||
0x3a, 0xd8, 0x8f, 0x56, 0x02, 0x7f, 0xcf, 0xa9, 0xc3, 0x4b, 0x62, 0x8e, 0xa8, 0x0d, 0xe7, 0x92,
|
||||
0x19, 0x22, 0xbc, 0x0f, 0x86, 0xa9, 0x70, 0x9a, 0x3c, 0xcf, 0x57, 0x1f, 0xfd, 0x3c, 0xb3, 0xde,
|
||||
0x17, 0x6d, 0x50, 0x42, 0x4d, 0x70, 0xd8, 0x91, 0xd6, 0xac, 0x4a, 0xec, 0xdb, 0x72, 0x96, 0x3c,
|
||||
0x26, 0x8e, 0x74, 0x65, 0x59, 0xd0, 0x90, 0xe2, 0x96, 0xfe, 0x6e, 0x80, 0xa9, 0x8e, 0x5f, 0x09,
|
||||
0xc1, 0xef, 0x19, 0x60, 0xac, 0xa6, 0x6d, 0x4f, 0x5e, 0x8c, 0xcd, 0xd3, 0xff, 0x12, 0x49, 0x53,
|
||||
0x2a, 0x7a, 0x09, 0x9d, 0x82, 0xda, 0x40, 0xe1, 0x0e, 0x30, 0x6b, 0x99, 0x1f, 0xe4, 0x65, 0x3e,
|
||||
0xf1, 0x5d, 0x6e, 0x35, 0x8b, 0xe6, 0x4a, 0x0f, 0x19, 0xd4, 0x73, 0x75, 0x65, 0xe1, 0xe3, 0xcf,
|
||||
0xe6, 0xce, 0x7d, 0xf2, 0xd9, 0xdc, 0xb9, 0x4f, 0x3f, 0x9b, 0x3b, 0xf7, 0x6e, 0x6b, 0xce, 0xf8,
|
||||
0xb8, 0x35, 0x67, 0x7c, 0xd2, 0x9a, 0x33, 0x3e, 0x6d, 0xcd, 0x19, 0x7f, 0x69, 0xcd, 0x19, 0x3f,
|
||||
0xf9, 0xeb, 0xdc, 0xb9, 0x37, 0x72, 0x8d, 0x1b, 0xff, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x19, 0x76,
|
||||
0x99, 0xb1, 0xa4, 0x2b, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *ConversionRequest) Marshal() (dAtA []byte, err error) {
|
||||
@ -1884,6 +1917,22 @@ func (m *JSONSchemaProps) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.XValidations) > 0 {
|
||||
for iNdEx := len(m.XValidations) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.XValidations[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x2
|
||||
i--
|
||||
dAtA[i] = 0xe2
|
||||
}
|
||||
}
|
||||
if m.XMapType != nil {
|
||||
i -= len(*m.XMapType)
|
||||
copy(dAtA[i:], *m.XMapType)
|
||||
@ -2560,6 +2609,39 @@ func (m *ServiceReference) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *ValidationRule) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *ValidationRule) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *ValidationRule) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
i -= len(m.Message)
|
||||
copy(dAtA[i:], m.Message)
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message)))
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
i -= len(m.Rule)
|
||||
copy(dAtA[i:], m.Rule)
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Rule)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *WebhookClientConfig) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
@ -3165,6 +3247,12 @@ func (m *JSONSchemaProps) Size() (n int) {
|
||||
l = len(*m.XMapType)
|
||||
n += 2 + l + sovGenerated(uint64(l))
|
||||
}
|
||||
if len(m.XValidations) > 0 {
|
||||
for _, e := range m.XValidations {
|
||||
l = e.Size()
|
||||
n += 2 + l + sovGenerated(uint64(l))
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@ -3240,6 +3328,19 @@ func (m *ServiceReference) Size() (n int) {
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *ValidationRule) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
l = len(m.Rule)
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
l = len(m.Message)
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *WebhookClientConfig) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
@ -3560,6 +3661,11 @@ func (this *JSONSchemaProps) String() string {
|
||||
repeatedStringForAnyOf += strings.Replace(strings.Replace(f.String(), "JSONSchemaProps", "JSONSchemaProps", 1), `&`, ``, 1) + ","
|
||||
}
|
||||
repeatedStringForAnyOf += "}"
|
||||
repeatedStringForXValidations := "[]ValidationRule{"
|
||||
for _, f := range this.XValidations {
|
||||
repeatedStringForXValidations += strings.Replace(strings.Replace(f.String(), "ValidationRule", "ValidationRule", 1), `&`, ``, 1) + ","
|
||||
}
|
||||
repeatedStringForXValidations += "}"
|
||||
keysForProperties := make([]string, 0, len(this.Properties))
|
||||
for k := range this.Properties {
|
||||
keysForProperties = append(keysForProperties, k)
|
||||
@ -3644,6 +3750,7 @@ func (this *JSONSchemaProps) String() string {
|
||||
`XListMapKeys:` + fmt.Sprintf("%v", this.XListMapKeys) + `,`,
|
||||
`XListType:` + valueToStringGenerated(this.XListType) + `,`,
|
||||
`XMapType:` + valueToStringGenerated(this.XMapType) + `,`,
|
||||
`XValidations:` + repeatedStringForXValidations + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
@ -3699,6 +3806,17 @@ func (this *ServiceReference) String() string {
|
||||
}, "")
|
||||
return s
|
||||
}
|
||||
func (this *ValidationRule) String() string {
|
||||
if this == nil {
|
||||
return "nil"
|
||||
}
|
||||
s := strings.Join([]string{`&ValidationRule{`,
|
||||
`Rule:` + fmt.Sprintf("%v", this.Rule) + `,`,
|
||||
`Message:` + fmt.Sprintf("%v", this.Message) + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
}
|
||||
func (this *WebhookClientConfig) String() string {
|
||||
if this == nil {
|
||||
return "nil"
|
||||
@ -8102,6 +8220,40 @@ func (m *JSONSchemaProps) Unmarshal(dAtA []byte) error {
|
||||
s := string(dAtA[iNdEx:postIndex])
|
||||
m.XMapType = &s
|
||||
iNdEx = postIndex
|
||||
case 44:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field XValidations", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.XValidations = append(m.XValidations, ValidationRule{})
|
||||
if err := m.XValidations[len(m.XValidations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipGenerated(dAtA[iNdEx:])
|
||||
@ -8634,6 +8786,120 @@ func (m *ServiceReference) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *ValidationRule) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: ValidationRule: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: ValidationRule: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Rule", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Rule = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Message = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipGenerated(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *WebhookClientConfig) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
|
@ -549,6 +549,14 @@ message JSONSchemaProps {
|
||||
// Atomic maps will be entirely replaced when updated.
|
||||
// +optional
|
||||
optional string xKubernetesMapType = 43;
|
||||
|
||||
// x-kubernetes-validations describes a list of validation rules written in the CEL expression language.
|
||||
// This field is an alpha-level. Using this field requires the feature gate `CustomResourceValidationExpressions` to be enabled.
|
||||
// +patchMergeKey=rule
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=rule
|
||||
repeated ValidationRule xKubernetesValidations = 44;
|
||||
}
|
||||
|
||||
// JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps
|
||||
@ -595,6 +603,70 @@ message ServiceReference {
|
||||
optional int32 port = 4;
|
||||
}
|
||||
|
||||
// ValidationRule describes a validation rule written in the CEL expression language.
|
||||
message ValidationRule {
|
||||
// Rule represents the expression which will be evaluated by CEL.
|
||||
// ref: https://github.com/google/cel-spec
|
||||
// The Rule is scoped to the location of the x-kubernetes-validations extension in the schema.
|
||||
// The `self` variable in the CEL expression is bound to the scoped value.
|
||||
// Example:
|
||||
// - Rule scoped to the root of a resource with a status subresource: {"rule": "self.status.actual <= self.spec.maxDesired"}
|
||||
//
|
||||
// If the Rule is scoped to an object with properties, the accessible properties of the object are field selectable
|
||||
// via `self.field` and field presence can be checked via `has(self.field)`. Null valued fields are treated as
|
||||
// absent fields in CEL expressions.
|
||||
// If the Rule is scoped to an object with additionalProperties (i.e. a map) the value of the map
|
||||
// are accessible via `self[mapKey]`, map containment can be checked via `mapKey in self` and all entries of the map
|
||||
// are accessible via CEL macros and functions such as `self.all(...)`.
|
||||
// If the Rule is scoped to an array, the elements of the array are accessible via `self[i]` and also by macros and
|
||||
// functions.
|
||||
// If the Rule is scoped to a scalar, `self` is bound to the scalar value.
|
||||
// Examples:
|
||||
// - Rule scoped to a map of objects: {"rule": "self.components['Widget'].priority < 10"}
|
||||
// - Rule scoped to a list of integers: {"rule": "self.values.all(value, value >= 0 && value < 100)"}
|
||||
// - Rule scoped to a string value: {"rule": "self.startsWith('kube')"}
|
||||
//
|
||||
// The `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the
|
||||
// object and from any x-kubernetes-embedded-resource annotated objects. No other metadata properties are accessible.
|
||||
//
|
||||
// Unknown data preserved in custom resources via x-kubernetes-preserve-unknown-fields is not accessible in CEL
|
||||
// expressions. This includes:
|
||||
// - Unknown field values that are preserved by object schemas with x-kubernetes-preserve-unknown-fields.
|
||||
// - Object properties where the property schema is of an "unknown type". An "unknown type" is recursively defined as:
|
||||
// - A schema with no type and x-kubernetes-preserve-unknown-fields set to true
|
||||
// - An array where the items schema is of an "unknown type"
|
||||
// - An object where the additionalProperties schema is of an "unknown type"
|
||||
//
|
||||
// Only property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible.
|
||||
// Accessible property names are escaped according to the following rules when accessed in the expression:
|
||||
// - '__' escapes to '__underscores__'
|
||||
// - '.' escapes to '__dot__'
|
||||
// - '-' escapes to '__dash__'
|
||||
// - '/' escapes to '__slash__'
|
||||
// - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:
|
||||
// "true", "false", "null", "in", "as", "break", "const", "continue", "else", "for", "function", "if",
|
||||
// "import", "let", "loop", "package", "namespace", "return".
|
||||
// Examples:
|
||||
// - Rule accessing a property named "namespace": {"rule": "self.__namespace__ > 0"}
|
||||
// - Rule accessing a property named "x-prop": {"rule": "self.x__dash__prop > 0"}
|
||||
// - Rule accessing a property named "redact__d": {"rule": "self.redact__underscores__d > 0"}
|
||||
//
|
||||
// Equality on arrays with x-kubernetes-list-type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1].
|
||||
// Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:
|
||||
// - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and
|
||||
// non-intersecting elements in `Y` are appended, retaining their partial order.
|
||||
// - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values
|
||||
// are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with
|
||||
// non-intersecting keys are appended, retaining their partial order.
|
||||
optional string rule = 1;
|
||||
|
||||
// Message represents the message displayed when validation fails. The message is required if the Rule contains
|
||||
// line breaks. The message must not contain line breaks.
|
||||
// If unset, the message is "failed rule: {Rule}".
|
||||
// e.g. "must be a URL with the host matching spec.host"
|
||||
optional string message = 2;
|
||||
}
|
||||
|
||||
// WebhookClientConfig contains the information to make a TLS connection with the webhook.
|
||||
message WebhookClientConfig {
|
||||
// url gives the location of the webhook, in standard URL form
|
||||
|
@ -161,6 +161,80 @@ type JSONSchemaProps struct {
|
||||
// Atomic maps will be entirely replaced when updated.
|
||||
// +optional
|
||||
XMapType *string `json:"x-kubernetes-map-type,omitempty" protobuf:"bytes,43,opt,name=xKubernetesMapType"`
|
||||
|
||||
// x-kubernetes-validations describes a list of validation rules written in the CEL expression language.
|
||||
// This field is an alpha-level. Using this field requires the feature gate `CustomResourceValidationExpressions` to be enabled.
|
||||
// +patchMergeKey=rule
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=rule
|
||||
XValidations ValidationRules `json:"x-kubernetes-validations,omitempty" patchStrategy:"merge" patchMergeKey:"rule" protobuf:"bytes,44,rep,name=xKubernetesValidations"`
|
||||
}
|
||||
|
||||
// ValidationRules describes a list of validation rules written in the CEL expression language.
|
||||
type ValidationRules []ValidationRule
|
||||
|
||||
// ValidationRule describes a validation rule written in the CEL expression language.
|
||||
type ValidationRule struct {
|
||||
// Rule represents the expression which will be evaluated by CEL.
|
||||
// ref: https://github.com/google/cel-spec
|
||||
// The Rule is scoped to the location of the x-kubernetes-validations extension in the schema.
|
||||
// The `self` variable in the CEL expression is bound to the scoped value.
|
||||
// Example:
|
||||
// - Rule scoped to the root of a resource with a status subresource: {"rule": "self.status.actual <= self.spec.maxDesired"}
|
||||
//
|
||||
// If the Rule is scoped to an object with properties, the accessible properties of the object are field selectable
|
||||
// via `self.field` and field presence can be checked via `has(self.field)`. Null valued fields are treated as
|
||||
// absent fields in CEL expressions.
|
||||
// If the Rule is scoped to an object with additionalProperties (i.e. a map) the value of the map
|
||||
// are accessible via `self[mapKey]`, map containment can be checked via `mapKey in self` and all entries of the map
|
||||
// are accessible via CEL macros and functions such as `self.all(...)`.
|
||||
// If the Rule is scoped to an array, the elements of the array are accessible via `self[i]` and also by macros and
|
||||
// functions.
|
||||
// If the Rule is scoped to a scalar, `self` is bound to the scalar value.
|
||||
// Examples:
|
||||
// - Rule scoped to a map of objects: {"rule": "self.components['Widget'].priority < 10"}
|
||||
// - Rule scoped to a list of integers: {"rule": "self.values.all(value, value >= 0 && value < 100)"}
|
||||
// - Rule scoped to a string value: {"rule": "self.startsWith('kube')"}
|
||||
//
|
||||
// The `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the
|
||||
// object and from any x-kubernetes-embedded-resource annotated objects. No other metadata properties are accessible.
|
||||
//
|
||||
// Unknown data preserved in custom resources via x-kubernetes-preserve-unknown-fields is not accessible in CEL
|
||||
// expressions. This includes:
|
||||
// - Unknown field values that are preserved by object schemas with x-kubernetes-preserve-unknown-fields.
|
||||
// - Object properties where the property schema is of an "unknown type". An "unknown type" is recursively defined as:
|
||||
// - A schema with no type and x-kubernetes-preserve-unknown-fields set to true
|
||||
// - An array where the items schema is of an "unknown type"
|
||||
// - An object where the additionalProperties schema is of an "unknown type"
|
||||
//
|
||||
// Only property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible.
|
||||
// Accessible property names are escaped according to the following rules when accessed in the expression:
|
||||
// - '__' escapes to '__underscores__'
|
||||
// - '.' escapes to '__dot__'
|
||||
// - '-' escapes to '__dash__'
|
||||
// - '/' escapes to '__slash__'
|
||||
// - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:
|
||||
// "true", "false", "null", "in", "as", "break", "const", "continue", "else", "for", "function", "if",
|
||||
// "import", "let", "loop", "package", "namespace", "return".
|
||||
// Examples:
|
||||
// - Rule accessing a property named "namespace": {"rule": "self.__namespace__ > 0"}
|
||||
// - Rule accessing a property named "x-prop": {"rule": "self.x__dash__prop > 0"}
|
||||
// - Rule accessing a property named "redact__d": {"rule": "self.redact__underscores__d > 0"}
|
||||
//
|
||||
// Equality on arrays with x-kubernetes-list-type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1].
|
||||
// Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:
|
||||
// - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and
|
||||
// non-intersecting elements in `Y` are appended, retaining their partial order.
|
||||
// - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values
|
||||
// are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with
|
||||
// non-intersecting keys are appended, retaining their partial order.
|
||||
Rule string `json:"rule" protobuf:"bytes,1,opt,name=rule"`
|
||||
// Message represents the message displayed when validation fails. The message is required if the Rule contains
|
||||
// line breaks. The message must not contain line breaks.
|
||||
// If unset, the message is "failed rule: {Rule}".
|
||||
// e.g. "must be a URL with the host matching spec.host"
|
||||
Message string `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"`
|
||||
}
|
||||
|
||||
// JSON represents any valid JSON value.
|
||||
|
@ -202,6 +202,16 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*ValidationRule)(nil), (*apiextensions.ValidationRule)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_ValidationRule_To_apiextensions_ValidationRule(a.(*ValidationRule), b.(*apiextensions.ValidationRule), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*apiextensions.ValidationRule)(nil), (*ValidationRule)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_apiextensions_ValidationRule_To_v1_ValidationRule(a.(*apiextensions.ValidationRule), b.(*ValidationRule), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*WebhookClientConfig)(nil), (*apiextensions.WebhookClientConfig)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_WebhookClientConfig_To_apiextensions_WebhookClientConfig(a.(*WebhookClientConfig), b.(*apiextensions.WebhookClientConfig), scope)
|
||||
}); err != nil {
|
||||
@ -883,6 +893,7 @@ func autoConvert_v1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(in *JSONSch
|
||||
out.XListMapKeys = *(*[]string)(unsafe.Pointer(&in.XListMapKeys))
|
||||
out.XListType = (*string)(unsafe.Pointer(in.XListType))
|
||||
out.XMapType = (*string)(unsafe.Pointer(in.XMapType))
|
||||
out.XValidations = *(*apiextensions.ValidationRules)(unsafe.Pointer(&in.XValidations))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1071,6 +1082,7 @@ func autoConvert_apiextensions_JSONSchemaProps_To_v1_JSONSchemaProps(in *apiexte
|
||||
out.XListMapKeys = *(*[]string)(unsafe.Pointer(&in.XListMapKeys))
|
||||
out.XListType = (*string)(unsafe.Pointer(in.XListType))
|
||||
out.XMapType = (*string)(unsafe.Pointer(in.XMapType))
|
||||
out.XValidations = *(*ValidationRules)(unsafe.Pointer(&in.XValidations))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1238,6 +1250,28 @@ func Convert_apiextensions_ServiceReference_To_v1_ServiceReference(in *apiextens
|
||||
return autoConvert_apiextensions_ServiceReference_To_v1_ServiceReference(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_ValidationRule_To_apiextensions_ValidationRule(in *ValidationRule, out *apiextensions.ValidationRule, s conversion.Scope) error {
|
||||
out.Rule = in.Rule
|
||||
out.Message = in.Message
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_ValidationRule_To_apiextensions_ValidationRule is an autogenerated conversion function.
|
||||
func Convert_v1_ValidationRule_To_apiextensions_ValidationRule(in *ValidationRule, out *apiextensions.ValidationRule, s conversion.Scope) error {
|
||||
return autoConvert_v1_ValidationRule_To_apiextensions_ValidationRule(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_apiextensions_ValidationRule_To_v1_ValidationRule(in *apiextensions.ValidationRule, out *ValidationRule, s conversion.Scope) error {
|
||||
out.Rule = in.Rule
|
||||
out.Message = in.Message
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_apiextensions_ValidationRule_To_v1_ValidationRule is an autogenerated conversion function.
|
||||
func Convert_apiextensions_ValidationRule_To_v1_ValidationRule(in *apiextensions.ValidationRule, out *ValidationRule, s conversion.Scope) error {
|
||||
return autoConvert_apiextensions_ValidationRule_To_v1_ValidationRule(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_WebhookClientConfig_To_apiextensions_WebhookClientConfig(in *WebhookClientConfig, out *apiextensions.WebhookClientConfig, s conversion.Scope) error {
|
||||
out.URL = (*string)(unsafe.Pointer(in.URL))
|
||||
if in.Service != nil {
|
||||
|
@ -611,6 +611,42 @@ func (in *ServiceReference) DeepCopy() *ServiceReference {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ValidationRule) DeepCopyInto(out *ValidationRule) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidationRule.
|
||||
func (in *ValidationRule) DeepCopy() *ValidationRule {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ValidationRule)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in ValidationRules) DeepCopyInto(out *ValidationRules) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(ValidationRules, len(*in))
|
||||
copy(*out, *in)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidationRules.
|
||||
func (in ValidationRules) DeepCopy() ValidationRules {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ValidationRules)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WebhookClientConfig) DeepCopyInto(out *WebhookClientConfig) {
|
||||
*out = *in
|
||||
|
@ -692,10 +692,38 @@ func (m *ServiceReference) XXX_DiscardUnknown() {
|
||||
|
||||
var xxx_messageInfo_ServiceReference proto.InternalMessageInfo
|
||||
|
||||
func (m *ValidationRule) Reset() { *m = ValidationRule{} }
|
||||
func (*ValidationRule) ProtoMessage() {}
|
||||
func (*ValidationRule) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_98a4cc6918394e53, []int{23}
|
||||
}
|
||||
func (m *ValidationRule) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *ValidationRule) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
func (m *ValidationRule) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ValidationRule.Merge(m, src)
|
||||
}
|
||||
func (m *ValidationRule) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *ValidationRule) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ValidationRule.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ValidationRule proto.InternalMessageInfo
|
||||
|
||||
func (m *WebhookClientConfig) Reset() { *m = WebhookClientConfig{} }
|
||||
func (*WebhookClientConfig) ProtoMessage() {}
|
||||
func (*WebhookClientConfig) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_98a4cc6918394e53, []int{23}
|
||||
return fileDescriptor_98a4cc6918394e53, []int{24}
|
||||
}
|
||||
func (m *WebhookClientConfig) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -748,6 +776,7 @@ func init() {
|
||||
proto.RegisterType((*JSONSchemaPropsOrBool)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1.JSONSchemaPropsOrBool")
|
||||
proto.RegisterType((*JSONSchemaPropsOrStringArray)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1.JSONSchemaPropsOrStringArray")
|
||||
proto.RegisterType((*ServiceReference)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1.ServiceReference")
|
||||
proto.RegisterType((*ValidationRule)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1.ValidationRule")
|
||||
proto.RegisterType((*WebhookClientConfig)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1.WebhookClientConfig")
|
||||
}
|
||||
|
||||
@ -756,196 +785,200 @@ func init() {
|
||||
}
|
||||
|
||||
var fileDescriptor_98a4cc6918394e53 = []byte{
|
||||
// 3022 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5a, 0xcb, 0x73, 0x23, 0x57,
|
||||
0xd5, 0x9f, 0x96, 0x2d, 0x5b, 0x3e, 0xb6, 0xc7, 0xf6, 0x9d, 0xb1, 0xd3, 0xe3, 0x4c, 0x2c, 0x8f,
|
||||
0xf2, 0x65, 0x3e, 0x27, 0x99, 0x91, 0x13, 0x93, 0x90, 0x90, 0x82, 0xa2, 0x2c, 0xdb, 0x13, 0x9c,
|
||||
0x8c, 0x1f, 0x5c, 0xcd, 0x24, 0x86, 0x3c, 0xdb, 0xea, 0x2b, 0xb9, 0xe3, 0x56, 0x77, 0x4f, 0xdf,
|
||||
0x6e, 0xd9, 0xae, 0x00, 0xc5, 0xa3, 0x52, 0x50, 0x14, 0x10, 0x8a, 0x64, 0x43, 0x15, 0x2c, 0x02,
|
||||
0xc5, 0x86, 0x05, 0x2c, 0xa0, 0xd8, 0xc0, 0x1f, 0x90, 0x65, 0x8a, 0x55, 0x16, 0x94, 0x20, 0x62,
|
||||
0xcb, 0x92, 0x2a, 0xaa, 0xbc, 0xa2, 0xee, 0xa3, 0x6f, 0xb7, 0x5a, 0xd2, 0xcc, 0x54, 0x46, 0xca,
|
||||
0xb0, 0xb3, 0xce, 0xeb, 0x77, 0xee, 0xb9, 0xe7, 0x9e, 0x7b, 0xee, 0x69, 0x43, 0xf5, 0xf0, 0x59,
|
||||
0x5a, 0xb4, 0xdc, 0xe5, 0xc3, 0x70, 0x9f, 0xf8, 0x0e, 0x09, 0x08, 0x5d, 0x6e, 0x10, 0xc7, 0x74,
|
||||
0xfd, 0x65, 0xc9, 0x30, 0x3c, 0x8b, 0x1c, 0x07, 0xc4, 0xa1, 0x96, 0xeb, 0xd0, 0xab, 0x86, 0x67,
|
||||
0x51, 0xe2, 0x37, 0x88, 0xbf, 0xec, 0x1d, 0xd6, 0x18, 0x8f, 0xb6, 0x0b, 0x2c, 0x37, 0x9e, 0xdc,
|
||||
0x27, 0x81, 0xf1, 0xe4, 0x72, 0x8d, 0x38, 0xc4, 0x37, 0x02, 0x62, 0x16, 0x3d, 0xdf, 0x0d, 0x5c,
|
||||
0xf4, 0x25, 0x61, 0xae, 0xd8, 0x26, 0xfd, 0x86, 0x32, 0x57, 0xf4, 0x0e, 0x6b, 0x8c, 0x47, 0xdb,
|
||||
0x05, 0x8a, 0xd2, 0xdc, 0xfc, 0xd5, 0x9a, 0x15, 0x1c, 0x84, 0xfb, 0xc5, 0x8a, 0x5b, 0x5f, 0xae,
|
||||
0xb9, 0x35, 0x77, 0x99, 0x5b, 0xdd, 0x0f, 0xab, 0xfc, 0x17, 0xff, 0xc1, 0xff, 0x12, 0x68, 0xf3,
|
||||
0x4f, 0xc5, 0xce, 0xd7, 0x8d, 0xca, 0x81, 0xe5, 0x10, 0xff, 0x24, 0xf6, 0xb8, 0x4e, 0x02, 0x63,
|
||||
0xb9, 0xd1, 0xe1, 0xe3, 0xfc, 0x72, 0x2f, 0x2d, 0x3f, 0x74, 0x02, 0xab, 0x4e, 0x3a, 0x14, 0x3e,
|
||||
0x7f, 0x27, 0x05, 0x5a, 0x39, 0x20, 0x75, 0x23, 0xad, 0x57, 0x38, 0xd5, 0x60, 0x66, 0xcd, 0x75,
|
||||
0x1a, 0xc4, 0x67, 0xab, 0xc4, 0xe4, 0x56, 0x48, 0x68, 0x80, 0x4a, 0x30, 0x14, 0x5a, 0xa6, 0xae,
|
||||
0x2d, 0x6a, 0x4b, 0x63, 0xa5, 0x27, 0x3e, 0x6c, 0xe6, 0xcf, 0xb4, 0x9a, 0xf9, 0xa1, 0x9b, 0x9b,
|
||||
0xeb, 0xa7, 0xcd, 0xfc, 0xa5, 0x5e, 0x48, 0xc1, 0x89, 0x47, 0x68, 0xf1, 0xe6, 0xe6, 0x3a, 0x66,
|
||||
0xca, 0xe8, 0x79, 0x98, 0x31, 0x09, 0xb5, 0x7c, 0x62, 0xae, 0xee, 0x6e, 0xbe, 0x24, 0xec, 0xeb,
|
||||
0x19, 0x6e, 0xf1, 0x82, 0xb4, 0x38, 0xb3, 0x9e, 0x16, 0xc0, 0x9d, 0x3a, 0x68, 0x0f, 0x46, 0xdd,
|
||||
0xfd, 0xb7, 0x48, 0x25, 0xa0, 0xfa, 0xd0, 0xe2, 0xd0, 0xd2, 0xf8, 0xca, 0xd5, 0x62, 0xbc, 0x83,
|
||||
0xca, 0x05, 0xbe, 0x6d, 0x72, 0xb1, 0x45, 0x6c, 0x1c, 0x6d, 0x44, 0x3b, 0x57, 0x9a, 0x92, 0x68,
|
||||
0xa3, 0x3b, 0xc2, 0x0a, 0x8e, 0xcc, 0x15, 0x7e, 0x9d, 0x01, 0x94, 0x5c, 0x3c, 0xf5, 0x5c, 0x87,
|
||||
0x92, 0xbe, 0xac, 0x9e, 0xc2, 0x74, 0x85, 0x5b, 0x0e, 0x88, 0x29, 0x71, 0xf5, 0xcc, 0xa7, 0xf1,
|
||||
0x5e, 0x97, 0xf8, 0xd3, 0x6b, 0x29, 0x73, 0xb8, 0x03, 0x00, 0xdd, 0x80, 0x11, 0x9f, 0xd0, 0xd0,
|
||||
0x0e, 0xf4, 0xa1, 0x45, 0x6d, 0x69, 0x7c, 0xe5, 0x4a, 0x4f, 0x28, 0x9e, 0xdf, 0x2c, 0xf9, 0x8a,
|
||||
0x8d, 0x27, 0x8b, 0xe5, 0xc0, 0x08, 0x42, 0x5a, 0x3a, 0x2b, 0x91, 0x46, 0x30, 0xb7, 0x81, 0xa5,
|
||||
0xad, 0xc2, 0x0f, 0x32, 0x30, 0x9d, 0x8c, 0x52, 0xc3, 0x22, 0x47, 0xe8, 0x08, 0x46, 0x7d, 0x91,
|
||||
0x2c, 0x3c, 0x4e, 0xe3, 0x2b, 0xbb, 0xc5, 0x7b, 0x3a, 0x56, 0xc5, 0x8e, 0x24, 0x2c, 0x8d, 0xb3,
|
||||
0x3d, 0x93, 0x3f, 0x70, 0x84, 0x86, 0xde, 0x86, 0x9c, 0x2f, 0x37, 0x8a, 0x67, 0xd3, 0xf8, 0xca,
|
||||
0x57, 0xfb, 0x88, 0x2c, 0x0c, 0x97, 0x26, 0x5a, 0xcd, 0x7c, 0x2e, 0xfa, 0x85, 0x15, 0x60, 0xe1,
|
||||
0xbd, 0x0c, 0x2c, 0xac, 0x85, 0x34, 0x70, 0xeb, 0x98, 0x50, 0x37, 0xf4, 0x2b, 0x64, 0xcd, 0xb5,
|
||||
0xc3, 0xba, 0xb3, 0x4e, 0xaa, 0x96, 0x63, 0x05, 0x2c, 0x5b, 0x17, 0x61, 0xd8, 0x31, 0xea, 0x44,
|
||||
0x66, 0xcf, 0x84, 0x8c, 0xe9, 0xf0, 0xb6, 0x51, 0x27, 0x98, 0x73, 0x98, 0x04, 0x4b, 0x16, 0x79,
|
||||
0x16, 0x94, 0xc4, 0x8d, 0x13, 0x8f, 0x60, 0xce, 0x41, 0x97, 0x61, 0xa4, 0xea, 0xfa, 0x75, 0x43,
|
||||
0xec, 0xe3, 0x58, 0xbc, 0x33, 0xd7, 0x38, 0x15, 0x4b, 0x2e, 0x7a, 0x1a, 0xc6, 0x4d, 0x42, 0x2b,
|
||||
0xbe, 0xe5, 0x31, 0x68, 0x7d, 0x98, 0x0b, 0x9f, 0x93, 0xc2, 0xe3, 0xeb, 0x31, 0x0b, 0x27, 0xe5,
|
||||
0xd0, 0x15, 0xc8, 0x79, 0xbe, 0xe5, 0xfa, 0x56, 0x70, 0xa2, 0x67, 0x17, 0xb5, 0xa5, 0x6c, 0x69,
|
||||
0x5a, 0xea, 0xe4, 0x76, 0x25, 0x1d, 0x2b, 0x09, 0xb4, 0x08, 0xb9, 0x17, 0xca, 0x3b, 0xdb, 0xbb,
|
||||
0x46, 0x70, 0xa0, 0x8f, 0x70, 0x84, 0x61, 0x26, 0x8d, 0x15, 0xb5, 0xf0, 0xb7, 0x0c, 0xe8, 0xe9,
|
||||
0xa8, 0x44, 0x21, 0x45, 0xd7, 0x20, 0x47, 0x03, 0x56, 0x71, 0x6a, 0x27, 0x32, 0x26, 0x8f, 0x45,
|
||||
0x60, 0x65, 0x49, 0x3f, 0x6d, 0xe6, 0xe7, 0x62, 0x8d, 0x88, 0xca, 0xe3, 0xa1, 0x74, 0xd1, 0x2f,
|
||||
0x35, 0x38, 0x77, 0x44, 0xf6, 0x0f, 0x5c, 0xf7, 0x70, 0xcd, 0xb6, 0x88, 0x13, 0xac, 0xb9, 0x4e,
|
||||
0xd5, 0xaa, 0xc9, 0x1c, 0xc0, 0xf7, 0x98, 0x03, 0x2f, 0x77, 0x5a, 0x2e, 0x3d, 0xd0, 0x6a, 0xe6,
|
||||
0xcf, 0x75, 0x61, 0xe0, 0x6e, 0x7e, 0xa0, 0x3d, 0xd0, 0x2b, 0xa9, 0x43, 0x22, 0x0b, 0x98, 0x28,
|
||||
0x5b, 0x63, 0xa5, 0x8b, 0xad, 0x66, 0x5e, 0x5f, 0xeb, 0x21, 0x83, 0x7b, 0x6a, 0x17, 0xbe, 0x37,
|
||||
0x94, 0x0e, 0x6f, 0x22, 0xdd, 0xde, 0x84, 0x1c, 0x3b, 0xc6, 0xa6, 0x11, 0x18, 0xf2, 0x20, 0x3e,
|
||||
0x71, 0x77, 0x87, 0x5e, 0xd4, 0x8c, 0x2d, 0x12, 0x18, 0x25, 0x24, 0x37, 0x04, 0x62, 0x1a, 0x56,
|
||||
0x56, 0xd1, 0x37, 0x61, 0x98, 0x7a, 0xa4, 0x22, 0x03, 0xfd, 0xca, 0xbd, 0x1e, 0xb6, 0x1e, 0x0b,
|
||||
0x29, 0x7b, 0xa4, 0x12, 0x9f, 0x05, 0xf6, 0x0b, 0x73, 0x58, 0xf4, 0x8e, 0x06, 0x23, 0x94, 0x17,
|
||||
0x28, 0x59, 0xd4, 0x5e, 0x1b, 0x94, 0x07, 0xa9, 0x2a, 0x28, 0x7e, 0x63, 0x09, 0x5e, 0xf8, 0x77,
|
||||
0x06, 0x2e, 0xf5, 0x52, 0x5d, 0x73, 0x1d, 0x53, 0x6c, 0xc7, 0xa6, 0x3c, 0xdb, 0x22, 0xd3, 0x9f,
|
||||
0x4e, 0x9e, 0xed, 0xd3, 0x66, 0xfe, 0x91, 0x3b, 0x1a, 0x48, 0x14, 0x81, 0x2f, 0xa8, 0x75, 0x8b,
|
||||
0x42, 0x71, 0xa9, 0xdd, 0xb1, 0xd3, 0x66, 0x7e, 0x4a, 0xa9, 0xb5, 0xfb, 0x8a, 0x1a, 0x80, 0x6c,
|
||||
0x83, 0x06, 0x37, 0x7c, 0xc3, 0xa1, 0xc2, 0xac, 0x55, 0x27, 0x32, 0x7c, 0x8f, 0xdd, 0x5d, 0x7a,
|
||||
0x30, 0x8d, 0xd2, 0xbc, 0x84, 0x44, 0xd7, 0x3b, 0xac, 0xe1, 0x2e, 0x08, 0xac, 0x6e, 0xf9, 0xc4,
|
||||
0xa0, 0xaa, 0x14, 0x25, 0x6e, 0x14, 0x46, 0xc5, 0x92, 0x8b, 0x1e, 0x85, 0xd1, 0x3a, 0xa1, 0xd4,
|
||||
0xa8, 0x11, 0x5e, 0x7f, 0xc6, 0xe2, 0x2b, 0x7a, 0x4b, 0x90, 0x71, 0xc4, 0x67, 0xfd, 0xc9, 0xc5,
|
||||
0x5e, 0x51, 0xbb, 0x6e, 0xd1, 0x00, 0xbd, 0xda, 0x71, 0x00, 0x8a, 0x77, 0xb7, 0x42, 0xa6, 0xcd,
|
||||
0xd3, 0x5f, 0x15, 0xbf, 0x88, 0x92, 0x48, 0xfe, 0x6f, 0x40, 0xd6, 0x0a, 0x48, 0x3d, 0xba, 0xbb,
|
||||
0x5f, 0x1e, 0x50, 0xee, 0x95, 0x26, 0xa5, 0x0f, 0xd9, 0x4d, 0x86, 0x86, 0x05, 0x68, 0xe1, 0x37,
|
||||
0x19, 0x78, 0xa8, 0x97, 0x0a, 0xbb, 0x50, 0x28, 0x8b, 0xb8, 0x67, 0x87, 0xbe, 0x61, 0xcb, 0x8c,
|
||||
0x53, 0x11, 0xdf, 0xe5, 0x54, 0x2c, 0xb9, 0xac, 0xe4, 0x53, 0xcb, 0xa9, 0x85, 0xb6, 0xe1, 0xcb,
|
||||
0x74, 0x52, 0xab, 0x2e, 0x4b, 0x3a, 0x56, 0x12, 0xa8, 0x08, 0x40, 0x0f, 0x5c, 0x3f, 0xe0, 0x18,
|
||||
0xb2, 0x7a, 0x9d, 0x65, 0x05, 0xa2, 0xac, 0xa8, 0x38, 0x21, 0xc1, 0x6e, 0xb4, 0x43, 0xcb, 0x31,
|
||||
0xe5, 0xae, 0xab, 0x53, 0xfc, 0xa2, 0xe5, 0x98, 0x98, 0x73, 0x18, 0xbe, 0x6d, 0xd1, 0x80, 0x51,
|
||||
0xe4, 0x96, 0xb7, 0x45, 0x9d, 0x4b, 0x2a, 0x09, 0x86, 0x5f, 0x61, 0x55, 0xdf, 0xf5, 0x2d, 0x42,
|
||||
0xf5, 0x91, 0x18, 0x7f, 0x4d, 0x51, 0x71, 0x42, 0xa2, 0xf0, 0xaf, 0x5c, 0xef, 0x24, 0x61, 0xa5,
|
||||
0x04, 0x3d, 0x0c, 0xd9, 0x9a, 0xef, 0x86, 0x9e, 0x8c, 0x92, 0x8a, 0xf6, 0xf3, 0x8c, 0x88, 0x05,
|
||||
0x8f, 0x65, 0x65, 0xa3, 0xad, 0x4d, 0x55, 0x59, 0x19, 0x35, 0xa7, 0x11, 0x1f, 0x7d, 0x47, 0x83,
|
||||
0xac, 0x23, 0x83, 0xc3, 0x52, 0xee, 0xd5, 0x01, 0xe5, 0x05, 0x0f, 0x6f, 0xec, 0xae, 0x88, 0xbc,
|
||||
0x40, 0x46, 0x4f, 0x41, 0x96, 0x56, 0x5c, 0x8f, 0xc8, 0xa8, 0x2f, 0x44, 0x42, 0x65, 0x46, 0x3c,
|
||||
0x6d, 0xe6, 0x27, 0x23, 0x73, 0x9c, 0x80, 0x85, 0x30, 0xfa, 0xbe, 0x06, 0xd0, 0x30, 0x6c, 0xcb,
|
||||
0x34, 0x78, 0xcb, 0x90, 0xe5, 0xee, 0xf7, 0x37, 0xad, 0x5f, 0x52, 0xe6, 0xc5, 0xa6, 0xc5, 0xbf,
|
||||
0x71, 0x02, 0x1a, 0xbd, 0xab, 0xc1, 0x04, 0x0d, 0xf7, 0x7d, 0xa9, 0x45, 0x79, 0x73, 0x31, 0xbe,
|
||||
0xf2, 0xb5, 0xbe, 0xfa, 0x52, 0x4e, 0x00, 0x94, 0xa6, 0x5b, 0xcd, 0xfc, 0x44, 0x92, 0x82, 0xdb,
|
||||
0x1c, 0x40, 0x3f, 0xd2, 0x20, 0xd7, 0x88, 0xee, 0xec, 0x51, 0x7e, 0xe0, 0x5f, 0x1f, 0xd0, 0xc6,
|
||||
0xca, 0x8c, 0x8a, 0x4f, 0x81, 0xea, 0x03, 0x94, 0x07, 0xe8, 0xcf, 0x1a, 0xe8, 0x86, 0x29, 0x0a,
|
||||
0xbc, 0x61, 0xef, 0xfa, 0x96, 0x13, 0x10, 0x5f, 0xf4, 0x9b, 0x54, 0xcf, 0x71, 0xf7, 0xfa, 0x7b,
|
||||
0x17, 0xa6, 0x7b, 0xd9, 0xd2, 0xa2, 0xf4, 0x4e, 0x5f, 0xed, 0xe1, 0x06, 0xee, 0xe9, 0x20, 0x4f,
|
||||
0xb4, 0xb8, 0xa5, 0xd1, 0xc7, 0x06, 0x90, 0x68, 0x71, 0x2f, 0x25, 0xab, 0x43, 0xdc, 0x41, 0x25,
|
||||
0xa0, 0xd1, 0x0e, 0xcc, 0x7a, 0x3e, 0xe1, 0x00, 0x37, 0x9d, 0x43, 0xc7, 0x3d, 0x72, 0xae, 0x59,
|
||||
0xc4, 0x36, 0xa9, 0x0e, 0x8b, 0xda, 0x52, 0xae, 0x74, 0xa1, 0xd5, 0xcc, 0xcf, 0xee, 0x76, 0x13,
|
||||
0xc0, 0xdd, 0xf5, 0x0a, 0xef, 0x0e, 0xa5, 0x5f, 0x01, 0xe9, 0x2e, 0x02, 0xbd, 0x2f, 0x56, 0x2f,
|
||||
0x62, 0x43, 0x75, 0x8d, 0xef, 0xd6, 0x9b, 0x03, 0x4a, 0x26, 0xd5, 0x06, 0xc4, 0x9d, 0x9c, 0x22,
|
||||
0x51, 0x9c, 0xf0, 0x03, 0xfd, 0x5c, 0x83, 0x49, 0xa3, 0x52, 0x21, 0x5e, 0x40, 0x4c, 0x51, 0xdc,
|
||||
0x33, 0x9f, 0x41, 0xfd, 0x9a, 0x95, 0x5e, 0x4d, 0xae, 0x26, 0xa1, 0x71, 0xbb, 0x27, 0xe8, 0x39,
|
||||
0x38, 0x4b, 0x03, 0xd7, 0x27, 0x66, 0xaa, 0x6d, 0x46, 0xad, 0x66, 0xfe, 0x6c, 0xb9, 0x8d, 0x83,
|
||||
0x53, 0x92, 0x85, 0xbf, 0x67, 0x21, 0x7f, 0x87, 0xa3, 0x76, 0x17, 0x0f, 0xb3, 0xcb, 0x30, 0xc2,
|
||||
0x97, 0x6b, 0xf2, 0xa8, 0xe4, 0x12, 0xad, 0x20, 0xa7, 0x62, 0xc9, 0x65, 0x17, 0x05, 0xc3, 0x67,
|
||||
0xed, 0xcb, 0x10, 0x17, 0x54, 0x17, 0x45, 0x59, 0x90, 0x71, 0xc4, 0x47, 0x2b, 0x00, 0x26, 0xf1,
|
||||
0x7c, 0xc2, 0x2e, 0x2b, 0x53, 0x1f, 0xe5, 0xd2, 0x6a, 0x93, 0xd6, 0x15, 0x07, 0x27, 0xa4, 0xd0,
|
||||
0x35, 0x40, 0xd1, 0x2f, 0xcb, 0x75, 0x5e, 0x36, 0x7c, 0xc7, 0x72, 0x6a, 0x7a, 0x8e, 0xbb, 0x3d,
|
||||
0xc7, 0xba, 0xb1, 0xf5, 0x0e, 0x2e, 0xee, 0xa2, 0x81, 0xde, 0x86, 0x11, 0x31, 0xf4, 0xe1, 0x37,
|
||||
0xc4, 0x00, 0xab, 0x3c, 0xf0, 0x18, 0x71, 0x28, 0x2c, 0x21, 0x3b, 0xab, 0x7b, 0xf6, 0x7e, 0x57,
|
||||
0xf7, 0xdb, 0x96, 0xd3, 0x91, 0xff, 0xf1, 0x72, 0x5a, 0xf8, 0x8f, 0x96, 0xae, 0x39, 0x89, 0xa5,
|
||||
0x96, 0x2b, 0x86, 0x4d, 0xd0, 0x3a, 0x4c, 0xb3, 0x17, 0x13, 0x26, 0x9e, 0x6d, 0x55, 0x0c, 0xca,
|
||||
0x1f, 0xec, 0x22, 0xd9, 0xd5, 0x0c, 0xa9, 0x9c, 0xe2, 0xe3, 0x0e, 0x0d, 0xf4, 0x02, 0x20, 0xf1,
|
||||
0x8a, 0x68, 0xb3, 0x23, 0x1a, 0x22, 0xf5, 0x1e, 0x28, 0x77, 0x48, 0xe0, 0x2e, 0x5a, 0x68, 0x0d,
|
||||
0x66, 0x6c, 0x63, 0x9f, 0xd8, 0x65, 0x62, 0x93, 0x4a, 0xe0, 0xfa, 0xdc, 0x94, 0x18, 0x69, 0xcc,
|
||||
0xb6, 0x9a, 0xf9, 0x99, 0xeb, 0x69, 0x26, 0xee, 0x94, 0x2f, 0x5c, 0x4a, 0x1f, 0xed, 0xe4, 0xc2,
|
||||
0xc5, 0xdb, 0xec, 0x83, 0x0c, 0xcc, 0xf7, 0xce, 0x0c, 0xf4, 0xdd, 0xf8, 0x09, 0x29, 0x5e, 0x08,
|
||||
0xaf, 0x0f, 0x2a, 0x0b, 0xe5, 0x1b, 0x12, 0x3a, 0xdf, 0x8f, 0xe8, 0x5b, 0xac, 0x5d, 0x33, 0xec,
|
||||
0x68, 0x68, 0xf5, 0xda, 0xc0, 0x5c, 0x60, 0x20, 0xa5, 0x31, 0xd1, 0x09, 0x1a, 0x36, 0x6f, 0xfc,
|
||||
0x0c, 0x9b, 0x14, 0x7e, 0xab, 0xa5, 0xa7, 0x08, 0xf1, 0x09, 0x46, 0x3f, 0xd6, 0x60, 0xca, 0xf5,
|
||||
0x88, 0xb3, 0xba, 0xbb, 0xf9, 0xd2, 0xe7, 0xc4, 0x49, 0x96, 0xa1, 0xda, 0xbe, 0x47, 0x3f, 0x5f,
|
||||
0x28, 0xef, 0x6c, 0x0b, 0x83, 0xbb, 0xbe, 0xeb, 0xd1, 0xd2, 0xb9, 0x56, 0x33, 0x3f, 0xb5, 0xd3,
|
||||
0x0e, 0x85, 0xd3, 0xd8, 0x85, 0x3a, 0xcc, 0x6e, 0x1c, 0x07, 0xc4, 0x77, 0x0c, 0x7b, 0xdd, 0xad,
|
||||
0x84, 0x75, 0xe2, 0x04, 0xc2, 0xd1, 0xd4, 0xc4, 0x4b, 0xbb, 0xcb, 0x89, 0xd7, 0x43, 0x30, 0x14,
|
||||
0xfa, 0xb6, 0xcc, 0xe2, 0x71, 0x35, 0xd1, 0xc5, 0xd7, 0x31, 0xa3, 0x17, 0x2e, 0xc1, 0x30, 0xf3,
|
||||
0x13, 0x5d, 0x80, 0x21, 0xdf, 0x38, 0xe2, 0x56, 0x27, 0x4a, 0xa3, 0x4c, 0x04, 0x1b, 0x47, 0x98,
|
||||
0xd1, 0x0a, 0x7f, 0x5a, 0x84, 0xa9, 0xd4, 0x5a, 0xd0, 0x3c, 0x64, 0xd4, 0x98, 0x18, 0xa4, 0xd1,
|
||||
0xcc, 0xe6, 0x3a, 0xce, 0x58, 0x26, 0x7a, 0x46, 0x15, 0x5f, 0x01, 0x9a, 0x57, 0x77, 0x09, 0xa7,
|
||||
0xb2, 0xfe, 0x3c, 0x36, 0xc7, 0x1c, 0x89, 0x0a, 0x27, 0xf3, 0x81, 0x54, 0xe5, 0x29, 0x11, 0x3e,
|
||||
0x90, 0x2a, 0x66, 0xb4, 0x4f, 0x3b, 0xee, 0x8b, 0xe6, 0x8d, 0xd9, 0xbb, 0x98, 0x37, 0x8e, 0xdc,
|
||||
0x76, 0xde, 0xf8, 0x30, 0x64, 0x03, 0x2b, 0xb0, 0x09, 0xbf, 0xc8, 0x12, 0xcf, 0xa8, 0x1b, 0x8c,
|
||||
0x88, 0x05, 0x0f, 0xbd, 0x05, 0xa3, 0x26, 0xa9, 0x1a, 0xa1, 0x1d, 0xf0, 0x3b, 0x6b, 0x7c, 0x65,
|
||||
0xad, 0x0f, 0x29, 0x24, 0x86, 0xc1, 0xeb, 0xc2, 0x2e, 0x8e, 0x00, 0xd0, 0x23, 0x30, 0x5a, 0x37,
|
||||
0x8e, 0xad, 0x7a, 0x58, 0xe7, 0x0d, 0xa6, 0x26, 0xc4, 0xb6, 0x04, 0x09, 0x47, 0x3c, 0x56, 0x19,
|
||||
0xc9, 0x71, 0xc5, 0x0e, 0xa9, 0xd5, 0x20, 0x92, 0x29, 0x9b, 0x3f, 0x55, 0x19, 0x37, 0x52, 0x7c,
|
||||
0xdc, 0xa1, 0xc1, 0xc1, 0x2c, 0x87, 0x2b, 0x8f, 0x27, 0xc0, 0x04, 0x09, 0x47, 0xbc, 0x76, 0x30,
|
||||
0x29, 0x3f, 0xd1, 0x0b, 0x4c, 0x2a, 0x77, 0x68, 0xa0, 0xc7, 0x61, 0xac, 0x6e, 0x1c, 0x5f, 0x27,
|
||||
0x4e, 0x2d, 0x38, 0xd0, 0x27, 0x17, 0xb5, 0xa5, 0xa1, 0xd2, 0x64, 0xab, 0x99, 0x1f, 0xdb, 0x8a,
|
||||
0x88, 0x38, 0xe6, 0x73, 0x61, 0xcb, 0x91, 0xc2, 0x67, 0x13, 0xc2, 0x11, 0x11, 0xc7, 0x7c, 0xd6,
|
||||
0xbd, 0x78, 0x46, 0xc0, 0x0e, 0x97, 0x3e, 0xd5, 0xfe, 0xcc, 0xdd, 0x15, 0x64, 0x1c, 0xf1, 0xd1,
|
||||
0x12, 0xe4, 0xea, 0xc6, 0x31, 0x1f, 0x49, 0xe8, 0xd3, 0xdc, 0x2c, 0x1f, 0x8c, 0x6f, 0x49, 0x1a,
|
||||
0x56, 0x5c, 0x2e, 0x69, 0x39, 0x42, 0x72, 0x26, 0x21, 0x29, 0x69, 0x58, 0x71, 0x59, 0x12, 0x87,
|
||||
0x8e, 0x75, 0x2b, 0x24, 0x42, 0x18, 0xf1, 0xc8, 0xa8, 0x24, 0xbe, 0x19, 0xb3, 0x70, 0x52, 0x0e,
|
||||
0x15, 0x01, 0xea, 0xa1, 0x1d, 0x58, 0x9e, 0x4d, 0x76, 0xaa, 0xfa, 0x39, 0x1e, 0x7f, 0xde, 0xf4,
|
||||
0x6f, 0x29, 0x2a, 0x4e, 0x48, 0x20, 0x02, 0xc3, 0xc4, 0x09, 0xeb, 0xfa, 0x79, 0x7e, 0xb1, 0xf7,
|
||||
0x25, 0x05, 0xd5, 0xc9, 0xd9, 0x70, 0xc2, 0x3a, 0xe6, 0xe6, 0xd1, 0x33, 0x30, 0x59, 0x37, 0x8e,
|
||||
0x59, 0x39, 0x20, 0x7e, 0x60, 0x11, 0xaa, 0xcf, 0xf2, 0xc5, 0xcf, 0xb0, 0x6e, 0x77, 0x2b, 0xc9,
|
||||
0xc0, 0xed, 0x72, 0x5c, 0xd1, 0x72, 0x12, 0x8a, 0x73, 0x09, 0xc5, 0x24, 0x03, 0xb7, 0xcb, 0xb1,
|
||||
0x48, 0xfb, 0xe4, 0x56, 0x68, 0xf9, 0xc4, 0xd4, 0x1f, 0xe0, 0x0d, 0xb2, 0xfc, 0x58, 0x21, 0x68,
|
||||
0x58, 0x71, 0x51, 0x23, 0x9a, 0x5d, 0xe9, 0xfc, 0x18, 0xde, 0xec, 0x6f, 0x25, 0xdf, 0xf1, 0x57,
|
||||
0x7d, 0xdf, 0x38, 0x11, 0x37, 0x4d, 0x72, 0x6a, 0x85, 0x28, 0x64, 0x0d, 0xdb, 0xde, 0xa9, 0xea,
|
||||
0x17, 0x78, 0xec, 0xfb, 0x7d, 0x83, 0xa8, 0xaa, 0xb3, 0xca, 0x40, 0xb0, 0xc0, 0x62, 0xa0, 0xae,
|
||||
0xc3, 0x52, 0x63, 0x7e, 0xb0, 0xa0, 0x3b, 0x0c, 0x04, 0x0b, 0x2c, 0xbe, 0x52, 0xe7, 0x64, 0xa7,
|
||||
0xaa, 0x3f, 0x38, 0xe0, 0x95, 0x32, 0x10, 0x2c, 0xb0, 0x90, 0x05, 0x43, 0x8e, 0x1b, 0xe8, 0x17,
|
||||
0x07, 0x72, 0x3d, 0xf3, 0x0b, 0x67, 0xdb, 0x0d, 0x30, 0xc3, 0x40, 0x3f, 0xd3, 0x00, 0xbc, 0x38,
|
||||
0x45, 0x1f, 0xea, 0xcb, 0x48, 0x24, 0x05, 0x59, 0x8c, 0x73, 0x7b, 0xc3, 0x09, 0xfc, 0x93, 0xf8,
|
||||
0x79, 0x94, 0x38, 0x03, 0x09, 0x2f, 0xd0, 0xaf, 0x34, 0x38, 0x9f, 0x6c, 0x93, 0x95, 0x7b, 0x0b,
|
||||
0x3c, 0x22, 0x37, 0xfa, 0x9d, 0xe6, 0x25, 0xd7, 0xb5, 0x4b, 0x7a, 0xab, 0x99, 0x3f, 0xbf, 0xda,
|
||||
0x05, 0x15, 0x77, 0xf5, 0x05, 0xfd, 0x4e, 0x83, 0x19, 0x59, 0x45, 0x13, 0x1e, 0xe6, 0x79, 0x00,
|
||||
0x49, 0xbf, 0x03, 0x98, 0xc6, 0x11, 0x71, 0x54, 0x1f, 0xd9, 0x3b, 0xf8, 0xb8, 0xd3, 0x35, 0xf4,
|
||||
0x47, 0x0d, 0x26, 0x4c, 0xe2, 0x11, 0xc7, 0x24, 0x4e, 0x85, 0xf9, 0xba, 0xd8, 0x97, 0x91, 0x45,
|
||||
0xda, 0xd7, 0xf5, 0x04, 0x84, 0x70, 0xb3, 0x28, 0xdd, 0x9c, 0x48, 0xb2, 0x4e, 0x9b, 0xf9, 0xb9,
|
||||
0x58, 0x35, 0xc9, 0xc1, 0x6d, 0x5e, 0xa2, 0xf7, 0x34, 0x98, 0x8a, 0x37, 0x40, 0x5c, 0x29, 0x97,
|
||||
0x06, 0x98, 0x07, 0xbc, 0x7d, 0x5d, 0x6d, 0x07, 0xc4, 0x69, 0x0f, 0xd0, 0xef, 0x35, 0xd6, 0xa9,
|
||||
0x45, 0xef, 0x3e, 0xaa, 0x17, 0x78, 0x2c, 0xdf, 0xe8, 0x7b, 0x2c, 0x15, 0x82, 0x08, 0xe5, 0x95,
|
||||
0xb8, 0x15, 0x54, 0x9c, 0xd3, 0x66, 0x7e, 0x36, 0x19, 0x49, 0xc5, 0xc0, 0x49, 0x0f, 0xd1, 0x0f,
|
||||
0x35, 0x98, 0x20, 0x71, 0xc7, 0x4d, 0xf5, 0x87, 0xfb, 0x12, 0xc4, 0xae, 0x4d, 0xbc, 0x78, 0xa9,
|
||||
0x27, 0x58, 0x14, 0xb7, 0x61, 0xb3, 0x0e, 0x92, 0x1c, 0x1b, 0x75, 0xcf, 0x26, 0xfa, 0xff, 0xf5,
|
||||
0xb9, 0x83, 0xdc, 0x10, 0x76, 0x71, 0x04, 0x80, 0xae, 0x40, 0xce, 0x09, 0x6d, 0xdb, 0xd8, 0xb7,
|
||||
0x89, 0xfe, 0x08, 0xef, 0x45, 0xd4, 0x48, 0x76, 0x5b, 0xd2, 0xb1, 0x92, 0x40, 0x55, 0x58, 0x3c,
|
||||
0x7e, 0x51, 0xfd, 0x7b, 0x52, 0xd7, 0xa1, 0xa1, 0x7e, 0x99, 0x5b, 0x99, 0x6f, 0x35, 0xf3, 0x73,
|
||||
0x7b, 0xdd, 0xc7, 0x8a, 0x77, 0xb4, 0x81, 0x5e, 0x81, 0x07, 0x13, 0x32, 0x1b, 0xf5, 0x7d, 0x62,
|
||||
0x9a, 0xc4, 0x8c, 0x1e, 0x6e, 0xfa, 0xff, 0x8b, 0xc1, 0x65, 0x74, 0xc0, 0xf7, 0xd2, 0x02, 0xf8,
|
||||
0x76, 0xda, 0xe8, 0x3a, 0xcc, 0x25, 0xd8, 0x9b, 0x4e, 0xb0, 0xe3, 0x97, 0x03, 0xdf, 0x72, 0x6a,
|
||||
0xfa, 0x12, 0xb7, 0x7b, 0x3e, 0x3a, 0x91, 0x7b, 0x09, 0x1e, 0xee, 0xa1, 0x83, 0xbe, 0xd2, 0x66,
|
||||
0x8d, 0x7f, 0x42, 0x33, 0xbc, 0x17, 0xc9, 0x09, 0xd5, 0x1f, 0xe5, 0xdd, 0x09, 0xdf, 0xec, 0xbd,
|
||||
0x04, 0x1d, 0xf7, 0x90, 0x47, 0x5f, 0x86, 0x73, 0x29, 0x0e, 0x7b, 0xa2, 0xe8, 0x8f, 0x89, 0xb7,
|
||||
0x06, 0xeb, 0x67, 0xf7, 0x22, 0x22, 0xee, 0x26, 0x89, 0xbe, 0x08, 0x28, 0x41, 0xde, 0x32, 0x3c,
|
||||
0xae, 0xff, 0xb8, 0x78, 0xf6, 0xb0, 0x1d, 0xdd, 0x93, 0x34, 0xdc, 0x45, 0x6e, 0x9e, 0xbd, 0x81,
|
||||
0x53, 0x35, 0x14, 0x4d, 0xc3, 0xd0, 0x21, 0x91, 0xff, 0xb7, 0x80, 0xd9, 0x9f, 0xc8, 0x84, 0x6c,
|
||||
0xc3, 0xb0, 0xc3, 0xe8, 0x19, 0xdf, 0xe7, 0xfb, 0x17, 0x0b, 0xe3, 0xcf, 0x65, 0x9e, 0xd5, 0xe6,
|
||||
0xdf, 0xd7, 0x60, 0xae, 0x7b, 0x69, 0xbf, 0xaf, 0x6e, 0xfd, 0x42, 0x83, 0x99, 0x8e, 0x2a, 0xde,
|
||||
0xc5, 0xa3, 0x5b, 0xed, 0x1e, 0xbd, 0xd2, 0xef, 0x72, 0x2c, 0xd2, 0x8f, 0xf7, 0xa0, 0x49, 0xf7,
|
||||
0x7e, 0xa2, 0xc1, 0x74, 0xba, 0x30, 0xde, 0xcf, 0x78, 0x15, 0xde, 0xcf, 0xc0, 0x5c, 0xf7, 0xd6,
|
||||
0x19, 0xf9, 0x6a, 0x46, 0x30, 0x98, 0x59, 0x4b, 0xb7, 0xb9, 0xec, 0x3b, 0x1a, 0x8c, 0xbf, 0xa5,
|
||||
0xe4, 0xa2, 0xef, 0xda, 0x7d, 0x9f, 0xf2, 0x44, 0x37, 0x51, 0xcc, 0xa0, 0x38, 0x89, 0x5b, 0xf8,
|
||||
0x83, 0x06, 0xb3, 0x5d, 0xaf, 0x58, 0x74, 0x19, 0x46, 0x0c, 0xdb, 0x76, 0x8f, 0xc4, 0xb0, 0x2e,
|
||||
0x31, 0x85, 0x5f, 0xe5, 0x54, 0x2c, 0xb9, 0x89, 0xe8, 0x65, 0x3e, 0xab, 0xe8, 0x15, 0xfe, 0xa2,
|
||||
0xc1, 0xc5, 0xdb, 0x65, 0xe2, 0x7d, 0xd9, 0xd2, 0x25, 0xc8, 0xc9, 0xf6, 0xf8, 0x84, 0x6f, 0xa7,
|
||||
0x2c, 0x76, 0xb2, 0x68, 0xf0, 0x7f, 0xe5, 0x12, 0x7f, 0x15, 0x3e, 0xd0, 0x60, 0xba, 0x4c, 0xfc,
|
||||
0x86, 0x55, 0x21, 0x98, 0x54, 0x89, 0x4f, 0x9c, 0x0a, 0x41, 0xcb, 0x30, 0xc6, 0x3f, 0x28, 0x7b,
|
||||
0x46, 0x25, 0xfa, 0x38, 0x32, 0x23, 0x43, 0x3e, 0xb6, 0x1d, 0x31, 0x70, 0x2c, 0xa3, 0x3e, 0xa4,
|
||||
0x64, 0x7a, 0x7e, 0x48, 0xb9, 0x08, 0xc3, 0x5e, 0x3c, 0xea, 0xcd, 0x31, 0x2e, 0x9f, 0xee, 0x72,
|
||||
0x2a, 0xe7, 0xba, 0x7e, 0xc0, 0xe7, 0x57, 0x59, 0xc9, 0x75, 0xfd, 0x00, 0x73, 0x6a, 0xe1, 0xaf,
|
||||
0x1a, 0x74, 0xfb, 0xa7, 0x2b, 0x74, 0x41, 0x8c, 0xf0, 0x12, 0x73, 0xb1, 0x68, 0x7c, 0x87, 0x1a,
|
||||
0x30, 0x4a, 0xc5, 0xaa, 0x64, 0xd4, 0x77, 0xee, 0x31, 0xea, 0xe9, 0x18, 0x89, 0xde, 0x21, 0xa2,
|
||||
0x46, 0x60, 0x2c, 0xf0, 0x15, 0xa3, 0x14, 0x3a, 0xa6, 0x9c, 0xea, 0x4e, 0x88, 0xc0, 0xaf, 0xad,
|
||||
0x0a, 0x1a, 0x56, 0xdc, 0xd2, 0xd5, 0x0f, 0x3f, 0x59, 0x38, 0xf3, 0xd1, 0x27, 0x0b, 0x67, 0x3e,
|
||||
0xfe, 0x64, 0xe1, 0xcc, 0xb7, 0x5b, 0x0b, 0xda, 0x87, 0xad, 0x05, 0xed, 0xa3, 0xd6, 0x82, 0xf6,
|
||||
0x71, 0x6b, 0x41, 0xfb, 0x47, 0x6b, 0x41, 0xfb, 0xe9, 0x3f, 0x17, 0xce, 0x7c, 0x7d, 0x54, 0xe2,
|
||||
0xff, 0x37, 0x00, 0x00, 0xff, 0xff, 0x26, 0x4f, 0x67, 0x24, 0x0a, 0x2d, 0x00, 0x00,
|
||||
// 3075 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5a, 0xcd, 0x73, 0x23, 0x47,
|
||||
0x15, 0xdf, 0x91, 0x2d, 0x5b, 0x6e, 0xdb, 0x6b, 0xbb, 0x77, 0xed, 0xcc, 0x3a, 0x1b, 0xcb, 0x56,
|
||||
0xc8, 0xe2, 0x24, 0xbb, 0x72, 0x62, 0x12, 0x12, 0x52, 0x50, 0x94, 0x65, 0x7b, 0x83, 0x93, 0xf5,
|
||||
0x07, 0xad, 0xdd, 0xc4, 0x90, 0xcf, 0xb6, 0xa6, 0x2d, 0x4f, 0x3c, 0x9a, 0x99, 0x9d, 0x9e, 0x91,
|
||||
0xed, 0x0a, 0x50, 0x7c, 0x54, 0x0a, 0x8a, 0x02, 0x42, 0x91, 0x5c, 0x28, 0xe0, 0x10, 0x28, 0x2e,
|
||||
0x1c, 0xe0, 0x00, 0x37, 0xf8, 0x03, 0x72, 0x4c, 0x51, 0x1c, 0x72, 0xa0, 0x04, 0x2b, 0xae, 0x1c,
|
||||
0xa9, 0xa2, 0xca, 0x27, 0xaa, 0x3f, 0xa6, 0xa7, 0x35, 0x92, 0x76, 0xb7, 0xb2, 0x52, 0x96, 0x9b,
|
||||
0xf5, 0xbe, 0x7e, 0xaf, 0x5f, 0xbf, 0x7e, 0xfd, 0xfa, 0x8d, 0xc1, 0xfe, 0xe1, 0xb3, 0xb4, 0x68,
|
||||
0x7b, 0x4b, 0x87, 0xd1, 0x1e, 0x09, 0x5c, 0x12, 0x12, 0xba, 0x54, 0x27, 0xae, 0xe5, 0x05, 0x4b,
|
||||
0x92, 0x81, 0x7d, 0x9b, 0x1c, 0x87, 0xc4, 0xa5, 0xb6, 0xe7, 0xd2, 0x2b, 0xd8, 0xb7, 0x29, 0x09,
|
||||
0xea, 0x24, 0x58, 0xf2, 0x0f, 0xab, 0x8c, 0x47, 0x5b, 0x05, 0x96, 0xea, 0x4f, 0xee, 0x91, 0x10,
|
||||
0x3f, 0xb9, 0x54, 0x25, 0x2e, 0x09, 0x70, 0x48, 0xac, 0xa2, 0x1f, 0x78, 0xa1, 0x07, 0xbf, 0x24,
|
||||
0xcc, 0x15, 0x5b, 0xa4, 0xdf, 0x50, 0xe6, 0x8a, 0xfe, 0x61, 0x95, 0xf1, 0x68, 0xab, 0x40, 0x51,
|
||||
0x9a, 0x9b, 0xbd, 0x52, 0xb5, 0xc3, 0x83, 0x68, 0xaf, 0x58, 0xf1, 0x6a, 0x4b, 0x55, 0xaf, 0xea,
|
||||
0x2d, 0x71, 0xab, 0x7b, 0xd1, 0x3e, 0xff, 0xc5, 0x7f, 0xf0, 0xbf, 0x04, 0xda, 0xec, 0x53, 0x89,
|
||||
0xf3, 0x35, 0x5c, 0x39, 0xb0, 0x5d, 0x12, 0x9c, 0x24, 0x1e, 0xd7, 0x48, 0x88, 0x97, 0xea, 0x6d,
|
||||
0x3e, 0xce, 0x2e, 0x75, 0xd3, 0x0a, 0x22, 0x37, 0xb4, 0x6b, 0xa4, 0x4d, 0xe1, 0xf3, 0x77, 0x52,
|
||||
0xa0, 0x95, 0x03, 0x52, 0xc3, 0x69, 0xbd, 0xc2, 0xa9, 0x01, 0xa6, 0x56, 0x3d, 0xb7, 0x4e, 0x02,
|
||||
0xb6, 0x4a, 0x44, 0x6e, 0x46, 0x84, 0x86, 0xb0, 0x04, 0x06, 0x22, 0xdb, 0x32, 0x8d, 0x79, 0x63,
|
||||
0x71, 0xa4, 0xf4, 0xc4, 0x87, 0x8d, 0xfc, 0x99, 0x66, 0x23, 0x3f, 0x70, 0x63, 0x63, 0xed, 0xb4,
|
||||
0x91, 0x5f, 0xe8, 0x86, 0x14, 0x9e, 0xf8, 0x84, 0x16, 0x6f, 0x6c, 0xac, 0x21, 0xa6, 0x0c, 0x9f,
|
||||
0x07, 0x53, 0x16, 0xa1, 0x76, 0x40, 0xac, 0x95, 0x9d, 0x8d, 0x97, 0x84, 0x7d, 0x33, 0xc3, 0x2d,
|
||||
0x5e, 0x90, 0x16, 0xa7, 0xd6, 0xd2, 0x02, 0xa8, 0x5d, 0x07, 0xee, 0x82, 0x61, 0x6f, 0xef, 0x2d,
|
||||
0x52, 0x09, 0xa9, 0x39, 0x30, 0x3f, 0xb0, 0x38, 0xba, 0x7c, 0xa5, 0x98, 0xec, 0xa0, 0x72, 0x81,
|
||||
0x6f, 0x9b, 0x5c, 0x6c, 0x11, 0xe1, 0xa3, 0xf5, 0x78, 0xe7, 0x4a, 0x13, 0x12, 0x6d, 0x78, 0x5b,
|
||||
0x58, 0x41, 0xb1, 0xb9, 0xc2, 0x6f, 0x32, 0x00, 0xea, 0x8b, 0xa7, 0xbe, 0xe7, 0x52, 0xd2, 0x93,
|
||||
0xd5, 0x53, 0x30, 0x59, 0xe1, 0x96, 0x43, 0x62, 0x49, 0x5c, 0x33, 0xf3, 0x49, 0xbc, 0x37, 0x25,
|
||||
0xfe, 0xe4, 0x6a, 0xca, 0x1c, 0x6a, 0x03, 0x80, 0xd7, 0xc1, 0x50, 0x40, 0x68, 0xe4, 0x84, 0xe6,
|
||||
0xc0, 0xbc, 0xb1, 0x38, 0xba, 0x7c, 0xb9, 0x2b, 0x14, 0xcf, 0x6f, 0x96, 0x7c, 0xc5, 0xfa, 0x93,
|
||||
0xc5, 0x72, 0x88, 0xc3, 0x88, 0x96, 0xce, 0x4a, 0xa4, 0x21, 0xc4, 0x6d, 0x20, 0x69, 0xab, 0xf0,
|
||||
0x83, 0x0c, 0x98, 0xd4, 0xa3, 0x54, 0xb7, 0xc9, 0x11, 0x3c, 0x02, 0xc3, 0x81, 0x48, 0x16, 0x1e,
|
||||
0xa7, 0xd1, 0xe5, 0x9d, 0xe2, 0x3d, 0x1d, 0xab, 0x62, 0x5b, 0x12, 0x96, 0x46, 0xd9, 0x9e, 0xc9,
|
||||
0x1f, 0x28, 0x46, 0x83, 0x6f, 0x83, 0x5c, 0x20, 0x37, 0x8a, 0x67, 0xd3, 0xe8, 0xf2, 0x57, 0x7b,
|
||||
0x88, 0x2c, 0x0c, 0x97, 0xc6, 0x9a, 0x8d, 0x7c, 0x2e, 0xfe, 0x85, 0x14, 0x60, 0xe1, 0xbd, 0x0c,
|
||||
0x98, 0x5b, 0x8d, 0x68, 0xe8, 0xd5, 0x10, 0xa1, 0x5e, 0x14, 0x54, 0xc8, 0xaa, 0xe7, 0x44, 0x35,
|
||||
0x77, 0x8d, 0xec, 0xdb, 0xae, 0x1d, 0xb2, 0x6c, 0x9d, 0x07, 0x83, 0x2e, 0xae, 0x11, 0x99, 0x3d,
|
||||
0x63, 0x32, 0xa6, 0x83, 0x5b, 0xb8, 0x46, 0x10, 0xe7, 0x30, 0x09, 0x96, 0x2c, 0xf2, 0x2c, 0x28,
|
||||
0x89, 0xeb, 0x27, 0x3e, 0x41, 0x9c, 0x03, 0x2f, 0x81, 0xa1, 0x7d, 0x2f, 0xa8, 0x61, 0xb1, 0x8f,
|
||||
0x23, 0xc9, 0xce, 0x5c, 0xe5, 0x54, 0x24, 0xb9, 0xf0, 0x69, 0x30, 0x6a, 0x11, 0x5a, 0x09, 0x6c,
|
||||
0x9f, 0x41, 0x9b, 0x83, 0x5c, 0xf8, 0x9c, 0x14, 0x1e, 0x5d, 0x4b, 0x58, 0x48, 0x97, 0x83, 0x97,
|
||||
0x41, 0xce, 0x0f, 0x6c, 0x2f, 0xb0, 0xc3, 0x13, 0x33, 0x3b, 0x6f, 0x2c, 0x66, 0x4b, 0x93, 0x52,
|
||||
0x27, 0xb7, 0x23, 0xe9, 0x48, 0x49, 0xc0, 0x79, 0x90, 0x7b, 0xa1, 0xbc, 0xbd, 0xb5, 0x83, 0xc3,
|
||||
0x03, 0x73, 0x88, 0x23, 0x0c, 0x32, 0x69, 0xa4, 0xa8, 0x85, 0xbf, 0x67, 0x80, 0x99, 0x8e, 0x4a,
|
||||
0x1c, 0x52, 0x78, 0x15, 0xe4, 0x68, 0xc8, 0x2a, 0x4e, 0xf5, 0x44, 0xc6, 0xe4, 0xb1, 0x18, 0xac,
|
||||
0x2c, 0xe9, 0xa7, 0x8d, 0xfc, 0x4c, 0xa2, 0x11, 0x53, 0x79, 0x3c, 0x94, 0x2e, 0xfc, 0x95, 0x01,
|
||||
0xce, 0x1d, 0x91, 0xbd, 0x03, 0xcf, 0x3b, 0x5c, 0x75, 0x6c, 0xe2, 0x86, 0xab, 0x9e, 0xbb, 0x6f,
|
||||
0x57, 0x65, 0x0e, 0xa0, 0x7b, 0xcc, 0x81, 0x97, 0xdb, 0x2d, 0x97, 0x1e, 0x68, 0x36, 0xf2, 0xe7,
|
||||
0x3a, 0x30, 0x50, 0x27, 0x3f, 0xe0, 0x2e, 0x30, 0x2b, 0xa9, 0x43, 0x22, 0x0b, 0x98, 0x28, 0x5b,
|
||||
0x23, 0xa5, 0x8b, 0xcd, 0x46, 0xde, 0x5c, 0xed, 0x22, 0x83, 0xba, 0x6a, 0x17, 0xbe, 0x37, 0x90,
|
||||
0x0e, 0xaf, 0x96, 0x6e, 0x6f, 0x82, 0x1c, 0x3b, 0xc6, 0x16, 0x0e, 0xb1, 0x3c, 0x88, 0x4f, 0xdc,
|
||||
0xdd, 0xa1, 0x17, 0x35, 0x63, 0x93, 0x84, 0xb8, 0x04, 0xe5, 0x86, 0x80, 0x84, 0x86, 0x94, 0x55,
|
||||
0xf8, 0x4d, 0x30, 0x48, 0x7d, 0x52, 0x91, 0x81, 0x7e, 0xe5, 0x5e, 0x0f, 0x5b, 0x97, 0x85, 0x94,
|
||||
0x7d, 0x52, 0x49, 0xce, 0x02, 0xfb, 0x85, 0x38, 0x2c, 0x7c, 0xc7, 0x00, 0x43, 0x94, 0x17, 0x28,
|
||||
0x59, 0xd4, 0x5e, 0xeb, 0x97, 0x07, 0xa9, 0x2a, 0x28, 0x7e, 0x23, 0x09, 0x5e, 0xf8, 0x4f, 0x06,
|
||||
0x2c, 0x74, 0x53, 0x5d, 0xf5, 0x5c, 0x4b, 0x6c, 0xc7, 0x86, 0x3c, 0xdb, 0x22, 0xd3, 0x9f, 0xd6,
|
||||
0xcf, 0xf6, 0x69, 0x23, 0xff, 0xc8, 0x1d, 0x0d, 0x68, 0x45, 0xe0, 0x0b, 0x6a, 0xdd, 0xa2, 0x50,
|
||||
0x2c, 0xb4, 0x3a, 0x76, 0xda, 0xc8, 0x4f, 0x28, 0xb5, 0x56, 0x5f, 0x61, 0x1d, 0x40, 0x07, 0xd3,
|
||||
0xf0, 0x7a, 0x80, 0x5d, 0x2a, 0xcc, 0xda, 0x35, 0x22, 0xc3, 0xf7, 0xd8, 0xdd, 0xa5, 0x07, 0xd3,
|
||||
0x28, 0xcd, 0x4a, 0x48, 0x78, 0xad, 0xcd, 0x1a, 0xea, 0x80, 0xc0, 0xea, 0x56, 0x40, 0x30, 0x55,
|
||||
0xa5, 0x48, 0xbb, 0x51, 0x18, 0x15, 0x49, 0x2e, 0x7c, 0x14, 0x0c, 0xd7, 0x08, 0xa5, 0xb8, 0x4a,
|
||||
0x78, 0xfd, 0x19, 0x49, 0xae, 0xe8, 0x4d, 0x41, 0x46, 0x31, 0x9f, 0xf5, 0x27, 0x17, 0xbb, 0x45,
|
||||
0xed, 0x9a, 0x4d, 0x43, 0xf8, 0x6a, 0xdb, 0x01, 0x28, 0xde, 0xdd, 0x0a, 0x99, 0x36, 0x4f, 0x7f,
|
||||
0x55, 0xfc, 0x62, 0x8a, 0x96, 0xfc, 0xdf, 0x00, 0x59, 0x3b, 0x24, 0xb5, 0xf8, 0xee, 0x7e, 0xb9,
|
||||
0x4f, 0xb9, 0x57, 0x1a, 0x97, 0x3e, 0x64, 0x37, 0x18, 0x1a, 0x12, 0xa0, 0x85, 0xdf, 0x66, 0xc0,
|
||||
0x43, 0xdd, 0x54, 0xd8, 0x85, 0x42, 0x59, 0xc4, 0x7d, 0x27, 0x0a, 0xb0, 0x23, 0x33, 0x4e, 0x45,
|
||||
0x7c, 0x87, 0x53, 0x91, 0xe4, 0xb2, 0x92, 0x4f, 0x6d, 0xb7, 0x1a, 0x39, 0x38, 0x90, 0xe9, 0xa4,
|
||||
0x56, 0x5d, 0x96, 0x74, 0xa4, 0x24, 0x60, 0x11, 0x00, 0x7a, 0xe0, 0x05, 0x21, 0xc7, 0x90, 0xd5,
|
||||
0xeb, 0x2c, 0x2b, 0x10, 0x65, 0x45, 0x45, 0x9a, 0x04, 0xbb, 0xd1, 0x0e, 0x6d, 0xd7, 0x92, 0xbb,
|
||||
0xae, 0x4e, 0xf1, 0x8b, 0xb6, 0x6b, 0x21, 0xce, 0x61, 0xf8, 0x8e, 0x4d, 0x43, 0x46, 0x91, 0x5b,
|
||||
0xde, 0x12, 0x75, 0x2e, 0xa9, 0x24, 0x18, 0x7e, 0x85, 0x55, 0x7d, 0x2f, 0xb0, 0x09, 0x35, 0x87,
|
||||
0x12, 0xfc, 0x55, 0x45, 0x45, 0x9a, 0x44, 0xe1, 0xdf, 0xb9, 0xee, 0x49, 0xc2, 0x4a, 0x09, 0x7c,
|
||||
0x18, 0x64, 0xab, 0x81, 0x17, 0xf9, 0x32, 0x4a, 0x2a, 0xda, 0xcf, 0x33, 0x22, 0x12, 0x3c, 0x96,
|
||||
0x95, 0xf5, 0x96, 0x36, 0x55, 0x65, 0x65, 0xdc, 0x9c, 0xc6, 0x7c, 0xf8, 0x1d, 0x03, 0x64, 0x5d,
|
||||
0x19, 0x1c, 0x96, 0x72, 0xaf, 0xf6, 0x29, 0x2f, 0x78, 0x78, 0x13, 0x77, 0x45, 0xe4, 0x05, 0x32,
|
||||
0x7c, 0x0a, 0x64, 0x69, 0xc5, 0xf3, 0x89, 0x8c, 0xfa, 0x5c, 0x2c, 0x54, 0x66, 0xc4, 0xd3, 0x46,
|
||||
0x7e, 0x3c, 0x36, 0xc7, 0x09, 0x48, 0x08, 0xc3, 0xef, 0x1b, 0x00, 0xd4, 0xb1, 0x63, 0x5b, 0x98,
|
||||
0xb7, 0x0c, 0x59, 0xee, 0x7e, 0x6f, 0xd3, 0xfa, 0x25, 0x65, 0x5e, 0x6c, 0x5a, 0xf2, 0x1b, 0x69,
|
||||
0xd0, 0xf0, 0x5d, 0x03, 0x8c, 0xd1, 0x68, 0x2f, 0x90, 0x5a, 0x94, 0x37, 0x17, 0xa3, 0xcb, 0x5f,
|
||||
0xeb, 0xa9, 0x2f, 0x65, 0x0d, 0xa0, 0x34, 0xd9, 0x6c, 0xe4, 0xc7, 0x74, 0x0a, 0x6a, 0x71, 0x00,
|
||||
0xfe, 0xc8, 0x00, 0xb9, 0x7a, 0x7c, 0x67, 0x0f, 0xf3, 0x03, 0xff, 0x7a, 0x9f, 0x36, 0x56, 0x66,
|
||||
0x54, 0x72, 0x0a, 0x54, 0x1f, 0xa0, 0x3c, 0x80, 0x7f, 0x36, 0x80, 0x89, 0x2d, 0x51, 0xe0, 0xb1,
|
||||
0xb3, 0x13, 0xd8, 0x6e, 0x48, 0x02, 0xd1, 0x6f, 0x52, 0x33, 0xc7, 0xdd, 0xeb, 0xed, 0x5d, 0x98,
|
||||
0xee, 0x65, 0x4b, 0xf3, 0xd2, 0x3b, 0x73, 0xa5, 0x8b, 0x1b, 0xa8, 0xab, 0x83, 0x3c, 0xd1, 0x92,
|
||||
0x96, 0xc6, 0x1c, 0xe9, 0x43, 0xa2, 0x25, 0xbd, 0x94, 0xac, 0x0e, 0x49, 0x07, 0xa5, 0x41, 0xc3,
|
||||
0x6d, 0x30, 0xed, 0x07, 0x84, 0x03, 0xdc, 0x70, 0x0f, 0x5d, 0xef, 0xc8, 0xbd, 0x6a, 0x13, 0xc7,
|
||||
0xa2, 0x26, 0x98, 0x37, 0x16, 0x73, 0xa5, 0x0b, 0xcd, 0x46, 0x7e, 0x7a, 0xa7, 0x93, 0x00, 0xea,
|
||||
0xac, 0x57, 0x78, 0x77, 0x20, 0xfd, 0x0a, 0x48, 0x77, 0x11, 0xf0, 0x7d, 0xb1, 0x7a, 0x11, 0x1b,
|
||||
0x6a, 0x1a, 0x7c, 0xb7, 0xde, 0xec, 0x53, 0x32, 0xa9, 0x36, 0x20, 0xe9, 0xe4, 0x14, 0x89, 0x22,
|
||||
0xcd, 0x0f, 0xf8, 0x73, 0x03, 0x8c, 0xe3, 0x4a, 0x85, 0xf8, 0x21, 0xb1, 0x44, 0x71, 0xcf, 0x7c,
|
||||
0x0a, 0xf5, 0x6b, 0x5a, 0x7a, 0x35, 0xbe, 0xa2, 0x43, 0xa3, 0x56, 0x4f, 0xe0, 0x73, 0xe0, 0x2c,
|
||||
0x0d, 0xbd, 0x80, 0x58, 0xa9, 0xb6, 0x19, 0x36, 0x1b, 0xf9, 0xb3, 0xe5, 0x16, 0x0e, 0x4a, 0x49,
|
||||
0x16, 0xfe, 0x91, 0x05, 0xf9, 0x3b, 0x1c, 0xb5, 0xbb, 0x78, 0x98, 0x5d, 0x02, 0x43, 0x7c, 0xb9,
|
||||
0x16, 0x8f, 0x4a, 0x4e, 0x6b, 0x05, 0x39, 0x15, 0x49, 0x2e, 0xbb, 0x28, 0x18, 0x3e, 0x6b, 0x5f,
|
||||
0x06, 0xb8, 0xa0, 0xba, 0x28, 0xca, 0x82, 0x8c, 0x62, 0x3e, 0x5c, 0x06, 0xc0, 0x22, 0x7e, 0x40,
|
||||
0xd8, 0x65, 0x65, 0x99, 0xc3, 0x5c, 0x5a, 0x6d, 0xd2, 0x9a, 0xe2, 0x20, 0x4d, 0x0a, 0x5e, 0x05,
|
||||
0x30, 0xfe, 0x65, 0x7b, 0xee, 0xcb, 0x38, 0x70, 0x6d, 0xb7, 0x6a, 0xe6, 0xb8, 0xdb, 0x33, 0xac,
|
||||
0x1b, 0x5b, 0x6b, 0xe3, 0xa2, 0x0e, 0x1a, 0xf0, 0x6d, 0x30, 0x24, 0x86, 0x3e, 0xfc, 0x86, 0xe8,
|
||||
0x63, 0x95, 0x07, 0x3c, 0x46, 0x1c, 0x0a, 0x49, 0xc8, 0xf6, 0xea, 0x9e, 0xbd, 0xdf, 0xd5, 0xfd,
|
||||
0xb6, 0xe5, 0x74, 0xe8, 0xff, 0xbc, 0x9c, 0x16, 0xfe, 0x6b, 0xa4, 0x6b, 0x8e, 0xb6, 0xd4, 0x72,
|
||||
0x05, 0x3b, 0x04, 0xae, 0x81, 0x49, 0xf6, 0x62, 0x42, 0xc4, 0x77, 0xec, 0x0a, 0xa6, 0xfc, 0xc1,
|
||||
0x2e, 0x92, 0x5d, 0xcd, 0x90, 0xca, 0x29, 0x3e, 0x6a, 0xd3, 0x80, 0x2f, 0x00, 0x28, 0x5e, 0x11,
|
||||
0x2d, 0x76, 0x44, 0x43, 0xa4, 0xde, 0x03, 0xe5, 0x36, 0x09, 0xd4, 0x41, 0x0b, 0xae, 0x82, 0x29,
|
||||
0x07, 0xef, 0x11, 0xa7, 0x4c, 0x1c, 0x52, 0x09, 0xbd, 0x80, 0x9b, 0x12, 0x23, 0x8d, 0xe9, 0x66,
|
||||
0x23, 0x3f, 0x75, 0x2d, 0xcd, 0x44, 0xed, 0xf2, 0x85, 0x85, 0xf4, 0xd1, 0xd6, 0x17, 0x2e, 0xde,
|
||||
0x66, 0x1f, 0x64, 0xc0, 0x6c, 0xf7, 0xcc, 0x80, 0xdf, 0x4d, 0x9e, 0x90, 0xe2, 0x85, 0xf0, 0x7a,
|
||||
0xbf, 0xb2, 0x50, 0xbe, 0x21, 0x41, 0xfb, 0xfb, 0x11, 0x7e, 0x8b, 0xb5, 0x6b, 0xd8, 0x89, 0x87,
|
||||
0x56, 0xaf, 0xf5, 0xcd, 0x05, 0x06, 0x52, 0x1a, 0x11, 0x9d, 0x20, 0x76, 0x78, 0xe3, 0x87, 0x1d,
|
||||
0x52, 0xf8, 0x9d, 0x91, 0x9e, 0x22, 0x24, 0x27, 0x18, 0xfe, 0xd8, 0x00, 0x13, 0x9e, 0x4f, 0xdc,
|
||||
0x95, 0x9d, 0x8d, 0x97, 0x3e, 0x27, 0x4e, 0xb2, 0x0c, 0xd5, 0xd6, 0x3d, 0xfa, 0xf9, 0x42, 0x79,
|
||||
0x7b, 0x4b, 0x18, 0xdc, 0x09, 0x3c, 0x9f, 0x96, 0xce, 0x35, 0x1b, 0xf9, 0x89, 0xed, 0x56, 0x28,
|
||||
0x94, 0xc6, 0x2e, 0xd4, 0xc0, 0xf4, 0xfa, 0x71, 0x48, 0x02, 0x17, 0x3b, 0x6b, 0x5e, 0x25, 0xaa,
|
||||
0x11, 0x37, 0x14, 0x8e, 0xa6, 0x26, 0x5e, 0xc6, 0x5d, 0x4e, 0xbc, 0x1e, 0x02, 0x03, 0x51, 0xe0,
|
||||
0xc8, 0x2c, 0x1e, 0x55, 0x13, 0x5d, 0x74, 0x0d, 0x31, 0x7a, 0x61, 0x01, 0x0c, 0x32, 0x3f, 0xe1,
|
||||
0x05, 0x30, 0x10, 0xe0, 0x23, 0x6e, 0x75, 0xac, 0x34, 0xcc, 0x44, 0x10, 0x3e, 0x42, 0x8c, 0x56,
|
||||
0xf8, 0xdb, 0x02, 0x98, 0x48, 0xad, 0x05, 0xce, 0x82, 0x8c, 0x1a, 0x13, 0x03, 0x69, 0x34, 0xb3,
|
||||
0xb1, 0x86, 0x32, 0xb6, 0x05, 0x9f, 0x51, 0xc5, 0x57, 0x80, 0xe6, 0xd5, 0x5d, 0xc2, 0xa9, 0xac,
|
||||
0x3f, 0x4f, 0xcc, 0x31, 0x47, 0xe2, 0xc2, 0xc9, 0x7c, 0x20, 0xfb, 0xf2, 0x94, 0x08, 0x1f, 0xc8,
|
||||
0x3e, 0x62, 0xb4, 0x4f, 0x3a, 0xee, 0x8b, 0xe7, 0x8d, 0xd9, 0xbb, 0x98, 0x37, 0x0e, 0xdd, 0x76,
|
||||
0xde, 0xf8, 0x30, 0xc8, 0x86, 0x76, 0xe8, 0x10, 0x7e, 0x91, 0x69, 0xcf, 0xa8, 0xeb, 0x8c, 0x88,
|
||||
0x04, 0x0f, 0xbe, 0x05, 0x86, 0x2d, 0xb2, 0x8f, 0x23, 0x27, 0xe4, 0x77, 0xd6, 0xe8, 0xf2, 0x6a,
|
||||
0x0f, 0x52, 0x48, 0x0c, 0x83, 0xd7, 0x84, 0x5d, 0x14, 0x03, 0xc0, 0x47, 0xc0, 0x70, 0x0d, 0x1f,
|
||||
0xdb, 0xb5, 0xa8, 0xc6, 0x1b, 0x4c, 0x43, 0x88, 0x6d, 0x0a, 0x12, 0x8a, 0x79, 0xac, 0x32, 0x92,
|
||||
0xe3, 0x8a, 0x13, 0x51, 0xbb, 0x4e, 0x24, 0x53, 0x36, 0x7f, 0xaa, 0x32, 0xae, 0xa7, 0xf8, 0xa8,
|
||||
0x4d, 0x83, 0x83, 0xd9, 0x2e, 0x57, 0x1e, 0xd5, 0xc0, 0x04, 0x09, 0xc5, 0xbc, 0x56, 0x30, 0x29,
|
||||
0x3f, 0xd6, 0x0d, 0x4c, 0x2a, 0xb7, 0x69, 0xc0, 0xc7, 0xc1, 0x48, 0x0d, 0x1f, 0x5f, 0x23, 0x6e,
|
||||
0x35, 0x3c, 0x30, 0xc7, 0xe7, 0x8d, 0xc5, 0x81, 0xd2, 0x78, 0xb3, 0x91, 0x1f, 0xd9, 0x8c, 0x89,
|
||||
0x28, 0xe1, 0x73, 0x61, 0xdb, 0x95, 0xc2, 0x67, 0x35, 0xe1, 0x98, 0x88, 0x12, 0x3e, 0xeb, 0x5e,
|
||||
0x7c, 0x1c, 0xb2, 0xc3, 0x65, 0x4e, 0xb4, 0x3e, 0x73, 0x77, 0x04, 0x19, 0xc5, 0x7c, 0xb8, 0x08,
|
||||
0x72, 0x35, 0x7c, 0xcc, 0x47, 0x12, 0xe6, 0x24, 0x37, 0xcb, 0x07, 0xe3, 0x9b, 0x92, 0x86, 0x14,
|
||||
0x97, 0x4b, 0xda, 0xae, 0x90, 0x9c, 0xd2, 0x24, 0x25, 0x0d, 0x29, 0x2e, 0x4b, 0xe2, 0xc8, 0xb5,
|
||||
0x6f, 0x46, 0x44, 0x08, 0x43, 0x1e, 0x19, 0x95, 0xc4, 0x37, 0x12, 0x16, 0xd2, 0xe5, 0x60, 0x11,
|
||||
0x80, 0x5a, 0xe4, 0x84, 0xb6, 0xef, 0x90, 0xed, 0x7d, 0xf3, 0x1c, 0x8f, 0x3f, 0x6f, 0xfa, 0x37,
|
||||
0x15, 0x15, 0x69, 0x12, 0x90, 0x80, 0x41, 0xe2, 0x46, 0x35, 0xf3, 0x3c, 0xbf, 0xd8, 0x7b, 0x92,
|
||||
0x82, 0xea, 0xe4, 0xac, 0xbb, 0x51, 0x0d, 0x71, 0xf3, 0xf0, 0x19, 0x30, 0x5e, 0xc3, 0xc7, 0xac,
|
||||
0x1c, 0x90, 0x20, 0xb4, 0x09, 0x35, 0xa7, 0xf9, 0xe2, 0xa7, 0x58, 0xb7, 0xbb, 0xa9, 0x33, 0x50,
|
||||
0xab, 0x1c, 0x57, 0xb4, 0x5d, 0x4d, 0x71, 0x46, 0x53, 0xd4, 0x19, 0xa8, 0x55, 0x8e, 0x45, 0x3a,
|
||||
0x20, 0x37, 0x23, 0x3b, 0x20, 0x96, 0xf9, 0x00, 0x6f, 0x90, 0xe5, 0xc7, 0x0a, 0x41, 0x43, 0x8a,
|
||||
0x0b, 0xeb, 0xf1, 0xec, 0xca, 0xe4, 0xc7, 0xf0, 0x46, 0x6f, 0x2b, 0xf9, 0x76, 0xb0, 0x12, 0x04,
|
||||
0xf8, 0x44, 0xdc, 0x34, 0xfa, 0xd4, 0x0a, 0x52, 0x90, 0xc5, 0x8e, 0xb3, 0xbd, 0x6f, 0x5e, 0xe0,
|
||||
0xb1, 0xef, 0xf5, 0x0d, 0xa2, 0xaa, 0xce, 0x0a, 0x03, 0x41, 0x02, 0x8b, 0x81, 0x7a, 0x2e, 0x4b,
|
||||
0x8d, 0xd9, 0xfe, 0x82, 0x6e, 0x33, 0x10, 0x24, 0xb0, 0xf8, 0x4a, 0xdd, 0x93, 0xed, 0x7d, 0xf3,
|
||||
0xc1, 0x3e, 0xaf, 0x94, 0x81, 0x20, 0x81, 0x05, 0x6d, 0x30, 0xe0, 0x7a, 0xa1, 0x79, 0xb1, 0x2f,
|
||||
0xd7, 0x33, 0xbf, 0x70, 0xb6, 0xbc, 0x10, 0x31, 0x0c, 0xf8, 0x33, 0x03, 0x00, 0x3f, 0x49, 0xd1,
|
||||
0x87, 0x7a, 0x32, 0x12, 0x49, 0x41, 0x16, 0x93, 0xdc, 0x5e, 0x77, 0xc3, 0xe0, 0x24, 0x79, 0x1e,
|
||||
0x69, 0x67, 0x40, 0xf3, 0x02, 0xfe, 0xda, 0x00, 0xe7, 0xf5, 0x36, 0x59, 0xb9, 0x37, 0xc7, 0x23,
|
||||
0x72, 0xbd, 0xd7, 0x69, 0x5e, 0xf2, 0x3c, 0xa7, 0x64, 0x36, 0x1b, 0xf9, 0xf3, 0x2b, 0x1d, 0x50,
|
||||
0x51, 0x47, 0x5f, 0xe0, 0xef, 0x0d, 0x30, 0x25, 0xab, 0xa8, 0xe6, 0x61, 0x9e, 0x07, 0x90, 0xf4,
|
||||
0x3a, 0x80, 0x69, 0x1c, 0x11, 0x47, 0xf5, 0x91, 0xbd, 0x8d, 0x8f, 0xda, 0x5d, 0x83, 0x7f, 0x32,
|
||||
0xc0, 0x98, 0x45, 0x7c, 0xe2, 0x5a, 0xc4, 0xad, 0x30, 0x5f, 0xe7, 0x7b, 0x32, 0xb2, 0x48, 0xfb,
|
||||
0xba, 0xa6, 0x41, 0x08, 0x37, 0x8b, 0xd2, 0xcd, 0x31, 0x9d, 0x75, 0xda, 0xc8, 0xcf, 0x24, 0xaa,
|
||||
0x3a, 0x07, 0xb5, 0x78, 0x09, 0xdf, 0x33, 0xc0, 0x44, 0xb2, 0x01, 0xe2, 0x4a, 0x59, 0xe8, 0x63,
|
||||
0x1e, 0xf0, 0xf6, 0x75, 0xa5, 0x15, 0x10, 0xa5, 0x3d, 0x80, 0x7f, 0x30, 0x58, 0xa7, 0x16, 0xbf,
|
||||
0xfb, 0xa8, 0x59, 0xe0, 0xb1, 0x7c, 0xa3, 0xe7, 0xb1, 0x54, 0x08, 0x22, 0x94, 0x97, 0x93, 0x56,
|
||||
0x50, 0x71, 0x4e, 0x1b, 0xf9, 0x69, 0x3d, 0x92, 0x8a, 0x81, 0x74, 0x0f, 0xe1, 0x0f, 0x0d, 0x30,
|
||||
0x46, 0x92, 0x8e, 0x9b, 0x9a, 0x0f, 0xf7, 0x24, 0x88, 0x1d, 0x9b, 0x78, 0xf1, 0x52, 0xd7, 0x58,
|
||||
0x14, 0xb5, 0x60, 0xb3, 0x0e, 0x92, 0x1c, 0xe3, 0x9a, 0xef, 0x10, 0xf3, 0x33, 0x3d, 0xee, 0x20,
|
||||
0xd7, 0x85, 0x5d, 0x14, 0x03, 0xc0, 0xcb, 0x20, 0xe7, 0x46, 0x8e, 0x83, 0xf7, 0x1c, 0x62, 0x3e,
|
||||
0xc2, 0x7b, 0x11, 0x35, 0x92, 0xdd, 0x92, 0x74, 0xa4, 0x24, 0xe0, 0x3e, 0x98, 0x3f, 0x7e, 0x51,
|
||||
0xfd, 0x7b, 0x52, 0xc7, 0xa1, 0xa1, 0x79, 0x89, 0x5b, 0x99, 0x6d, 0x36, 0xf2, 0x33, 0xbb, 0x9d,
|
||||
0xc7, 0x8a, 0x77, 0xb4, 0x01, 0x5f, 0x01, 0x0f, 0x6a, 0x32, 0xeb, 0xb5, 0x3d, 0x62, 0x59, 0xc4,
|
||||
0x8a, 0x1f, 0x6e, 0xe6, 0x67, 0xc5, 0xe0, 0x32, 0x3e, 0xe0, 0xbb, 0x69, 0x01, 0x74, 0x3b, 0x6d,
|
||||
0x78, 0x0d, 0xcc, 0x68, 0xec, 0x0d, 0x37, 0xdc, 0x0e, 0xca, 0x61, 0x60, 0xbb, 0x55, 0x73, 0x91,
|
||||
0xdb, 0x3d, 0x1f, 0x9f, 0xc8, 0x5d, 0x8d, 0x87, 0xba, 0xe8, 0xc0, 0xaf, 0xb4, 0x58, 0xe3, 0x9f,
|
||||
0xd0, 0xb0, 0xff, 0x22, 0x39, 0xa1, 0xe6, 0xa3, 0xbc, 0x3b, 0xe1, 0x9b, 0xbd, 0xab, 0xd1, 0x51,
|
||||
0x17, 0x79, 0xf8, 0x65, 0x70, 0x2e, 0xc5, 0x61, 0x4f, 0x14, 0xf3, 0x31, 0xf1, 0xd6, 0x60, 0xfd,
|
||||
0xec, 0x6e, 0x4c, 0x44, 0x9d, 0x24, 0xe1, 0x17, 0x01, 0xd4, 0xc8, 0x9b, 0xd8, 0xe7, 0xfa, 0x8f,
|
||||
0x8b, 0x67, 0x0f, 0xdb, 0xd1, 0x5d, 0x49, 0x43, 0x1d, 0xe4, 0xe0, 0x2f, 0x8c, 0x96, 0x95, 0x24,
|
||||
0xaf, 0x63, 0x6a, 0x5e, 0xe6, 0xe7, 0x77, 0xf3, 0x1e, 0xb3, 0x50, 0xfb, 0x0e, 0x12, 0x39, 0x44,
|
||||
0x0b, 0xb3, 0x06, 0x85, 0xba, 0xb8, 0x30, 0xcb, 0x5e, 0xe8, 0xa9, 0x0a, 0x0f, 0x27, 0xc1, 0xc0,
|
||||
0x21, 0x91, 0xff, 0x55, 0x81, 0xd8, 0x9f, 0xd0, 0x02, 0xd9, 0x3a, 0x76, 0xa2, 0x78, 0xc8, 0xd0,
|
||||
0xe3, 0xee, 0x00, 0x09, 0xe3, 0xcf, 0x65, 0x9e, 0x35, 0x66, 0xdf, 0x37, 0xc0, 0x4c, 0xe7, 0x8b,
|
||||
0xe7, 0xbe, 0xba, 0xf5, 0x4b, 0x03, 0x4c, 0xb5, 0xdd, 0x31, 0x1d, 0x3c, 0xba, 0xd9, 0xea, 0xd1,
|
||||
0x2b, 0xbd, 0xbe, 0x2c, 0xc4, 0xe1, 0xe0, 0x1d, 0xb2, 0xee, 0xde, 0x4f, 0x0c, 0x30, 0x99, 0x2e,
|
||||
0xdb, 0xf7, 0x33, 0x5e, 0x85, 0xf7, 0x33, 0x60, 0xa6, 0x73, 0x63, 0x0f, 0x03, 0x35, 0xc1, 0xe8,
|
||||
0xcf, 0x24, 0xa8, 0xd3, 0xd4, 0xf8, 0x1d, 0x03, 0x8c, 0xbe, 0xa5, 0xe4, 0xe2, 0xaf, 0xee, 0x3d,
|
||||
0x9f, 0x41, 0xc5, 0xf7, 0x64, 0xc2, 0xa0, 0x48, 0xc7, 0x2d, 0xfc, 0xd1, 0x00, 0xd3, 0x1d, 0x1b,
|
||||
0x00, 0x78, 0x09, 0x0c, 0x61, 0xc7, 0xf1, 0x8e, 0xc4, 0x28, 0x51, 0xfb, 0x46, 0xb0, 0xc2, 0xa9,
|
||||
0x48, 0x72, 0xb5, 0xe8, 0x65, 0x3e, 0xad, 0xe8, 0x15, 0xfe, 0x62, 0x80, 0x8b, 0xb7, 0xcb, 0xc4,
|
||||
0xfb, 0xb2, 0xa5, 0x8b, 0x20, 0x27, 0x9b, 0xf7, 0x13, 0xbe, 0x9d, 0xb2, 0x14, 0xcb, 0xa2, 0xc1,
|
||||
0xff, 0xd1, 0x4c, 0xfc, 0x55, 0xf8, 0xc0, 0x00, 0x93, 0x65, 0x12, 0xd4, 0xed, 0x0a, 0x41, 0x64,
|
||||
0x9f, 0x04, 0xc4, 0xad, 0x10, 0xb8, 0x04, 0x46, 0xf8, 0xe7, 0x6e, 0x1f, 0x57, 0xe2, 0x4f, 0x37,
|
||||
0x53, 0x32, 0xe4, 0x23, 0x5b, 0x31, 0x03, 0x25, 0x32, 0xea, 0x33, 0x4f, 0xa6, 0xeb, 0x67, 0x9e,
|
||||
0x8b, 0x60, 0xd0, 0x4f, 0x06, 0xd1, 0x39, 0xc6, 0xe5, 0xb3, 0x67, 0x4e, 0xe5, 0x5c, 0x2f, 0x08,
|
||||
0xf9, 0x74, 0x2d, 0x2b, 0xb9, 0x5e, 0x10, 0x22, 0x4e, 0x2d, 0xbc, 0x06, 0xce, 0xb6, 0x96, 0x71,
|
||||
0x86, 0x17, 0x44, 0x4e, 0xdb, 0x67, 0x25, 0xc6, 0x43, 0x9c, 0xa3, 0xff, 0xb7, 0x4b, 0xe6, 0x0e,
|
||||
0xff, 0xed, 0xf2, 0x57, 0x03, 0x74, 0xfa, 0x8f, 0x33, 0x78, 0x41, 0xcc, 0x2f, 0xb5, 0xa1, 0x60,
|
||||
0x3c, 0xbb, 0x84, 0x75, 0x30, 0x4c, 0x45, 0xd0, 0xe4, 0xa6, 0x6e, 0xdf, 0xe3, 0xa6, 0xa6, 0xb7,
|
||||
0x40, 0x34, 0x4e, 0x31, 0x35, 0x06, 0x63, 0xfb, 0x5a, 0xc1, 0xa5, 0xc8, 0xb5, 0xe4, 0x48, 0x7b,
|
||||
0x4c, 0xec, 0xeb, 0xea, 0x8a, 0xa0, 0x21, 0xc5, 0x2d, 0x5d, 0xf9, 0xf0, 0xd6, 0xdc, 0x99, 0x8f,
|
||||
0x6e, 0xcd, 0x9d, 0xf9, 0xf8, 0xd6, 0xdc, 0x99, 0x6f, 0x37, 0xe7, 0x8c, 0x0f, 0x9b, 0x73, 0xc6,
|
||||
0x47, 0xcd, 0x39, 0xe3, 0xe3, 0xe6, 0x9c, 0xf1, 0xcf, 0xe6, 0x9c, 0xf1, 0xd3, 0x7f, 0xcd, 0x9d,
|
||||
0xf9, 0xfa, 0xb0, 0xc4, 0xff, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xae, 0xd4, 0x36, 0xef, 0x07,
|
||||
0x2e, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *ConversionRequest) Marshal() (dAtA []byte, err error) {
|
||||
@ -1911,6 +1944,22 @@ func (m *JSONSchemaProps) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.XValidations) > 0 {
|
||||
for iNdEx := len(m.XValidations) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.XValidations[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x2
|
||||
i--
|
||||
dAtA[i] = 0xe2
|
||||
}
|
||||
}
|
||||
if m.XMapType != nil {
|
||||
i -= len(*m.XMapType)
|
||||
copy(dAtA[i:], *m.XMapType)
|
||||
@ -2587,6 +2636,39 @@ func (m *ServiceReference) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *ValidationRule) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *ValidationRule) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *ValidationRule) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
i -= len(m.Message)
|
||||
copy(dAtA[i:], m.Message)
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message)))
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
i -= len(m.Rule)
|
||||
copy(dAtA[i:], m.Rule)
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Rule)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *WebhookClientConfig) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
@ -3172,6 +3254,12 @@ func (m *JSONSchemaProps) Size() (n int) {
|
||||
l = len(*m.XMapType)
|
||||
n += 2 + l + sovGenerated(uint64(l))
|
||||
}
|
||||
if len(m.XValidations) > 0 {
|
||||
for _, e := range m.XValidations {
|
||||
l = e.Size()
|
||||
n += 2 + l + sovGenerated(uint64(l))
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@ -3247,6 +3335,19 @@ func (m *ServiceReference) Size() (n int) {
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *ValidationRule) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
l = len(m.Rule)
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
l = len(m.Message)
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *WebhookClientConfig) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
@ -3558,6 +3659,11 @@ func (this *JSONSchemaProps) String() string {
|
||||
repeatedStringForAnyOf += strings.Replace(strings.Replace(f.String(), "JSONSchemaProps", "JSONSchemaProps", 1), `&`, ``, 1) + ","
|
||||
}
|
||||
repeatedStringForAnyOf += "}"
|
||||
repeatedStringForXValidations := "[]ValidationRule{"
|
||||
for _, f := range this.XValidations {
|
||||
repeatedStringForXValidations += strings.Replace(strings.Replace(f.String(), "ValidationRule", "ValidationRule", 1), `&`, ``, 1) + ","
|
||||
}
|
||||
repeatedStringForXValidations += "}"
|
||||
keysForProperties := make([]string, 0, len(this.Properties))
|
||||
for k := range this.Properties {
|
||||
keysForProperties = append(keysForProperties, k)
|
||||
@ -3642,6 +3748,7 @@ func (this *JSONSchemaProps) String() string {
|
||||
`XListMapKeys:` + fmt.Sprintf("%v", this.XListMapKeys) + `,`,
|
||||
`XListType:` + valueToStringGenerated(this.XListType) + `,`,
|
||||
`XMapType:` + valueToStringGenerated(this.XMapType) + `,`,
|
||||
`XValidations:` + repeatedStringForXValidations + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
@ -3697,6 +3804,17 @@ func (this *ServiceReference) String() string {
|
||||
}, "")
|
||||
return s
|
||||
}
|
||||
func (this *ValidationRule) String() string {
|
||||
if this == nil {
|
||||
return "nil"
|
||||
}
|
||||
s := strings.Join([]string{`&ValidationRule{`,
|
||||
`Rule:` + fmt.Sprintf("%v", this.Rule) + `,`,
|
||||
`Message:` + fmt.Sprintf("%v", this.Message) + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
}
|
||||
func (this *WebhookClientConfig) String() string {
|
||||
if this == nil {
|
||||
return "nil"
|
||||
@ -8260,6 +8378,40 @@ func (m *JSONSchemaProps) Unmarshal(dAtA []byte) error {
|
||||
s := string(dAtA[iNdEx:postIndex])
|
||||
m.XMapType = &s
|
||||
iNdEx = postIndex
|
||||
case 44:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field XValidations", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.XValidations = append(m.XValidations, ValidationRule{})
|
||||
if err := m.XValidations[len(m.XValidations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipGenerated(dAtA[iNdEx:])
|
||||
@ -8792,6 +8944,120 @@ func (m *ServiceReference) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *ValidationRule) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: ValidationRule: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: ValidationRule: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Rule", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Rule = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Message = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipGenerated(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *WebhookClientConfig) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
|
@ -601,6 +601,14 @@ message JSONSchemaProps {
|
||||
// Atomic maps will be entirely replaced when updated.
|
||||
// +optional
|
||||
optional string xKubernetesMapType = 43;
|
||||
|
||||
// x-kubernetes-validations describes a list of validation rules written in the CEL expression language.
|
||||
// This field is an alpha-level. Using this field requires the feature gate `CustomResourceValidationExpressions` to be enabled.
|
||||
// +patchMergeKey=rule
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=rule
|
||||
repeated ValidationRule xKubernetesValidations = 44;
|
||||
}
|
||||
|
||||
// JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps
|
||||
@ -647,6 +655,70 @@ message ServiceReference {
|
||||
optional int32 port = 4;
|
||||
}
|
||||
|
||||
// ValidationRule describes a validation rule written in the CEL expression language.
|
||||
message ValidationRule {
|
||||
// Rule represents the expression which will be evaluated by CEL.
|
||||
// ref: https://github.com/google/cel-spec
|
||||
// The Rule is scoped to the location of the x-kubernetes-validations extension in the schema.
|
||||
// The `self` variable in the CEL expression is bound to the scoped value.
|
||||
// Example:
|
||||
// - Rule scoped to the root of a resource with a status subresource: {"rule": "self.status.actual <= self.spec.maxDesired"}
|
||||
//
|
||||
// If the Rule is scoped to an object with properties, the accessible properties of the object are field selectable
|
||||
// via `self.field` and field presence can be checked via `has(self.field)`. Null valued fields are treated as
|
||||
// absent fields in CEL expressions.
|
||||
// If the Rule is scoped to an object with additionalProperties (i.e. a map) the value of the map
|
||||
// are accessible via `self[mapKey]`, map containment can be checked via `mapKey in self` and all entries of the map
|
||||
// are accessible via CEL macros and functions such as `self.all(...)`.
|
||||
// If the Rule is scoped to an array, the elements of the array are accessible via `self[i]` and also by macros and
|
||||
// functions.
|
||||
// If the Rule is scoped to a scalar, `self` is bound to the scalar value.
|
||||
// Examples:
|
||||
// - Rule scoped to a map of objects: {"rule": "self.components['Widget'].priority < 10"}
|
||||
// - Rule scoped to a list of integers: {"rule": "self.values.all(value, value >= 0 && value < 100)"}
|
||||
// - Rule scoped to a string value: {"rule": "self.startsWith('kube')"}
|
||||
//
|
||||
// The `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the
|
||||
// object and from any x-kubernetes-embedded-resource annotated objects. No other metadata properties are accessible.
|
||||
//
|
||||
// Unknown data preserved in custom resources via x-kubernetes-preserve-unknown-fields is not accessible in CEL
|
||||
// expressions. This includes:
|
||||
// - Unknown field values that are preserved by object schemas with x-kubernetes-preserve-unknown-fields.
|
||||
// - Object properties where the property schema is of an "unknown type". An "unknown type" is recursively defined as:
|
||||
// - A schema with no type and x-kubernetes-preserve-unknown-fields set to true
|
||||
// - An array where the items schema is of an "unknown type"
|
||||
// - An object where the additionalProperties schema is of an "unknown type"
|
||||
//
|
||||
// Only property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible.
|
||||
// Accessible property names are escaped according to the following rules when accessed in the expression:
|
||||
// - '__' escapes to '__underscores__'
|
||||
// - '.' escapes to '__dot__'
|
||||
// - '-' escapes to '__dash__'
|
||||
// - '/' escapes to '__slash__'
|
||||
// - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:
|
||||
// "true", "false", "null", "in", "as", "break", "const", "continue", "else", "for", "function", "if",
|
||||
// "import", "let", "loop", "package", "namespace", "return".
|
||||
// Examples:
|
||||
// - Rule accessing a property named "namespace": {"rule": "self.__namespace__ > 0"}
|
||||
// - Rule accessing a property named "x-prop": {"rule": "self.x__dash__prop > 0"}
|
||||
// - Rule accessing a property named "redact__d": {"rule": "self.redact__underscores__d > 0"}
|
||||
//
|
||||
// Equality on arrays with x-kubernetes-list-type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1].
|
||||
// Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:
|
||||
// - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and
|
||||
// non-intersecting elements in `Y` are appended, retaining their partial order.
|
||||
// - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values
|
||||
// are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with
|
||||
// non-intersecting keys are appended, retaining their partial order.
|
||||
optional string rule = 1;
|
||||
|
||||
// Message represents the message displayed when validation fails. The message is required if the Rule contains
|
||||
// line breaks. The message must not contain line breaks.
|
||||
// If unset, the message is "failed rule: {Rule}".
|
||||
// e.g. "must be a URL with the host matching spec.host"
|
||||
optional string message = 2;
|
||||
}
|
||||
|
||||
// WebhookClientConfig contains the information to make a TLS connection with the webhook.
|
||||
message WebhookClientConfig {
|
||||
// url gives the location of the webhook, in standard URL form
|
||||
|
@ -161,6 +161,80 @@ type JSONSchemaProps struct {
|
||||
// Atomic maps will be entirely replaced when updated.
|
||||
// +optional
|
||||
XMapType *string `json:"x-kubernetes-map-type,omitempty" protobuf:"bytes,43,opt,name=xKubernetesMapType"`
|
||||
|
||||
// x-kubernetes-validations describes a list of validation rules written in the CEL expression language.
|
||||
// This field is an alpha-level. Using this field requires the feature gate `CustomResourceValidationExpressions` to be enabled.
|
||||
// +patchMergeKey=rule
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=rule
|
||||
XValidations ValidationRules `json:"x-kubernetes-validations,omitempty" patchStrategy:"merge" patchMergeKey:"rule" protobuf:"bytes,44,rep,name=xKubernetesValidations"`
|
||||
}
|
||||
|
||||
// ValidationRules describes a list of validation rules written in the CEL expression language.
|
||||
type ValidationRules []ValidationRule
|
||||
|
||||
// ValidationRule describes a validation rule written in the CEL expression language.
|
||||
type ValidationRule struct {
|
||||
// Rule represents the expression which will be evaluated by CEL.
|
||||
// ref: https://github.com/google/cel-spec
|
||||
// The Rule is scoped to the location of the x-kubernetes-validations extension in the schema.
|
||||
// The `self` variable in the CEL expression is bound to the scoped value.
|
||||
// Example:
|
||||
// - Rule scoped to the root of a resource with a status subresource: {"rule": "self.status.actual <= self.spec.maxDesired"}
|
||||
//
|
||||
// If the Rule is scoped to an object with properties, the accessible properties of the object are field selectable
|
||||
// via `self.field` and field presence can be checked via `has(self.field)`. Null valued fields are treated as
|
||||
// absent fields in CEL expressions.
|
||||
// If the Rule is scoped to an object with additionalProperties (i.e. a map) the value of the map
|
||||
// are accessible via `self[mapKey]`, map containment can be checked via `mapKey in self` and all entries of the map
|
||||
// are accessible via CEL macros and functions such as `self.all(...)`.
|
||||
// If the Rule is scoped to an array, the elements of the array are accessible via `self[i]` and also by macros and
|
||||
// functions.
|
||||
// If the Rule is scoped to a scalar, `self` is bound to the scalar value.
|
||||
// Examples:
|
||||
// - Rule scoped to a map of objects: {"rule": "self.components['Widget'].priority < 10"}
|
||||
// - Rule scoped to a list of integers: {"rule": "self.values.all(value, value >= 0 && value < 100)"}
|
||||
// - Rule scoped to a string value: {"rule": "self.startsWith('kube')"}
|
||||
//
|
||||
// The `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the
|
||||
// object and from any x-kubernetes-embedded-resource annotated objects. No other metadata properties are accessible.
|
||||
//
|
||||
// Unknown data preserved in custom resources via x-kubernetes-preserve-unknown-fields is not accessible in CEL
|
||||
// expressions. This includes:
|
||||
// - Unknown field values that are preserved by object schemas with x-kubernetes-preserve-unknown-fields.
|
||||
// - Object properties where the property schema is of an "unknown type". An "unknown type" is recursively defined as:
|
||||
// - A schema with no type and x-kubernetes-preserve-unknown-fields set to true
|
||||
// - An array where the items schema is of an "unknown type"
|
||||
// - An object where the additionalProperties schema is of an "unknown type"
|
||||
//
|
||||
// Only property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible.
|
||||
// Accessible property names are escaped according to the following rules when accessed in the expression:
|
||||
// - '__' escapes to '__underscores__'
|
||||
// - '.' escapes to '__dot__'
|
||||
// - '-' escapes to '__dash__'
|
||||
// - '/' escapes to '__slash__'
|
||||
// - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:
|
||||
// "true", "false", "null", "in", "as", "break", "const", "continue", "else", "for", "function", "if",
|
||||
// "import", "let", "loop", "package", "namespace", "return".
|
||||
// Examples:
|
||||
// - Rule accessing a property named "namespace": {"rule": "self.__namespace__ > 0"}
|
||||
// - Rule accessing a property named "x-prop": {"rule": "self.x__dash__prop > 0"}
|
||||
// - Rule accessing a property named "redact__d": {"rule": "self.redact__underscores__d > 0"}
|
||||
//
|
||||
// Equality on arrays with x-kubernetes-list-type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1].
|
||||
// Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:
|
||||
// - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and
|
||||
// non-intersecting elements in `Y` are appended, retaining their partial order.
|
||||
// - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values
|
||||
// are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with
|
||||
// non-intersecting keys are appended, retaining their partial order.
|
||||
Rule string `json:"rule" protobuf:"bytes,1,opt,name=rule"`
|
||||
// Message represents the message displayed when validation fails. The message is required if the Rule contains
|
||||
// line breaks. The message must not contain line breaks.
|
||||
// If unset, the message is "failed rule: {Rule}".
|
||||
// e.g. "must be a URL with the host matching spec.host"
|
||||
Message string `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"`
|
||||
}
|
||||
|
||||
// JSON represents any valid JSON value.
|
||||
|
@ -222,6 +222,16 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*ValidationRule)(nil), (*apiextensions.ValidationRule)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1beta1_ValidationRule_To_apiextensions_ValidationRule(a.(*ValidationRule), b.(*apiextensions.ValidationRule), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*apiextensions.ValidationRule)(nil), (*ValidationRule)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_apiextensions_ValidationRule_To_v1beta1_ValidationRule(a.(*apiextensions.ValidationRule), b.(*ValidationRule), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*WebhookClientConfig)(nil), (*apiextensions.WebhookClientConfig)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1beta1_WebhookClientConfig_To_apiextensions_WebhookClientConfig(a.(*WebhookClientConfig), b.(*apiextensions.WebhookClientConfig), scope)
|
||||
}); err != nil {
|
||||
@ -936,6 +946,7 @@ func autoConvert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(in *JS
|
||||
out.XListMapKeys = *(*[]string)(unsafe.Pointer(&in.XListMapKeys))
|
||||
out.XListType = (*string)(unsafe.Pointer(in.XListType))
|
||||
out.XMapType = (*string)(unsafe.Pointer(in.XMapType))
|
||||
out.XValidations = *(*apiextensions.ValidationRules)(unsafe.Pointer(&in.XValidations))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1124,6 +1135,7 @@ func autoConvert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(in *ap
|
||||
out.XListMapKeys = *(*[]string)(unsafe.Pointer(&in.XListMapKeys))
|
||||
out.XListType = (*string)(unsafe.Pointer(in.XListType))
|
||||
out.XMapType = (*string)(unsafe.Pointer(in.XMapType))
|
||||
out.XValidations = *(*ValidationRules)(unsafe.Pointer(&in.XValidations))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1291,6 +1303,28 @@ func Convert_apiextensions_ServiceReference_To_v1beta1_ServiceReference(in *apie
|
||||
return autoConvert_apiextensions_ServiceReference_To_v1beta1_ServiceReference(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_ValidationRule_To_apiextensions_ValidationRule(in *ValidationRule, out *apiextensions.ValidationRule, s conversion.Scope) error {
|
||||
out.Rule = in.Rule
|
||||
out.Message = in.Message
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1beta1_ValidationRule_To_apiextensions_ValidationRule is an autogenerated conversion function.
|
||||
func Convert_v1beta1_ValidationRule_To_apiextensions_ValidationRule(in *ValidationRule, out *apiextensions.ValidationRule, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_ValidationRule_To_apiextensions_ValidationRule(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_apiextensions_ValidationRule_To_v1beta1_ValidationRule(in *apiextensions.ValidationRule, out *ValidationRule, s conversion.Scope) error {
|
||||
out.Rule = in.Rule
|
||||
out.Message = in.Message
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_apiextensions_ValidationRule_To_v1beta1_ValidationRule is an autogenerated conversion function.
|
||||
func Convert_apiextensions_ValidationRule_To_v1beta1_ValidationRule(in *apiextensions.ValidationRule, out *ValidationRule, s conversion.Scope) error {
|
||||
return autoConvert_apiextensions_ValidationRule_To_v1beta1_ValidationRule(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_WebhookClientConfig_To_apiextensions_WebhookClientConfig(in *WebhookClientConfig, out *apiextensions.WebhookClientConfig, s conversion.Scope) error {
|
||||
out.URL = (*string)(unsafe.Pointer(in.URL))
|
||||
if in.Service != nil {
|
||||
|
@ -636,6 +636,42 @@ func (in *ServiceReference) DeepCopy() *ServiceReference {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ValidationRule) DeepCopyInto(out *ValidationRule) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidationRule.
|
||||
func (in *ValidationRule) DeepCopy() *ValidationRule {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ValidationRule)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in ValidationRules) DeepCopyInto(out *ValidationRules) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(ValidationRules, len(*in))
|
||||
copy(*out, *in)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidationRules.
|
||||
func (in ValidationRules) DeepCopy() ValidationRules {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ValidationRules)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WebhookClientConfig) DeepCopyInto(out *WebhookClientConfig) {
|
||||
*out = *in
|
||||
|
@ -19,11 +19,13 @@ package validation
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apihelpers"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel"
|
||||
structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
genericvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||
@ -909,6 +911,40 @@ func ValidateCustomResourceDefinitionOpenAPISchema(schema *apiextensions.JSONSch
|
||||
}
|
||||
}
|
||||
|
||||
if len(schema.XValidations) > 0 {
|
||||
for i, rule := range schema.XValidations {
|
||||
trimmedRule := strings.TrimSpace(rule.Rule)
|
||||
trimmedMsg := strings.TrimSpace(rule.Message)
|
||||
if len(trimmedRule) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("x-kubernetes-validations").Index(i).Child("rule"), "rule is not specified"))
|
||||
} else if len(rule.Message) > 0 && len(trimmedMsg) == 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("x-kubernetes-validations").Index(i).Child("message"), rule.Message, "message must be non-empty if specified"))
|
||||
} else if hasNewlines(trimmedMsg) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("x-kubernetes-validations").Index(i).Child("message"), rule.Message, "message must not contain line breaks"))
|
||||
} else if hasNewlines(trimmedRule) && len(trimmedMsg) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("x-kubernetes-validations").Index(i).Child("message"), "message must be specified if rule contains line breaks"))
|
||||
}
|
||||
}
|
||||
|
||||
structural, err := structuralschema.NewStructural(schema)
|
||||
if err == nil {
|
||||
compResults, err := cel.Compile(structural, isRoot)
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.InternalError(fldPath.Child("x-kubernetes-validations"), err))
|
||||
} else {
|
||||
for i, cr := range compResults {
|
||||
if cr.Error != nil {
|
||||
if cr.Error.Type == cel.ErrorTypeRequired {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("x-kubernetes-validations").Index(i).Child("rule"), cr.Error.Detail))
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("x-kubernetes-validations").Index(i).Child("rule"), schema.XValidations[i], cr.Error.Detail))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.requireMapListKeysMapSetValidation {
|
||||
allErrs = append(allErrs, validateMapListKeysMapSet(schema, fldPath)...)
|
||||
}
|
||||
@ -916,6 +952,11 @@ func ValidateCustomResourceDefinitionOpenAPISchema(schema *apiextensions.JSONSch
|
||||
return allErrs
|
||||
}
|
||||
|
||||
var newlineMatcher = regexp.MustCompile(`[\n\r]+`) // valid newline chars in CEL grammar
|
||||
func hasNewlines(s string) bool {
|
||||
return newlineMatcher.MatchString(s)
|
||||
}
|
||||
|
||||
func validateMapListKeysMapSet(schema *apiextensions.JSONSchemaProps, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
@ -1112,7 +1153,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", "XPreserveUnknownFields"}
|
||||
var allowedFieldsAtRootSchema = []string{"Description", "Type", "Format", "Title", "Maximum", "ExclusiveMaximum", "Minimum", "ExclusiveMinimum", "MaxLength", "MinLength", "Pattern", "MaxItems", "MinItems", "UniqueItems", "MultipleOf", "Required", "Items", "Properties", "ExternalDocs", "Example", "XPreserveUnknownFields", "XValidations"}
|
||||
|
||||
func allowedAtRootSchema(field string) bool {
|
||||
for _, v := range allowedFieldsAtRootSchema {
|
||||
@ -1144,16 +1185,16 @@ func allVersionsSpecifyOpenAPISchema(spec *apiextensions.CustomResourceDefinitio
|
||||
}
|
||||
|
||||
func specHasDefaults(spec *apiextensions.CustomResourceDefinitionSpec) bool {
|
||||
return hasSchemaWith(spec, schemaHasDefaults)
|
||||
return HasSchemaWith(spec, schemaHasDefaults)
|
||||
}
|
||||
|
||||
func schemaHasDefaults(s *apiextensions.JSONSchemaProps) bool {
|
||||
return schemaHas(s, func(s *apiextensions.JSONSchemaProps) bool {
|
||||
return SchemaHas(s, func(s *apiextensions.JSONSchemaProps) bool {
|
||||
return s.Default != nil
|
||||
})
|
||||
}
|
||||
|
||||
func hasSchemaWith(spec *apiextensions.CustomResourceDefinitionSpec, pred func(s *apiextensions.JSONSchemaProps) bool) bool {
|
||||
func HasSchemaWith(spec *apiextensions.CustomResourceDefinitionSpec, pred func(s *apiextensions.JSONSchemaProps) bool) bool {
|
||||
if spec.Validation != nil && spec.Validation.OpenAPIV3Schema != nil && pred(spec.Validation.OpenAPIV3Schema) {
|
||||
return true
|
||||
}
|
||||
@ -1165,7 +1206,7 @@ func hasSchemaWith(spec *apiextensions.CustomResourceDefinitionSpec, pred func(s
|
||||
return false
|
||||
}
|
||||
|
||||
func schemaHas(s *apiextensions.JSONSchemaProps, pred func(s *apiextensions.JSONSchemaProps) bool) bool {
|
||||
func SchemaHas(s *apiextensions.JSONSchemaProps, pred func(s *apiextensions.JSONSchemaProps) bool) bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
@ -1175,60 +1216,60 @@ func schemaHas(s *apiextensions.JSONSchemaProps, pred func(s *apiextensions.JSON
|
||||
}
|
||||
|
||||
if s.Items != nil {
|
||||
if s.Items != nil && schemaHas(s.Items.Schema, pred) {
|
||||
if s.Items != nil && SchemaHas(s.Items.Schema, pred) {
|
||||
return true
|
||||
}
|
||||
for _, s := range s.Items.JSONSchemas {
|
||||
if schemaHas(&s, pred) {
|
||||
if SchemaHas(&s, pred) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, s := range s.AllOf {
|
||||
if schemaHas(&s, pred) {
|
||||
if SchemaHas(&s, pred) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, s := range s.AnyOf {
|
||||
if schemaHas(&s, pred) {
|
||||
if SchemaHas(&s, pred) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, s := range s.OneOf {
|
||||
if schemaHas(&s, pred) {
|
||||
if SchemaHas(&s, pred) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if schemaHas(s.Not, pred) {
|
||||
if SchemaHas(s.Not, pred) {
|
||||
return true
|
||||
}
|
||||
for _, s := range s.Properties {
|
||||
if schemaHas(&s, pred) {
|
||||
if SchemaHas(&s, pred) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if s.AdditionalProperties != nil {
|
||||
if schemaHas(s.AdditionalProperties.Schema, pred) {
|
||||
if SchemaHas(s.AdditionalProperties.Schema, pred) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, s := range s.PatternProperties {
|
||||
if schemaHas(&s, pred) {
|
||||
if SchemaHas(&s, pred) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if s.AdditionalItems != nil {
|
||||
if schemaHas(s.AdditionalItems.Schema, pred) {
|
||||
if SchemaHas(s.AdditionalItems.Schema, pred) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, s := range s.Definitions {
|
||||
if schemaHas(&s, pred) {
|
||||
if SchemaHas(&s, pred) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, d := range s.Dependencies {
|
||||
if schemaHas(d.Schema, pred) {
|
||||
if SchemaHas(d.Schema, pred) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -1249,8 +1290,8 @@ func specHasKubernetesExtensions(spec *apiextensions.CustomResourceDefinitionSpe
|
||||
}
|
||||
|
||||
func schemaHasKubernetesExtensions(s *apiextensions.JSONSchemaProps) bool {
|
||||
return schemaHas(s, func(s *apiextensions.JSONSchemaProps) bool {
|
||||
return s.XEmbeddedResource || s.XPreserveUnknownFields != nil || s.XIntOrString || len(s.XListMapKeys) > 0 || s.XListType != nil
|
||||
return SchemaHas(s, func(s *apiextensions.JSONSchemaProps) bool {
|
||||
return s.XEmbeddedResource || s.XPreserveUnknownFields != nil || s.XIntOrString || len(s.XListMapKeys) > 0 || s.XListType != nil || len(s.XValidations) > 0
|
||||
})
|
||||
}
|
||||
|
||||
@ -1322,12 +1363,12 @@ func schemaHasUnprunedDefaults(schema *apiextensions.JSONSchemaProps) (bool, err
|
||||
|
||||
// requireAtomicSetType returns true if the old CRD spec as at least one x-kubernetes-list-type=set with non-atomic items type.
|
||||
func requireAtomicSetType(oldCRDSpec *apiextensions.CustomResourceDefinitionSpec) bool {
|
||||
return !hasSchemaWith(oldCRDSpec, hasNonAtomicSetType)
|
||||
return !HasSchemaWith(oldCRDSpec, hasNonAtomicSetType)
|
||||
}
|
||||
|
||||
// hasNonAtomicSetType recurses over the schema and returns whether any list of type "set" as non-atomic item types.
|
||||
func hasNonAtomicSetType(schema *apiextensions.JSONSchemaProps) bool {
|
||||
return schemaHas(schema, func(schema *apiextensions.JSONSchemaProps) bool {
|
||||
return SchemaHas(schema, func(schema *apiextensions.JSONSchemaProps) bool {
|
||||
if schema.XListType != nil && *schema.XListType == "set" && schema.Items != nil && schema.Items.Schema != nil { // we don't support schema.Items.JSONSchemas
|
||||
is := schema.Items.Schema
|
||||
switch is.Type {
|
||||
@ -1344,11 +1385,11 @@ func hasNonAtomicSetType(schema *apiextensions.JSONSchemaProps) bool {
|
||||
}
|
||||
|
||||
func requireMapListKeysMapSetValidation(oldCRDSpec *apiextensions.CustomResourceDefinitionSpec) bool {
|
||||
return !hasSchemaWith(oldCRDSpec, hasInvalidMapListKeysMapSet)
|
||||
return !HasSchemaWith(oldCRDSpec, hasInvalidMapListKeysMapSet)
|
||||
}
|
||||
|
||||
func hasInvalidMapListKeysMapSet(schema *apiextensions.JSONSchemaProps) bool {
|
||||
return schemaHas(schema, func(schema *apiextensions.JSONSchemaProps) bool {
|
||||
return SchemaHas(schema, func(schema *apiextensions.JSONSchemaProps) bool {
|
||||
return len(validateMapListKeysMapSet(schema, field.NewPath(""))) > 0
|
||||
})
|
||||
}
|
||||
@ -1426,7 +1467,7 @@ func specHasInvalidTypes(spec *apiextensions.CustomResourceDefinitionSpec) bool
|
||||
|
||||
// SchemaHasInvalidTypes returns true if it contains invalid offending openapi-v3 specification.
|
||||
func SchemaHasInvalidTypes(s *apiextensions.JSONSchemaProps) bool {
|
||||
return schemaHas(s, func(s *apiextensions.JSONSchemaProps) bool {
|
||||
return SchemaHas(s, func(s *apiextensions.JSONSchemaProps) bool {
|
||||
return len(s.Type) > 0 && !openapiV3Types.Has(s.Type)
|
||||
})
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -528,6 +528,42 @@ func (in *ServiceReference) DeepCopy() *ServiceReference {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ValidationRule) DeepCopyInto(out *ValidationRule) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidationRule.
|
||||
func (in *ValidationRule) DeepCopy() *ValidationRule {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ValidationRule)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in ValidationRules) DeepCopyInto(out *ValidationRules) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(ValidationRules, len(*in))
|
||||
copy(*out, *in)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidationRules.
|
||||
func (in ValidationRules) DeepCopy() ValidationRules {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ValidationRules)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WebhookClientConfig) DeepCopyInto(out *WebhookClientConfig) {
|
||||
*out = *in
|
||||
|
@ -0,0 +1,127 @@
|
||||
/*
|
||||
Copyright 2021 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 cel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/ext"
|
||||
|
||||
expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
celmodel "k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model"
|
||||
)
|
||||
|
||||
// ScopedVarName is the variable name assigned to the locally scoped data element of a CEL valid.
|
||||
const ScopedVarName = "self"
|
||||
|
||||
// CompilationResult represents the cel compilation result for one rule
|
||||
type CompilationResult struct {
|
||||
Program cel.Program
|
||||
Error *Error
|
||||
}
|
||||
|
||||
// Compile compiles all the XValidations rules (without recursing into the schema) and returns a slice containing a
|
||||
// CompilationResult for each ValidationRule, or an error.
|
||||
// Each CompilationResult may contain:
|
||||
/// - non-nil Program, nil Error: The program was compiled successfully
|
||||
// - nil Program, non-nil Error: Compilation resulted in an error
|
||||
// - nil Program, nil Error: The provided rule was empty so compilation was not attempted
|
||||
func Compile(s *schema.Structural, isResourceRoot bool) ([]CompilationResult, error) {
|
||||
if len(s.Extensions.XValidations) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
celRules := s.Extensions.XValidations
|
||||
|
||||
var propDecls []*expr.Decl
|
||||
var root *celmodel.DeclType
|
||||
var ok bool
|
||||
env, err := cel.NewEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reg := celmodel.NewRegistry(env)
|
||||
scopedTypeName := generateUniqueSelfTypeName()
|
||||
rt, err := celmodel.NewRuleTypes(scopedTypeName, s, isResourceRoot, reg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rt == nil {
|
||||
return nil, nil
|
||||
}
|
||||
opts, err := rt.EnvOptions(env.TypeProvider())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root, ok = rt.FindDeclType(scopedTypeName)
|
||||
if !ok {
|
||||
rootDecl := celmodel.SchemaDeclType(s, isResourceRoot)
|
||||
if rootDecl == nil {
|
||||
return nil, fmt.Errorf("rule declared on schema that does not support validation rules type: '%s' x-kubernetes-preserve-unknown-fields: '%t'", s.Type, s.XPreserveUnknownFields)
|
||||
}
|
||||
root = rootDecl.MaybeAssignTypeName(scopedTypeName)
|
||||
}
|
||||
propDecls = append(propDecls, decls.NewVar(ScopedVarName, root.ExprType()))
|
||||
opts = append(opts, cel.Declarations(propDecls...))
|
||||
opts = append(opts, ext.Strings())
|
||||
env, err = env.Extend(opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// compResults is the return value which saves a list of compilation results in the same order as x-kubernetes-validations rules.
|
||||
compResults := make([]CompilationResult, len(celRules))
|
||||
for i, rule := range celRules {
|
||||
var compilationResult CompilationResult
|
||||
if len(strings.TrimSpace(rule.Rule)) == 0 {
|
||||
// include a compilation result, but leave both program and error nil per documented return semantics of this
|
||||
// function
|
||||
} else {
|
||||
ast, issues := env.Compile(rule.Rule)
|
||||
if issues != nil {
|
||||
compilationResult.Error = &Error{ErrorTypeInvalid, "compilation failed: " + issues.String()}
|
||||
} else if !proto.Equal(ast.ResultType(), decls.Bool) {
|
||||
compilationResult.Error = &Error{ErrorTypeInvalid, "cel expression must evaluate to a bool"}
|
||||
} else {
|
||||
prog, err := env.Program(ast)
|
||||
if err != nil {
|
||||
compilationResult.Error = &Error{ErrorTypeInvalid, "program instantiation failed: " + err.Error()}
|
||||
} else {
|
||||
compilationResult.Program = prog
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compResults[i] = compilationResult
|
||||
}
|
||||
|
||||
return compResults, nil
|
||||
}
|
||||
|
||||
// generateUniqueSelfTypeName creates a placeholder type name to use in a CEL programs for cases
|
||||
// where we do not wish to expose a stable type name to CEL validator rule authors. For this to effectively prevent
|
||||
// developers from depending on the generated name (i.e. using it in CEL programs), it must be changed each time a
|
||||
// CRD is created or updated.
|
||||
func generateUniqueSelfTypeName() string {
|
||||
return fmt.Sprintf("selfType%d", time.Now().Nanosecond())
|
||||
}
|
@ -0,0 +1,491 @@
|
||||
/*
|
||||
Copyright 2021 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 cel
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
)
|
||||
|
||||
type validationMatch struct {
|
||||
errorType ErrorType
|
||||
contains string
|
||||
}
|
||||
|
||||
func invalidError(contains string) validationMatch {
|
||||
return validationMatch{errorType: ErrorTypeInvalid, contains: contains}
|
||||
}
|
||||
|
||||
func (v validationMatch) matches(err *Error) bool {
|
||||
return err.Type == v.errorType && strings.Contains(err.Error(), v.contains)
|
||||
}
|
||||
|
||||
func TestCelCompilation(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
input schema.Structural
|
||||
expectedErrors []validationMatch
|
||||
}{
|
||||
{
|
||||
name: "valid object",
|
||||
input: schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
},
|
||||
Properties: map[string]schema.Structural{
|
||||
"minReplicas": {
|
||||
Generic: schema.Generic{
|
||||
Type: "integer",
|
||||
},
|
||||
},
|
||||
"maxReplicas": {
|
||||
Generic: schema.Generic{
|
||||
Type: "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
Extensions: schema.Extensions{
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "self.minReplicas < self.maxReplicas",
|
||||
Message: "minReplicas should be smaller than maxReplicas",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid for string",
|
||||
input: schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "string",
|
||||
},
|
||||
Extensions: schema.Extensions{
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "self.startsWith('s')",
|
||||
Message: "scoped field should start with 's'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid for byte",
|
||||
input: schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "string",
|
||||
},
|
||||
ValueValidation: &schema.ValueValidation{
|
||||
Format: "byte",
|
||||
},
|
||||
Extensions: schema.Extensions{
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "string(self).endsWith('s')",
|
||||
Message: "scoped field should end with 's'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid for boolean",
|
||||
input: schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "boolean",
|
||||
},
|
||||
Extensions: schema.Extensions{
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "self == true",
|
||||
Message: "scoped field should be true",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid for integer",
|
||||
input: schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "integer",
|
||||
},
|
||||
Extensions: schema.Extensions{
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "self > 0",
|
||||
Message: "scoped field should be greater than 0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid for number",
|
||||
input: schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "number",
|
||||
},
|
||||
Extensions: schema.Extensions{
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "self > 1.0",
|
||||
Message: "scoped field should be greater than 1.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid nested object of object",
|
||||
input: schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
},
|
||||
Properties: map[string]schema.Structural{
|
||||
"nestedObj": {
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
},
|
||||
Properties: map[string]schema.Structural{
|
||||
"val": {
|
||||
Generic: schema.Generic{
|
||||
Type: "integer",
|
||||
},
|
||||
ValueValidation: &schema.ValueValidation{
|
||||
Format: "int64",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Extensions: schema.Extensions{
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "self.nestedObj.val == 10",
|
||||
Message: "val should be equal to 10",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid nested object of array",
|
||||
input: schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
},
|
||||
Properties: map[string]schema.Structural{
|
||||
"nestedObj": {
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Extensions: schema.Extensions{
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self.nestedObj[0]) == 10",
|
||||
Message: "size of first element in nestedObj should be equal to 10",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid nested array of array",
|
||||
input: schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Extensions: schema.Extensions{
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self[0][0]) == 10",
|
||||
Message: "size of items under items of scoped field should be equal to 10",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid nested array of object",
|
||||
input: schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
},
|
||||
Properties: map[string]schema.Structural{
|
||||
"nestedObj": {
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
},
|
||||
Properties: map[string]schema.Structural{
|
||||
"val": {
|
||||
Generic: schema.Generic{
|
||||
Type: "integer",
|
||||
},
|
||||
ValueValidation: &schema.ValueValidation{
|
||||
Format: "int64",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Extensions: schema.Extensions{
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "self[0].nestedObj.val == 10",
|
||||
Message: "val under nestedObj under properties under items should be equal to 10",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid map",
|
||||
input: schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
AdditionalProperties: &schema.StructuralOrBool{
|
||||
Bool: true,
|
||||
Structural: &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "boolean",
|
||||
Nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Extensions: schema.Extensions{
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "size of scoped field should be greater than 0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid checking for number",
|
||||
input: schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "number",
|
||||
},
|
||||
Extensions: schema.Extensions{
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) == 10",
|
||||
Message: "size of scoped field should be equal to 10",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: []validationMatch{
|
||||
invalidError("compilation failed"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "compilation failure",
|
||||
input: schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "integer",
|
||||
},
|
||||
Extensions: schema.Extensions{
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) == 10",
|
||||
Message: "size of scoped field should be equal to 10",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: []validationMatch{
|
||||
invalidError("compilation failed"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid for escaping",
|
||||
input: schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
},
|
||||
Properties: map[string]schema.Structural{
|
||||
"namespace": {
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"if": {
|
||||
Generic: schema.Generic{
|
||||
Type: "integer",
|
||||
},
|
||||
},
|
||||
"self": {
|
||||
Generic: schema.Generic{
|
||||
Type: "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
Extensions: schema.Extensions{
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self.__namespace__[0]) == 10",
|
||||
Message: "size of first element in nestedObj should be equal to 10",
|
||||
},
|
||||
{
|
||||
Rule: "self.__if__ == 10",
|
||||
},
|
||||
{
|
||||
Rule: "self.self == 10",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid for escaping",
|
||||
input: schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
},
|
||||
Properties: map[string]schema.Structural{
|
||||
"namespace": {
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"if": {
|
||||
Generic: schema.Generic{
|
||||
Type: "integer",
|
||||
},
|
||||
},
|
||||
"self": {
|
||||
Generic: schema.Generic{
|
||||
Type: "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
Extensions: schema.Extensions{
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self.namespace[0]) == 10",
|
||||
Message: "size of first element in nestedObj should be equal to 10",
|
||||
},
|
||||
{
|
||||
Rule: "self.if == 10",
|
||||
},
|
||||
{
|
||||
Rule: "self == 10",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: []validationMatch{
|
||||
invalidError("undefined field 'namespace'"),
|
||||
invalidError("undefined field 'if'"),
|
||||
invalidError("found no matching overload"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
compilationResults, err := Compile(&tt.input, false)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, but got: %v", err)
|
||||
}
|
||||
|
||||
seenErrs := make([]bool, len(compilationResults))
|
||||
|
||||
for _, expectedError := range tt.expectedErrors {
|
||||
found := false
|
||||
for i, result := range compilationResults {
|
||||
if expectedError.matches(result.Error) && !seenErrs[i] {
|
||||
found = true
|
||||
seenErrs[i] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("expected error: %v", expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
for i, seen := range seenErrs {
|
||||
if !seen && compilationResults[i].Error != nil {
|
||||
t.Errorf("unexpected error: %v", compilationResults[i].Error)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2021 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 cel
|
||||
|
||||
// Error is an implementation of the 'error' interface, which represents a
|
||||
// XValidation error.
|
||||
type Error struct {
|
||||
Type ErrorType
|
||||
Detail string
|
||||
}
|
||||
|
||||
var _ error = &Error{}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (v *Error) Error() string {
|
||||
return v.Detail
|
||||
}
|
||||
|
||||
// ErrorType is a machine readable value providing more detail about why
|
||||
// a XValidation is invalid.
|
||||
type ErrorType string
|
||||
|
||||
const (
|
||||
// ErrorTypeRequired is used to report withNullable values that are not
|
||||
// provided (e.g. empty strings, null values, or empty arrays). See
|
||||
// Required().
|
||||
ErrorTypeRequired ErrorType = "RuleRequired"
|
||||
// ErrorTypeInvalid is used to report malformed values
|
||||
ErrorTypeInvalid ErrorType = "RuleInvalid"
|
||||
// ErrorTypeInternal is used to report other errors that are not related
|
||||
// to user input. See InternalError().
|
||||
ErrorTypeInternal ErrorType = "InternalError"
|
||||
)
|
@ -0,0 +1,223 @@
|
||||
/*
|
||||
Copyright 2021 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 cel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/interpreter"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// Validator parallels the structure of schema.Structural and includes the compiled CEL programs
|
||||
// for the x-kubernetes-validations of each schema node.
|
||||
type Validator struct {
|
||||
Items *Validator
|
||||
Properties map[string]Validator
|
||||
|
||||
AdditionalProperties *Validator
|
||||
|
||||
compiledRules []CompilationResult
|
||||
|
||||
// Program compilation is pre-checked at CRD creation/update time, so we don't expect compilation to fail
|
||||
// they are recompiled and added to this type, and it does, it is an internal bug.
|
||||
// But if somehow we get any compilation errors, we track them and then surface them as validation errors.
|
||||
compilationErr error
|
||||
|
||||
// isResourceRoot is true if this validator node is for the root of a resource. Either the root of the
|
||||
// custom resource being validated, or the root of an XEmbeddedResource object.
|
||||
isResourceRoot bool
|
||||
}
|
||||
|
||||
// NewValidator returns compiles all the CEL programs defined in x-kubernetes-validations extensions
|
||||
// of the Structural schema and returns a custom resource validator that contains nested
|
||||
// validators for all items, properties and additionalProperties that transitively contain validator rules.
|
||||
// Returns nil only if there no validator rules in the Structural schema. May return a validator containing
|
||||
// only errors.
|
||||
func NewValidator(s *schema.Structural) *Validator {
|
||||
return validator(s, true)
|
||||
}
|
||||
|
||||
func validator(s *schema.Structural, isResourceRoot bool) *Validator {
|
||||
compiledRules, err := Compile(s, isResourceRoot)
|
||||
var itemsValidator, additionalPropertiesValidator *Validator
|
||||
var propertiesValidators map[string]Validator
|
||||
if s.Items != nil {
|
||||
itemsValidator = validator(s.Items, s.Items.XEmbeddedResource)
|
||||
}
|
||||
if len(s.Properties) > 0 {
|
||||
propertiesValidators = make(map[string]Validator, len(s.Properties))
|
||||
for k, prop := range s.Properties {
|
||||
if p := validator(&prop, prop.XEmbeddedResource); p != nil {
|
||||
propertiesValidators[k] = *p
|
||||
}
|
||||
}
|
||||
}
|
||||
if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil {
|
||||
additionalPropertiesValidator = validator(s.AdditionalProperties.Structural, s.AdditionalProperties.Structural.XEmbeddedResource)
|
||||
}
|
||||
if len(compiledRules) > 0 || err != nil || itemsValidator != nil || additionalPropertiesValidator != nil || len(propertiesValidators) > 0 {
|
||||
return &Validator{
|
||||
compiledRules: compiledRules,
|
||||
compilationErr: err,
|
||||
isResourceRoot: isResourceRoot,
|
||||
Items: itemsValidator,
|
||||
AdditionalProperties: additionalPropertiesValidator,
|
||||
Properties: propertiesValidators,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validates all x-kubernetes-validations rules in Validator against obj and returns any errors.
|
||||
func (s *Validator) Validate(fldPath *field.Path, sts *schema.Structural, obj interface{}) field.ErrorList {
|
||||
if s == nil || obj == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
errs := s.validateExpressions(fldPath, sts, obj)
|
||||
switch obj := obj.(type) {
|
||||
case []interface{}:
|
||||
return append(errs, s.validateArray(fldPath, sts, obj)...)
|
||||
case map[string]interface{}:
|
||||
return append(errs, s.validateMap(fldPath, sts, obj)...)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func (s *Validator) validateExpressions(fldPath *field.Path, sts *schema.Structural, obj interface{}) (errs field.ErrorList) {
|
||||
if obj == nil {
|
||||
// We only validate non-null values. Rules that need to check for the state of a nullable value or the presence of an optional
|
||||
// field must do so from the surrounding schema. E.g. if an array has nullable string items, a rule on the array
|
||||
// schema can check if items are null, but a rule on the nullable string schema only validates the non-null strings.
|
||||
return nil
|
||||
}
|
||||
if s.compilationErr != nil {
|
||||
errs = append(errs, field.Invalid(fldPath, obj, fmt.Sprintf("rule compiler initialization error: %v", s.compilationErr)))
|
||||
return errs
|
||||
}
|
||||
if len(s.compiledRules) == 0 {
|
||||
return nil // nothing to do
|
||||
}
|
||||
if s.isResourceRoot {
|
||||
sts = model.WithTypeAndObjectMeta(sts)
|
||||
}
|
||||
activation := NewValidationActivation(obj, sts)
|
||||
for i, compiled := range s.compiledRules {
|
||||
rule := sts.XValidations[i]
|
||||
if compiled.Error != nil {
|
||||
errs = append(errs, field.Invalid(fldPath, obj, fmt.Sprintf("rule compile error: %v", compiled.Error)))
|
||||
continue
|
||||
}
|
||||
if compiled.Program == nil {
|
||||
// rule is empty
|
||||
continue
|
||||
}
|
||||
evalResult, _, err := compiled.Program.Eval(activation)
|
||||
if err != nil {
|
||||
// see types.Err for list of well defined error types
|
||||
if strings.HasPrefix(err.Error(), "no such overload") {
|
||||
// Most overload errors are caught by the compiler, which provides details on where exactly in the rule
|
||||
// error was found. Here, an overload error has occurred at runtime no details are provided, so we
|
||||
// append a more descriptive error message. This error can only occur when static type checking has
|
||||
// been bypassed. int-or-string is typed as dynamic and so bypasses compiler type checking.
|
||||
errs = append(errs, field.Invalid(fldPath, obj, fmt.Sprintf("'%v': call arguments did not match a supported operator, function or macro signature for rule: %v", err, ruleErrorString(rule))))
|
||||
} else {
|
||||
// no such key: {key}, index out of bounds: {index}, integer overflow, division by zero, ...
|
||||
errs = append(errs, field.Invalid(fldPath, obj, fmt.Sprintf("%v evaluating rule: %v", err, ruleErrorString(rule))))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if evalResult != types.True {
|
||||
if len(rule.Message) != 0 {
|
||||
errs = append(errs, field.Invalid(fldPath, obj, rule.Message))
|
||||
} else {
|
||||
errs = append(errs, field.Invalid(fldPath, obj, fmt.Sprintf("failed rule: %s", ruleErrorString(rule))))
|
||||
}
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func ruleErrorString(rule apiextensions.ValidationRule) string {
|
||||
if len(rule.Message) > 0 {
|
||||
return strings.TrimSpace(rule.Message)
|
||||
}
|
||||
return strings.TrimSpace(rule.Rule)
|
||||
}
|
||||
|
||||
type validationActivation struct {
|
||||
self ref.Val
|
||||
}
|
||||
|
||||
func NewValidationActivation(obj interface{}, structural *schema.Structural) *validationActivation {
|
||||
return &validationActivation{self: UnstructuredToVal(obj, structural)}
|
||||
}
|
||||
|
||||
func (a *validationActivation) ResolveName(name string) (interface{}, bool) {
|
||||
if name == ScopedVarName {
|
||||
return a.self, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (a *validationActivation) Parent() interpreter.Activation {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Validator) validateMap(fldPath *field.Path, sts *schema.Structural, obj map[string]interface{}) (errs field.ErrorList) {
|
||||
if s == nil || obj == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if s.AdditionalProperties != nil && sts.AdditionalProperties != nil && sts.AdditionalProperties.Structural != nil {
|
||||
for k, v := range obj {
|
||||
errs = append(errs, s.AdditionalProperties.Validate(fldPath.Key(k), sts.AdditionalProperties.Structural, v)...)
|
||||
}
|
||||
}
|
||||
if s.Properties != nil && sts.Properties != nil {
|
||||
for k, v := range obj {
|
||||
stsProp, stsOk := sts.Properties[k]
|
||||
sub, ok := s.Properties[k]
|
||||
if ok && stsOk {
|
||||
errs = append(errs, sub.Validate(fldPath.Child(k), &stsProp, v)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func (s *Validator) validateArray(fldPath *field.Path, sts *schema.Structural, obj []interface{}) field.ErrorList {
|
||||
var errs field.ErrorList
|
||||
|
||||
if s.Items != nil && sts.Items != nil {
|
||||
for i := range obj {
|
||||
errs = append(errs, s.Items.Validate(fldPath.Index(i), sts.Items, obj[i])...)
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,703 @@
|
||||
/*
|
||||
Copyright 2021 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 cel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
|
||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model"
|
||||
"k8s.io/kube-openapi/pkg/validation/strfmt"
|
||||
)
|
||||
|
||||
// UnstructuredToVal converts a Kubernetes unstructured data element to a CEL Val.
|
||||
// The root schema of custom resource schema is expected contain type meta and object meta schemas.
|
||||
// If Embedded resources do not contain type meta and object meta schemas, they will be added automatically.
|
||||
func UnstructuredToVal(unstructured interface{}, schema *structuralschema.Structural) ref.Val {
|
||||
if unstructured == nil {
|
||||
if schema.Nullable {
|
||||
return types.NullValue
|
||||
}
|
||||
return types.NewErr("invalid data, got null for schema with nullable=false")
|
||||
}
|
||||
if schema.XIntOrString {
|
||||
switch v := unstructured.(type) {
|
||||
case string:
|
||||
return types.String(v)
|
||||
case int:
|
||||
return types.Int(v)
|
||||
case int32:
|
||||
return types.Int(v)
|
||||
case int64:
|
||||
return types.Int(v)
|
||||
}
|
||||
return types.NewErr("invalid data, expected XIntOrString value to be either a string or integer")
|
||||
}
|
||||
if schema.Type == "object" {
|
||||
m, ok := unstructured.(map[string]interface{})
|
||||
if !ok {
|
||||
return types.NewErr("invalid data, expected map[string]interface{} to match the provided schema with type=object")
|
||||
}
|
||||
if schema.XEmbeddedResource || schema.Properties != nil {
|
||||
if schema.XEmbeddedResource {
|
||||
schema = model.WithTypeAndObjectMeta(schema)
|
||||
}
|
||||
return &unstructuredMap{
|
||||
value: m,
|
||||
schema: schema,
|
||||
propSchema: func(key string) (*structuralschema.Structural, bool) {
|
||||
if schema, ok := schema.Properties[key]; ok {
|
||||
return &schema, true
|
||||
}
|
||||
return nil, false
|
||||
},
|
||||
}
|
||||
}
|
||||
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Structural != nil {
|
||||
return &unstructuredMap{
|
||||
value: m,
|
||||
schema: schema,
|
||||
propSchema: func(key string) (*structuralschema.Structural, bool) {
|
||||
return schema.AdditionalProperties.Structural, true
|
||||
},
|
||||
}
|
||||
}
|
||||
// A object with x-preserve-unknown-fields but no properties or additionalProperties is treated
|
||||
// as an empty object.
|
||||
if schema.XPreserveUnknownFields {
|
||||
return &unstructuredMap{
|
||||
value: m,
|
||||
schema: schema,
|
||||
propSchema: func(key string) (*structuralschema.Structural, bool) {
|
||||
return nil, false
|
||||
},
|
||||
}
|
||||
}
|
||||
return types.NewErr("invalid object type, expected either Properties or AdditionalProperties with Allows=true and non-empty Schema")
|
||||
}
|
||||
|
||||
if schema.Type == "array" {
|
||||
l, ok := unstructured.([]interface{})
|
||||
if !ok {
|
||||
return types.NewErr("invalid data, expected []interface{} to match the provided schema with type=array")
|
||||
}
|
||||
if schema.Items == nil {
|
||||
return types.NewErr("invalid array type, expected Items with a non-empty Schema")
|
||||
}
|
||||
typedList := unstructuredList{elements: l, itemsSchema: schema.Items}
|
||||
listType := schema.XListType
|
||||
if listType != nil {
|
||||
switch *listType {
|
||||
case "map":
|
||||
mapKeys := schema.Extensions.XListMapKeys
|
||||
return &unstructuredMapList{unstructuredList: typedList, escapedKeyProps: escapeKeyProps(mapKeys)}
|
||||
case "set":
|
||||
return &unstructuredSetList{unstructuredList: typedList}
|
||||
case "atomic":
|
||||
return &typedList
|
||||
default:
|
||||
return types.NewErr("invalid x-kubernetes-list-type, expected 'map', 'set' or 'atomic' but got %s", *listType)
|
||||
}
|
||||
}
|
||||
return &typedList
|
||||
}
|
||||
|
||||
if schema.Type == "string" {
|
||||
str, ok := unstructured.(string)
|
||||
if !ok {
|
||||
return types.NewErr("invalid data, expected string, got %T", unstructured)
|
||||
}
|
||||
if schema.ValueValidation != nil {
|
||||
switch schema.ValueValidation.Format {
|
||||
case "duration":
|
||||
d, err := strfmt.ParseDuration(str)
|
||||
if err != nil {
|
||||
return types.NewErr("Invalid duration %s: %v", str, err)
|
||||
}
|
||||
return types.Duration{Duration: d}
|
||||
case "date":
|
||||
d, err := time.Parse(strfmt.RFC3339FullDate, str) // strfmt uses this format for OpenAPIv3 value validation
|
||||
if err != nil {
|
||||
return types.NewErr("Invalid date formatted string %s: %v", str, err)
|
||||
}
|
||||
return types.Timestamp{Time: d}
|
||||
case "date-time":
|
||||
d, err := strfmt.ParseDateTime(str)
|
||||
if err != nil {
|
||||
return types.NewErr("Invalid date-time formatted string %s: %v", str, err)
|
||||
}
|
||||
return types.Timestamp{Time: time.Time(d)}
|
||||
case "byte":
|
||||
base64 := strfmt.Base64{}
|
||||
err := base64.UnmarshalText([]byte(str))
|
||||
if err != nil {
|
||||
return types.NewErr("Invalid byte formatted string %s: %v", str, err)
|
||||
}
|
||||
return types.Bytes(base64)
|
||||
}
|
||||
}
|
||||
return types.String(str)
|
||||
}
|
||||
if schema.Type == "number" {
|
||||
switch v := unstructured.(type) {
|
||||
// float representations of whole numbers (e.g. 1.0, 0.0) can convert to int representations (e.g. 1, 0) in yaml
|
||||
// to json translation, and then get parsed as int64s
|
||||
case int:
|
||||
return types.Double(v)
|
||||
case int32:
|
||||
return types.Double(v)
|
||||
case int64:
|
||||
return types.Double(v)
|
||||
|
||||
case float32:
|
||||
return types.Double(v)
|
||||
case float64:
|
||||
return types.Double(v)
|
||||
default:
|
||||
return types.NewErr("invalid data, expected float, got %T", unstructured)
|
||||
}
|
||||
}
|
||||
if schema.Type == "integer" {
|
||||
switch v := unstructured.(type) {
|
||||
case int:
|
||||
return types.Int(v)
|
||||
case int32:
|
||||
return types.Int(v)
|
||||
case int64:
|
||||
return types.Int(v)
|
||||
default:
|
||||
return types.NewErr("invalid data, expected int, got %T", unstructured)
|
||||
}
|
||||
}
|
||||
if schema.Type == "boolean" {
|
||||
b, ok := unstructured.(bool)
|
||||
if !ok {
|
||||
return types.NewErr("invalid data, expected bool, got %T", unstructured)
|
||||
}
|
||||
return types.Bool(b)
|
||||
}
|
||||
|
||||
if schema.XPreserveUnknownFields {
|
||||
return &unknownPreserved{u: unstructured}
|
||||
}
|
||||
|
||||
return types.NewErr("invalid type, expected object, array, number, integer, boolean or string, or no type with x-kubernetes-int-or-string or x-kubernetes-preserve-unknown-fields is true, got %s", schema.Type)
|
||||
}
|
||||
|
||||
// unknownPreserved represents unknown data preserved in custom resources via x-kubernetes-preserve-unknown-fields.
|
||||
// It preserves the data at runtime without assuming it is of any particular type and supports only equality checking.
|
||||
// unknownPreserved should be used only for values are not directly accessible in CEL expressions, i.e. for data
|
||||
// where there is no corresponding CEL type declaration.
|
||||
type unknownPreserved struct {
|
||||
u interface{}
|
||||
}
|
||||
|
||||
func (t *unknownPreserved) ConvertToNative(refType reflect.Type) (interface{}, error) {
|
||||
return nil, fmt.Errorf("type conversion to '%s' not supported for values preserved by x-kubernetes-preserve-unknown-fields", refType)
|
||||
}
|
||||
|
||||
func (t *unknownPreserved) ConvertToType(typeValue ref.Type) ref.Val {
|
||||
return types.NewErr("type conversion to '%s' not supported for values preserved by x-kubernetes-preserve-unknown-fields", typeValue.TypeName())
|
||||
}
|
||||
|
||||
func (t *unknownPreserved) Equal(other ref.Val) ref.Val {
|
||||
return types.Bool(equality.Semantic.DeepEqual(t.u, other.Value()))
|
||||
}
|
||||
|
||||
func (t *unknownPreserved) Type() ref.Type {
|
||||
return types.UnknownType
|
||||
}
|
||||
|
||||
func (t *unknownPreserved) Value() interface{} {
|
||||
return t.u // used by Equal checks
|
||||
}
|
||||
|
||||
// unstructuredMapList represents an unstructured data instance of an OpenAPI array with x-kubernetes-list-type=map.
|
||||
type unstructuredMapList struct {
|
||||
unstructuredList
|
||||
escapedKeyProps []string
|
||||
|
||||
sync.Once // for for lazy load of mapOfList since it is only needed if Equals is called
|
||||
mapOfList map[interface{}]interface{}
|
||||
}
|
||||
|
||||
func (t *unstructuredMapList) getMap() map[interface{}]interface{} {
|
||||
t.Do(func() {
|
||||
t.mapOfList = make(map[interface{}]interface{}, len(t.elements))
|
||||
for _, e := range t.elements {
|
||||
t.mapOfList[t.toMapKey(e)] = e
|
||||
}
|
||||
})
|
||||
return t.mapOfList
|
||||
}
|
||||
|
||||
// toMapKey returns a valid golang map key for the given element of the map list.
|
||||
// element must be a valid map list entry where all map key props are scalar types (which are comparable in go
|
||||
// and valid for use in a golang map key).
|
||||
func (t *unstructuredMapList) toMapKey(element interface{}) interface{} {
|
||||
eObj, ok := element.(map[string]interface{})
|
||||
if !ok {
|
||||
return types.NewErr("unexpected data format for element of array with x-kubernetes-list-type=map: %T", element)
|
||||
}
|
||||
// Arrays are comparable in go and may be used as map keys, but maps and slices are not.
|
||||
// So we can special case small numbers of key props as arrays and fall back to serialization
|
||||
// for larger numbers of key props
|
||||
if len(t.escapedKeyProps) == 1 {
|
||||
return eObj[t.escapedKeyProps[0]]
|
||||
}
|
||||
if len(t.escapedKeyProps) == 2 {
|
||||
return [2]interface{}{eObj[t.escapedKeyProps[0]], eObj[t.escapedKeyProps[1]]}
|
||||
}
|
||||
if len(t.escapedKeyProps) == 3 {
|
||||
return [3]interface{}{eObj[t.escapedKeyProps[0]], eObj[t.escapedKeyProps[1]], eObj[t.escapedKeyProps[2]]}
|
||||
}
|
||||
|
||||
key := make([]interface{}, len(t.escapedKeyProps))
|
||||
for i, kf := range t.escapedKeyProps {
|
||||
key[i] = eObj[kf]
|
||||
}
|
||||
return fmt.Sprintf("%v", key)
|
||||
}
|
||||
|
||||
// Equal on a map list ignores list element order.
|
||||
func (t *unstructuredMapList) Equal(other ref.Val) ref.Val {
|
||||
oMapList, ok := other.(traits.Lister)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
sz := types.Int(len(t.elements))
|
||||
if sz != oMapList.Size() {
|
||||
return types.False
|
||||
}
|
||||
tMap := t.getMap()
|
||||
for it := oMapList.Iterator(); it.HasNext() == types.True; {
|
||||
v := it.Next()
|
||||
k := t.toMapKey(v.Value())
|
||||
tVal, ok := tMap[k]
|
||||
if !ok {
|
||||
return types.False
|
||||
}
|
||||
eq := UnstructuredToVal(tVal, t.itemsSchema).Equal(v)
|
||||
if eq != types.True {
|
||||
return eq // either false or error
|
||||
}
|
||||
}
|
||||
return types.True
|
||||
}
|
||||
|
||||
// Add for a map list `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values
|
||||
// are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with
|
||||
// non-intersecting keys are appended, retaining their partial order.
|
||||
func (t *unstructuredMapList) Add(other ref.Val) ref.Val {
|
||||
oMapList, ok := other.(traits.Lister)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
elements := make([]interface{}, len(t.elements))
|
||||
keyToIdx := map[interface{}]int{}
|
||||
for i, e := range t.elements {
|
||||
k := t.toMapKey(e)
|
||||
keyToIdx[k] = i
|
||||
elements[i] = e
|
||||
}
|
||||
for it := oMapList.Iterator(); it.HasNext() == types.True; {
|
||||
v := it.Next().Value()
|
||||
k := t.toMapKey(v)
|
||||
if overwritePosition, ok := keyToIdx[k]; ok {
|
||||
elements[overwritePosition] = v
|
||||
} else {
|
||||
elements = append(elements, v)
|
||||
}
|
||||
}
|
||||
return &unstructuredMapList{
|
||||
unstructuredList: unstructuredList{elements: elements, itemsSchema: t.itemsSchema},
|
||||
escapedKeyProps: t.escapedKeyProps,
|
||||
}
|
||||
}
|
||||
|
||||
// escapeKeyProps returns identifiers with Escape applied to each.
|
||||
// Identifiers that cannot be escaped are left as-is. They are inaccessible to CEL programs but are
|
||||
// are still needed internally to perform equality checks.
|
||||
func escapeKeyProps(idents []string) []string {
|
||||
result := make([]string, len(idents))
|
||||
for i, prop := range idents {
|
||||
if escaped, ok := model.Escape(prop); ok {
|
||||
result[i] = escaped
|
||||
} else {
|
||||
result[i] = prop
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// unstructuredSetList represents an unstructured data instance of an OpenAPI array with x-kubernetes-list-type=set.
|
||||
type unstructuredSetList struct {
|
||||
unstructuredList
|
||||
escapedKeyProps []string
|
||||
|
||||
sync.Once // for for lazy load of setOfList since it is only needed if Equals is called
|
||||
set map[interface{}]struct{}
|
||||
}
|
||||
|
||||
func (t *unstructuredSetList) getSet() map[interface{}]struct{} {
|
||||
// sets are only allowed to contain scalar elements, which are comparable in go, and can safely be used as
|
||||
// golang map keys
|
||||
t.Do(func() {
|
||||
t.set = make(map[interface{}]struct{}, len(t.elements))
|
||||
for _, e := range t.elements {
|
||||
t.set[e] = struct{}{}
|
||||
}
|
||||
})
|
||||
return t.set
|
||||
}
|
||||
|
||||
// Equal on a map list ignores list element order.
|
||||
func (t *unstructuredSetList) Equal(other ref.Val) ref.Val {
|
||||
oSetList, ok := other.(traits.Lister)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
sz := types.Int(len(t.elements))
|
||||
if sz != oSetList.Size() {
|
||||
return types.False
|
||||
}
|
||||
tSet := t.getSet()
|
||||
for it := oSetList.Iterator(); it.HasNext() == types.True; {
|
||||
next := it.Next().Value()
|
||||
_, ok := tSet[next]
|
||||
if !ok {
|
||||
return types.False
|
||||
}
|
||||
}
|
||||
return types.True
|
||||
}
|
||||
|
||||
// Add for a set list `X + Y` performs a union where the array positions of all elements in `X` are preserved and
|
||||
// non-intersecting elements in `Y` are appended, retaining their partial order.
|
||||
func (t *unstructuredSetList) Add(other ref.Val) ref.Val {
|
||||
oSetList, ok := other.(traits.Lister)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
elements := t.elements
|
||||
set := t.getSet()
|
||||
for it := oSetList.Iterator(); it.HasNext() == types.True; {
|
||||
next := it.Next().Value()
|
||||
if _, ok := set[next]; !ok {
|
||||
set[next] = struct{}{}
|
||||
elements = append(elements, next)
|
||||
}
|
||||
}
|
||||
return &unstructuredSetList{
|
||||
unstructuredList: unstructuredList{elements: elements, itemsSchema: t.itemsSchema},
|
||||
escapedKeyProps: t.escapedKeyProps,
|
||||
}
|
||||
}
|
||||
|
||||
// unstructuredList represents an unstructured data instance of an OpenAPI array with x-kubernetes-list-type=atomic (the default).
|
||||
type unstructuredList struct {
|
||||
elements []interface{}
|
||||
itemsSchema *structuralschema.Structural
|
||||
}
|
||||
|
||||
var _ = traits.Lister(&unstructuredList{})
|
||||
|
||||
func (t *unstructuredList) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
switch typeDesc.Kind() {
|
||||
case reflect.Slice:
|
||||
return t.elements, nil
|
||||
}
|
||||
return nil, fmt.Errorf("type conversion error from '%s' to '%s'", t.Type(), typeDesc)
|
||||
}
|
||||
|
||||
func (t *unstructuredList) ConvertToType(typeValue ref.Type) ref.Val {
|
||||
switch typeValue {
|
||||
case types.ListType:
|
||||
return t
|
||||
case types.TypeType:
|
||||
return types.ListType
|
||||
}
|
||||
return types.NewErr("type conversion error from '%s' to '%s'", t.Type(), typeValue.TypeName())
|
||||
}
|
||||
|
||||
func (t *unstructuredList) Equal(other ref.Val) ref.Val {
|
||||
oList, ok := other.(traits.Lister)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
sz := types.Int(len(t.elements))
|
||||
if sz != oList.Size() {
|
||||
return types.False
|
||||
}
|
||||
for i := types.Int(0); i < sz; i++ {
|
||||
eq := t.Get(i).Equal(oList.Get(i))
|
||||
if eq != types.True {
|
||||
return eq // either false or error
|
||||
}
|
||||
}
|
||||
return types.True
|
||||
}
|
||||
|
||||
func (t *unstructuredList) Type() ref.Type {
|
||||
return types.ListType
|
||||
}
|
||||
|
||||
func (t *unstructuredList) Value() interface{} {
|
||||
return t.elements
|
||||
}
|
||||
|
||||
func (t *unstructuredList) Add(other ref.Val) ref.Val {
|
||||
oList, ok := other.(traits.Lister)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
elements := t.elements
|
||||
for it := oList.Iterator(); it.HasNext() == types.True; {
|
||||
next := it.Next().Value()
|
||||
elements = append(elements, next)
|
||||
}
|
||||
|
||||
return &unstructuredList{elements: elements, itemsSchema: t.itemsSchema}
|
||||
}
|
||||
|
||||
func (t *unstructuredList) Contains(val ref.Val) ref.Val {
|
||||
if types.IsUnknownOrError(val) {
|
||||
return val
|
||||
}
|
||||
var err ref.Val
|
||||
sz := len(t.elements)
|
||||
for i := 0; i < sz; i++ {
|
||||
elem := UnstructuredToVal(t.elements[i], t.itemsSchema)
|
||||
cmp := elem.Equal(val)
|
||||
b, ok := cmp.(types.Bool)
|
||||
if !ok && err == nil {
|
||||
err = types.MaybeNoSuchOverloadErr(cmp)
|
||||
}
|
||||
if b == types.True {
|
||||
return types.True
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return types.False
|
||||
}
|
||||
|
||||
func (t *unstructuredList) Get(idx ref.Val) ref.Val {
|
||||
iv, isInt := idx.(types.Int)
|
||||
if !isInt {
|
||||
return types.ValOrErr(idx, "unsupported index: %v", idx)
|
||||
}
|
||||
i := int(iv)
|
||||
if i < 0 || i >= len(t.elements) {
|
||||
return types.NewErr("index out of bounds: %v", idx)
|
||||
}
|
||||
return UnstructuredToVal(t.elements[i], t.itemsSchema)
|
||||
}
|
||||
|
||||
func (t *unstructuredList) Iterator() traits.Iterator {
|
||||
items := make([]ref.Val, len(t.elements))
|
||||
for i, item := range t.elements {
|
||||
itemCopy := item
|
||||
items[i] = UnstructuredToVal(itemCopy, t.itemsSchema)
|
||||
}
|
||||
return &listIterator{unstructuredList: t, items: items}
|
||||
}
|
||||
|
||||
type listIterator struct {
|
||||
*unstructuredList
|
||||
items []ref.Val
|
||||
idx int
|
||||
}
|
||||
|
||||
func (it *listIterator) HasNext() ref.Val {
|
||||
return types.Bool(it.idx < len(it.items))
|
||||
}
|
||||
|
||||
func (it *listIterator) Next() ref.Val {
|
||||
item := it.items[it.idx]
|
||||
it.idx++
|
||||
return item
|
||||
}
|
||||
|
||||
func (t *unstructuredList) Size() ref.Val {
|
||||
return types.Int(len(t.elements))
|
||||
}
|
||||
|
||||
// unstructuredMap represented an unstructured data instance of an OpenAPI object.
|
||||
type unstructuredMap struct {
|
||||
value map[string]interface{}
|
||||
schema *structuralschema.Structural
|
||||
// propSchema finds the schema to use for a particular map key.
|
||||
propSchema func(key string) (*structuralschema.Structural, bool)
|
||||
}
|
||||
|
||||
var _ = traits.Mapper(&unstructuredMap{})
|
||||
|
||||
func (t *unstructuredMap) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
switch typeDesc.Kind() {
|
||||
case reflect.Map:
|
||||
return t.value, nil
|
||||
}
|
||||
return nil, fmt.Errorf("type conversion error from '%s' to '%s'", t.Type(), typeDesc)
|
||||
}
|
||||
|
||||
func (t *unstructuredMap) ConvertToType(typeValue ref.Type) ref.Val {
|
||||
switch typeValue {
|
||||
case types.MapType:
|
||||
return t
|
||||
case types.TypeType:
|
||||
return types.MapType
|
||||
}
|
||||
return types.NewErr("type conversion error from '%s' to '%s'", t.Type(), typeValue.TypeName())
|
||||
}
|
||||
|
||||
func (t *unstructuredMap) Equal(other ref.Val) ref.Val {
|
||||
oMap, isMap := other.(traits.Mapper)
|
||||
if !isMap {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
if t.Size() != oMap.Size() {
|
||||
return types.False
|
||||
}
|
||||
for key, value := range t.value {
|
||||
if propSchema, ok := t.propSchema(key); ok {
|
||||
ov, found := oMap.Find(types.String(key))
|
||||
if !found {
|
||||
return types.False
|
||||
}
|
||||
v := UnstructuredToVal(value, propSchema)
|
||||
vEq := v.Equal(ov)
|
||||
if vEq != types.True {
|
||||
return vEq // either false or error
|
||||
}
|
||||
} else {
|
||||
// Must be an object with properties.
|
||||
// Since we've encountered an unknown field, fallback to unstructured equality checking.
|
||||
ouMap, ok := other.(*unstructuredMap)
|
||||
if !ok {
|
||||
// The compiler ensures equality is against the same type of object, so this should be unreachable
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
if oValue, ok := ouMap.value[key]; ok {
|
||||
if !equality.Semantic.DeepEqual(value, oValue) {
|
||||
return types.False
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return types.True
|
||||
}
|
||||
|
||||
func (t *unstructuredMap) Type() ref.Type {
|
||||
return types.MapType
|
||||
}
|
||||
|
||||
func (t *unstructuredMap) Value() interface{} {
|
||||
return t.value
|
||||
}
|
||||
|
||||
func (t *unstructuredMap) Contains(key ref.Val) ref.Val {
|
||||
v, found := t.Find(key)
|
||||
if v != nil && types.IsUnknownOrError(v) {
|
||||
return v
|
||||
}
|
||||
|
||||
return types.Bool(found)
|
||||
}
|
||||
|
||||
func (t *unstructuredMap) Get(key ref.Val) ref.Val {
|
||||
v, found := t.Find(key)
|
||||
if found {
|
||||
return v
|
||||
}
|
||||
return types.ValOrErr(key, "no such key: %v", key)
|
||||
}
|
||||
|
||||
func (t *unstructuredMap) Iterator() traits.Iterator {
|
||||
isObject := t.schema.Properties != nil
|
||||
keys := make([]ref.Val, len(t.value))
|
||||
i := 0
|
||||
for k := range t.value {
|
||||
if _, ok := t.propSchema(k); ok {
|
||||
mapKey := k
|
||||
if isObject {
|
||||
if escaped, ok := model.Escape(k); ok {
|
||||
mapKey = escaped
|
||||
}
|
||||
}
|
||||
keys[i] = types.String(mapKey)
|
||||
i++
|
||||
}
|
||||
}
|
||||
return &mapIterator{unstructuredMap: t, keys: keys}
|
||||
}
|
||||
|
||||
type mapIterator struct {
|
||||
*unstructuredMap
|
||||
keys []ref.Val
|
||||
idx int
|
||||
}
|
||||
|
||||
func (it *mapIterator) HasNext() ref.Val {
|
||||
return types.Bool(it.idx < len(it.keys))
|
||||
}
|
||||
|
||||
func (it *mapIterator) Next() ref.Val {
|
||||
key := it.keys[it.idx]
|
||||
it.idx++
|
||||
return key
|
||||
}
|
||||
|
||||
func (t *unstructuredMap) Size() ref.Val {
|
||||
return types.Int(len(t.value))
|
||||
}
|
||||
|
||||
func (t *unstructuredMap) Find(key ref.Val) (ref.Val, bool) {
|
||||
isObject := t.schema.Properties != nil
|
||||
keyStr, ok := key.(types.String)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(key), true
|
||||
}
|
||||
k := keyStr.Value().(string)
|
||||
if isObject {
|
||||
k, ok = model.Unescape(k)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
if v, ok := t.value[k]; ok {
|
||||
// If this is an object with properties, not an object with additionalProperties,
|
||||
// then null valued nullable fields are treated the same as absent optional fields.
|
||||
if isObject && v == nil {
|
||||
return nil, false
|
||||
}
|
||||
if propSchema, ok := t.propSchema(k); ok {
|
||||
return UnstructuredToVal(v, propSchema), true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
@ -0,0 +1,621 @@
|
||||
/*
|
||||
Copyright 2021 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 cel
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
listTypeSet = "set"
|
||||
listTypeMap = "map"
|
||||
stringSchema = schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "string",
|
||||
},
|
||||
}
|
||||
intSchema = schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "integer",
|
||||
},
|
||||
ValueValidation: &schema.ValueValidation{
|
||||
Format: "int64",
|
||||
},
|
||||
}
|
||||
mapListElementSchema = schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
},
|
||||
Properties: map[string]schema.Structural{
|
||||
"key": stringSchema,
|
||||
"val": intSchema,
|
||||
},
|
||||
}
|
||||
mapListSchema = schema.Structural{
|
||||
Extensions: schema.Extensions{XListType: &listTypeMap, XListMapKeys: []string{"key"}},
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &mapListElementSchema,
|
||||
}
|
||||
multiKeyMapListSchema = schema.Structural{
|
||||
Extensions: schema.Extensions{XListType: &listTypeMap, XListMapKeys: []string{"key1", "key2"}},
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
},
|
||||
Properties: map[string]schema.Structural{
|
||||
"key1": stringSchema,
|
||||
"key2": stringSchema,
|
||||
"val": intSchema,
|
||||
},
|
||||
},
|
||||
}
|
||||
setListSchema = schema.Structural{
|
||||
Extensions: schema.Extensions{XListType: &listTypeSet},
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &stringSchema,
|
||||
}
|
||||
atomicListSchema = schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &stringSchema,
|
||||
}
|
||||
objectSchema = schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
},
|
||||
Properties: map[string]schema.Structural{
|
||||
"field1": stringSchema,
|
||||
"field2": stringSchema,
|
||||
},
|
||||
}
|
||||
mapSchema = schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
AdditionalProperties: &schema.StructuralOrBool{
|
||||
Bool: true,
|
||||
Structural: &stringSchema,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestEquality(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
lhs ref.Val
|
||||
rhs ref.Val
|
||||
equal bool
|
||||
}{
|
||||
{
|
||||
name: "map lists are equal regardless of order",
|
||||
lhs: UnstructuredToVal([]interface{}{
|
||||
map[string]interface{}{
|
||||
"key": "a",
|
||||
"val": 1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key": "b",
|
||||
"val": 2,
|
||||
},
|
||||
}, &mapListSchema),
|
||||
rhs: UnstructuredToVal([]interface{}{
|
||||
map[string]interface{}{
|
||||
"key": "b",
|
||||
"val": 2,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key": "a",
|
||||
"val": 1,
|
||||
},
|
||||
}, &mapListSchema),
|
||||
equal: true,
|
||||
},
|
||||
{
|
||||
name: "map lists are not equal if contents differs",
|
||||
lhs: UnstructuredToVal([]interface{}{
|
||||
map[string]interface{}{
|
||||
"key": "a",
|
||||
"val": 1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key": "b",
|
||||
"val": 2,
|
||||
},
|
||||
}, &mapListSchema),
|
||||
rhs: UnstructuredToVal([]interface{}{
|
||||
map[string]interface{}{
|
||||
"key": "a",
|
||||
"val": 1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key": "b",
|
||||
"val": 3,
|
||||
},
|
||||
}, &mapListSchema),
|
||||
equal: false,
|
||||
},
|
||||
{
|
||||
name: "map lists are not equal if length differs",
|
||||
lhs: UnstructuredToVal([]interface{}{
|
||||
map[string]interface{}{
|
||||
"key": "a",
|
||||
"val": 1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key": "b",
|
||||
"val": 2,
|
||||
},
|
||||
}, &mapListSchema),
|
||||
rhs: UnstructuredToVal([]interface{}{
|
||||
map[string]interface{}{
|
||||
"key": "a",
|
||||
"val": 1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key": "b",
|
||||
"val": 2,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key": "c",
|
||||
"val": 3,
|
||||
},
|
||||
}, &mapListSchema),
|
||||
equal: false,
|
||||
},
|
||||
{
|
||||
name: "multi-key map lists are equal regardless of order",
|
||||
lhs: UnstructuredToVal([]interface{}{
|
||||
map[string]interface{}{
|
||||
"key1": "a1",
|
||||
"key2": "a2",
|
||||
"val": 1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key1": "b1",
|
||||
"key2": "b2",
|
||||
"val": 2,
|
||||
},
|
||||
}, &multiKeyMapListSchema),
|
||||
rhs: UnstructuredToVal([]interface{}{
|
||||
map[string]interface{}{
|
||||
"key1": "b1",
|
||||
"key2": "b2",
|
||||
"val": 2,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key1": "a1",
|
||||
"key2": "a2",
|
||||
"val": 1,
|
||||
},
|
||||
}, &multiKeyMapListSchema),
|
||||
equal: true,
|
||||
},
|
||||
{
|
||||
name: "multi-key map lists with different contents are not equal",
|
||||
lhs: UnstructuredToVal([]interface{}{
|
||||
map[string]interface{}{
|
||||
"key1": "a1",
|
||||
"key2": "a2",
|
||||
"val": 1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key1": "b1",
|
||||
"key2": "b2",
|
||||
"val": 2,
|
||||
},
|
||||
}, &multiKeyMapListSchema),
|
||||
rhs: UnstructuredToVal([]interface{}{
|
||||
map[string]interface{}{
|
||||
"key1": "a1",
|
||||
"key2": "a2",
|
||||
"val": 1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key1": "b1",
|
||||
"key2": "b2",
|
||||
"val": 3,
|
||||
},
|
||||
}, &multiKeyMapListSchema),
|
||||
equal: false,
|
||||
},
|
||||
{
|
||||
name: "multi-key map lists with different keys are not equal",
|
||||
lhs: UnstructuredToVal([]interface{}{
|
||||
map[string]interface{}{
|
||||
"key1": "a1",
|
||||
"key2": "a2",
|
||||
"val": 1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key1": "b1",
|
||||
"key2": "b2",
|
||||
"val": 2,
|
||||
},
|
||||
}, &multiKeyMapListSchema),
|
||||
rhs: UnstructuredToVal([]interface{}{
|
||||
map[string]interface{}{
|
||||
"key1": "a1",
|
||||
"key2": "a2",
|
||||
"val": 1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key1": "c1",
|
||||
"key2": "c2",
|
||||
"val": 3,
|
||||
},
|
||||
}, &multiKeyMapListSchema),
|
||||
equal: false,
|
||||
},
|
||||
{
|
||||
name: "multi-key map lists with different lengths are not equal",
|
||||
lhs: UnstructuredToVal([]interface{}{
|
||||
map[string]interface{}{
|
||||
"key1": "a1",
|
||||
"key2": "a2",
|
||||
"val": 1,
|
||||
},
|
||||
}, &multiKeyMapListSchema),
|
||||
rhs: UnstructuredToVal([]interface{}{
|
||||
map[string]interface{}{
|
||||
"key1": "a1",
|
||||
"key2": "a2",
|
||||
"val": 1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key1": "b1",
|
||||
"key2": "b2",
|
||||
"val": 3,
|
||||
},
|
||||
}, &multiKeyMapListSchema),
|
||||
equal: false,
|
||||
},
|
||||
{
|
||||
name: "set lists are equal regardless of order",
|
||||
lhs: UnstructuredToVal([]interface{}{"a", "b"}, &setListSchema),
|
||||
rhs: UnstructuredToVal([]interface{}{"b", "a"}, &setListSchema),
|
||||
equal: true,
|
||||
},
|
||||
{
|
||||
name: "set lists are not equal if contents differ",
|
||||
lhs: UnstructuredToVal([]interface{}{"a", "b"}, &setListSchema),
|
||||
rhs: UnstructuredToVal([]interface{}{"a", "c"}, &setListSchema),
|
||||
equal: false,
|
||||
},
|
||||
{
|
||||
name: "set lists are not equal if lengths differ",
|
||||
lhs: UnstructuredToVal([]interface{}{"a", "b"}, &setListSchema),
|
||||
rhs: UnstructuredToVal([]interface{}{"a", "b", "c"}, &setListSchema),
|
||||
equal: false,
|
||||
},
|
||||
{
|
||||
name: "identical atomic lists are equal",
|
||||
lhs: UnstructuredToVal([]interface{}{"a", "b"}, &atomicListSchema),
|
||||
rhs: UnstructuredToVal([]interface{}{"a", "b"}, &atomicListSchema),
|
||||
equal: true,
|
||||
},
|
||||
{
|
||||
name: "atomic lists are not equal if order differs",
|
||||
lhs: UnstructuredToVal([]interface{}{"a", "b"}, &atomicListSchema),
|
||||
rhs: UnstructuredToVal([]interface{}{"b", "a"}, &atomicListSchema),
|
||||
equal: false,
|
||||
},
|
||||
{
|
||||
name: "atomic lists are not equal if contents differ",
|
||||
lhs: UnstructuredToVal([]interface{}{"a", "b"}, &atomicListSchema),
|
||||
rhs: UnstructuredToVal([]interface{}{"a", "c"}, &atomicListSchema),
|
||||
equal: false,
|
||||
},
|
||||
{
|
||||
name: "atomic lists are not equal if lengths differ",
|
||||
lhs: UnstructuredToVal([]interface{}{"a", "b"}, &atomicListSchema),
|
||||
rhs: UnstructuredToVal([]interface{}{"a", "b", "c"}, &atomicListSchema),
|
||||
equal: false,
|
||||
},
|
||||
{
|
||||
name: "identical objects are equal",
|
||||
lhs: UnstructuredToVal(map[string]interface{}{"field1": "a", "field2": "b"}, &objectSchema),
|
||||
rhs: UnstructuredToVal(map[string]interface{}{"field1": "a", "field2": "b"}, &objectSchema),
|
||||
equal: true,
|
||||
},
|
||||
{
|
||||
name: "objects are equal regardless of field order",
|
||||
lhs: UnstructuredToVal(map[string]interface{}{"field1": "a", "field2": "b"}, &objectSchema),
|
||||
rhs: UnstructuredToVal(map[string]interface{}{"field2": "b", "field1": "a"}, &objectSchema),
|
||||
equal: true,
|
||||
},
|
||||
{
|
||||
name: "objects are not equal if contents differs",
|
||||
lhs: UnstructuredToVal(map[string]interface{}{"field1": "a", "field2": "b"}, &objectSchema),
|
||||
rhs: UnstructuredToVal(map[string]interface{}{"field1": "a", "field2": "c"}, &objectSchema),
|
||||
equal: false,
|
||||
},
|
||||
{
|
||||
name: "objects are not equal if length differs",
|
||||
lhs: UnstructuredToVal(map[string]interface{}{"field1": "a", "field2": "b"}, &objectSchema),
|
||||
rhs: UnstructuredToVal(map[string]interface{}{"field1": "a"}, &objectSchema),
|
||||
equal: false,
|
||||
},
|
||||
{
|
||||
name: "identical maps are equal",
|
||||
lhs: UnstructuredToVal(map[string]interface{}{"key1": "a", "key2": "b"}, &mapSchema),
|
||||
rhs: UnstructuredToVal(map[string]interface{}{"key1": "a", "key2": "b"}, &mapSchema),
|
||||
equal: true,
|
||||
},
|
||||
{
|
||||
name: "maps are equal regardless of field order",
|
||||
lhs: UnstructuredToVal(map[string]interface{}{"key1": "a", "key2": "b"}, &mapSchema),
|
||||
rhs: UnstructuredToVal(map[string]interface{}{"key2": "b", "key1": "a"}, &mapSchema),
|
||||
equal: true,
|
||||
},
|
||||
{
|
||||
name: "maps are not equal if contents differs",
|
||||
lhs: UnstructuredToVal(map[string]interface{}{"key1": "a", "key2": "b"}, &mapSchema),
|
||||
rhs: UnstructuredToVal(map[string]interface{}{"key1": "a", "key2": "c"}, &mapSchema),
|
||||
equal: false,
|
||||
},
|
||||
{
|
||||
name: "maps are not equal if length differs",
|
||||
lhs: UnstructuredToVal(map[string]interface{}{"key1": "a", "key2": "b"}, &mapSchema),
|
||||
rhs: UnstructuredToVal(map[string]interface{}{"key1": "a", "key2": "b", "key3": "c"}, &mapSchema),
|
||||
equal: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Compare types with schema against themselves
|
||||
if tc.lhs.Equal(tc.rhs) != types.Bool(tc.equal) {
|
||||
t.Errorf("expected Equals to return %v", tc.equal)
|
||||
}
|
||||
if tc.rhs.Equal(tc.lhs) != types.Bool(tc.equal) {
|
||||
t.Errorf("expected Equals to return %v", tc.equal)
|
||||
}
|
||||
|
||||
// Compare types with schema against native types. This is slightly different than how
|
||||
// CEL performs equality against data literals, but is a good sanity check.
|
||||
if tc.lhs.Equal(types.DefaultTypeAdapter.NativeToValue(tc.rhs.Value())) != types.Bool(tc.equal) {
|
||||
t.Errorf("expected unstructuredVal.Equals(<native type>) to return %v", tc.equal)
|
||||
}
|
||||
if tc.rhs.Equal(types.DefaultTypeAdapter.NativeToValue(tc.lhs.Value())) != types.Bool(tc.equal) {
|
||||
t.Errorf("expected unstructuredVal.Equals(<native type>) to return %v", tc.equal)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLister(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
unstructured []interface{}
|
||||
schema *schema.Structural
|
||||
itemSchema *schema.Structural
|
||||
size int64
|
||||
notContains []ref.Val
|
||||
addition []interface{}
|
||||
expectAdded []interface{}
|
||||
}{
|
||||
{
|
||||
name: "map list",
|
||||
unstructured: []interface{}{
|
||||
map[string]interface{}{
|
||||
"key": "a",
|
||||
"val": 1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key": "b",
|
||||
"val": 2,
|
||||
},
|
||||
},
|
||||
schema: &mapListSchema,
|
||||
itemSchema: &mapListElementSchema,
|
||||
size: 2,
|
||||
notContains: []ref.Val{
|
||||
UnstructuredToVal(map[string]interface{}{
|
||||
"key": "a",
|
||||
"val": 2,
|
||||
}, &mapListElementSchema),
|
||||
UnstructuredToVal(map[string]interface{}{
|
||||
"key": "c",
|
||||
"val": 1,
|
||||
}, &mapListElementSchema),
|
||||
},
|
||||
addition: []interface{}{
|
||||
map[string]interface{}{
|
||||
"key": "b",
|
||||
"val": 3,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key": "c",
|
||||
"val": 4,
|
||||
},
|
||||
},
|
||||
expectAdded: []interface{}{
|
||||
map[string]interface{}{
|
||||
"key": "a",
|
||||
"val": 1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key": "b",
|
||||
"val": 3,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"key": "c",
|
||||
"val": 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set list",
|
||||
unstructured: []interface{}{"a", "b"},
|
||||
schema: &setListSchema,
|
||||
itemSchema: &stringSchema,
|
||||
size: 2,
|
||||
notContains: []ref.Val{UnstructuredToVal("c", &stringSchema)},
|
||||
addition: []interface{}{"b", "c"},
|
||||
expectAdded: []interface{}{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
name: "atomic list",
|
||||
unstructured: []interface{}{"a", "b"},
|
||||
schema: &atomicListSchema,
|
||||
itemSchema: &stringSchema,
|
||||
size: 2,
|
||||
notContains: []ref.Val{UnstructuredToVal("c", &stringSchema)},
|
||||
addition: []interface{}{"b", "c"},
|
||||
expectAdded: []interface{}{"a", "b", "b", "c"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
lister := UnstructuredToVal(tc.unstructured, tc.schema).(traits.Lister)
|
||||
if lister.Size().Value() != tc.size {
|
||||
t.Errorf("Expected Size to return %d but got %d", tc.size, lister.Size().Value())
|
||||
}
|
||||
iter := lister.Iterator()
|
||||
for i := 0; i < int(tc.size); i++ {
|
||||
get := lister.Get(types.Int(i)).Value()
|
||||
if !reflect.DeepEqual(get, tc.unstructured[i]) {
|
||||
t.Errorf("Expected Get to return %v for index %d but got %v", tc.unstructured[i], i, get)
|
||||
}
|
||||
if iter.HasNext() != types.True {
|
||||
t.Error("Expected HasNext to return true")
|
||||
}
|
||||
next := iter.Next().Value()
|
||||
if !reflect.DeepEqual(next, tc.unstructured[i]) {
|
||||
t.Errorf("Expected Next to return %v for index %d but got %v", tc.unstructured[i], i, next)
|
||||
}
|
||||
}
|
||||
if iter.HasNext() != types.False {
|
||||
t.Error("Expected HasNext to return false")
|
||||
}
|
||||
for _, contains := range tc.unstructured {
|
||||
if lister.Contains(UnstructuredToVal(contains, tc.itemSchema)) != types.True {
|
||||
t.Errorf("Expected Contains to return true for %v", contains)
|
||||
}
|
||||
}
|
||||
for _, notContains := range tc.notContains {
|
||||
if lister.Contains(notContains) != types.False {
|
||||
t.Errorf("Expected Contains to return false for %v", notContains)
|
||||
}
|
||||
}
|
||||
|
||||
addition := UnstructuredToVal(tc.addition, tc.schema).(traits.Lister)
|
||||
added := lister.Add(addition).Value()
|
||||
if !reflect.DeepEqual(added, tc.expectAdded) {
|
||||
t.Errorf("Expected Add to return %v but got %v", tc.expectAdded, added)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapper(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
unstructured map[string]interface{}
|
||||
schema *schema.Structural
|
||||
propertySchema func(key string) (*schema.Structural, bool)
|
||||
size int64
|
||||
notContains []ref.Val
|
||||
}{
|
||||
{
|
||||
name: "object",
|
||||
unstructured: map[string]interface{}{
|
||||
"field1": "a",
|
||||
"field2": "b",
|
||||
},
|
||||
schema: &objectSchema,
|
||||
propertySchema: func(key string) (*schema.Structural, bool) {
|
||||
if s, ok := objectSchema.Properties[key]; ok {
|
||||
return &s, true
|
||||
}
|
||||
return nil, false
|
||||
},
|
||||
size: 2,
|
||||
notContains: []ref.Val{
|
||||
UnstructuredToVal("field3", &stringSchema),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "map",
|
||||
unstructured: map[string]interface{}{
|
||||
"key1": "a",
|
||||
"key2": "b",
|
||||
},
|
||||
schema: &mapSchema,
|
||||
propertySchema: func(key string) (*schema.Structural, bool) { return mapSchema.AdditionalProperties.Structural, true },
|
||||
size: 2,
|
||||
notContains: []ref.Val{
|
||||
UnstructuredToVal("key3", &stringSchema),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mapper := UnstructuredToVal(tc.unstructured, tc.schema).(traits.Mapper)
|
||||
if mapper.Size().Value() != tc.size {
|
||||
t.Errorf("Expected Size to return %d but got %d", tc.size, mapper.Size().Value())
|
||||
}
|
||||
iter := mapper.Iterator()
|
||||
iterResults := map[interface{}]struct{}{}
|
||||
keys := map[interface{}]struct{}{}
|
||||
for k := range tc.unstructured {
|
||||
keys[k] = struct{}{}
|
||||
get := mapper.Get(types.String(k)).Value()
|
||||
if !reflect.DeepEqual(get, tc.unstructured[k]) {
|
||||
t.Errorf("Expected Get to return %v for key %s but got %v", tc.unstructured[k], k, get)
|
||||
}
|
||||
if iter.HasNext() != types.True {
|
||||
t.Error("Expected HasNext to return true")
|
||||
}
|
||||
iterResults[iter.Next().Value()] = struct{}{}
|
||||
}
|
||||
if !reflect.DeepEqual(iterResults, keys) {
|
||||
t.Errorf("Expected accumulation of iterator.Next calls to be %v but got %v", keys, iterResults)
|
||||
}
|
||||
if iter.HasNext() != types.False {
|
||||
t.Error("Expected HasNext to return false")
|
||||
}
|
||||
for contains := range tc.unstructured {
|
||||
if mapper.Contains(UnstructuredToVal(contains, &stringSchema)) != types.True {
|
||||
t.Errorf("Expected Contains to return true for %v", contains)
|
||||
}
|
||||
}
|
||||
for _, notContains := range tc.notContains {
|
||||
if mapper.Contains(notContains) != types.False {
|
||||
t.Errorf("Expected Contains to return false for %v", notContains)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -18,7 +18,6 @@ package schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
)
|
||||
|
||||
@ -247,6 +246,7 @@ func newExtensions(s *apiextensions.JSONSchemaProps) (*Extensions, error) {
|
||||
XListMapKeys: s.XListMapKeys,
|
||||
XListType: s.XListType,
|
||||
XMapType: s.XMapType,
|
||||
XValidations: s.XValidations,
|
||||
}
|
||||
|
||||
if s.XPreserveUnknownFields != nil {
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"reflect"
|
||||
|
||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel"
|
||||
schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning"
|
||||
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
|
||||
@ -84,6 +85,8 @@ func validate(pth *field.Path, s *structuralschema.Structural, rootSchema *struc
|
||||
allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, fmt.Sprintf("must result in valid metadata: %v", errs.ToAggregate())))
|
||||
} else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 {
|
||||
allErrs = append(allErrs, errs...)
|
||||
} else if celValidator := cel.NewValidator(s); celValidator != nil {
|
||||
allErrs = append(allErrs, celValidator.Validate(pth.Child("default"), s, s.Default.Object)...)
|
||||
}
|
||||
} else {
|
||||
// check whether default is pruned
|
||||
@ -102,6 +105,8 @@ func validate(pth *field.Path, s *structuralschema.Structural, rootSchema *struc
|
||||
allErrs = append(allErrs, errs...)
|
||||
} else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 {
|
||||
allErrs = append(allErrs, errs...)
|
||||
} else if celValidator := cel.NewValidator(s); celValidator != nil {
|
||||
allErrs = append(allErrs, celValidator.Validate(pth.Child("default"), s, s.Default.Object)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,6 +87,9 @@ func (x *Extensions) toKubeOpenAPI(ret *spec.Schema) {
|
||||
if x.XMapType != nil {
|
||||
ret.VendorExtensible.AddExtension("x-kubernetes-map-type", *x.XMapType)
|
||||
}
|
||||
if len(x.XValidations) > 0 {
|
||||
ret.VendorExtensible.AddExtension("x-kubernetes-validations", x.XValidations)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *ValueValidation) toKubeOpenAPI(ret *spec.Schema) {
|
||||
|
@ -75,6 +75,7 @@ func validateListSetsAndMapsArray(fldPath *field.Path, s *schema.Structural, obj
|
||||
case "map":
|
||||
errs = append(errs, validateListMap(fldPath, s, obj)...)
|
||||
}
|
||||
// if a case is ever added here then one should also be added to pkg/apiserver/schema/cel/values.go
|
||||
}
|
||||
|
||||
if s.Items != nil {
|
||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package schema
|
||||
|
||||
import (
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
@ -127,6 +128,9 @@ type Extensions struct {
|
||||
// Atomic maps will be entirely replaced when updated.
|
||||
// +optional
|
||||
XMapType *string
|
||||
|
||||
// x-kubernetes-validations describes a list of validation rules for expression validation.
|
||||
XValidations apiextensions.ValidationRules
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
@ -324,6 +324,9 @@ func validateNestedValueValidation(v *NestedValueValidation, skipAnyOf, skipAllO
|
||||
if v.ForbiddenExtensions.XMapType != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-map-type"), "must be undefined to be structural"))
|
||||
}
|
||||
if len(v.ForbiddenExtensions.XValidations) > 0 {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-validations"), "must be empty to be structural"))
|
||||
}
|
||||
|
||||
// forbid reasoning about metadata because it can lead to metadata restriction we don't want
|
||||
if _, found := v.Properties["metadata"]; found {
|
||||
|
@ -21,6 +21,10 @@ limitations under the License.
|
||||
|
||||
package schema
|
||||
|
||||
import (
|
||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
)
|
||||
|
||||
// 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
|
||||
@ -39,6 +43,11 @@ func (in *Extensions) DeepCopyInto(out *Extensions) {
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.XValidations != nil {
|
||||
in, out := &in.XValidations, &out.XValidations
|
||||
*out = make(apiextensions.ValidationRules, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -18,19 +18,9 @@ package validation
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/ext"
|
||||
|
||||
expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
openapierrors "k8s.io/kube-openapi/pkg/validation/errors"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
@ -264,6 +254,9 @@ func ConvertJSONSchemaPropsWithPostProcess(in *apiextensions.JSONSchemaProps, ou
|
||||
if in.XMapType != nil {
|
||||
out.VendorExtensible.AddExtension("x-kubernetes-map-type", *in.XMapType)
|
||||
}
|
||||
if len(in.XValidations) != 0 {
|
||||
out.VendorExtensible.AddExtension("x-kubernetes-validations", in.XValidations)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -339,45 +332,3 @@ func convertJSONSchemaPropsOrStringArray(in *apiextensions.JSONSchemaPropsOrStri
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompileAndValidate provides a sanity check of the CEL validation functionality until we wire in the
|
||||
// full functionality.
|
||||
func CompileAndValidate(s *schema.Structural, bindings map[string]interface{}, rule string) (bool, error) {
|
||||
env, err := cel.NewEnv()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
reg := model.NewRegistry(env)
|
||||
rt, err := model.NewRuleTypes("testType", s, reg)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
opts, err := rt.EnvOptions(env.TypeProvider())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
root, ok := rt.FindDeclType("testType")
|
||||
if !ok {
|
||||
root = model.SchemaDeclType(s).MaybeAssignTypeName("testType")
|
||||
}
|
||||
propDecls := []*expr.Decl{decls.NewVar("self", root.ExprType())}
|
||||
opts = append(opts, cel.Declarations(propDecls...))
|
||||
opts = append(opts, ext.Strings())
|
||||
env, err = env.Extend(opts...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ast, issues := env.Compile(rule)
|
||||
if issues != nil {
|
||||
return false, fmt.Errorf("issues: %v", issues)
|
||||
}
|
||||
prog, err := env.Program(ast)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
evalResult, _, err := prog.Eval(bindings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return evalResult == types.True, nil
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import (
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
apiextensionsfuzzer "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@ -485,32 +484,3 @@ func TestItemsProperty(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCompileAndValidate is a placeholder test of CEL validation that will be removed when the actual functionality
|
||||
// is wired in.
|
||||
func TestCompileAndValidate(t *testing.T) {
|
||||
s := &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
},
|
||||
Properties: map[string]schema.Structural{
|
||||
"name": {
|
||||
Generic: schema.Generic{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
bindings := map[string]interface{}{
|
||||
"self": map[string]interface{}{
|
||||
"name": "kube",
|
||||
},
|
||||
}
|
||||
result, err := CompileAndValidate(s, bindings, "self.name == 'kube'")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !result {
|
||||
t.Error("Expected expression to evaluate to true")
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +81,19 @@ func (a statusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.O
|
||||
|
||||
// ValidateUpdate is the default update validation for an end user updating status.
|
||||
func (a statusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||
return a.customResourceStrategy.validator.ValidateStatusUpdate(ctx, obj, old, a.scale)
|
||||
var errs field.ErrorList
|
||||
errs = append(errs, a.customResourceStrategy.validator.ValidateStatusUpdate(ctx, obj, old, a.scale)...)
|
||||
|
||||
// validate embedded resources
|
||||
if u, ok := obj.(*unstructured.Unstructured); ok {
|
||||
v := obj.GetObjectKind().GroupVersionKind().Version
|
||||
|
||||
// validate x-kubernetes-validations rules
|
||||
if celValidator, ok := a.customResourceStrategy.celValidators[v]; ok {
|
||||
errs = append(errs, celValidator.Validate(nil, a.customResourceStrategy.structuralSchemas[v], u.Object)...)
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// WarningsOnUpdate returns warnings for the given update.
|
||||
|
@ -19,6 +19,11 @@ package customresource
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel"
|
||||
structurallisttype "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype"
|
||||
schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@ -28,14 +33,12 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
apiserverstorage "k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kube-openapi/pkg/validation/validate"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
structurallisttype "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype"
|
||||
schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
)
|
||||
|
||||
@ -47,12 +50,23 @@ type customResourceStrategy struct {
|
||||
namespaceScoped bool
|
||||
validator customResourceValidator
|
||||
structuralSchemas map[string]*structuralschema.Structural
|
||||
celValidators map[string]*cel.Validator
|
||||
status *apiextensions.CustomResourceSubresourceStatus
|
||||
scale *apiextensions.CustomResourceSubresourceScale
|
||||
kind schema.GroupVersionKind
|
||||
}
|
||||
|
||||
func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.GroupVersionKind, schemaValidator, statusSchemaValidator *validate.SchemaValidator, structuralSchemas map[string]*structuralschema.Structural, status *apiextensions.CustomResourceSubresourceStatus, scale *apiextensions.CustomResourceSubresourceScale) customResourceStrategy {
|
||||
celValidators := map[string]*cel.Validator{}
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.CustomResourceValidationExpressions) {
|
||||
for name, s := range structuralSchemas {
|
||||
v := cel.NewValidator(s) // CEL programs are compiled and cached here
|
||||
if v != nil {
|
||||
celValidators[name] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return customResourceStrategy{
|
||||
ObjectTyper: typer,
|
||||
NameGenerator: names.SimpleNameGenerator,
|
||||
@ -66,6 +80,7 @@ func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.Gr
|
||||
statusSchemaValidator: statusSchemaValidator,
|
||||
},
|
||||
structuralSchemas: structuralSchemas,
|
||||
celValidators: celValidators,
|
||||
kind: kind,
|
||||
}
|
||||
}
|
||||
@ -156,6 +171,11 @@ func (a customResourceStrategy) Validate(ctx context.Context, obj runtime.Object
|
||||
|
||||
// validate x-kubernetes-list-type "map" and "set" invariant
|
||||
errs = append(errs, structurallisttype.ValidateListSetsAndMaps(nil, a.structuralSchemas[v], u.Object)...)
|
||||
|
||||
// validate x-kubernetes-validations rules
|
||||
if celValidator, ok := a.celValidators[v]; ok {
|
||||
errs = append(errs, celValidator.Validate(nil, a.structuralSchemas[v], u.Object)...)
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
@ -204,6 +224,11 @@ func (a customResourceStrategy) ValidateUpdate(ctx context.Context, obj, old run
|
||||
errs = append(errs, structurallisttype.ValidateListSetsAndMaps(nil, a.structuralSchemas[v], uNew.Object)...)
|
||||
}
|
||||
|
||||
// validate x-kubernetes-validations rules
|
||||
if celValidator, ok := a.celValidators[v]; ok {
|
||||
errs = append(errs, celValidator.Validate(nil, a.structuralSchemas[v], uNew.Object)...)
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
|
@ -28,9 +28,11 @@ import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
)
|
||||
|
||||
@ -77,6 +79,8 @@ func (strategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
dropDisabledFields(crd, nil)
|
||||
}
|
||||
|
||||
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
||||
@ -105,6 +109,8 @@ func (strategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
dropDisabledFields(newCRD, oldCRD)
|
||||
}
|
||||
|
||||
// Validate validates a new CustomResourceDefinition.
|
||||
@ -222,3 +228,54 @@ func MatchCustomResourceDefinition(label labels.Selector, field fields.Selector)
|
||||
func CustomResourceDefinitionToSelectableFields(obj *apiextensions.CustomResourceDefinition) fields.Set {
|
||||
return generic.ObjectMetaFieldsSet(&obj.ObjectMeta, true)
|
||||
}
|
||||
|
||||
// dropDisabledFields drops disabled fields that are not used if their associated feature gates
|
||||
// are not enabled.
|
||||
func dropDisabledFields(newCRD *apiextensions.CustomResourceDefinition, oldCRD *apiextensions.CustomResourceDefinition) {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.CustomResourceValidationExpressions) && (oldCRD == nil || (oldCRD != nil && !specHasXValidations(&oldCRD.Spec))) {
|
||||
if newCRD.Spec.Validation != nil {
|
||||
dropXValidationsField(newCRD.Spec.Validation.OpenAPIV3Schema)
|
||||
}
|
||||
for _, v := range newCRD.Spec.Versions {
|
||||
if v.Schema != nil {
|
||||
dropXValidationsField(v.Schema.OpenAPIV3Schema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dropXValidationsField drops field XValidations from CRD schema
|
||||
func dropXValidationsField(schema *apiextensions.JSONSchemaProps) {
|
||||
if schema == nil {
|
||||
return
|
||||
}
|
||||
schema.XValidations = nil
|
||||
if schema.AdditionalProperties != nil {
|
||||
dropXValidationsField(schema.AdditionalProperties.Schema)
|
||||
}
|
||||
for def, jsonSchema := range schema.Properties {
|
||||
dropXValidationsField(&jsonSchema)
|
||||
schema.Properties[def] = jsonSchema
|
||||
}
|
||||
if schema.Items != nil {
|
||||
dropXValidationsField(schema.Items.Schema)
|
||||
for i, jsonSchema := range schema.Items.JSONSchemas {
|
||||
dropXValidationsField(&jsonSchema)
|
||||
schema.Items.JSONSchemas[i] = jsonSchema
|
||||
}
|
||||
}
|
||||
for def, jsonSchemaPropsOrStringArray := range schema.Dependencies {
|
||||
dropXValidationsField(jsonSchemaPropsOrStringArray.Schema)
|
||||
schema.Dependencies[def] = jsonSchemaPropsOrStringArray
|
||||
}
|
||||
}
|
||||
|
||||
func specHasXValidations(spec *apiextensions.CustomResourceDefinitionSpec) bool {
|
||||
return validation.HasSchemaWith(spec, schemaHasXValidations)
|
||||
}
|
||||
|
||||
func schemaHasXValidations(s *apiextensions.JSONSchemaProps) bool {
|
||||
return validation.SchemaHas(s, func(s *apiextensions.JSONSchemaProps) bool {
|
||||
return s.XValidations != nil
|
||||
})
|
||||
}
|
||||
|
@ -19,11 +19,15 @@ package customresourcedefinition
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
@ -187,3 +191,483 @@ func TestValidateAPIApproval(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDropDisabledFields tests if the drop functionality is working fine or not with feature gate switch
|
||||
func TestDropDisabledFields(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
enableXValidations bool
|
||||
crd *apiextensions.CustomResourceDefinition
|
||||
oldCRD *apiextensions.CustomResourceDefinition
|
||||
expectedCRD *apiextensions.CustomResourceDefinition
|
||||
}{
|
||||
{
|
||||
name: "For creation, FG disabled, no XValidations, no field drop",
|
||||
enableXValidations: false,
|
||||
crd: &apiextensions.CustomResourceDefinition{},
|
||||
oldCRD: nil,
|
||||
expectedCRD: &apiextensions.CustomResourceDefinition{},
|
||||
},
|
||||
{
|
||||
name: "For creation, FG disabled, empty XValidations, no field drop",
|
||||
enableXValidations: false,
|
||||
crd: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{},
|
||||
},
|
||||
},
|
||||
oldCRD: nil,
|
||||
expectedCRD: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "For creation, FG disabled, set XValidations, drop XValidations",
|
||||
enableXValidations: false,
|
||||
crd: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "openAPIV3Schema should contain more than 0 element.",
|
||||
},
|
||||
},
|
||||
Dependencies: apiextensions.JSONSchemaDependencies{
|
||||
"test": apiextensions.JSONSchemaPropsOrStringArray{
|
||||
Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "size of scoped field should be greater than 0.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"subRule": {
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "isTest == true",
|
||||
Message: "isTest should be true.",
|
||||
},
|
||||
},
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"isTest": {
|
||||
Type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
oldCRD: nil,
|
||||
expectedCRD: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Dependencies: apiextensions.JSONSchemaDependencies{
|
||||
"test": apiextensions.JSONSchemaPropsOrStringArray{
|
||||
Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"subRule": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"isTest": {
|
||||
Type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "For creation, FG enabled, set XValidations, update with XValidations",
|
||||
enableXValidations: true,
|
||||
crd: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "openAPIV3Schema should contain more than 0 element.",
|
||||
},
|
||||
},
|
||||
Dependencies: apiextensions.JSONSchemaDependencies{
|
||||
"test": apiextensions.JSONSchemaPropsOrStringArray{
|
||||
Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "size of scoped field should be greater than 0.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"subRule": {
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "isTest == true",
|
||||
Message: "isTest should be true.",
|
||||
},
|
||||
},
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"isTest": {
|
||||
Type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
oldCRD: nil,
|
||||
expectedCRD: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "openAPIV3Schema should contain more than 0 element.",
|
||||
},
|
||||
},
|
||||
Dependencies: apiextensions.JSONSchemaDependencies{
|
||||
"test": apiextensions.JSONSchemaPropsOrStringArray{
|
||||
Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "size of scoped field should be greater than 0.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"subRule": {
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "isTest == true",
|
||||
Message: "isTest should be true.",
|
||||
},
|
||||
},
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"isTest": {
|
||||
Type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "For update, FG disabled, oldCRD XValidation in use, don't drop XValidations",
|
||||
enableXValidations: false,
|
||||
crd: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "openAPIV3Schema should contain more than 0 element.",
|
||||
},
|
||||
},
|
||||
Dependencies: apiextensions.JSONSchemaDependencies{
|
||||
"test": apiextensions.JSONSchemaPropsOrStringArray{
|
||||
Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "size of scoped field should be greater than 0.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"subRule": {
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "isTest == true",
|
||||
Message: "isTest should be true.",
|
||||
},
|
||||
},
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"isTest": {
|
||||
Type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
oldCRD: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Dependencies: apiextensions.JSONSchemaDependencies{
|
||||
"test": apiextensions.JSONSchemaPropsOrStringArray{
|
||||
Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "size of scoped field should be greater than 0.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedCRD: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "openAPIV3Schema should contain more than 0 element.",
|
||||
},
|
||||
},
|
||||
Dependencies: apiextensions.JSONSchemaDependencies{
|
||||
"test": apiextensions.JSONSchemaPropsOrStringArray{
|
||||
Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "size of scoped field should be greater than 0.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"subRule": {
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "isTest == true",
|
||||
Message: "isTest should be true.",
|
||||
},
|
||||
},
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"isTest": {
|
||||
Type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "For update, FG disabled, oldCRD has no XValidations, drop XValidations",
|
||||
enableXValidations: false,
|
||||
crd: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "openAPIV3Schema should contain more than 0 element.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
oldCRD: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedCRD: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "For update, FG enabled, oldCRD has XValidations, updated to newCRD",
|
||||
enableXValidations: true,
|
||||
crd: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "openAPIV3Schema should contain more than 0 element.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
oldCRD: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "old data",
|
||||
Message: "old data",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedCRD: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "openAPIV3Schema should contain more than 0 element.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "For update, FG enabled, oldCRD has no XValidations, updated to newCRD",
|
||||
enableXValidations: true,
|
||||
crd: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "openAPIV3Schema should contain more than 0 element.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
oldCRD: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedCRD: &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foos.sigs.k8s.io", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "valid"}, ResourceVersion: "1"},
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Validation: &apiextensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "size(self) > 0",
|
||||
Message: "openAPIV3Schema should contain more than 0 element.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CustomResourceValidationExpressions, tc.enableXValidations)()
|
||||
old := tc.oldCRD.DeepCopy()
|
||||
|
||||
dropDisabledFields(tc.crd, tc.oldCRD)
|
||||
|
||||
// old crd should never be changed
|
||||
if diff := cmp.Diff(tc.oldCRD, old); diff != "" {
|
||||
t.Fatalf("old crd changed from %v to %v", tc.oldCRD, old)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.expectedCRD, tc.crd); diff != "" {
|
||||
t.Fatalf("unexpected crd: %v", tc.crd)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -17,14 +17,11 @@ limitations under the License.
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"regexp"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// TODO: replace escaping with new rules described in kEP update
|
||||
|
||||
// celReservedSymbols is a list of RESERVED symbols defined in the CEL lexer.
|
||||
// No identifiers are allowed to collide with these symbols.
|
||||
// https://github.com/google/cel-spec/blob/master/doc/langdef.md#syntax
|
||||
@ -36,47 +33,78 @@ var celReservedSymbols = sets.NewString(
|
||||
"var", "void", "while",
|
||||
)
|
||||
|
||||
// celLanguageIdentifiers is a list of identifiers that are part of the CEL language.
|
||||
// This does NOT include builtin macro or function identifiers.
|
||||
// https://github.com/google/cel-spec/blob/master/doc/langdef.md#values
|
||||
var celLanguageIdentifiers = sets.NewString(
|
||||
"int", "uint", "double", "bool", "string", "bytes", "list", "map", "null_type", "type",
|
||||
)
|
||||
// expandMatcher matches the escape sequence, characters that are escaped, and characters that are unsupported
|
||||
var expandMatcher = regexp.MustCompile(`(__|[-./]|[^a-zA-Z0-9-./_])`)
|
||||
|
||||
// IsRootReserved returns true if an identifier is reserved by CEL. Declaring root variables in CEL with
|
||||
// these identifiers is not allowed and would result in an "overlapping identifier for name '<identifier>'"
|
||||
// CEL compilation error.
|
||||
func IsRootReserved(prop string) bool {
|
||||
return celLanguageIdentifiers.Has(prop)
|
||||
}
|
||||
|
||||
// Escape escapes identifiers in the AlwaysReservedIdentifiers set by prefixing ident with "_" and by prefixing
|
||||
// any ident already prefixed with N '_' with N+1 '_'.
|
||||
// For an identifier that does not require escaping, the identifier is returned as-is.
|
||||
func Escape(ident string) string {
|
||||
if strings.HasPrefix(ident, "_") || celReservedSymbols.Has(ident) {
|
||||
return "_" + ident
|
||||
// Escape escapes ident and returns a CEL identifier (of the form '[a-zA-Z_][a-zA-Z0-9_]*'), or returns
|
||||
// false if the ident does not match the supported input format of `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*`.
|
||||
// Escaping Rules:
|
||||
// - '__' escapes to '__underscores__'
|
||||
// - '.' escapes to '__dot__'
|
||||
// - '-' escapes to '__dash__'
|
||||
// - '/' escapes to '__slash__'
|
||||
// - Identifiers that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are: "true", "false",
|
||||
// "null", "in", "as", "break", "const", "continue", "else", "for", "function", "if", "import", "let", loop", "package",
|
||||
// "namespace", "return".
|
||||
func Escape(ident string) (string, bool) {
|
||||
if len(ident) == 0 || ('0' <= ident[0] && ident[0] <= '9') {
|
||||
return "", false
|
||||
}
|
||||
return ident
|
||||
}
|
||||
|
||||
// EscapeSlice returns identifiers with Escape applied to each.
|
||||
func EscapeSlice(idents []string) []string {
|
||||
result := make([]string, len(idents))
|
||||
for i, prop := range idents {
|
||||
result[i] = Escape(prop)
|
||||
if celReservedSymbols.Has(ident) {
|
||||
return "__" + ident + "__", true
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Unescape unescapes an identifier escaped by Escape.
|
||||
func Unescape(escaped string) string {
|
||||
if strings.HasPrefix(escaped, "_") {
|
||||
trimmed := strings.TrimPrefix(escaped, "_")
|
||||
if strings.HasPrefix(trimmed, "_") || celReservedSymbols.Has(trimmed) {
|
||||
return trimmed
|
||||
ok := true
|
||||
ident = expandMatcher.ReplaceAllStringFunc(ident, func(s string) string {
|
||||
switch s {
|
||||
case "__":
|
||||
return "__underscores__"
|
||||
case ".":
|
||||
return "__dot__"
|
||||
case "-":
|
||||
return "__dash__"
|
||||
case "/":
|
||||
return "__slash__"
|
||||
default: // matched a unsupported supported
|
||||
ok = false
|
||||
return ""
|
||||
}
|
||||
panic(fmt.Sprintf("failed to unescape improperly escaped string: %v", escaped))
|
||||
})
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return escaped
|
||||
return ident, true
|
||||
}
|
||||
|
||||
var unexpandMatcher = regexp.MustCompile(`(_{2}[^_]+_{2})`)
|
||||
|
||||
// Unescape unescapes an CEL identifier containing the escape sequences described in Escape, or return false if the
|
||||
// string contains invalid escape sequences. The escaped input is expected to be a valid CEL identifier, but is
|
||||
// not checked.
|
||||
func Unescape(escaped string) (string, bool) {
|
||||
ok := true
|
||||
escaped = unexpandMatcher.ReplaceAllStringFunc(escaped, func(s string) string {
|
||||
contents := s[2 : len(s)-2]
|
||||
switch contents {
|
||||
case "underscores":
|
||||
return "__"
|
||||
case "dot":
|
||||
return "."
|
||||
case "dash":
|
||||
return "-"
|
||||
case "slash":
|
||||
return "/"
|
||||
}
|
||||
if celReservedSymbols.Has(contents) {
|
||||
if len(s) != len(escaped) {
|
||||
ok = false
|
||||
}
|
||||
return contents
|
||||
}
|
||||
ok = false
|
||||
return ""
|
||||
})
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return escaped, true
|
||||
}
|
||||
|
@ -17,99 +17,156 @@ limitations under the License.
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
fuzz "github.com/google/gofuzz"
|
||||
)
|
||||
|
||||
// TestEscaping tests that property names are escaped as expected.
|
||||
func TestEscaping(t *testing.T) {
|
||||
cases := []struct{
|
||||
unescaped string
|
||||
escaped string
|
||||
reservedAtRoot bool
|
||||
} {
|
||||
cases := []struct {
|
||||
unescaped string
|
||||
escaped string
|
||||
unescapable bool
|
||||
}{
|
||||
// '.', '-', '/' and '__' are escaped since
|
||||
// CEL only allows identifiers of the form: [a-zA-Z_][a-zA-Z0-9_]*
|
||||
{unescaped: "a.a", escaped: "a__dot__a"},
|
||||
{unescaped: "a-a", escaped: "a__dash__a"},
|
||||
{unescaped: "a__a", escaped: "a__underscores__a"},
|
||||
{unescaped: "a.-/__a", escaped: "a__dot____dash____slash____underscores__a"},
|
||||
{unescaped: "a._a", escaped: "a__dot___a"},
|
||||
{unescaped: "a__.__a", escaped: "a__underscores____dot____underscores__a"},
|
||||
{unescaped: "a___a", escaped: "a__underscores___a"},
|
||||
{unescaped: "a____a", escaped: "a__underscores____underscores__a"},
|
||||
{unescaped: "a__dot__a", escaped: "a__underscores__dot__underscores__a"},
|
||||
{unescaped: "a__underscores__a", escaped: "a__underscores__underscores__underscores__a"},
|
||||
// CEL lexer RESERVED keywords must be escaped
|
||||
{ unescaped: "true", escaped: "_true" },
|
||||
{ unescaped: "false", escaped: "_false" },
|
||||
{ unescaped: "null", escaped: "_null" },
|
||||
{ unescaped: "in", escaped: "_in" },
|
||||
{ unescaped: "as", escaped: "_as" },
|
||||
{ unescaped: "break", escaped: "_break" },
|
||||
{ unescaped: "const", escaped: "_const" },
|
||||
{ unescaped: "continue", escaped: "_continue" },
|
||||
{ unescaped: "else", escaped: "_else" },
|
||||
{ unescaped: "for", escaped: "_for" },
|
||||
{ unescaped: "function", escaped: "_function" },
|
||||
{ unescaped: "if", escaped: "_if" },
|
||||
{ unescaped: "import", escaped: "_import" },
|
||||
{ unescaped: "let", escaped: "_let" },
|
||||
{ unescaped: "loop", escaped: "_loop" },
|
||||
{ unescaped: "package", escaped: "_package" },
|
||||
{ unescaped: "namespace", escaped: "_namespace" },
|
||||
{ unescaped: "return", escaped: "_return" },
|
||||
{ unescaped: "var", escaped: "_var" },
|
||||
{ unescaped: "void", escaped: "_void" },
|
||||
{ unescaped: "while", escaped: "_while" },
|
||||
// CEL language identifiers do not need to be escaped, but collide with builtin language identifier if bound as
|
||||
// root variable names.
|
||||
// i.e. "self.int == 1" is legal, but "int == 1" is not.
|
||||
{ unescaped: "int", escaped: "int", reservedAtRoot: true },
|
||||
{ unescaped: "uint", escaped: "uint", reservedAtRoot: true },
|
||||
{ unescaped: "double", escaped: "double", reservedAtRoot: true },
|
||||
{ unescaped: "bool", escaped: "bool", reservedAtRoot: true },
|
||||
{ unescaped: "string", escaped: "string", reservedAtRoot: true },
|
||||
{ unescaped: "bytes", escaped: "bytes", reservedAtRoot: true },
|
||||
{ unescaped: "list", escaped: "list", reservedAtRoot: true },
|
||||
{ unescaped: "map", escaped: "map", reservedAtRoot: true },
|
||||
{ unescaped: "null_type", escaped: "null_type", reservedAtRoot: true },
|
||||
{ unescaped: "type", escaped: "type", reservedAtRoot: true },
|
||||
// To prevent escaping from colliding with other identifiers, all identifiers prefixed by _s are escaped by
|
||||
// prefixing them with N+1 _s.
|
||||
{ unescaped: "_if", escaped: "__if" },
|
||||
{ unescaped: "__if", escaped: "___if" },
|
||||
{ unescaped: "___if", escaped: "____if" },
|
||||
{ unescaped: "_has", escaped: "__has" },
|
||||
{ unescaped: "_int", escaped: "__int" },
|
||||
{ unescaped: "_anything", escaped: "__anything" },
|
||||
// CEL macro and function names do not need to be escaped because the parser can disambiguate them from the function and
|
||||
// macro identifiers.
|
||||
{ unescaped: "has", escaped: "has" },
|
||||
{ unescaped: "all", escaped: "all" },
|
||||
{ unescaped: "exists", escaped: "exists" },
|
||||
{ unescaped: "exists_one", escaped: "exists_one" },
|
||||
{ unescaped: "filter", escaped: "filter" },
|
||||
{ unescaped: "size", escaped: "size" },
|
||||
{ unescaped: "contains", escaped: "contains" },
|
||||
{ unescaped: "startsWith", escaped: "startsWith" },
|
||||
{ unescaped: "endsWith", escaped: "endsWith" },
|
||||
{ unescaped: "matches", escaped: "matches" },
|
||||
{ unescaped: "duration", escaped: "duration" },
|
||||
{ unescaped: "timestamp", escaped: "timestamp" },
|
||||
{ unescaped: "getDate", escaped: "getDate" },
|
||||
{ unescaped: "getDayOfMonth", escaped: "getDayOfMonth" },
|
||||
{ unescaped: "getDayOfWeek", escaped: "getDayOfWeek" },
|
||||
{ unescaped: "getFullYear", escaped: "getFullYear" },
|
||||
{ unescaped: "getHours", escaped: "getHours" },
|
||||
{ unescaped: "getMilliseconds", escaped: "getMilliseconds" },
|
||||
{ unescaped: "getMinutes", escaped: "getMinutes" },
|
||||
{ unescaped: "getMonth", escaped: "getMonth" },
|
||||
{ unescaped: "getSeconds", escaped: "getSeconds" },
|
||||
{unescaped: "true", escaped: "__true__"},
|
||||
{unescaped: "false", escaped: "__false__"},
|
||||
{unescaped: "null", escaped: "__null__"},
|
||||
{unescaped: "in", escaped: "__in__"},
|
||||
{unescaped: "as", escaped: "__as__"},
|
||||
{unescaped: "break", escaped: "__break__"},
|
||||
{unescaped: "const", escaped: "__const__"},
|
||||
{unescaped: "continue", escaped: "__continue__"},
|
||||
{unescaped: "else", escaped: "__else__"},
|
||||
{unescaped: "for", escaped: "__for__"},
|
||||
{unescaped: "function", escaped: "__function__"},
|
||||
{unescaped: "if", escaped: "__if__"},
|
||||
{unescaped: "import", escaped: "__import__"},
|
||||
{unescaped: "let", escaped: "__let__"},
|
||||
{unescaped: "loop", escaped: "__loop__"},
|
||||
{unescaped: "package", escaped: "__package__"},
|
||||
{unescaped: "namespace", escaped: "__namespace__"},
|
||||
{unescaped: "return", escaped: "__return__"},
|
||||
{unescaped: "var", escaped: "__var__"},
|
||||
{unescaped: "void", escaped: "__void__"},
|
||||
{unescaped: "while", escaped: "__while__"},
|
||||
// Not all property names are escapable
|
||||
{unescaped: "@", unescapable: true},
|
||||
{unescaped: "1up", unescapable: true},
|
||||
{unescaped: "👑", unescapable: true},
|
||||
// CEL macro and function names do not need to be escaped because the parser keeps identifiers in a
|
||||
// different namespace than function and macro names.
|
||||
{unescaped: "has", escaped: "has"},
|
||||
{unescaped: "all", escaped: "all"},
|
||||
{unescaped: "exists", escaped: "exists"},
|
||||
{unescaped: "exists_one", escaped: "exists_one"},
|
||||
{unescaped: "filter", escaped: "filter"},
|
||||
{unescaped: "size", escaped: "size"},
|
||||
{unescaped: "contains", escaped: "contains"},
|
||||
{unescaped: "startsWith", escaped: "startsWith"},
|
||||
{unescaped: "endsWith", escaped: "endsWith"},
|
||||
{unescaped: "matches", escaped: "matches"},
|
||||
{unescaped: "duration", escaped: "duration"},
|
||||
{unescaped: "timestamp", escaped: "timestamp"},
|
||||
{unescaped: "getDate", escaped: "getDate"},
|
||||
{unescaped: "getDayOfMonth", escaped: "getDayOfMonth"},
|
||||
{unescaped: "getDayOfWeek", escaped: "getDayOfWeek"},
|
||||
{unescaped: "getFullYear", escaped: "getFullYear"},
|
||||
{unescaped: "getHours", escaped: "getHours"},
|
||||
{unescaped: "getMilliseconds", escaped: "getMilliseconds"},
|
||||
{unescaped: "getMinutes", escaped: "getMinutes"},
|
||||
{unescaped: "getMonth", escaped: "getMonth"},
|
||||
{unescaped: "getSeconds", escaped: "getSeconds"},
|
||||
// we don't escape a single _
|
||||
{unescaped: "_if", escaped: "_if"},
|
||||
{unescaped: "_has", escaped: "_has"},
|
||||
{unescaped: "_int", escaped: "_int"},
|
||||
{unescaped: "_anything", escaped: "_anything"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.unescaped, func(t *testing.T) {
|
||||
e := Escape(tc.unescaped)
|
||||
e, escapable := Escape(tc.unescaped)
|
||||
if tc.unescapable {
|
||||
if escapable {
|
||||
t.Errorf("Expected escapable=false, but got %t", escapable)
|
||||
}
|
||||
return
|
||||
}
|
||||
if !escapable {
|
||||
t.Fatalf("Expected escapable=true, but got %t", escapable)
|
||||
}
|
||||
if tc.escaped != e {
|
||||
t.Errorf("Expected %s to escape to %s, but got %s", tc.unescaped, tc.escaped, e)
|
||||
}
|
||||
u := Unescape(tc.escaped)
|
||||
if tc.unescaped != u {
|
||||
t.Errorf("Expected %s to unescape to %s, but got %s", tc.escaped, tc.unescaped, e)
|
||||
|
||||
if !validCelIdent.MatchString(e) {
|
||||
t.Errorf("Expected %s to escape to a valid CEL identifier, but got %s", tc.unescaped, e)
|
||||
}
|
||||
|
||||
isRootReserved := IsRootReserved(tc.unescaped)
|
||||
if tc.reservedAtRoot != isRootReserved {
|
||||
t.Errorf("Expected isRootReserved=%t for %s, but got %t", tc.reservedAtRoot, tc.unescaped, isRootReserved)
|
||||
u, ok := Unescape(tc.escaped)
|
||||
if !ok {
|
||||
t.Fatalf("Expected %s to be escapable, but it was not", tc.escaped)
|
||||
}
|
||||
if tc.unescaped != u {
|
||||
t.Errorf("Expected %s to unescape to %s, but got %s", tc.escaped, tc.unescaped, u)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnescapeMalformed(t *testing.T) {
|
||||
for _, s := range []string{"__int__extra", "__illegal__"} {
|
||||
t.Run(s, func(t *testing.T) {
|
||||
e, ok := Unescape(s)
|
||||
if ok {
|
||||
t.Fatalf("Expected %s to be unescapable, but it escaped to: %s", s, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscapingFuzz(t *testing.T) {
|
||||
fuzzer := fuzz.New()
|
||||
for i := 0; i < 1000; i++ {
|
||||
var unescaped string
|
||||
fuzzer.Fuzz(&unescaped)
|
||||
t.Run(fmt.Sprintf("%d - '%s'", i, unescaped), func(t *testing.T) {
|
||||
if len(unescaped) == 0 {
|
||||
return
|
||||
}
|
||||
escaped, ok := Escape(unescaped)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if !validCelIdent.MatchString(escaped) {
|
||||
t.Errorf("Expected %s to escape to a valid CEL identifier, but got %s", unescaped, escaped)
|
||||
}
|
||||
u, ok := Unescape(escaped)
|
||||
if !ok {
|
||||
t.Fatalf("Expected %s to be unescapable, but it was not", escaped)
|
||||
}
|
||||
if unescaped != u {
|
||||
t.Errorf("Expected %s to unescape to %s, but got %s", escaped, unescaped, u)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var validCelIdent = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
|
||||
|
@ -18,45 +18,61 @@ import (
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
)
|
||||
|
||||
|
||||
// SchemaDeclTypes constructs a top-down set of DeclType instances whose name is derived from the root
|
||||
// type name provided on the call, if not set to a custom type.
|
||||
func SchemaDeclTypes(s *schema.Structural, maybeRootType string) (*DeclType, map[string]*DeclType) {
|
||||
root := SchemaDeclType(s).MaybeAssignTypeName(maybeRootType)
|
||||
types := FieldTypeMap(maybeRootType, root)
|
||||
return root, types
|
||||
}
|
||||
|
||||
// SchemaDeclType returns the cel type name associated with the schema element.
|
||||
func SchemaDeclType(s *schema.Structural) *DeclType {
|
||||
// SchemaDeclType converts the structural schema to a CEL declaration, or returns nil if the
|
||||
// the structural schema should not be exposed in CEL expressions.
|
||||
// Set isResourceRoot to true for the root of a custom resource or embedded resource.
|
||||
//
|
||||
// Schemas with XPreserveUnknownFields not exposed unless they are objects. Array and "maps" schemas
|
||||
// are not exposed if their items or additionalProperties schemas are not exposed. Object Properties are not exposed
|
||||
// if their schema is not exposed.
|
||||
//
|
||||
// The CEL declaration for objects with XPreserveUnknownFields does not expose unknown fields.
|
||||
func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
if s.XIntOrString {
|
||||
// schemas using this extension are not required to have a type, so they must be handled before type lookup
|
||||
return intOrStringType
|
||||
}
|
||||
declType, found := openAPISchemaTypes[s.Type]
|
||||
if !found {
|
||||
return nil
|
||||
// schemas using XIntOrString are not required to have a type.
|
||||
|
||||
// intOrStringType represents the x-kubernetes-int-or-string union type in CEL expressions.
|
||||
// In CEL, the type is represented as dynamic value, which can be thought of as a union type of all types.
|
||||
// All type checking for XIntOrString is deferred to runtime, so all access to values of this type must
|
||||
// be guarded with a type check, e.g.:
|
||||
//
|
||||
// To require that the string representation be a percentage:
|
||||
// `type(intOrStringField) == string && intOrStringField.matches(r'(\d+(\.\d+)?%)')`
|
||||
// To validate requirements on both the int and string representation:
|
||||
// `type(intOrStringField) == int ? intOrStringField < 5 : double(intOrStringField.replace('%', '')) < 0.5
|
||||
//
|
||||
return DynType
|
||||
}
|
||||
|
||||
// We ignore XPreserveUnknownFields since we don't support validation rules on
|
||||
// data that we don't have schema information for.
|
||||
|
||||
if s.XEmbeddedResource {
|
||||
// 'apiVersion', 'kind', 'metadata.name' and 'metadata.generateName' are always accessible
|
||||
// to validation rules since this part of the schema is well known and validated when CRDs
|
||||
// are created and updated.
|
||||
if isResourceRoot {
|
||||
// 'apiVersion', 'kind', 'metadata.name' and 'metadata.generateName' are always accessible to validator rules
|
||||
// at the root of resources, even if not specified in the schema.
|
||||
// This includes the root of a custom resource and the root of XEmbeddedResource objects.
|
||||
s = WithTypeAndObjectMeta(s)
|
||||
}
|
||||
|
||||
switch declType.TypeName() {
|
||||
case ListType.TypeName():
|
||||
return NewListType(SchemaDeclType(s.Items))
|
||||
case MapType.TypeName():
|
||||
switch s.Type {
|
||||
case "array":
|
||||
if s.Items != nil {
|
||||
itemsType := SchemaDeclType(s.Items, s.Items.XEmbeddedResource)
|
||||
if itemsType != nil {
|
||||
return NewListType(itemsType)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case "object":
|
||||
if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil {
|
||||
return NewMapType(StringType, SchemaDeclType(s.AdditionalProperties.Structural))
|
||||
propsType := SchemaDeclType(s.AdditionalProperties.Structural, s.AdditionalProperties.Structural.XEmbeddedResource)
|
||||
if propsType != nil {
|
||||
return NewMapType(StringType, propsType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
fields := make(map[string]*DeclField, len(s.Properties))
|
||||
|
||||
@ -73,23 +89,23 @@ func SchemaDeclType(s *schema.Structural) *DeclType {
|
||||
enumValues = append(enumValues, e.Object)
|
||||
}
|
||||
}
|
||||
if fieldType := SchemaDeclType(&prop); fieldType != nil {
|
||||
fields[Escape(name)] = &DeclField{
|
||||
Name: Escape(name),
|
||||
Required: required[name],
|
||||
Type: fieldType,
|
||||
defaultValue: prop.Default.Object,
|
||||
enumValues: enumValues, // Enum values are represented as strings in CEL
|
||||
if fieldType := SchemaDeclType(&prop, prop.XEmbeddedResource); fieldType != nil {
|
||||
if propName, ok := Escape(name); ok {
|
||||
fields[propName] = &DeclField{
|
||||
Name: propName,
|
||||
Required: required[name],
|
||||
Type: fieldType,
|
||||
defaultValue: prop.Default.Object,
|
||||
enumValues: enumValues, // Enum values are represented as strings in CEL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return NewObjectType("object", fields)
|
||||
case StringType.TypeName():
|
||||
case "string":
|
||||
if s.ValueValidation != nil {
|
||||
switch s.ValueValidation.Format {
|
||||
case "byte":
|
||||
return StringType // OpenAPIv3 byte format represents base64 encoded string
|
||||
case "binary":
|
||||
return BytesType
|
||||
case "duration":
|
||||
return DurationType
|
||||
@ -97,39 +113,17 @@ func SchemaDeclType(s *schema.Structural) *DeclType {
|
||||
return TimestampType
|
||||
}
|
||||
}
|
||||
return StringType
|
||||
case "boolean":
|
||||
return BoolType
|
||||
case "number":
|
||||
return DoubleType
|
||||
case "integer":
|
||||
return IntType
|
||||
}
|
||||
return declType
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
openAPISchemaTypes = map[string]*DeclType{
|
||||
"boolean": BoolType,
|
||||
"number": DoubleType,
|
||||
"integer": IntType,
|
||||
"null": NullType,
|
||||
"string": StringType,
|
||||
"date": DateType,
|
||||
"array": ListType,
|
||||
"object": MapType,
|
||||
}
|
||||
|
||||
// intOrStringType represents the x-kubernetes-int-or-string union type in CEL expressions.
|
||||
// In CEL, the type is represented as an object where either the srtVal
|
||||
// or intVal field is set. In CEL, this allows for typesafe expressions like:
|
||||
//
|
||||
// require that the string representation be a percentage:
|
||||
// `has(intOrStringField.strVal) && intOrStringField.strVal.matches(r'(\d+(\.\d+)?%)')`
|
||||
// validate requirements on both the int and string representation:
|
||||
// `has(intOrStringField.intVal) ? intOrStringField.intVal < 5 : double(intOrStringField.strVal.replace('%', '')) < 0.5
|
||||
//
|
||||
intOrStringType = NewObjectType("intOrString", map[string]*DeclField{
|
||||
"strVal": {Name: "strVal", Type: StringType},
|
||||
"intVal": {Name: "intVal", Type: IntType},
|
||||
})
|
||||
)
|
||||
|
||||
// TODO: embedded objects should have objectMeta only, and name and generateName are both optional
|
||||
|
||||
// WithTypeAndObjectMeta ensures the kind, apiVersion and
|
||||
// metadata.name and metadata.generateName properties are specified, making a shallow copy of the provided schema if needed.
|
||||
func WithTypeAndObjectMeta(s *schema.Structural) *schema.Structural {
|
||||
|
@ -21,12 +21,13 @@ import (
|
||||
"github.com/google/cel-go/common/types"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
)
|
||||
|
||||
func TestSchemaDeclType(t *testing.T) {
|
||||
ts := testSchema()
|
||||
cust := SchemaDeclType(ts)
|
||||
cust := SchemaDeclType(ts, false)
|
||||
if cust.TypeName() != "object" {
|
||||
t.Errorf("incorrect type name, got %v, wanted object", cust.TypeName())
|
||||
}
|
||||
@ -82,7 +83,8 @@ func TestSchemaDeclType(t *testing.T) {
|
||||
|
||||
func TestSchemaDeclTypes(t *testing.T) {
|
||||
ts := testSchema()
|
||||
cust, typeMap := SchemaDeclTypes(ts, "CustomObject")
|
||||
cust := SchemaDeclType(ts, true).MaybeAssignTypeName("CustomObject")
|
||||
typeMap := FieldTypeMap("CustomObject", cust)
|
||||
nested, _ := cust.FindField("nested")
|
||||
metadata, _ := cust.FindField("metadata")
|
||||
expectedObjTypeMap := map[string]*DeclType{
|
||||
|
@ -24,9 +24,9 @@ import (
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
)
|
||||
|
||||
@ -306,15 +306,19 @@ func (f *DeclField) EnumValues() []ref.Val {
|
||||
// NewRuleTypes returns an Open API Schema-based type-system which is CEL compatible.
|
||||
func NewRuleTypes(kind string,
|
||||
schema *schema.Structural,
|
||||
isResourceRoot bool,
|
||||
res Resolver) (*RuleTypes, error) {
|
||||
// Note, if the schema indicates that it's actually based on another proto
|
||||
// then prefer the proto definition. For expressions in the proto, a new field
|
||||
// annotation will be needed to indicate the expected environment and type of
|
||||
// the expression.
|
||||
schemaTypes, err := newSchemaTypeProvider(kind, schema)
|
||||
schemaTypes, err := newSchemaTypeProvider(kind, schema, isResourceRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if schemaTypes == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &RuleTypes{
|
||||
Schema: schema,
|
||||
ruleSchemaDeclTypes: schemaTypes,
|
||||
@ -467,7 +471,6 @@ func (rt *RuleTypes) convertToCustomType(dyn *DynValue, declType *DeclType) *Dyn
|
||||
dyn.SetValue(obj)
|
||||
return dyn
|
||||
}
|
||||
// TODO: handle complex map types which have non-string keys.
|
||||
fieldType := declType.ElemType
|
||||
for _, f := range v.fieldMap {
|
||||
f.Ref = rt.convertToCustomType(f.Ref, fieldType)
|
||||
@ -485,8 +488,12 @@ func (rt *RuleTypes) convertToCustomType(dyn *DynValue, declType *DeclType) *Dyn
|
||||
}
|
||||
}
|
||||
|
||||
func newSchemaTypeProvider(kind string, schema *schema.Structural) (*schemaTypeProvider, error) {
|
||||
root := SchemaDeclType(schema).MaybeAssignTypeName(kind)
|
||||
func newSchemaTypeProvider(kind string, schema *schema.Structural, isResourceRoot bool) (*schemaTypeProvider, error) {
|
||||
delType := SchemaDeclType(schema, isResourceRoot)
|
||||
if delType == nil {
|
||||
return nil, nil
|
||||
}
|
||||
root := delType.MaybeAssignTypeName(kind)
|
||||
types := FieldTypeMap(kind, root)
|
||||
return &schemaTypeProvider{
|
||||
root: root,
|
||||
|
@ -67,7 +67,7 @@ func TestTypes_MapType(t *testing.T) {
|
||||
func TestTypes_RuleTypesFieldMapping(t *testing.T) {
|
||||
stdEnv, _ := cel.NewEnv()
|
||||
reg := NewRegistry(stdEnv)
|
||||
rt, err := NewRuleTypes("CustomObject", testSchema(), reg)
|
||||
rt, err := NewRuleTypes("CustomObject", testSchema(), true, reg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -205,8 +205,6 @@ func (sv *structValue) ConvertToNative(typeDesc reflect.Type) (interface{}, erro
|
||||
return nil, fmt.Errorf("type conversion error from object to '%v'", typeDesc)
|
||||
}
|
||||
|
||||
// TODO: Special case handling for protobuf Struct and Any if needed
|
||||
|
||||
// Unwrap pointers, but track their use.
|
||||
isPtr := false
|
||||
if typeDesc.Kind() == reflect.Ptr {
|
||||
|
497
test/integration/apiserver/crd_validation_expressions_test.go
Normal file
497
test/integration/apiserver/crd_validation_expressions_test.go
Normal file
@ -0,0 +1,497 @@
|
||||
/*
|
||||
Copyright 2021 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 apiserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/dynamic"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
||||
"k8s.io/kubernetes/test/integration/framework"
|
||||
)
|
||||
|
||||
// TestCustomResourceValidatorsWithDisabledFeatureGate test that x-kubernetes-validations work as expected when the
|
||||
// feature gate is disabled.
|
||||
func TestCustomResourceValidatorsWithDisabledFeatureGate(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CustomResourceValidationExpressions, false)()
|
||||
|
||||
server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer server.TearDownFn()
|
||||
config := server.ClientConfig
|
||||
|
||||
apiExtensionClient, err := clientset.NewForConfig(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dynamicClient, err := dynamic.NewForConfig(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("x-kubernetes-validations fields MUST be dropped from CRDs that are created when feature gate is disabled", func(t *testing.T) {
|
||||
schemaWithFeatureGateOff := crdWithSchema(t, "WithFeatureGateOff", structuralSchemaWithValidators)
|
||||
crdWithFeatureGateOff, err := fixtures.CreateNewV1CustomResourceDefinition(schemaWithFeatureGateOff, apiExtensionClient, dynamicClient)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s := crdWithFeatureGateOff.Spec.Versions[0].Schema.OpenAPIV3Schema
|
||||
if len(s.XValidations) != 0 {
|
||||
t.Errorf("Expected CRD to have no x-kubernetes-validatons rules but got: %v", s.XValidations)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestCustomResourceValidators tests x-kubernetes-validations compile and validate as expected when the feature gate
|
||||
// is enabled.
|
||||
func TestCustomResourceValidators(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CustomResourceValidationExpressions, true)()
|
||||
|
||||
server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer server.TearDownFn()
|
||||
config := server.ClientConfig
|
||||
|
||||
apiExtensionClient, err := clientset.NewForConfig(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dynamicClient, err := dynamic.NewForConfig(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("Structural schema", func(t *testing.T) {
|
||||
structuralWithValidators := crdWithSchema(t, "Structural", structuralSchemaWithValidators)
|
||||
crd, err := fixtures.CreateNewV1CustomResourceDefinition(structuralWithValidators, apiExtensionClient, dynamicClient)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: crd.Spec.Group,
|
||||
Version: crd.Spec.Versions[0].Name,
|
||||
Resource: crd.Spec.Names.Plural,
|
||||
}
|
||||
crClient := dynamicClient.Resource(gvr)
|
||||
|
||||
t.Run("CRD creation MUST allow data that is valid according to x-kubernetes-validations", func(t *testing.T) {
|
||||
name1 := names.SimpleNameGenerator.GenerateName("cr-1")
|
||||
_, err = crClient.Create(context.TODO(), &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": gvr.Group + "/" + gvr.Version,
|
||||
"kind": crd.Spec.Names.Kind,
|
||||
"metadata": map[string]interface{}{
|
||||
"name": name1,
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"x": int64(2),
|
||||
"y": int64(2),
|
||||
"limit": int64(123),
|
||||
},
|
||||
}}, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create custom resource: %v", err)
|
||||
}
|
||||
})
|
||||
t.Run("custom resource create and update MUST NOT allow data that is invalid according to x-kubernetes-validations if the feature gate is enabled", func(t *testing.T) {
|
||||
name1 := names.SimpleNameGenerator.GenerateName("cr-1")
|
||||
|
||||
// a spec create that is invalid MUST fail validation
|
||||
cr := &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": gvr.Group + "/" + gvr.Version,
|
||||
"kind": crd.Spec.Names.Kind,
|
||||
"metadata": map[string]interface{}{
|
||||
"name": name1,
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"x": int64(-1),
|
||||
"y": int64(0),
|
||||
},
|
||||
}}
|
||||
|
||||
// a spec create that is invalid MUST fail validation
|
||||
_, err = crClient.Create(context.TODO(), cr, metav1.CreateOptions{})
|
||||
if err == nil {
|
||||
t.Fatal("Expected create of invalid custom resource to fail")
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "failed rule: self.spec.x + self.spec.y") {
|
||||
t.Fatalf("Expected error to contain %s but got %v", "failed rule: self.spec.x + self.spec.y", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// a spec create that is valid MUST pass validation
|
||||
cr.Object["spec"] = map[string]interface{}{
|
||||
"x": int64(2),
|
||||
"y": int64(2),
|
||||
"extra": "anything?",
|
||||
"floatMap": map[string]interface{}{
|
||||
"key1": 0.2,
|
||||
"key2": 0.3,
|
||||
},
|
||||
"assocList": []interface{}{
|
||||
map[string]interface{}{
|
||||
"k": "a",
|
||||
"v": "1",
|
||||
},
|
||||
},
|
||||
"limit": nil,
|
||||
}
|
||||
|
||||
cr, err := crClient.Create(context.TODO(), cr, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error creating custom resource: %v", err)
|
||||
}
|
||||
|
||||
// spec updates that are invalid MUST fail validation
|
||||
cases := []struct {
|
||||
name string
|
||||
spec map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "spec vs. status default value",
|
||||
spec: map[string]interface{}{
|
||||
"x": 3,
|
||||
"y": -4,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested string field",
|
||||
spec: map[string]interface{}{
|
||||
"extra": "something",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested array",
|
||||
spec: map[string]interface{}{
|
||||
"floatMap": map[string]interface{}{
|
||||
"key1": 0.1,
|
||||
"key2": 0.2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested associative list",
|
||||
spec: map[string]interface{}{
|
||||
"assocList": []interface{}{
|
||||
map[string]interface{}{
|
||||
"k": "a",
|
||||
"v": "2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cr.Object["spec"] = tc.spec
|
||||
|
||||
_, err = crClient.Update(context.TODO(), cr, metav1.UpdateOptions{})
|
||||
if err == nil {
|
||||
t.Fatal("Expected invalid update of custom resource to fail")
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "failed rule") {
|
||||
t.Fatalf("Expected error to contain %s but got %v", "failed rule", err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// a status update that is invalid MUST fail validation
|
||||
cr.Object["status"] = map[string]interface{}{
|
||||
"z": int64(5),
|
||||
}
|
||||
_, err = crClient.UpdateStatus(context.TODO(), cr, metav1.UpdateOptions{})
|
||||
if err == nil {
|
||||
t.Fatal("Expected invalid update of custom resource status to fail")
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "failed rule: self.spec.x + self.spec.y") {
|
||||
t.Fatalf("Expected error to contain %s but got %v", "failed rule: self.spec.x + self.spec.y", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// a status update this is valid MUST pass validation
|
||||
cr.Object["status"] = map[string]interface{}{
|
||||
"z": int64(3),
|
||||
}
|
||||
|
||||
_, err = crClient.UpdateStatus(context.TODO(), cr, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error updating custom resource status: %v", err)
|
||||
}
|
||||
})
|
||||
})
|
||||
t.Run("CRD writes MUST fail for a non-structural schema containing x-kubernetes-validations", func(t *testing.T) {
|
||||
// The only way for a non-structural schema to exist is for it to already be persisted in etcd as a non-structural CRD.
|
||||
nonStructuralCRD, err := fixtures.CreateCRDUsingRemovedAPI(server.EtcdClient, server.EtcdStoragePrefix, nonStructuralCrdWithValidations(), apiExtensionClient, dynamicClient)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error non-structural CRD by writing directly to etcd: %v", err)
|
||||
}
|
||||
// Double check that the schema is non-structural
|
||||
crd, err := apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), nonStructuralCRD.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
nonStructural := false
|
||||
for _, c := range crd.Status.Conditions {
|
||||
if c.Type == apiextensionsv1.NonStructuralSchema {
|
||||
nonStructural = true
|
||||
}
|
||||
}
|
||||
if !nonStructural {
|
||||
t.Fatal("Expected CRD to be non-structural")
|
||||
}
|
||||
|
||||
//Try to change it
|
||||
crd.Spec.Versions[0].Schema.OpenAPIV3Schema.XValidations = apiextensionsv1.ValidationRules{
|
||||
{
|
||||
Rule: "has(self.foo)",
|
||||
},
|
||||
}
|
||||
_, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Update(context.TODO(), crd, metav1.UpdateOptions{})
|
||||
if err == nil {
|
||||
t.Fatal("Expected error")
|
||||
}
|
||||
})
|
||||
t.Run("CRD creation MUST fail if a x-kubernetes-validations rule accesses a metadata field other than name", func(t *testing.T) {
|
||||
structuralWithValidators := crdWithSchema(t, "InvalidStructuralMetadata", structuralSchemaWithInvalidMetadataValidators)
|
||||
_, err := fixtures.CreateNewV1CustomResourceDefinition(structuralWithValidators, apiExtensionClient, dynamicClient)
|
||||
if err == nil {
|
||||
t.Error("Expected error creating custom resource but got none")
|
||||
} else if !strings.Contains(err.Error(), "undefined field 'labels'") {
|
||||
t.Errorf("Expected error to contain %s but got %v", "undefined field 'labels'", err.Error())
|
||||
}
|
||||
})
|
||||
t.Run("CRD creation MUST pass if a x-kubernetes-validations rule accesses metadata.name", func(t *testing.T) {
|
||||
structuralWithValidators := crdWithSchema(t, "ValidStructuralMetadata", structuralSchemaWithValidMetadataValidators)
|
||||
_, err := fixtures.CreateNewV1CustomResourceDefinition(structuralWithValidators, apiExtensionClient, dynamicClient)
|
||||
if err != nil {
|
||||
t.Error("Unexpected error creating custom resource but metadata validation rule")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func nonStructuralCrdWithValidations() *apiextensionsv1beta1.CustomResourceDefinition {
|
||||
return &apiextensionsv1beta1.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foos.nonstructural.cr.bar.com",
|
||||
},
|
||||
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
|
||||
Group: "nonstructural.cr.bar.com",
|
||||
Version: "v1",
|
||||
Scope: apiextensionsv1beta1.NamespaceScoped,
|
||||
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
|
||||
Plural: "foos",
|
||||
Kind: "Foo",
|
||||
},
|
||||
Validation: &apiextensionsv1beta1.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextensionsv1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{
|
||||
"foo": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func crdWithSchema(t *testing.T, kind string, schemaJson []byte) *apiextensionsv1.CustomResourceDefinition {
|
||||
plural := strings.ToLower(kind) + "s"
|
||||
var c apiextensionsv1.CustomResourceValidation
|
||||
err := json.Unmarshal(schemaJson, &c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return &apiextensionsv1.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s.mygroup.example.com", plural)},
|
||||
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
|
||||
Group: "mygroup.example.com",
|
||||
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{
|
||||
Name: "v1beta1",
|
||||
Served: true,
|
||||
Storage: true,
|
||||
Schema: &c,
|
||||
Subresources: &apiextensionsv1.CustomResourceSubresources{
|
||||
Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
|
||||
},
|
||||
}},
|
||||
Names: apiextensionsv1.CustomResourceDefinitionNames{
|
||||
Plural: plural,
|
||||
Kind: kind,
|
||||
},
|
||||
Scope: apiextensionsv1.ClusterScoped,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var structuralSchemaWithValidators = []byte(`
|
||||
{
|
||||
"openAPIV3Schema": {
|
||||
"description": "CRD with CEL validators",
|
||||
"type": "object",
|
||||
"x-kubernetes-validations": [
|
||||
{
|
||||
"rule": "self.spec.x + self.spec.y >= (has(self.status) ? self.status.z : 0)"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"spec": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {
|
||||
"type": "integer",
|
||||
"default": 0
|
||||
},
|
||||
"y": {
|
||||
"type": "integer",
|
||||
"default": 0
|
||||
},
|
||||
"extra": {
|
||||
"type": "string",
|
||||
"x-kubernetes-validations": [
|
||||
{
|
||||
"rule": "self.startsWith('anything')"
|
||||
}
|
||||
]
|
||||
},
|
||||
"floatMap": {
|
||||
"type": "object",
|
||||
"additionalProperties": { "type": "number" },
|
||||
"x-kubernetes-validations": [
|
||||
{
|
||||
"rule": "self.all(k, self[k] >= 0.2)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"assocList": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"k": { "type": "string" },
|
||||
"v": { "type": "string" }
|
||||
},
|
||||
"required": ["k"]
|
||||
},
|
||||
"x-kubernetes-list-type": "map",
|
||||
"x-kubernetes-list-map-keys": ["k"],
|
||||
"x-kubernetes-validations": [
|
||||
{
|
||||
"rule": "self.exists(e, e.k == 'a' && e.v == '1')"
|
||||
}
|
||||
]
|
||||
},
|
||||
"limit": {
|
||||
"nullable": true,
|
||||
"x-kubernetes-validations": [
|
||||
{
|
||||
"rule": "type(self) == int && self == 123"
|
||||
}
|
||||
],
|
||||
"x-kubernetes-int-or-string": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"z": {
|
||||
"type": "integer",
|
||||
"default": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
var structuralSchemaWithValidMetadataValidators = []byte(`
|
||||
{
|
||||
"openAPIV3Schema": {
|
||||
"description": "CRD with CEL validators",
|
||||
"type": "object",
|
||||
"x-kubernetes-validations": [
|
||||
{
|
||||
"rule": "self.metadata.name.size() > 3"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"status": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
var structuralSchemaWithInvalidMetadataValidators = []byte(`
|
||||
{
|
||||
"openAPIV3Schema": {
|
||||
"description": "CRD with CEL validators",
|
||||
"type": "object",
|
||||
"x-kubernetes-validations": [
|
||||
{
|
||||
"rule": "self.metadata.labels.size() > 0"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"status": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
@ -1354,6 +1354,7 @@ 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/schema/cel
|
||||
k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting
|
||||
k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype
|
||||
k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta
|
||||
|
Loading…
Reference in New Issue
Block a user