From 42ee56f093133402ed860d4c5f54b049041386c9 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Mon, 4 Mar 2024 09:13:19 +0100 Subject: [PATCH] dra api: implement semver attribute value type This adds support for semantic version comparison to the CEL support in the "named resources" structured parameter model. For example, it can be used to check that an instance supports a certain API level. To minimize the risk, the new "semver" type is only defined in the CEL environment for DRA expressions, not in the base library. See https://github.com/kubernetes/kubernetes/pull/123664 for a PR which adds it to the base library. Validation of semver strings is done with the regular expression from semver.org. The actual evaluation at runtime then uses semver/v4. --- api/api-rules/violation_exceptions.list | 1 + api/openapi-spec/swagger.json | 4 + ...is__resource.k8s.io__v1alpha2_openapi.json | 4 + pkg/apis/resource/namedresources.go | 14 +- .../namedresources/validation/validation.go | 29 +- .../validation/validation_test.go | 43 ++- .../v1alpha2/zz_generated.conversion.go | 2 + pkg/apis/resource/zz_generated.deepcopy.go | 5 + pkg/generated/openapi/zz_generated.openapi.go | 14 + .../api/resource/v1alpha2/generated.pb.go | 329 ++++++++++-------- .../api/resource/v1alpha2/generated.proto | 3 + .../api/resource/v1alpha2/namedresources.go | 14 +- .../v1alpha2/zz_generated.deepcopy.go | 5 + ...rce.k8s.io.v1alpha2.NodeResourceSlice.json | 3 +- ...ource.k8s.io.v1alpha2.NodeResourceSlice.pb | Bin 519 -> 533 bytes ...rce.k8s.io.v1alpha2.NodeResourceSlice.yaml | 1 + .../applyconfigurations/internal/internal.go | 3 + .../v1alpha2/namedresourcesattribute.go | 8 + .../v1alpha2/namedresourcesattributevalue.go | 9 + .../k8s.io/dynamic-resource-allocation/go.mod | 1 + .../k8s.io/dynamic-resource-allocation/go.sum | 1 + .../structured/namedresources/cel/compile.go | 72 ++-- .../structured/namedresources/cel/semver.go | 73 ++++ .../namedresources/cel/semver_test.go | 209 +++++++++++ .../namedresources/cel/semverlib.go | 238 +++++++++++++ 25 files changed, 891 insertions(+), 194 deletions(-) create mode 100644 staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/semver.go create mode 100644 staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/semver_test.go create mode 100644 staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/semverlib.go diff --git a/api/api-rules/violation_exceptions.list b/api/api-rules/violation_exceptions.list index ad6ea5d5ff7..9658074af87 100644 --- a/api/api-rules/violation_exceptions.list +++ b/api/api-rules/violation_exceptions.list @@ -57,6 +57,7 @@ API rule violation: names_match,k8s.io/api/resource/v1alpha2,NamedResourcesAttri API rule violation: names_match,k8s.io/api/resource/v1alpha2,NamedResourcesAttributeValue,QuantityValue API rule violation: names_match,k8s.io/api/resource/v1alpha2,NamedResourcesAttributeValue,StringSliceValue API rule violation: names_match,k8s.io/api/resource/v1alpha2,NamedResourcesAttributeValue,StringValue +API rule violation: names_match,k8s.io/api/resource/v1alpha2,NamedResourcesAttributeValue,VersionValue API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,Ref API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,Schema API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XEmbeddedResource diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 98b12379620..28208b252ee 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -14900,6 +14900,10 @@ "stringSlice": { "$ref": "#/definitions/io.k8s.api.resource.v1alpha2.NamedResourcesStringSlice", "description": "StringSliceValue is an array of strings." + }, + "version": { + "description": "VersionValue is a semantic version according to semver.org spec 2.0.0.", + "type": "string" } }, "required": [ diff --git a/api/openapi-spec/v3/apis__resource.k8s.io__v1alpha2_openapi.json b/api/openapi-spec/v3/apis__resource.k8s.io__v1alpha2_openapi.json index eb9fed5e5d1..0d9a3b97082 100644 --- a/api/openapi-spec/v3/apis__resource.k8s.io__v1alpha2_openapi.json +++ b/api/openapi-spec/v3/apis__resource.k8s.io__v1alpha2_openapi.json @@ -228,6 +228,10 @@ } ], "description": "StringSliceValue is an array of strings." + }, + "version": { + "description": "VersionValue is a semantic version according to semver.org spec 2.0.0.", + "type": "string" } }, "required": [ diff --git a/pkg/apis/resource/namedresources.go b/pkg/apis/resource/namedresources.go index e64d2796a0e..cee94e34a84 100644 --- a/pkg/apis/resource/namedresources.go +++ b/pkg/apis/resource/namedresources.go @@ -59,7 +59,8 @@ type NamedResourcesAttributeValue struct { StringValue *string // StringSliceValue is an array of strings. StringSliceValue *NamedResourcesStringSlice - // TODO: VersionValue *SemVersion + // VersionValue is a semantic version according to semver.org spec 2.0.0. + VersionValue *string } // NamedResourcesIntSlice contains a slice of 64-bit integers. @@ -74,17 +75,6 @@ type NamedResourcesStringSlice struct { Strings []string } -// TODO -// -// A wrapper around https://pkg.go.dev/github.com/blang/semver/v4#Version which -// is encoded as a string. During decoding, it validates that the string -// can be parsed using tolerant parsing (currently trims spaces, removes a "v" prefix, -// adds a 0 patch number to versions with only major and minor components specified, -// and removes leading 0s). -// type SemVersion struct { -// semver.Version -//} - // NamedResourcesRequest is used in ResourceRequestModel. type NamedResourcesRequest struct { // Selector is a CEL expression which must evaluate to true if a diff --git a/pkg/apis/resource/structured/namedresources/validation/validation.go b/pkg/apis/resource/structured/namedresources/validation/validation.go index a9f703753e8..92748c4edc8 100644 --- a/pkg/apis/resource/structured/namedresources/validation/validation.go +++ b/pkg/apis/resource/structured/namedresources/validation/validation.go @@ -18,6 +18,7 @@ package validation import ( "fmt" + "regexp" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" @@ -63,6 +64,27 @@ func validateInstances(instances []resource.NamedResourcesInstance, fldPath *fie return allErrs } +var ( + numericIdentifier = `(0|[1-9]\d*)` + + preReleaseIdentifier = `(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + + buildIdentifier = `[0-9a-zA-Z-]+` + + semverRe = regexp.MustCompile(`^` + + + // dot-separated version segments (e.g. 1.2.3) + numericIdentifier + `\.` + numericIdentifier + `\.` + numericIdentifier + + + // optional dot-separated prerelease segments (e.g. -alpha.PRERELEASE.1) + `(-` + preReleaseIdentifier + `(\.` + preReleaseIdentifier + `)*)?` + + + // optional dot-separated build identifier segments (e.g. +build.id.20240305) + `(\+` + buildIdentifier + `(\.` + buildIdentifier + `)*)?` + + + `$`) +) + func validateAttributes(attributes []resource.NamedResourcesAttribute, fldPath *field.Path) field.ErrorList { var allErrs field.ErrorList attributeNames := sets.New[string]() @@ -95,7 +117,12 @@ func validateAttributes(attributes []resource.NamedResourcesAttribute, fldPath * if attribute.StringSliceValue != nil { entries.Insert("stringSlice") } - // TODO: VersionValue + if attribute.VersionValue != nil { + entries.Insert("version") + if !semverRe.MatchString(*attribute.VersionValue) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("version"), *attribute.VersionValue, "must be a string compatible with semver.org spec 2.0.0")) + } + } switch len(entries) { case 0: diff --git a/pkg/apis/resource/structured/namedresources/validation/validation_test.go b/pkg/apis/resource/structured/namedresources/validation/validation_test.go index 22f8f57196e..7fedfcae556 100644 --- a/pkg/apis/resource/structured/namedresources/validation/validation_test.go +++ b/pkg/apis/resource/structured/namedresources/validation/validation_test.go @@ -75,7 +75,44 @@ func TestValidateResources(t *testing.T) { "string-slice": { resources: testResources([]resourceapi.NamedResourcesInstance{{Name: goodName, Attributes: []resourceapi.NamedResourcesAttribute{{Name: goodName, NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{StringSliceValue: &resourceapi.NamedResourcesStringSlice{Strings: []string{"hello"}}}}}}}), }, - // TODO: semver + "version-okay": { + resources: testResources([]resourceapi.NamedResourcesInstance{{Name: goodName, Attributes: []resourceapi.NamedResourcesAttribute{{Name: goodName, NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{VersionValue: ptr.To("1.0.0")}}}}}), + }, + "version-beta": { + resources: testResources([]resourceapi.NamedResourcesInstance{{Name: goodName, Attributes: []resourceapi.NamedResourcesAttribute{{Name: goodName, NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{VersionValue: ptr.To("1.0.0-beta")}}}}}), + }, + "version-beta-1": { + resources: testResources([]resourceapi.NamedResourcesInstance{{Name: goodName, Attributes: []resourceapi.NamedResourcesAttribute{{Name: goodName, NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{VersionValue: ptr.To("1.0.0-beta.1")}}}}}), + }, + "version-build": { + resources: testResources([]resourceapi.NamedResourcesInstance{{Name: goodName, Attributes: []resourceapi.NamedResourcesAttribute{{Name: goodName, NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{VersionValue: ptr.To("1.0.0+build")}}}}}), + }, + "version-build-1": { + resources: testResources([]resourceapi.NamedResourcesInstance{{Name: goodName, Attributes: []resourceapi.NamedResourcesAttribute{{Name: goodName, NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{VersionValue: ptr.To("1.0.0+build.1")}}}}}), + }, + "version-beta-1-build-1": { + resources: testResources([]resourceapi.NamedResourcesInstance{{Name: goodName, Attributes: []resourceapi.NamedResourcesAttribute{{Name: goodName, NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{VersionValue: ptr.To("1.0.0-beta.1+build.1")}}}}}), + }, + "version-bad": { + wantFailures: field.ErrorList{field.Invalid(field.NewPath("instances").Index(0).Child("attributes").Index(0).Child("version"), "1.0", "must be a string compatible with semver.org spec 2.0.0")}, + resources: testResources([]resourceapi.NamedResourcesInstance{{Name: goodName, Attributes: []resourceapi.NamedResourcesAttribute{{Name: goodName, NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{VersionValue: ptr.To("1.0")}}}}}), + }, + "version-bad-leading-zeros": { + wantFailures: field.ErrorList{field.Invalid(field.NewPath("instances").Index(0).Child("attributes").Index(0).Child("version"), "01.0.0", "must be a string compatible with semver.org spec 2.0.0")}, + resources: testResources([]resourceapi.NamedResourcesInstance{{Name: goodName, Attributes: []resourceapi.NamedResourcesAttribute{{Name: goodName, NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{VersionValue: ptr.To("01.0.0")}}}}}), + }, + "version-bad-leading-zeros-middle": { + wantFailures: field.ErrorList{field.Invalid(field.NewPath("instances").Index(0).Child("attributes").Index(0).Child("version"), "1.00.0", "must be a string compatible with semver.org spec 2.0.0")}, + resources: testResources([]resourceapi.NamedResourcesInstance{{Name: goodName, Attributes: []resourceapi.NamedResourcesAttribute{{Name: goodName, NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{VersionValue: ptr.To("1.00.0")}}}}}), + }, + "version-bad-leading-zeros-end": { + wantFailures: field.ErrorList{field.Invalid(field.NewPath("instances").Index(0).Child("attributes").Index(0).Child("version"), "1.0.00", "must be a string compatible with semver.org spec 2.0.0")}, + resources: testResources([]resourceapi.NamedResourcesInstance{{Name: goodName, Attributes: []resourceapi.NamedResourcesAttribute{{Name: goodName, NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{VersionValue: ptr.To("1.0.00")}}}}}), + }, + "version-bad-spaces": { + wantFailures: field.ErrorList{field.Invalid(field.NewPath("instances").Index(0).Child("attributes").Index(0).Child("version"), " 1.0.0 ", "must be a string compatible with semver.org spec 2.0.0")}, + resources: testResources([]resourceapi.NamedResourcesInstance{{Name: goodName, Attributes: []resourceapi.NamedResourcesAttribute{{Name: goodName, NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{VersionValue: ptr.To(" 1.0.0 ")}}}}}), + }, "empty-attribute": { wantFailures: field.ErrorList{field.Required(field.NewPath("instances").Index(0).Child("attributes").Index(0), "exactly one value must be set")}, resources: testResources([]resourceapi.NamedResourcesInstance{{Name: goodName, Attributes: []resourceapi.NamedResourcesAttribute{{Name: goodName}}}}), @@ -132,7 +169,9 @@ func TestValidateSelector(t *testing.T) { "stringslice": { selector: `attributes.stringslice["name"].isSorted()`, }, - // TODO: semver + "version": { + selector: `attributes.version["name"].isGreaterThan(semver("1.0.0"))`, + }, } for name, scenario := range scenarios { diff --git a/pkg/apis/resource/v1alpha2/zz_generated.conversion.go b/pkg/apis/resource/v1alpha2/zz_generated.conversion.go index 0562983865a..e99c229accb 100644 --- a/pkg/apis/resource/v1alpha2/zz_generated.conversion.go +++ b/pkg/apis/resource/v1alpha2/zz_generated.conversion.go @@ -679,6 +679,7 @@ func autoConvert_v1alpha2_NamedResourcesAttributeValue_To_resource_NamedResource out.IntSliceValue = (*resource.NamedResourcesIntSlice)(unsafe.Pointer(in.IntSliceValue)) out.StringValue = (*string)(unsafe.Pointer(in.StringValue)) out.StringSliceValue = (*resource.NamedResourcesStringSlice)(unsafe.Pointer(in.StringSliceValue)) + out.VersionValue = (*string)(unsafe.Pointer(in.VersionValue)) return nil } @@ -694,6 +695,7 @@ func autoConvert_resource_NamedResourcesAttributeValue_To_v1alpha2_NamedResource out.IntSliceValue = (*v1alpha2.NamedResourcesIntSlice)(unsafe.Pointer(in.IntSliceValue)) out.StringValue = (*string)(unsafe.Pointer(in.StringValue)) out.StringSliceValue = (*v1alpha2.NamedResourcesStringSlice)(unsafe.Pointer(in.StringSliceValue)) + out.VersionValue = (*string)(unsafe.Pointer(in.VersionValue)) return nil } diff --git a/pkg/apis/resource/zz_generated.deepcopy.go b/pkg/apis/resource/zz_generated.deepcopy.go index 44d2aa3bc94..df0fd44e2d2 100644 --- a/pkg/apis/resource/zz_generated.deepcopy.go +++ b/pkg/apis/resource/zz_generated.deepcopy.go @@ -187,6 +187,11 @@ func (in *NamedResourcesAttributeValue) DeepCopyInto(out *NamedResourcesAttribut *out = new(NamedResourcesStringSlice) (*in).DeepCopyInto(*out) } + if in.VersionValue != nil { + in, out := &in.VersionValue, &out.VersionValue + *out = new(string) + **out = **in + } return } diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index eccec33d32f..32ac61db12d 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -44907,6 +44907,13 @@ func schema_k8sio_api_resource_v1alpha2_NamedResourcesAttribute(ref common.Refer Ref: ref("k8s.io/api/resource/v1alpha2.NamedResourcesStringSlice"), }, }, + "version": { + SchemaProps: spec.SchemaProps{ + Description: "VersionValue is a semantic version according to semver.org spec 2.0.0.", + Type: []string{"string"}, + Format: "", + }, + }, }, Required: []string{"name"}, }, @@ -44962,6 +44969,13 @@ func schema_k8sio_api_resource_v1alpha2_NamedResourcesAttributeValue(ref common. Ref: ref("k8s.io/api/resource/v1alpha2.NamedResourcesStringSlice"), }, }, + "version": { + SchemaProps: spec.SchemaProps{ + Description: "VersionValue is a semantic version according to semver.org spec 2.0.0.", + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, diff --git a/staging/src/k8s.io/api/resource/v1alpha2/generated.pb.go b/staging/src/k8s.io/api/resource/v1alpha2/generated.pb.go index 82fb3c0f1c6..0144435a342 100644 --- a/staging/src/k8s.io/api/resource/v1alpha2/generated.pb.go +++ b/staging/src/k8s.io/api/resource/v1alpha2/generated.pb.go @@ -1331,147 +1331,149 @@ func init() { } var fileDescriptor_4312f5b44a31ec02 = []byte{ - // 2231 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x1a, 0x4b, 0x6f, 0x1b, 0xc7, - 0xd9, 0x4b, 0xd2, 0x16, 0xf5, 0xc9, 0xa2, 0xa4, 0xb5, 0x64, 0xd1, 0x8e, 0x42, 0x2a, 0x8b, 0x16, - 0x15, 0x50, 0x9b, 0x8c, 0xe5, 0xc4, 0x31, 0xd2, 0xb4, 0x80, 0xd7, 0x8a, 0x5d, 0xa1, 0x89, 0xa3, - 0x0c, 0x63, 0x27, 0x4e, 0x5f, 0x59, 0x71, 0xc7, 0xd2, 0xd6, 0xe4, 0x2e, 0xbd, 0x33, 0x54, 0x6c, - 0xf4, 0x62, 0x14, 0x7d, 0x5d, 0x0a, 0xa4, 0x68, 0x51, 0xb4, 0xa7, 0x9e, 0x8a, 0xa2, 0x97, 0x5e, - 0xda, 0x7b, 0x0f, 0x41, 0x1b, 0x03, 0xbd, 0x38, 0x68, 0x81, 0x06, 0x3d, 0x10, 0x35, 0xfb, 0x13, - 0x7a, 0xcb, 0xa9, 0x98, 0xc7, 0x3e, 0x66, 0xb9, 0x4b, 0x71, 0xd9, 0x46, 0x48, 0x4e, 0xd2, 0xce, - 0x7c, 0xef, 0xef, 0x9b, 0xef, 0x31, 0x43, 0x38, 0x77, 0xf7, 0x32, 0x69, 0x38, 0x5e, 0xd3, 0xea, - 0x39, 0x4d, 0x1f, 0x13, 0xaf, 0xef, 0xb7, 0x71, 0xf3, 0xe0, 0x82, 0xd5, 0xe9, 0xed, 0x5b, 0x9b, - 0xcd, 0x3d, 0xec, 0x62, 0xdf, 0xa2, 0xd8, 0x6e, 0xf4, 0x7c, 0x8f, 0x7a, 0xfa, 0x9a, 0x80, 0x6e, - 0x58, 0x3d, 0xa7, 0x11, 0x40, 0x37, 0x02, 0xe8, 0xb3, 0xe7, 0xf7, 0x1c, 0xba, 0xdf, 0xdf, 0x6d, - 0xb4, 0xbd, 0x6e, 0x73, 0xcf, 0xdb, 0xf3, 0x9a, 0x1c, 0x69, 0xb7, 0x7f, 0x87, 0x7f, 0xf1, 0x0f, - 0xfe, 0x9f, 0x20, 0x76, 0xd6, 0x88, 0xb1, 0x6e, 0x7b, 0x3e, 0x63, 0x9b, 0x64, 0x78, 0xf6, 0xb9, - 0x08, 0xa6, 0x6b, 0xb5, 0xf7, 0x1d, 0x17, 0xfb, 0x0f, 0x9a, 0xbd, 0xbb, 0x7b, 0xaa, 0xbc, 0x79, - 0xb0, 0x48, 0xb3, 0x8b, 0xa9, 0x95, 0xc6, 0xab, 0x99, 0x85, 0xe5, 0xf7, 0x5d, 0xea, 0x74, 0x47, - 0xd9, 0x5c, 0x3a, 0x0c, 0x81, 0xb4, 0xf7, 0x71, 0xd7, 0x4a, 0xe2, 0x19, 0xbf, 0x2c, 0xc0, 0xe2, - 0x95, 0x4e, 0xc7, 0x6b, 0x5b, 0xd4, 0xf1, 0x5c, 0x84, 0x49, 0xbf, 0x43, 0x75, 0x0f, 0x16, 0x02, - 0x7d, 0xbe, 0x6a, 0xb9, 0x76, 0x07, 0x93, 0xaa, 0xb6, 0x5e, 0xdc, 0x98, 0xdb, 0x3c, 0xd7, 0x18, - 0x67, 0xf4, 0x06, 0x52, 0x90, 0xcc, 0xd5, 0x47, 0x83, 0xfa, 0xb1, 0xe1, 0xa0, 0xbe, 0xa0, 0xae, - 0x13, 0x94, 0xa4, 0xae, 0xef, 0xc2, 0xa2, 0x75, 0x60, 0x39, 0x1d, 0x6b, 0xb7, 0x83, 0x5f, 0x73, - 0x6f, 0x78, 0x36, 0x26, 0xd5, 0xc2, 0xba, 0xb6, 0x31, 0xb7, 0xb9, 0x1e, 0xe7, 0xc8, 0x3c, 0xd3, - 0x38, 0xb8, 0xd0, 0x60, 0x00, 0x2d, 0xdc, 0xc1, 0x6d, 0xea, 0xf9, 0xe6, 0xf2, 0x70, 0x50, 0x5f, - 0xbc, 0x92, 0xc0, 0x46, 0x23, 0xf4, 0xf4, 0x26, 0xcc, 0x92, 0x7d, 0xcb, 0xc7, 0x6c, 0xad, 0x5a, - 0x5c, 0xd7, 0x36, 0xca, 0xe6, 0x92, 0x14, 0x70, 0xb6, 0x15, 0x6c, 0xa0, 0x08, 0xc6, 0xf8, 0xa9, - 0x06, 0x2b, 0x49, 0xd3, 0xbc, 0xea, 0xd9, 0xb8, 0xa3, 0xdf, 0x87, 0x8a, 0x6b, 0x75, 0xb1, 0x1d, - 0xe8, 0xc5, 0xcc, 0xc3, 0x84, 0x7d, 0x69, 0xbc, 0x79, 0x6e, 0x28, 0x38, 0x49, 0xd2, 0xa6, 0x3e, - 0x1c, 0xd4, 0x2b, 0x2a, 0x0c, 0x4a, 0xf0, 0x31, 0x7e, 0x5f, 0x80, 0xd3, 0x5b, 0xbe, 0x73, 0x80, - 0xfd, 0x11, 0xa7, 0xfd, 0x58, 0x83, 0xd5, 0x03, 0xec, 0xda, 0x9e, 0x8f, 0xf0, 0xbd, 0x3e, 0x26, - 0x74, 0xc7, 0xf2, 0xad, 0x2e, 0xa6, 0xd8, 0x0f, 0xc4, 0x3b, 0x1f, 0x13, 0x2f, 0x0c, 0x92, 0x46, - 0xef, 0xee, 0x5e, 0x43, 0x06, 0x49, 0x03, 0x59, 0xef, 0xbe, 0x7c, 0x9f, 0x62, 0x97, 0x38, 0x9e, - 0x6b, 0xd6, 0xa5, 0x75, 0x56, 0x6f, 0xa5, 0x53, 0x45, 0x59, 0xec, 0x98, 0x28, 0x2b, 0x56, 0x9a, - 0xe5, 0xa4, 0x53, 0x2f, 0x8e, 0xb7, 0x53, 0xaa, 0xd1, 0xcd, 0xa7, 0xa5, 0x38, 0xe9, 0x3e, 0x41, - 0xe9, 0x0c, 0x8d, 0x5f, 0x14, 0xa0, 0x22, 0x0c, 0x26, 0xc5, 0x24, 0xfa, 0x26, 0x80, 0xcd, 0x57, - 0x98, 0xad, 0xb9, 0x69, 0x66, 0x4d, 0x5d, 0x12, 0x87, 0xad, 0x70, 0x07, 0xc5, 0xa0, 0x74, 0x02, - 0x8b, 0x42, 0xd9, 0x98, 0x51, 0x0b, 0xd3, 0x18, 0xb5, 0x2a, 0x19, 0x2d, 0xde, 0x4a, 0x90, 0x43, - 0x23, 0x0c, 0xf4, 0xaf, 0x43, 0xd9, 0x97, 0x42, 0x57, 0x8b, 0xfc, 0xfc, 0x9d, 0x9f, 0xec, 0xfc, - 0x49, 0x55, 0xcd, 0x45, 0xc9, 0xac, 0x1c, 0xe8, 0x8e, 0x42, 0x82, 0x86, 0x09, 0xb5, 0xf1, 0xf1, - 0xa8, 0xaf, 0x43, 0xc9, 0x8d, 0x2c, 0x74, 0x52, 0xd2, 0x2a, 0x71, 0xdb, 0xf0, 0x1d, 0xe3, 0x2f, - 0x1a, 0xac, 0x26, 0x88, 0x50, 0xea, 0x3b, 0xbb, 0x7d, 0x8a, 0x0f, 0xc7, 0x66, 0x51, 0x52, 0xb1, - 0x02, 0xf8, 0x5b, 0x56, 0xa7, 0x8f, 0xa5, 0x49, 0x5f, 0xcc, 0x75, 0x8c, 0x14, 0x0a, 0xe6, 0xe7, - 0x24, 0xa3, 0xb5, 0x71, 0x50, 0x28, 0xc1, 0xd7, 0xf8, 0x53, 0x11, 0xc6, 0x22, 0xe8, 0xdf, 0x84, - 0xf2, 0xbd, 0xbe, 0xe5, 0x52, 0x87, 0x3e, 0xa8, 0x9e, 0xe0, 0x42, 0x36, 0x32, 0xfd, 0xae, 0x48, - 0xfd, 0xba, 0xc4, 0x32, 0x97, 0x86, 0x83, 0xfa, 0x7c, 0xf0, 0x25, 0xa4, 0x08, 0x49, 0xea, 0xcf, - 0x40, 0x69, 0xd7, 0xf3, 0xc4, 0xf1, 0x28, 0x9b, 0xf3, 0x2c, 0x25, 0x99, 0x9e, 0xd7, 0x11, 0x60, - 0x7c, 0x4b, 0xaf, 0x41, 0xd1, 0x71, 0x69, 0x75, 0x66, 0x5d, 0xdb, 0x28, 0x9a, 0x27, 0x99, 0x53, - 0xb7, 0x5d, 0x2a, 0x00, 0xd8, 0x86, 0xde, 0x86, 0xb2, 0xe3, 0xd2, 0x56, 0xc7, 0x69, 0xe3, 0x6a, - 0x99, 0x4b, 0xf8, 0x5c, 0x1e, 0x33, 0x6e, 0x4b, 0x5c, 0x21, 0x67, 0xf0, 0x25, 0xe5, 0x0c, 0x08, - 0xeb, 0x5f, 0x80, 0x13, 0x84, 0xfa, 0x8e, 0xbb, 0x57, 0x3d, 0xce, 0xdd, 0xba, 0x30, 0x1c, 0xd4, - 0xe7, 0x5a, 0x7c, 0x45, 0x80, 0xca, 0x6d, 0xdd, 0x83, 0x39, 0xf1, 0x9f, 0x10, 0x68, 0x96, 0x0b, - 0xf4, 0x42, 0x1e, 0x81, 0x5a, 0x11, 0xba, 0x48, 0xf1, 0xb1, 0x05, 0xc1, 0x2b, 0xce, 0xc1, 0xd8, - 0x82, 0x65, 0x15, 0xff, 0x9a, 0xd3, 0xa1, 0xd8, 0xd7, 0xcf, 0x41, 0x99, 0xc8, 0x4a, 0x21, 0x43, - 0x31, 0x3c, 0x14, 0x41, 0x05, 0x41, 0x21, 0x84, 0xf1, 0x1b, 0x0d, 0x4e, 0x27, 0xed, 0x42, 0xa8, - 0xe5, 0xb6, 0x27, 0x89, 0x67, 0x07, 0x20, 0x0c, 0x2b, 0x96, 0x1d, 0xd8, 0x81, 0x7d, 0x7e, 0xaa, - 0x50, 0x8e, 0xd2, 0x51, 0xb8, 0x44, 0x50, 0x8c, 0xb8, 0x71, 0x69, 0x54, 0x4c, 0xe9, 0xa1, 0x35, - 0x28, 0x39, 0x2e, 0x15, 0xf5, 0xba, 0x68, 0x96, 0x99, 0x88, 0xdb, 0x2e, 0x25, 0x88, 0xaf, 0x1a, - 0x2f, 0xc3, 0x4a, 0xa2, 0xc0, 0x88, 0x74, 0x90, 0xd3, 0x4c, 0x0f, 0x47, 0xce, 0x7d, 0xf8, 0x8f, - 0x8e, 0x61, 0xd6, 0x91, 0x36, 0x0b, 0xba, 0x86, 0x9c, 0x81, 0x28, 0x90, 0xa3, 0xe2, 0x1c, 0xac, - 0x10, 0x14, 0x51, 0x36, 0x4c, 0x38, 0x93, 0x19, 0x2f, 0xfa, 0xe7, 0x61, 0x46, 0xc4, 0x86, 0x90, - 0x60, 0xd6, 0x9c, 0x1b, 0x0e, 0xea, 0x33, 0x02, 0x82, 0xa0, 0x60, 0xcf, 0xf8, 0xa1, 0x06, 0x4b, - 0xac, 0x37, 0x08, 0x68, 0x88, 0xe2, 0x7e, 0x2f, 0xa3, 0xb8, 0xe7, 0x72, 0x65, 0xf8, 0xcf, 0x44, - 0x55, 0xfd, 0xc3, 0x82, 0x2a, 0x88, 0xd0, 0xe2, 0x1d, 0x28, 0xb3, 0xf6, 0xd0, 0xb6, 0xa8, 0x25, - 0x45, 0x78, 0x76, 0x5c, 0xce, 0x21, 0x0d, 0x06, 0xcd, 0xda, 0xa3, 0xd7, 0x76, 0xbf, 0x83, 0xdb, - 0xf4, 0x55, 0x4c, 0xad, 0x28, 0x90, 0xa2, 0x35, 0x14, 0x52, 0x65, 0x5e, 0x77, 0x3d, 0x1b, 0xf3, - 0x3a, 0x58, 0x50, 0xbd, 0x7e, 0x43, 0xae, 0xa3, 0x10, 0x22, 0x51, 0x37, 0x8b, 0x13, 0xd5, 0xcd, - 0xfb, 0xb0, 0xe4, 0x26, 0x2d, 0x5c, 0x2d, 0x71, 0x65, 0x9a, 0x87, 0xd8, 0x33, 0x89, 0x66, 0x9e, - 0x91, 0xbc, 0x46, 0x7d, 0x86, 0x46, 0x99, 0x18, 0x7f, 0xd5, 0x60, 0x65, 0xc4, 0xa6, 0xaf, 0x38, - 0x84, 0xea, 0xdf, 0x18, 0xb1, 0x6b, 0x63, 0x32, 0xbb, 0x32, 0x6c, 0x6e, 0xd5, 0xd0, 0x4a, 0xc1, - 0x4a, 0xcc, 0xa6, 0x6f, 0xc0, 0x71, 0x87, 0xe2, 0x6e, 0x90, 0x00, 0x72, 0x68, 0x29, 0x72, 0xdd, - 0xbc, 0xa4, 0x7d, 0x7c, 0x9b, 0x51, 0x41, 0x82, 0x98, 0xf1, 0xc7, 0x02, 0x2c, 0xef, 0x78, 0x76, - 0xab, 0xbd, 0x8f, 0xed, 0x7e, 0xc7, 0x71, 0xf7, 0xae, 0x7a, 0x2e, 0xc5, 0xf7, 0xe9, 0x11, 0x04, - 0xc9, 0x5b, 0x50, 0x22, 0x3d, 0xdc, 0x96, 0xb5, 0xf9, 0xd2, 0x78, 0x7d, 0xd2, 0x64, 0x6c, 0xf5, - 0x70, 0x3b, 0x4a, 0x98, 0xec, 0x0b, 0x71, 0x8a, 0xfa, 0x3b, 0xac, 0x9a, 0x58, 0xb4, 0x4f, 0x78, - 0x30, 0xcd, 0x6d, 0x5e, 0x9e, 0x82, 0x36, 0xc7, 0x37, 0x2b, 0x92, 0xfa, 0x09, 0xf1, 0x8d, 0x24, - 0x5d, 0xe3, 0x43, 0x0d, 0xaa, 0x69, 0x68, 0x47, 0x10, 0x07, 0x6f, 0xaa, 0x71, 0xb0, 0x99, 0x5f, - 0xb7, 0x8c, 0x50, 0x78, 0x2f, 0x43, 0x27, 0x66, 0x58, 0xfd, 0x32, 0x9c, 0x14, 0x59, 0x1a, 0xdb, - 0x2c, 0xb4, 0x64, 0x2e, 0x5f, 0x96, 0x84, 0x4e, 0xb6, 0x62, 0x7b, 0x48, 0x81, 0xd4, 0x5f, 0x84, - 0x4a, 0xcf, 0xa3, 0xd8, 0xa5, 0x8e, 0xd5, 0x09, 0x06, 0x30, 0x96, 0x3a, 0x79, 0xfe, 0xda, 0x51, - 0x76, 0x50, 0x02, 0xd2, 0xf8, 0x95, 0x06, 0x67, 0xb3, 0xbd, 0xa3, 0x7f, 0x17, 0x2a, 0x81, 0xc6, - 0x57, 0x3b, 0x96, 0xd3, 0x0d, 0xea, 0xc2, 0x97, 0x26, 0xeb, 0x66, 0x39, 0x4e, 0x44, 0x5b, 0xba, - 0xfc, 0xb4, 0xd4, 0xa9, 0xa2, 0x80, 0x11, 0x94, 0x60, 0x65, 0xfc, 0xba, 0x00, 0xf3, 0x0a, 0xc8, - 0x11, 0x1c, 0x99, 0xd7, 0x95, 0x23, 0xd3, 0xcc, 0xa3, 0x66, 0xd6, 0x59, 0xb9, 0x9d, 0x38, 0x2b, - 0x17, 0xf2, 0x10, 0x1d, 0x7f, 0x48, 0x86, 0x1a, 0xd4, 0x14, 0xf8, 0xab, 0x9e, 0x4b, 0xfa, 0x5d, - 0x36, 0x31, 0xdd, 0xc1, 0x3e, 0x66, 0xcd, 0xcf, 0x39, 0x28, 0x5b, 0x3d, 0xe7, 0xba, 0xef, 0xf5, - 0x7b, 0xc9, 0xf6, 0xe0, 0xca, 0xce, 0x36, 0x5f, 0x47, 0x21, 0x04, 0x83, 0x0e, 0x24, 0x92, 0x65, - 0x22, 0x36, 0x88, 0xc8, 0x09, 0x25, 0x84, 0x08, 0x1b, 0xab, 0x52, 0x66, 0x63, 0x65, 0x42, 0xb1, - 0xef, 0xd8, 0xb2, 0xe5, 0x7c, 0x56, 0x02, 0x14, 0x6f, 0x6e, 0x6f, 0x7d, 0x3c, 0xa8, 0x3f, 0x93, - 0x75, 0xef, 0x41, 0x1f, 0xf4, 0x30, 0x69, 0xdc, 0xdc, 0xde, 0x42, 0x0c, 0xd9, 0x78, 0x5f, 0x83, - 0x25, 0x45, 0xc9, 0x23, 0x48, 0x01, 0x3b, 0x6a, 0x0a, 0xf8, 0x62, 0x0e, 0x97, 0x65, 0x9c, 0xfd, - 0x9f, 0x15, 0x61, 0x55, 0x81, 0x8b, 0x4d, 0x8b, 0x9f, 0x7c, 0x58, 0xbf, 0x0b, 0xf3, 0xe1, 0xf5, - 0xd1, 0x35, 0xdf, 0xeb, 0xca, 0xf8, 0xfe, 0x4a, 0x0e, 0xbd, 0x62, 0xf3, 0x6e, 0x10, 0x5c, 0x62, - 0xe2, 0xb8, 0x1e, 0x27, 0x8c, 0x54, 0x3e, 0xb9, 0xaf, 0x6e, 0xf4, 0x0e, 0x54, 0x6c, 0x65, 0xe8, - 0xaf, 0x96, 0x26, 0xb9, 0xbf, 0x52, 0x2f, 0x0a, 0xa2, 0x14, 0xa3, 0xae, 0xa3, 0x04, 0x6d, 0xe3, - 0x1f, 0x1a, 0x3c, 0x95, 0xa1, 0xe5, 0x11, 0x44, 0xd9, 0xdb, 0x6a, 0x94, 0x3d, 0x3f, 0x95, 0x37, - 0x32, 0xe2, 0xed, 0xe7, 0x1a, 0xac, 0x1f, 0xe6, 0xbf, 0x9c, 0xc9, 0x61, 0x1d, 0x4a, 0x77, 0x1d, - 0xd7, 0x96, 0xfd, 0x66, 0x78, 0xdc, 0xbf, 0xe6, 0xb8, 0x36, 0xe2, 0x3b, 0x61, 0x42, 0x28, 0x66, - 0xde, 0x3b, 0x3c, 0xd4, 0xe0, 0xe9, 0xb1, 0xd5, 0x61, 0x82, 0x69, 0xed, 0xcb, 0xb0, 0xd0, 0x77, - 0x49, 0xdf, 0xa1, 0x2c, 0x60, 0xe2, 0x05, 0xef, 0xd4, 0x70, 0x50, 0x5f, 0xb8, 0xa9, 0x6e, 0xa1, - 0x24, 0xac, 0xf1, 0xdb, 0x42, 0x22, 0x9f, 0xf0, 0xf2, 0x7b, 0x1d, 0x96, 0x62, 0xe5, 0x87, 0x90, - 0xd8, 0x0d, 0x53, 0xd8, 0xbd, 0xa2, 0x24, 0x00, 0x1a, 0xc5, 0x61, 0x47, 0xad, 0x17, 0x37, 0xf5, - 0xff, 0xf3, 0xa8, 0x29, 0x1b, 0x48, 0xe5, 0xa3, 0xef, 0x40, 0x25, 0xba, 0x48, 0x63, 0x9d, 0xb4, - 0x74, 0xc3, 0x46, 0x70, 0x16, 0xae, 0x28, 0xbb, 0x1f, 0x8f, 0xac, 0xa0, 0x04, 0xbe, 0xf1, 0x9f, - 0x02, 0x9c, 0x4a, 0x29, 0x47, 0x53, 0x5d, 0xc3, 0x7d, 0x0b, 0x20, 0xa2, 0x2e, 0x6d, 0xd2, 0xc8, - 0x77, 0x99, 0x68, 0x56, 0xf8, 0x5c, 0x1d, 0xad, 0xc6, 0x28, 0xea, 0x04, 0xe6, 0x7c, 0x4c, 0xb0, - 0x7f, 0x80, 0xed, 0x6b, 0x9e, 0x2f, 0x2f, 0xdd, 0x5e, 0xca, 0x61, 0xf4, 0x91, 0xd2, 0x69, 0x9e, - 0x92, 0x2a, 0xcd, 0xa1, 0x88, 0x30, 0x8a, 0x73, 0xd1, 0x5b, 0xb0, 0x62, 0xe3, 0xf8, 0xed, 0x25, - 0x4f, 0x2b, 0xd8, 0xe6, 0x15, 0xb1, 0x1c, 0xdd, 0x7b, 0x6e, 0xa5, 0x01, 0xa1, 0x74, 0x5c, 0xe3, - 0xef, 0x1a, 0xac, 0x28, 0x92, 0xbd, 0x81, 0xbb, 0xbd, 0x8e, 0x45, 0x8f, 0x62, 0xac, 0xbc, 0xad, - 0xb4, 0x3f, 0x2f, 0xe4, 0x30, 0x5f, 0x20, 0x64, 0x56, 0x1b, 0x64, 0xfc, 0x4d, 0x83, 0x33, 0xa9, - 0x18, 0x47, 0x90, 0x68, 0xdf, 0x52, 0x13, 0xed, 0xc5, 0x29, 0xf4, 0xca, 0x48, 0xb3, 0x8f, 0xb3, - 0xb4, 0x6a, 0x89, 0x31, 0xe9, 0xb3, 0xd7, 0xaf, 0x1a, 0x1f, 0x14, 0x95, 0xb6, 0x9b, 0x1c, 0x45, - 0x7f, 0xa2, 0x66, 0x94, 0xc2, 0x44, 0x19, 0x65, 0x24, 0xd1, 0x16, 0x73, 0x26, 0x5a, 0x42, 0xa6, - 0x4b, 0xb4, 0xb7, 0x61, 0x5e, 0xad, 0x3e, 0xa5, 0x09, 0xdf, 0xbb, 0x38, 0xe9, 0x96, 0x52, 0x9d, - 0x54, 0x4a, 0xfa, 0x2b, 0xb0, 0x4c, 0xa8, 0xdf, 0x6f, 0xd3, 0xbe, 0x8f, 0xed, 0xd8, 0x83, 0xc5, - 0x71, 0x9e, 0x4f, 0xaa, 0xc3, 0x41, 0x7d, 0xb9, 0x95, 0xb2, 0x8f, 0x52, 0xb1, 0x92, 0x9d, 0x33, - 0x21, 0x9f, 0xe6, 0xce, 0x99, 0x64, 0x75, 0x32, 0xef, 0xab, 0x9d, 0x73, 0xdc, 0x6b, 0x9f, 0x85, - 0xce, 0x79, 0x4c, 0x94, 0x8d, 0xed, 0x9c, 0x69, 0xca, 0xbb, 0x95, 0xa8, 0x6a, 0x87, 0x94, 0xcd, - 0xe4, 0xf3, 0x54, 0xae, 0x87, 0xab, 0x37, 0x61, 0xe6, 0x0e, 0xbf, 0x7e, 0x9f, 0xb0, 0xef, 0x0e, - 0x14, 0x15, 0x77, 0xf6, 0xe6, 0x82, 0x64, 0x35, 0x23, 0xbe, 0x09, 0x0a, 0xa8, 0x25, 0x3b, 0xed, - 0xb8, 0x55, 0x3e, 0xcd, 0x9d, 0x76, 0x5c, 0xce, 0x8c, 0xf8, 0xfc, 0xb3, 0xda, 0x69, 0xa7, 0xfa, - 0xfb, 0xe8, 0x3b, 0x6d, 0x36, 0x79, 0xb1, 0xbf, 0xa4, 0x67, 0xb5, 0x83, 0x09, 0x3d, 0x9c, 0xbc, - 0x6e, 0x04, 0x1b, 0x28, 0x82, 0x31, 0x3e, 0xd0, 0xa0, 0xa2, 0xba, 0x73, 0xaa, 0x46, 0xef, 0xa1, - 0x06, 0xa7, 0x7c, 0x85, 0x4c, 0xfc, 0xfd, 0xf8, 0x42, 0x9e, 0x70, 0x12, 0x97, 0xc7, 0x4f, 0x49, - 0x86, 0xa7, 0x52, 0x36, 0x51, 0x1a, 0x2b, 0xe3, 0x07, 0x1a, 0xa4, 0x01, 0xeb, 0x6e, 0xc6, 0xfb, - 0xc0, 0x66, 0x9e, 0xf7, 0x01, 0x19, 0xe9, 0x93, 0x3c, 0x0e, 0xfc, 0x33, 0x66, 0x51, 0xf1, 0x7b, - 0x89, 0xa9, 0x2c, 0xba, 0x0e, 0x25, 0x7e, 0x2c, 0x12, 0xd1, 0xb0, 0x65, 0x51, 0x0b, 0xf1, 0x1d, - 0xdd, 0x87, 0x4a, 0x54, 0x00, 0xd8, 0x3a, 0x2f, 0x18, 0x87, 0x5e, 0xf9, 0x46, 0xa5, 0x24, 0xf1, - 0xf3, 0x0f, 0xae, 0x5c, 0x4b, 0xa1, 0x88, 0x12, 0x1c, 0x8c, 0x9f, 0x14, 0x60, 0x21, 0xf1, 0x6a, - 0x9d, 0xfa, 0xd6, 0xae, 0x7d, 0xd2, 0x6f, 0xed, 0xdf, 0xd7, 0x60, 0xd9, 0x57, 0x05, 0x89, 0x47, - 0xdc, 0x66, 0xae, 0x87, 0x77, 0x11, 0x72, 0x6b, 0x92, 0xfd, 0x72, 0xda, 0x2e, 0x4a, 0xe5, 0x66, - 0xfc, 0x48, 0x83, 0x54, 0x70, 0xdd, 0xcb, 0x88, 0xba, 0x8b, 0xf9, 0x5e, 0xa5, 0xc4, 0xef, 0x02, - 0x26, 0x09, 0xbb, 0x3f, 0x14, 0xa1, 0x9a, 0xe5, 0x5a, 0xfd, 0x7b, 0x1a, 0xac, 0x08, 0x13, 0x26, - 0x72, 0xd5, 0x74, 0x8e, 0x0a, 0x47, 0x9c, 0x5b, 0x69, 0x34, 0x51, 0x3a, 0x2b, 0x55, 0x88, 0xf8, - 0xbc, 0x3b, 0xdd, 0x2f, 0x33, 0x46, 0x85, 0x50, 0x66, 0xe8, 0x74, 0x56, 0xca, 0x13, 0x5a, 0xe9, - 0xd0, 0x27, 0xb4, 0x6f, 0xc3, 0x8c, 0xcf, 0xa7, 0x50, 0xd6, 0x8c, 0x4d, 0xf0, 0x34, 0x9a, 0xfe, - 0x53, 0x9f, 0xa8, 0x40, 0x8a, 0x6f, 0x82, 0x02, 0xaa, 0xc6, 0xef, 0x34, 0x18, 0x89, 0xf6, 0xa9, - 0xd2, 0x85, 0x05, 0xd0, 0xfb, 0x1f, 0x0d, 0x1a, 0xb2, 0x88, 0x59, 0x31, 0x46, 0xd4, 0x34, 0x1f, - 0x3d, 0xa9, 0x1d, 0x7b, 0xfc, 0xa4, 0x76, 0xec, 0xa3, 0x27, 0xb5, 0x63, 0x0f, 0x87, 0x35, 0xed, - 0xd1, 0xb0, 0xa6, 0x3d, 0x1e, 0xd6, 0xb4, 0x8f, 0x86, 0x35, 0xed, 0x5f, 0xc3, 0x9a, 0xf6, 0xde, - 0xbf, 0x6b, 0xc7, 0xde, 0x5e, 0x1b, 0xf7, 0xa3, 0xc0, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x15, - 0x38, 0x05, 0x05, 0x33, 0x28, 0x00, 0x00, + // 2257 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x3a, 0xcd, 0x6f, 0x1b, 0xc7, + 0xf5, 0x5e, 0x92, 0xb6, 0xa8, 0x27, 0x8b, 0x92, 0xd6, 0x92, 0x4d, 0x3b, 0x0a, 0xa9, 0x2c, 0x7e, + 0x3f, 0x54, 0x68, 0x6d, 0x32, 0x96, 0x13, 0xc7, 0x48, 0xd3, 0x02, 0x5e, 0x2b, 0x76, 0x85, 0x26, + 0x8e, 0x32, 0x8c, 0x95, 0x38, 0xfd, 0xca, 0x8a, 0x3b, 0x96, 0xb6, 0x26, 0x77, 0xe9, 0x9d, 0xa1, + 0x62, 0xa3, 0x17, 0xa3, 0xe8, 0xd7, 0xa5, 0x40, 0x8a, 0x16, 0x45, 0x7b, 0xea, 0xa9, 0x28, 0x7a, + 0xe9, 0xa5, 0xfd, 0x0f, 0x82, 0x36, 0x06, 0x7a, 0x71, 0xd0, 0x02, 0x0d, 0x7a, 0x20, 0x6a, 0xf6, + 0xd8, 0x63, 0x6f, 0x39, 0x15, 0xf3, 0xb1, 0x1f, 0xb3, 0xdc, 0xa5, 0xb8, 0x6c, 0x23, 0x24, 0x27, + 0x72, 0x66, 0xde, 0xf7, 0x7b, 0xf3, 0xde, 0x9b, 0x99, 0x85, 0xf3, 0x77, 0xaf, 0x90, 0x86, 0xe3, + 0x35, 0xad, 0x9e, 0xd3, 0xf4, 0x31, 0xf1, 0xfa, 0x7e, 0x1b, 0x37, 0x0f, 0x2e, 0x5a, 0x9d, 0xde, + 0xbe, 0xb5, 0xd1, 0xdc, 0xc3, 0x2e, 0xf6, 0x2d, 0x8a, 0xed, 0x46, 0xcf, 0xf7, 0xa8, 0xa7, 0xaf, + 0x0a, 0xe8, 0x86, 0xd5, 0x73, 0x1a, 0x01, 0x74, 0x23, 0x80, 0x3e, 0x77, 0x61, 0xcf, 0xa1, 0xfb, + 0xfd, 0xdd, 0x46, 0xdb, 0xeb, 0x36, 0xf7, 0xbc, 0x3d, 0xaf, 0xc9, 0x91, 0x76, 0xfb, 0x77, 0xf8, + 0x88, 0x0f, 0xf8, 0x3f, 0x41, 0xec, 0x9c, 0x11, 0x63, 0xdd, 0xf6, 0x7c, 0xc6, 0x36, 0xc9, 0xf0, + 0xdc, 0x73, 0x11, 0x4c, 0xd7, 0x6a, 0xef, 0x3b, 0x2e, 0xf6, 0x1f, 0x34, 0x7b, 0x77, 0xf7, 0x54, + 0x79, 0xf3, 0x60, 0x91, 0x66, 0x17, 0x53, 0x2b, 0x8d, 0x57, 0x33, 0x0b, 0xcb, 0xef, 0xbb, 0xd4, + 0xe9, 0x8e, 0xb2, 0xb9, 0x7c, 0x18, 0x02, 0x69, 0xef, 0xe3, 0xae, 0x95, 0xc4, 0x33, 0x7e, 0x51, + 0x80, 0xc5, 0xab, 0x9d, 0x8e, 0xd7, 0xb6, 0xa8, 0xe3, 0xb9, 0x08, 0x93, 0x7e, 0x87, 0xea, 0x1e, + 0x2c, 0x04, 0xfa, 0x7c, 0xc5, 0x72, 0xed, 0x0e, 0x26, 0x55, 0x6d, 0xad, 0xb8, 0x3e, 0xb7, 0x71, + 0xbe, 0x31, 0xce, 0xe8, 0x0d, 0xa4, 0x20, 0x99, 0x67, 0x1e, 0x0d, 0xea, 0xc7, 0x86, 0x83, 0xfa, + 0x82, 0x3a, 0x4f, 0x50, 0x92, 0xba, 0xbe, 0x0b, 0x8b, 0xd6, 0x81, 0xe5, 0x74, 0xac, 0xdd, 0x0e, + 0x7e, 0xcd, 0xbd, 0xe9, 0xd9, 0x98, 0x54, 0x0b, 0x6b, 0xda, 0xfa, 0xdc, 0xc6, 0x5a, 0x9c, 0x23, + 0xf3, 0x4c, 0xe3, 0xe0, 0x62, 0x83, 0x01, 0xb4, 0x70, 0x07, 0xb7, 0xa9, 0xe7, 0x9b, 0xcb, 0xc3, + 0x41, 0x7d, 0xf1, 0x6a, 0x02, 0x1b, 0x8d, 0xd0, 0xd3, 0x9b, 0x30, 0x4b, 0xf6, 0x2d, 0x1f, 0xb3, + 0xb9, 0x6a, 0x71, 0x4d, 0x5b, 0x2f, 0x9b, 0x4b, 0x52, 0xc0, 0xd9, 0x56, 0xb0, 0x80, 0x22, 0x18, + 0xe3, 0x27, 0x1a, 0xac, 0x24, 0x4d, 0xf3, 0xaa, 0x67, 0xe3, 0x8e, 0x7e, 0x1f, 0x2a, 0xae, 0xd5, + 0xc5, 0x76, 0xa0, 0x17, 0x33, 0x0f, 0x13, 0xf6, 0xa5, 0xf1, 0xe6, 0xb9, 0xa9, 0xe0, 0x24, 0x49, + 0x9b, 0xfa, 0x70, 0x50, 0xaf, 0xa8, 0x30, 0x28, 0xc1, 0xc7, 0xf8, 0x5d, 0x01, 0x4e, 0x6f, 0xfa, + 0xce, 0x01, 0xf6, 0x47, 0x9c, 0xf6, 0x23, 0x0d, 0xce, 0x1c, 0x60, 0xd7, 0xf6, 0x7c, 0x84, 0xef, + 0xf5, 0x31, 0xa1, 0xdb, 0x96, 0x6f, 0x75, 0x31, 0xc5, 0x7e, 0x20, 0xde, 0x85, 0x98, 0x78, 0x61, + 0x90, 0x34, 0x7a, 0x77, 0xf7, 0x1a, 0x32, 0x48, 0x1a, 0xc8, 0x7a, 0xf7, 0xe5, 0xfb, 0x14, 0xbb, + 0xc4, 0xf1, 0x5c, 0xb3, 0x2e, 0xad, 0x73, 0x66, 0x27, 0x9d, 0x2a, 0xca, 0x62, 0xc7, 0x44, 0x59, + 0xb1, 0xd2, 0x2c, 0x27, 0x9d, 0x7a, 0x69, 0xbc, 0x9d, 0x52, 0x8d, 0x6e, 0x3e, 0x2d, 0xc5, 0x49, + 0xf7, 0x09, 0x4a, 0x67, 0x68, 0xfc, 0xbc, 0x00, 0x15, 0x61, 0x30, 0x29, 0x26, 0xd1, 0x37, 0x00, + 0x6c, 0x3e, 0xc3, 0x6c, 0xcd, 0x4d, 0x33, 0x6b, 0xea, 0x92, 0x38, 0x6c, 0x86, 0x2b, 0x28, 0x06, + 0xa5, 0x13, 0x58, 0x14, 0xca, 0xc6, 0x8c, 0x5a, 0x98, 0xc6, 0xa8, 0x55, 0xc9, 0x68, 0x71, 0x27, + 0x41, 0x0e, 0x8d, 0x30, 0xd0, 0xbf, 0x06, 0x65, 0x5f, 0x0a, 0x5d, 0x2d, 0xf2, 0xfd, 0x77, 0x61, + 0xb2, 0xfd, 0x27, 0x55, 0x35, 0x17, 0x25, 0xb3, 0x72, 0xa0, 0x3b, 0x0a, 0x09, 0x1a, 0x26, 0xd4, + 0xc6, 0xc7, 0xa3, 0xbe, 0x06, 0x25, 0x37, 0xb2, 0xd0, 0x49, 0x49, 0xab, 0xc4, 0x6d, 0xc3, 0x57, + 0x8c, 0x3f, 0x69, 0x70, 0x26, 0x41, 0x84, 0x52, 0xdf, 0xd9, 0xed, 0x53, 0x7c, 0x38, 0x36, 0x8b, + 0x92, 0x8a, 0x15, 0xc0, 0xef, 0x58, 0x9d, 0x3e, 0x96, 0x26, 0x7d, 0x31, 0xd7, 0x36, 0x52, 0x28, + 0x98, 0xff, 0x27, 0x19, 0xad, 0x8e, 0x83, 0x42, 0x09, 0xbe, 0xc6, 0xbf, 0x8a, 0x30, 0x16, 0x41, + 0xff, 0x06, 0x94, 0xef, 0xf5, 0x2d, 0x97, 0x3a, 0xf4, 0x41, 0xf5, 0x04, 0x17, 0xb2, 0x91, 0xe9, + 0x77, 0x45, 0xea, 0xd7, 0x25, 0x96, 0xb9, 0x34, 0x1c, 0xd4, 0xe7, 0x83, 0x91, 0x90, 0x22, 0x24, + 0xa9, 0x3f, 0x03, 0xa5, 0x5d, 0xcf, 0x13, 0xdb, 0xa3, 0x6c, 0xce, 0xb3, 0x94, 0x64, 0x7a, 0x5e, + 0x47, 0x80, 0xf1, 0x25, 0xbd, 0x06, 0x45, 0xc7, 0xa5, 0xd5, 0x99, 0x35, 0x6d, 0xbd, 0x68, 0x9e, + 0x64, 0x4e, 0xdd, 0x72, 0xa9, 0x00, 0x60, 0x0b, 0x7a, 0x1b, 0xca, 0x8e, 0x4b, 0x5b, 0x1d, 0xa7, + 0x8d, 0xab, 0x65, 0x2e, 0xe1, 0x73, 0x79, 0xcc, 0xb8, 0x25, 0x71, 0x85, 0x9c, 0xc1, 0x48, 0xca, + 0x19, 0x10, 0xd6, 0x3f, 0x07, 0x27, 0x08, 0xf5, 0x1d, 0x77, 0xaf, 0x7a, 0x9c, 0xbb, 0x75, 0x61, + 0x38, 0xa8, 0xcf, 0xb5, 0xf8, 0x8c, 0x00, 0x95, 0xcb, 0xba, 0x07, 0x73, 0xe2, 0x9f, 0x10, 0x68, + 0x96, 0x0b, 0xf4, 0x42, 0x1e, 0x81, 0x5a, 0x11, 0xba, 0x48, 0xf1, 0xb1, 0x09, 0xc1, 0x2b, 0xce, + 0x41, 0xff, 0x3c, 0xcc, 0x1c, 0x60, 0x9f, 0x6d, 0xb1, 0x2a, 0x70, 0xd1, 0x16, 0x87, 0x83, 0xfa, + 0xc9, 0x1d, 0x31, 0x25, 0xe0, 0x03, 0x00, 0x63, 0x13, 0x96, 0x55, 0x5e, 0xd7, 0x9d, 0x0e, 0xc5, + 0xbe, 0x7e, 0x1e, 0xca, 0x44, 0x56, 0x15, 0x19, 0xb6, 0xe1, 0x06, 0x0a, 0xaa, 0x0d, 0x0a, 0x21, + 0x8c, 0x5f, 0x6b, 0x70, 0x3a, 0x69, 0x43, 0x42, 0x2d, 0xb7, 0x3d, 0x49, 0xec, 0x3b, 0x00, 0x61, + 0x08, 0xb2, 0x4c, 0xc2, 0x36, 0xf7, 0xf3, 0x53, 0x85, 0x7d, 0x94, 0xba, 0xc2, 0x29, 0x82, 0x62, + 0xc4, 0x8d, 0xcb, 0xa3, 0x62, 0x4a, 0x6f, 0xae, 0x42, 0xc9, 0x71, 0xa9, 0xa8, 0xed, 0x45, 0xb3, + 0xcc, 0x44, 0xdc, 0x72, 0x29, 0x41, 0x7c, 0xd6, 0x78, 0x19, 0x56, 0x12, 0xc5, 0x48, 0xa4, 0x8e, + 0x9c, 0x66, 0x7a, 0x38, 0x92, 0x23, 0xc2, 0x3f, 0x3a, 0x86, 0x59, 0x47, 0xda, 0x2c, 0xe8, 0x30, + 0x72, 0x06, 0xad, 0x40, 0x8e, 0x0a, 0x79, 0x30, 0x43, 0x50, 0x44, 0xd9, 0x30, 0xe1, 0x6c, 0x66, + 0x6c, 0xe9, 0xff, 0x0f, 0x33, 0x22, 0x8e, 0x84, 0x04, 0xb3, 0xe6, 0xdc, 0x70, 0x50, 0x9f, 0x11, + 0x10, 0x04, 0x05, 0x6b, 0xc6, 0x0f, 0x34, 0x58, 0x62, 0x7d, 0x44, 0x40, 0x43, 0x34, 0x02, 0xf7, + 0x32, 0x1a, 0x81, 0x5c, 0xae, 0x0c, 0xff, 0x4c, 0xd4, 0x01, 0x7c, 0x58, 0x50, 0x05, 0x11, 0x5a, + 0xbc, 0x03, 0x65, 0xd6, 0x4a, 0xda, 0x16, 0xb5, 0xa4, 0x08, 0xcf, 0x8e, 0xcb, 0x4f, 0xa4, 0xc1, + 0xa0, 0x59, 0x2b, 0xf5, 0xda, 0xee, 0xb7, 0x71, 0x9b, 0xbe, 0x8a, 0xa9, 0x15, 0x05, 0x52, 0x34, + 0x87, 0x42, 0xaa, 0xcc, 0xeb, 0xae, 0x67, 0x63, 0x5e, 0x33, 0x0b, 0xaa, 0xd7, 0x6f, 0xca, 0x79, + 0x14, 0x42, 0x24, 0x6a, 0x6c, 0x71, 0xa2, 0x1a, 0x7b, 0x1f, 0x96, 0xdc, 0xa4, 0x85, 0xab, 0x25, + 0xae, 0x4c, 0xf3, 0x10, 0x7b, 0x26, 0xd1, 0xcc, 0xb3, 0x92, 0xd7, 0xa8, 0xcf, 0xd0, 0x28, 0x13, + 0xe3, 0xcf, 0x1a, 0xac, 0x8c, 0xd8, 0xf4, 0x15, 0x87, 0x50, 0xfd, 0xeb, 0x23, 0x76, 0x6d, 0x4c, + 0x66, 0x57, 0x86, 0xcd, 0xad, 0x1a, 0x5a, 0x29, 0x98, 0x89, 0xd9, 0xf4, 0x0d, 0x38, 0xee, 0x50, + 0xdc, 0x0d, 0x12, 0x40, 0x0e, 0x2d, 0x45, 0x5e, 0x9c, 0x97, 0xb4, 0x8f, 0x6f, 0x31, 0x2a, 0x48, + 0x10, 0x33, 0xfe, 0x50, 0x80, 0xe5, 0x6d, 0xcf, 0x6e, 0xb5, 0xf7, 0xb1, 0xdd, 0xef, 0x38, 0xee, + 0xde, 0x35, 0xcf, 0xa5, 0xf8, 0x3e, 0x3d, 0x82, 0x20, 0x79, 0x0b, 0x4a, 0xa4, 0x87, 0xdb, 0xb2, + 0x8e, 0x5f, 0x1e, 0xaf, 0x4f, 0x9a, 0x8c, 0xad, 0x1e, 0x6e, 0x47, 0x09, 0x93, 0x8d, 0x10, 0xa7, + 0xa8, 0xbf, 0xc3, 0x2a, 0x8f, 0x45, 0xfb, 0x84, 0x07, 0xd3, 0xdc, 0xc6, 0x95, 0x29, 0x68, 0x73, + 0x7c, 0xb3, 0x22, 0xa9, 0x9f, 0x10, 0x63, 0x24, 0xe9, 0x1a, 0x1f, 0x6a, 0x50, 0x4d, 0x43, 0x3b, + 0x82, 0x38, 0x78, 0x53, 0x8d, 0x83, 0x8d, 0xfc, 0xba, 0x65, 0x84, 0xc2, 0x7b, 0x19, 0x3a, 0x31, + 0xc3, 0xea, 0x57, 0xe0, 0xa4, 0xc8, 0xd2, 0xd8, 0x66, 0xa1, 0x25, 0x73, 0xf9, 0xb2, 0x24, 0x74, + 0xb2, 0x15, 0x5b, 0x43, 0x0a, 0xa4, 0xfe, 0x22, 0x54, 0x7a, 0x1e, 0xc5, 0x2e, 0x75, 0xac, 0x4e, + 0x70, 0x58, 0x63, 0xa9, 0x93, 0xe7, 0xaf, 0x6d, 0x65, 0x05, 0x25, 0x20, 0x8d, 0x5f, 0x6a, 0x70, + 0x2e, 0xdb, 0x3b, 0xfa, 0x77, 0xa0, 0x12, 0x68, 0x7c, 0xad, 0x63, 0x39, 0xdd, 0xa0, 0x2e, 0x7c, + 0x71, 0xb2, 0xce, 0x97, 0xe3, 0x44, 0xb4, 0xa5, 0xcb, 0x4f, 0x4b, 0x9d, 0x2a, 0x0a, 0x18, 0x41, + 0x09, 0x56, 0xc6, 0xaf, 0x0a, 0x30, 0xaf, 0x80, 0x1c, 0xc1, 0x96, 0x79, 0x5d, 0xd9, 0x32, 0xcd, + 0x3c, 0x6a, 0x66, 0xed, 0x95, 0xdb, 0x89, 0xbd, 0x72, 0x31, 0x0f, 0xd1, 0xf1, 0x9b, 0x64, 0xa8, + 0x41, 0x4d, 0x81, 0xbf, 0xe6, 0xb9, 0xa4, 0xdf, 0x65, 0xa7, 0xab, 0x3b, 0xd8, 0xc7, 0xac, 0xf9, + 0x39, 0x0f, 0x65, 0xab, 0xe7, 0xdc, 0xf0, 0xbd, 0x7e, 0x2f, 0xd9, 0x1e, 0x5c, 0xdd, 0xde, 0xe2, + 0xf3, 0x28, 0x84, 0x60, 0xd0, 0x81, 0x44, 0xb2, 0x4c, 0xc4, 0x0e, 0x2d, 0xf2, 0x34, 0x13, 0x42, + 0x84, 0x8d, 0x55, 0x29, 0xb3, 0xb1, 0x32, 0xa1, 0xd8, 0x77, 0x6c, 0xd9, 0x9e, 0x3e, 0x2b, 0x01, + 0x8a, 0xb7, 0xb6, 0x36, 0x3f, 0x1e, 0xd4, 0x9f, 0xc9, 0xba, 0x23, 0xa1, 0x0f, 0x7a, 0x98, 0x34, + 0x6e, 0x6d, 0x6d, 0x22, 0x86, 0x6c, 0xbc, 0xaf, 0xc1, 0x92, 0xa2, 0xe4, 0x11, 0xa4, 0x80, 0x6d, + 0x35, 0x05, 0x7c, 0x21, 0x87, 0xcb, 0x32, 0xf6, 0xfe, 0x4f, 0x8b, 0x70, 0x46, 0x81, 0x8b, 0x9d, + 0x2c, 0x3f, 0xf9, 0xb0, 0x7e, 0x17, 0xe6, 0xc3, 0xab, 0xa6, 0xeb, 0xbe, 0xd7, 0x95, 0xf1, 0xfd, + 0xe5, 0x1c, 0x7a, 0xc5, 0xce, 0xc6, 0x41, 0x70, 0x89, 0xd3, 0xc9, 0x8d, 0x38, 0x61, 0xa4, 0xf2, + 0xc9, 0x7d, 0xcd, 0xa3, 0x77, 0xa0, 0x62, 0x2b, 0x17, 0x04, 0xd5, 0xd2, 0x24, 0x77, 0x5d, 0xea, + 0xa5, 0x42, 0x94, 0x62, 0xd4, 0x79, 0x94, 0xa0, 0x6d, 0xfc, 0x4d, 0x83, 0xa7, 0x32, 0xb4, 0x3c, + 0x82, 0x28, 0x7b, 0x5b, 0x8d, 0xb2, 0xe7, 0xa7, 0xf2, 0x46, 0x46, 0xbc, 0xfd, 0x4c, 0x83, 0xb5, + 0xc3, 0xfc, 0x97, 0x33, 0x39, 0xac, 0x41, 0xe9, 0xae, 0xe3, 0xda, 0xb2, 0xdf, 0x0c, 0xb7, 0xfb, + 0x57, 0x1d, 0xd7, 0x46, 0x7c, 0x25, 0x4c, 0x08, 0xc5, 0xcc, 0x3b, 0x8a, 0x87, 0x1a, 0x3c, 0x3d, + 0xb6, 0x3a, 0x4c, 0x70, 0x5a, 0xfb, 0x12, 0x2c, 0xf4, 0x5d, 0xd2, 0x77, 0x28, 0x0b, 0x98, 0x78, + 0xc1, 0x3b, 0x35, 0x1c, 0xd4, 0x17, 0x6e, 0xa9, 0x4b, 0x28, 0x09, 0x6b, 0xfc, 0xa6, 0x90, 0xc8, + 0x27, 0xbc, 0xfc, 0xde, 0x80, 0xa5, 0x58, 0xf9, 0x21, 0x24, 0x76, 0x1b, 0x15, 0x76, 0xaf, 0x28, + 0x09, 0x80, 0x46, 0x71, 0xd8, 0x56, 0xeb, 0xc5, 0x4d, 0xfd, 0xbf, 0xdc, 0x6a, 0xca, 0x02, 0x52, + 0xf9, 0xe8, 0xdb, 0x50, 0x89, 0x2e, 0xdd, 0x58, 0x27, 0x2d, 0xdd, 0xb0, 0x1e, 0xec, 0x85, 0xab, + 0xca, 0xea, 0xc7, 0x23, 0x33, 0x28, 0x81, 0x6f, 0xfc, 0xbb, 0x00, 0xa7, 0x52, 0xca, 0xd1, 0x54, + 0x57, 0x76, 0xdf, 0x04, 0x88, 0xa8, 0x4b, 0x9b, 0x34, 0xf2, 0x5d, 0x3c, 0x9a, 0x15, 0x7e, 0xae, + 0x8e, 0x66, 0x63, 0x14, 0x75, 0x02, 0x73, 0x3e, 0x26, 0xd8, 0x3f, 0xc0, 0xf6, 0x75, 0xcf, 0x97, + 0x17, 0x74, 0x2f, 0xe5, 0x30, 0xfa, 0x48, 0xe9, 0x34, 0x4f, 0x49, 0x95, 0xe6, 0x50, 0x44, 0x18, + 0xc5, 0xb9, 0xe8, 0x2d, 0x58, 0xb1, 0x71, 0xfc, 0xa6, 0x93, 0xa7, 0x15, 0x6c, 0xf3, 0x8a, 0x58, + 0x8e, 0xee, 0x48, 0x37, 0xd3, 0x80, 0x50, 0x3a, 0xae, 0xf1, 0x57, 0x0d, 0x56, 0x14, 0xc9, 0xde, + 0xc0, 0xdd, 0x5e, 0xc7, 0xa2, 0x47, 0x71, 0xac, 0xbc, 0xad, 0xb4, 0x3f, 0x2f, 0xe4, 0x30, 0x5f, + 0x20, 0x64, 0x56, 0x1b, 0x64, 0xfc, 0x45, 0x83, 0xb3, 0xa9, 0x18, 0x47, 0x90, 0x68, 0xdf, 0x52, + 0x13, 0xed, 0xa5, 0x29, 0xf4, 0xca, 0x48, 0xb3, 0x8f, 0xb3, 0xb4, 0x6a, 0x89, 0x63, 0xd2, 0x67, + 0xaf, 0x5f, 0x35, 0x3e, 0x28, 0x2a, 0x6d, 0x37, 0x39, 0x8a, 0xfe, 0x44, 0xcd, 0x28, 0x85, 0x89, + 0x32, 0xca, 0x48, 0xa2, 0x2d, 0xe6, 0x4c, 0xb4, 0x84, 0x4c, 0x97, 0x68, 0x6f, 0xc3, 0xbc, 0x5a, + 0x7d, 0x4a, 0x13, 0xbe, 0x8d, 0x71, 0xd2, 0x2d, 0xa5, 0x3a, 0xa9, 0x94, 0xf4, 0x57, 0x60, 0x99, + 0x50, 0xbf, 0xdf, 0xa6, 0x7d, 0x1f, 0xdb, 0xb1, 0xc7, 0x8d, 0xe3, 0x3c, 0x9f, 0x54, 0x87, 0x83, + 0xfa, 0x72, 0x2b, 0x65, 0x1d, 0xa5, 0x62, 0x25, 0x3b, 0x67, 0x42, 0x3e, 0xcd, 0x9d, 0x33, 0xc9, + 0xea, 0x64, 0xde, 0x57, 0x3b, 0xe7, 0xb8, 0xd7, 0x3e, 0x0b, 0x9d, 0xf3, 0x98, 0x28, 0x1b, 0xdb, + 0x39, 0xd3, 0x94, 0x37, 0x2e, 0x51, 0xd5, 0x0e, 0x29, 0x9b, 0xc9, 0xa7, 0xac, 0x5c, 0x8f, 0x5c, + 0x6f, 0xc2, 0xcc, 0x1d, 0x7e, 0xfd, 0x3e, 0x61, 0xdf, 0x1d, 0x28, 0x2a, 0xee, 0xec, 0xcd, 0x05, + 0xc9, 0x6a, 0x46, 0x8c, 0x09, 0x0a, 0xa8, 0x25, 0x3b, 0xed, 0xb8, 0x55, 0x3e, 0xcd, 0x9d, 0x76, + 0x5c, 0xce, 0x8c, 0xf8, 0xfc, 0xa3, 0xda, 0x69, 0xa7, 0xfa, 0xfb, 0xe8, 0x3b, 0x6d, 0x76, 0xf2, + 0x62, 0xbf, 0xa4, 0x67, 0xb5, 0x83, 0x13, 0x7a, 0x78, 0xf2, 0xba, 0x19, 0x2c, 0xa0, 0x08, 0xc6, + 0xf8, 0x40, 0x83, 0x8a, 0xea, 0xce, 0xa9, 0x1a, 0xbd, 0x87, 0x1a, 0x9c, 0xf2, 0x15, 0x32, 0xf1, + 0xb7, 0xe6, 0x8b, 0x79, 0xc2, 0x49, 0x5c, 0x1e, 0x3f, 0x25, 0x19, 0x9e, 0x4a, 0x59, 0x44, 0x69, + 0xac, 0x8c, 0xef, 0x6b, 0x90, 0x06, 0xac, 0xbb, 0x19, 0xef, 0x03, 0x1b, 0x79, 0xde, 0x07, 0x64, + 0xa4, 0x4f, 0xf2, 0x38, 0xf0, 0xf7, 0x98, 0x45, 0xc5, 0xb7, 0x15, 0x53, 0x59, 0x74, 0x0d, 0x4a, + 0x7c, 0x5b, 0x24, 0xa2, 0x61, 0xd3, 0xa2, 0x16, 0xe2, 0x2b, 0xba, 0x0f, 0x95, 0xa8, 0x00, 0xb0, + 0x79, 0x5e, 0x30, 0x0e, 0xbd, 0xf2, 0x8d, 0x4a, 0x49, 0xe2, 0x53, 0x11, 0xae, 0x5c, 0x4b, 0xa1, + 0x88, 0x12, 0x1c, 0x8c, 0x1f, 0x17, 0x60, 0x21, 0xf1, 0xc2, 0x9d, 0xfa, 0x2e, 0xaf, 0x7d, 0xd2, + 0xef, 0xf2, 0xdf, 0xd3, 0x60, 0xd9, 0x57, 0x05, 0x89, 0x47, 0xdc, 0x46, 0xae, 0x47, 0x7a, 0x11, + 0x72, 0xab, 0x92, 0xfd, 0x72, 0xda, 0x2a, 0x4a, 0xe5, 0x66, 0xfc, 0x50, 0x83, 0x54, 0x70, 0xdd, + 0xcb, 0x88, 0xba, 0x4b, 0xf9, 0x5e, 0xa5, 0xc4, 0x37, 0x04, 0x93, 0x84, 0xdd, 0xef, 0x8b, 0x50, + 0xcd, 0x72, 0xad, 0xfe, 0x5d, 0x0d, 0x56, 0x84, 0x09, 0x13, 0xb9, 0x6a, 0x3a, 0x47, 0x85, 0x47, + 0x9c, 0x9d, 0x34, 0x9a, 0x28, 0x9d, 0x95, 0x2a, 0x44, 0xfc, 0xbc, 0x3b, 0xdd, 0x57, 0x1c, 0xa3, + 0x42, 0x28, 0x67, 0xe8, 0x74, 0x56, 0xca, 0x13, 0x5a, 0xe9, 0xd0, 0x27, 0xb4, 0x6f, 0xc1, 0x8c, + 0xcf, 0x4f, 0xa1, 0xac, 0x19, 0x9b, 0xe0, 0x69, 0x34, 0xfd, 0xb3, 0xa0, 0xa8, 0x40, 0x8a, 0x31, + 0x41, 0x01, 0x55, 0xe3, 0xb7, 0x1a, 0x8c, 0x44, 0xfb, 0x54, 0xe9, 0xc2, 0x02, 0xe8, 0xfd, 0x97, + 0x06, 0x0d, 0x59, 0xc4, 0xac, 0x18, 0x23, 0x6a, 0x9a, 0x8f, 0x9e, 0xd4, 0x8e, 0x3d, 0x7e, 0x52, + 0x3b, 0xf6, 0xd1, 0x93, 0xda, 0xb1, 0x87, 0xc3, 0x9a, 0xf6, 0x68, 0x58, 0xd3, 0x1e, 0x0f, 0x6b, + 0xda, 0x47, 0xc3, 0x9a, 0xf6, 0x8f, 0x61, 0x4d, 0x7b, 0xef, 0x9f, 0xb5, 0x63, 0x6f, 0xaf, 0x8e, + 0xfb, 0x80, 0xf0, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x03, 0x20, 0x09, 0x51, 0x5f, 0x28, 0x00, + 0x00, } func (m *AllocationResult) Marshal() (dAtA []byte, err error) { @@ -1747,6 +1749,13 @@ func (m *NamedResourcesAttributeValue) MarshalToSizedBuffer(dAtA []byte) (int, e _ = i var l int _ = l + if m.VersionValue != nil { + i -= len(*m.VersionValue) + copy(dAtA[i:], *m.VersionValue) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.VersionValue))) + i-- + dAtA[i] = 0x52 + } if m.StringSliceValue != nil { { size, err := m.StringSliceValue.MarshalToSizedBuffer(dAtA[:i]) @@ -3592,6 +3601,10 @@ func (m *NamedResourcesAttributeValue) Size() (n int) { l = m.StringSliceValue.Size() n += 1 + l + sovGenerated(uint64(l)) } + if m.VersionValue != nil { + l = len(*m.VersionValue) + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -4289,6 +4302,7 @@ func (this *NamedResourcesAttributeValue) String() string { `IntValue:` + valueToStringGenerated(this.IntValue) + `,`, `IntSliceValue:` + strings.Replace(this.IntSliceValue.String(), "NamedResourcesIntSlice", "NamedResourcesIntSlice", 1) + `,`, `StringSliceValue:` + strings.Replace(this.StringSliceValue.String(), "NamedResourcesStringSlice", "NamedResourcesStringSlice", 1) + `,`, + `VersionValue:` + valueToStringGenerated(this.VersionValue) + `,`, `}`, }, "") return s @@ -5695,6 +5709,39 @@ func (m *NamedResourcesAttributeValue) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VersionValue", 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 + } + s := string(dAtA[iNdEx:postIndex]) + m.VersionValue = &s + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/staging/src/k8s.io/api/resource/v1alpha2/generated.proto b/staging/src/k8s.io/api/resource/v1alpha2/generated.proto index e5ddc261137..210bce01bc0 100644 --- a/staging/src/k8s.io/api/resource/v1alpha2/generated.proto +++ b/staging/src/k8s.io/api/resource/v1alpha2/generated.proto @@ -134,6 +134,9 @@ message NamedResourcesAttributeValue { // StringSliceValue is an array of strings. optional NamedResourcesStringSlice stringSlice = 9; + + // VersionValue is a semantic version according to semver.org spec 2.0.0. + optional string version = 10; } // NamedResourcesFilter is used in ResourceFilterModel. diff --git a/staging/src/k8s.io/api/resource/v1alpha2/namedresources.go b/staging/src/k8s.io/api/resource/v1alpha2/namedresources.go index 1af5c7d234e..e28f6cc4331 100644 --- a/staging/src/k8s.io/api/resource/v1alpha2/namedresources.go +++ b/staging/src/k8s.io/api/resource/v1alpha2/namedresources.go @@ -70,7 +70,8 @@ type NamedResourcesAttributeValue struct { StringValue *string `json:"string,omitempty" protobuf:"bytes,5,opt,name=string"` // StringSliceValue is an array of strings. StringSliceValue *NamedResourcesStringSlice `json:"stringSlice,omitempty" protobuf:"bytes,9,rep,name=stringSlice"` - // TODO: VersionValue *SemVersion `json:"version,omitempty" protobuf:"bytes,7,opt,name=version"` + // VersionValue is a semantic version according to semver.org spec 2.0.0. + VersionValue *string `json:"version,omitempty" protobuf:"bytes,10,opt,name=version"` } // NamedResourcesIntSlice contains a slice of 64-bit integers. @@ -89,17 +90,6 @@ type NamedResourcesStringSlice struct { Strings []string `json:"strings" protobuf:"bytes,1,opt,name=strings"` } -// TODO -// -// A wrapper around https://pkg.go.dev/github.com/blang/semver/v4#Version which -// is encoded as a string. During decoding, it validates that the string -// can be parsed using tolerant parsing (currently trims spaces, removes a "v" prefix, -// adds a 0 patch number to versions with only major and minor components specified, -// and removes leading 0s). -// type NamedResourcesSemVersion struct { -// semver.Version -//} - // NamedResourcesRequest is used in ResourceRequestModel. type NamedResourcesRequest struct { // Selector is a CEL expression which must evaluate to true if a diff --git a/staging/src/k8s.io/api/resource/v1alpha2/zz_generated.deepcopy.go b/staging/src/k8s.io/api/resource/v1alpha2/zz_generated.deepcopy.go index f5d3a01bb1b..89539070a2b 100644 --- a/staging/src/k8s.io/api/resource/v1alpha2/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/resource/v1alpha2/zz_generated.deepcopy.go @@ -183,6 +183,11 @@ func (in *NamedResourcesAttributeValue) DeepCopyInto(out *NamedResourcesAttribut *out = new(NamedResourcesStringSlice) (*in).DeepCopyInto(*out) } + if in.VersionValue != nil { + in, out := &in.VersionValue, &out.VersionValue + *out = new(string) + **out = **in + } return } diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha2.NodeResourceSlice.json b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha2.NodeResourceSlice.json index fbd9cd70f09..218ee89bc47 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha2.NodeResourceSlice.json +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha2.NodeResourceSlice.json @@ -65,7 +65,8 @@ "strings": [ "stringsValue" ] - } + }, + "version": "versionValue" } ] } diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha2.NodeResourceSlice.pb b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha2.NodeResourceSlice.pb index b1efc51b7e79e20c6da053e22873f060efac0d89..ba0323b80fa7b3426f3676f1054aeb4cc7d06e46 100644 GIT binary patch delta 67 zcmZo?naVQ3gz@%9(^f|LFs=};ATG|l#N5=d#GKMpAy;IM#pIieT0%iQWvNBQnfZBO J6;cdJ3;@Dw6sZ6J delta 53 ycmbQr(#|r$gz@Y~(^f`#4=y(@7cS1c#N5=d#GKMpA#-Go*5sRvS}alwN(=z0SPtd@ diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha2.NodeResourceSlice.yaml b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha2.NodeResourceSlice.yaml index a47bdb4a409..a1c13f739c8 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha2.NodeResourceSlice.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha2.NodeResourceSlice.yaml @@ -47,5 +47,6 @@ namedResources: stringSlice: strings: - stringsValue + version: versionValue name: nameValue nodeName: nodeNameValue diff --git a/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go b/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go index 93121ac8443..396ecf04a0c 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go @@ -12001,6 +12001,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: stringSlice type: namedType: io.k8s.api.resource.v1alpha2.NamedResourcesStringSlice + - name: version + type: + scalar: string - name: io.k8s.api.resource.v1alpha2.NamedResourcesFilter map: fields: diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha2/namedresourcesattribute.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha2/namedresourcesattribute.go index 43cd9044b3a..d9545d054f3 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha2/namedresourcesattribute.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha2/namedresourcesattribute.go @@ -90,3 +90,11 @@ func (b *NamedResourcesAttributeApplyConfiguration) WithStringSliceValue(value * b.StringSliceValue = value return b } + +// WithVersionValue sets the VersionValue field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the VersionValue field is set to the value of the last call. +func (b *NamedResourcesAttributeApplyConfiguration) WithVersionValue(value string) *NamedResourcesAttributeApplyConfiguration { + b.VersionValue = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha2/namedresourcesattributevalue.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha2/namedresourcesattributevalue.go index ad2c7e185c1..e0b19650a92 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha2/namedresourcesattributevalue.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha2/namedresourcesattributevalue.go @@ -31,6 +31,7 @@ type NamedResourcesAttributeValueApplyConfiguration struct { IntSliceValue *NamedResourcesIntSliceApplyConfiguration `json:"intSlice,omitempty"` StringValue *string `json:"string,omitempty"` StringSliceValue *NamedResourcesStringSliceApplyConfiguration `json:"stringSlice,omitempty"` + VersionValue *string `json:"version,omitempty"` } // NamedResourcesAttributeValueApplyConfiguration constructs an declarative configuration of the NamedResourcesAttributeValue type for use with @@ -86,3 +87,11 @@ func (b *NamedResourcesAttributeValueApplyConfiguration) WithStringSliceValue(va b.StringSliceValue = value return b } + +// WithVersionValue sets the VersionValue field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the VersionValue field is set to the value of the last call. +func (b *NamedResourcesAttributeValueApplyConfiguration) WithVersionValue(value string) *NamedResourcesAttributeValueApplyConfiguration { + b.VersionValue = &value + return b +} diff --git a/staging/src/k8s.io/dynamic-resource-allocation/go.mod b/staging/src/k8s.io/dynamic-resource-allocation/go.mod index 0881b6010d3..d800a1a80f3 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/go.mod +++ b/staging/src/k8s.io/dynamic-resource-allocation/go.mod @@ -5,6 +5,7 @@ module k8s.io/dynamic-resource-allocation go 1.22.0 require ( + github.com/blang/semver/v4 v4.0.0 github.com/go-logr/logr v1.4.1 github.com/google/cel-go v0.17.8 github.com/google/go-cmp v0.6.0 diff --git a/staging/src/k8s.io/dynamic-resource-allocation/go.sum b/staging/src/k8s.io/dynamic-resource-allocation/go.sum index 2c0c8948be5..e846de407b6 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/go.sum +++ b/staging/src/k8s.io/dynamic-resource-allocation/go.sum @@ -6,6 +6,7 @@ github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/g github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= diff --git a/staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/compile.go b/staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/compile.go index 8cbc7985559..b61f1eacd4c 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/compile.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/compile.go @@ -21,6 +21,7 @@ import ( "fmt" "reflect" + "github.com/blang/semver/v4" "github.com/google/cel-go/cel" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/traits" @@ -105,43 +106,54 @@ var valueTypes = map[string]struct { celType *cel.Type // get returns nil if the attribute doesn't have the type, otherwise // the value of that type. - get func(attr resourceapi.NamedResourcesAttribute) any + get func(attr resourceapi.NamedResourcesAttribute) (any, error) }{ - "quantity": {apiservercel.QuantityType, func(attr resourceapi.NamedResourcesAttribute) any { + "quantity": {apiservercel.QuantityType, func(attr resourceapi.NamedResourcesAttribute) (any, error) { if attr.QuantityValue == nil { - return nil + return nil, nil } - return apiservercel.Quantity{Quantity: attr.QuantityValue} + return apiservercel.Quantity{Quantity: attr.QuantityValue}, nil }}, - "bool": {cel.BoolType, func(attr resourceapi.NamedResourcesAttribute) any { + "bool": {cel.BoolType, func(attr resourceapi.NamedResourcesAttribute) (any, error) { if attr.BoolValue == nil { - return nil + return nil, nil } - return *attr.BoolValue + return *attr.BoolValue, nil }}, - "int": {cel.IntType, func(attr resourceapi.NamedResourcesAttribute) any { + "int": {cel.IntType, func(attr resourceapi.NamedResourcesAttribute) (any, error) { if attr.IntValue == nil { - return nil + return nil, nil } - return *attr.IntValue + return *attr.IntValue, nil }}, - "intslice": {types.NewListType(cel.IntType), func(attr resourceapi.NamedResourcesAttribute) any { + "intslice": {types.NewListType(cel.IntType), func(attr resourceapi.NamedResourcesAttribute) (any, error) { if attr.IntSliceValue == nil { - return nil + return nil, nil } - return attr.IntSliceValue.Ints + return attr.IntSliceValue.Ints, nil }}, - "string": {cel.StringType, func(attr resourceapi.NamedResourcesAttribute) any { + "string": {cel.StringType, func(attr resourceapi.NamedResourcesAttribute) (any, error) { if attr.StringValue == nil { - return nil + return nil, nil } - return *attr.StringValue + return *attr.StringValue, nil }}, - "stringslice": {types.NewListType(cel.StringType), func(attr resourceapi.NamedResourcesAttribute) any { + "stringslice": {types.NewListType(cel.StringType), func(attr resourceapi.NamedResourcesAttribute) (any, error) { if attr.StringSliceValue == nil { - return nil + return nil, nil } - return attr.StringSliceValue.Strings + return attr.StringSliceValue.Strings, nil + }}, + "version": {SemverType, func(attr resourceapi.NamedResourcesAttribute) (any, error) { + if attr.VersionValue == nil { + return nil, nil + } + v, err := semver.Parse(*attr.VersionValue) + if err != nil { + return nil, fmt.Errorf("parse semantic version: %v", err) + } + + return Semver{Version: v}, nil }}, } @@ -150,7 +162,11 @@ var boolType = reflect.TypeOf(true) func (c CompilationResult) Evaluate(ctx context.Context, attributes []resourceapi.NamedResourcesAttribute) (bool, error) { variables := make(map[string]any, len(valueTypes)) for name, valueType := range valueTypes { - variables[attributesVarPrefix+name] = buildValueMapper(c.Environment.CELTypeAdapter(), attributes, valueType.get) + m, err := buildValueMapper(c.Environment.CELTypeAdapter(), attributes, valueType.get) + if err != nil { + return false, fmt.Errorf("extract attributes with type %s: %v", name, err) + } + variables[attributesVarPrefix+name] = m } result, _, err := c.Program.ContextEval(ctx, variables) if err != nil { @@ -172,7 +188,9 @@ func mustBuildEnv() *environment.EnvSet { versioned := []environment.VersionedOptions{ { IntroducedVersion: version.MajorMinor(1, 30), - EnvOptions: buildVersionedAttributes(), + EnvOptions: append(buildVersionedAttributes(), + SemverLib(), + ), }, } envset, err := envset.Extend(versioned...) @@ -190,17 +208,21 @@ func buildVersionedAttributes() []cel.EnvOption { return options } -func buildValueMapper(adapter types.Adapter, attributes []resourceapi.NamedResourcesAttribute, get func(resourceapi.NamedResourcesAttribute) any) traits.Mapper { +func buildValueMapper(adapter types.Adapter, attributes []resourceapi.NamedResourcesAttribute, get func(resourceapi.NamedResourcesAttribute) (any, error)) (traits.Mapper, error) { // This implementation constructs a map and then let's cel handle the // lookup and iteration. This is done for the sake of simplicity. // Whether it's faster than writing a custom mapper depends on // real-world attribute sets and CEL expressions and would have to be // benchmarked. valueMap := make(map[string]any) - for _, attribute := range attributes { - if value := get(attribute); value != nil { + for name, attribute := range attributes { + value, err := get(attribute) + if err != nil { + return nil, fmt.Errorf("attribute %q: %v", name, err) + } + if value != nil { valueMap[attribute.Name] = value } } - return types.NewStringInterfaceMap(adapter, valueMap) + return types.NewStringInterfaceMap(adapter, valueMap), nil } diff --git a/staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/semver.go b/staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/semver.go new file mode 100644 index 00000000000..c53b9c306a0 --- /dev/null +++ b/staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/semver.go @@ -0,0 +1,73 @@ +/* +Copyright 2024 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" + + "github.com/blang/semver/v4" + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" +) + +var ( + SemverType = cel.ObjectType("kubernetes.Semver") +) + +// Semver provdes a CEL representation of a [semver.Version]. +type Semver struct { + semver.Version +} + +func (v Semver) ConvertToNative(typeDesc reflect.Type) (interface{}, error) { + if reflect.TypeOf(v.Version).AssignableTo(typeDesc) { + return v.Version, nil + } + if reflect.TypeOf("").AssignableTo(typeDesc) { + return v.Version.String(), nil + } + return nil, fmt.Errorf("type conversion error from 'Semver' to '%v'", typeDesc) +} + +func (v Semver) ConvertToType(typeVal ref.Type) ref.Val { + switch typeVal { + case SemverType: + return v + case types.TypeType: + return SemverType + default: + return types.NewErr("type conversion error from '%s' to '%s'", SemverType, typeVal) + } +} + +func (v Semver) Equal(other ref.Val) ref.Val { + otherDur, ok := other.(Semver) + if !ok { + return types.MaybeNoSuchOverloadErr(other) + } + return types.Bool(v.Version.EQ(otherDur.Version)) +} + +func (v Semver) Type() ref.Type { + return SemverType +} + +func (v Semver) Value() interface{} { + return v.Version +} diff --git a/staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/semver_test.go b/staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/semver_test.go new file mode 100644 index 00000000000..38f43d15fb1 --- /dev/null +++ b/staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/semver_test.go @@ -0,0 +1,209 @@ +/* +Copyright 2023 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_test + +import ( + "regexp" + "testing" + + "github.com/blang/semver/v4" + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" + "github.com/stretchr/testify/require" + + "k8s.io/apimachinery/pkg/util/sets" + library "k8s.io/dynamic-resource-allocation/structured/namedresources/cel" +) + +func testSemver(t *testing.T, expr string, expectResult ref.Val, expectRuntimeErrPattern string, expectCompileErrs []string) { + env, err := cel.NewEnv( + library.SemverLib(), + ) + if err != nil { + t.Fatalf("%v", err) + } + compiled, issues := env.Compile(expr) + + if len(expectCompileErrs) > 0 { + missingCompileErrs := []string{} + matchedCompileErrs := sets.New[int]() + for _, expectedCompileErr := range expectCompileErrs { + compiledPattern, err := regexp.Compile(expectedCompileErr) + if err != nil { + t.Fatalf("failed to compile expected err regex: %v", err) + } + + didMatch := false + + for i, compileError := range issues.Errors() { + if compiledPattern.Match([]byte(compileError.Message)) { + didMatch = true + matchedCompileErrs.Insert(i) + } + } + + if !didMatch { + missingCompileErrs = append(missingCompileErrs, expectedCompileErr) + } else if len(matchedCompileErrs) != len(issues.Errors()) { + unmatchedErrs := []cel.Error{} + for i, issue := range issues.Errors() { + if !matchedCompileErrs.Has(i) { + unmatchedErrs = append(unmatchedErrs, *issue) + } + } + require.Empty(t, unmatchedErrs, "unexpected compilation errors") + } + } + + require.Empty(t, missingCompileErrs, "expected compilation errors") + return + } else if len(issues.Errors()) > 0 { + for _, err := range issues.Errors() { + t.Errorf("unexpected compile error: %v", err) + } + t.FailNow() + } + + prog, err := env.Program(compiled) + if err != nil { + t.Fatalf("%v", err) + } + res, _, err := prog.Eval(map[string]interface{}{}) + if len(expectRuntimeErrPattern) > 0 { + if err == nil { + t.Fatalf("no runtime error thrown. Expected: %v", expectRuntimeErrPattern) + } else if matched, regexErr := regexp.MatchString(expectRuntimeErrPattern, err.Error()); regexErr != nil { + t.Fatalf("failed to compile expected err regex: %v", regexErr) + } else if !matched { + t.Fatalf("unexpected err: %v", err) + } + } else if err != nil { + t.Fatalf("%v", err) + } else if expectResult != nil { + converted := res.Equal(expectResult).Value().(bool) + require.True(t, converted, "expectation not equal to output") + } else { + t.Fatal("expected result must not be nil") + } + +} + +func TestSemver(t *testing.T) { + trueVal := types.Bool(true) + falseVal := types.Bool(false) + + cases := []struct { + name string + expr string + expectValue ref.Val + expectedCompileErr []string + expectedRuntimeErr string + }{ + { + name: "parse", + expr: `semver("1.2.3")`, + expectValue: library.Semver{Version: semver.MustParse("1.2.3")}, + }, + { + name: "parseInvalidVersion", + expr: `semver("v1.0")`, + expectedRuntimeErr: "No Major.Minor.Patch elements found", + }, + { + name: "isSemver", + expr: `isSemver("1.2.3-beta.1+build.1")`, + expectValue: trueVal, + }, + { + name: "isSemver_false", + expr: `isSemver("v1.0")`, + expectValue: falseVal, + }, + { + name: "isSemver_noOverload", + expr: `isSemver([1, 2, 3])`, + expectedCompileErr: []string{"found no matching overload for 'isSemver' applied to.*"}, + }, + { + name: "equality_reflexivity", + expr: `semver("1.2.3") == semver("1.2.3")`, + expectValue: trueVal, + }, + { + name: "inequality", + expr: `semver("1.2.3") == semver("1.0.0")`, + expectValue: falseVal, + }, + { + name: "semver_less", + expr: `semver("1.0.0").isLessThan(semver("1.2.3"))`, + expectValue: trueVal, + }, + { + name: "semver_less_false", + expr: `semver("1.0.0").isLessThan(semver("1.0.0"))`, + expectValue: falseVal, + }, + { + name: "semver_greater", + expr: `semver("1.2.3").isGreaterThan(semver("1.0.0"))`, + expectValue: trueVal, + }, + { + name: "semver_greater_false", + expr: `semver("1.0.0").isGreaterThan(semver("1.0.0"))`, + expectValue: falseVal, + }, + { + name: "compare_equal", + expr: `semver("1.2.3").compareTo(semver("1.2.3"))`, + expectValue: types.Int(0), + }, + { + name: "compare_less", + expr: `semver("1.0.0").compareTo(semver("1.2.3"))`, + expectValue: types.Int(-1), + }, + { + name: "compare_greater", + expr: `semver("1.2.3").compareTo(semver("1.0.0"))`, + expectValue: types.Int(1), + }, + { + name: "major", + expr: `semver("1.2.3").major()`, + expectValue: types.Int(1), + }, + { + name: "minor", + expr: `semver("1.2.3").minor()`, + expectValue: types.Int(2), + }, + { + name: "patch", + expr: `semver("1.2.3").patch()`, + expectValue: types.Int(3), + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + testSemver(t, c.expr, c.expectValue, c.expectedRuntimeErr, c.expectedCompileErr) + }) + } +} diff --git a/staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/semverlib.go b/staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/semverlib.go new file mode 100644 index 00000000000..c9470761e73 --- /dev/null +++ b/staging/src/k8s.io/dynamic-resource-allocation/structured/namedresources/cel/semverlib.go @@ -0,0 +1,238 @@ +/* +Copyright 2024 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 ( + "github.com/blang/semver/v4" + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" +) + +// Semver provides a CEL function library extension for [semver.Version]. +// +// semver +// +// Converts a string to a semantic version or results in an error if the string is not a valid semantic version. Refer +// to semver.org documentation for information on accepted patterns. +// +// semver() +// +// Examples: +// +// semver('1.0.0') // returns a Semver +// semver('0.1.0-alpha.1') // returns a Semver +// semver('200K') // error +// semver('Three') // error +// semver('Mi') // error +// +// isSemver +// +// Returns true if a string is a valid Semver. isSemver returns true if and +// only if semver does not result in error. +// +// isSemver( ) +// +// Examples: +// +// isSemver('1.0.0') // returns true +// isSemver('v1.0') // returns true (tolerant parsing) +// isSemver('hello') // returns false +// +// Conversion to Scalars: +// +// - major/minor/patch: return the major version number as int64. +// +// .major() +// +// Examples: +// +// semver("1.2.3").major() // returns 1 +// +// Comparisons +// +// - isGreaterThan: Returns true if and only if the receiver is greater than the operand +// +// - isLessThan: Returns true if and only if the receiver is less than the operand +// +// - compareTo: Compares receiver to operand and returns 0 if they are equal, 1 if the receiver is greater, or -1 if the receiver is less than the operand +// +// +// .isLessThan() +// .isGreaterThan() +// .compareTo() +// +// Examples: +// +// semver("1.2.3").compareTo(semver("1.2.3")) // returns 0 +// semver("1.2.3").compareTo(semver("2.0.0")) // returns -1 +// semver("1.2.3").compareTo(semver("0.1.2")) // returns 1 + +func SemverLib() cel.EnvOption { + return cel.Lib(semverLib) +} + +var semverLib = &semverLibType{} + +type semverLibType struct{} + +func (*semverLibType) LibraryName() string { + return "k8s.semver" +} + +func (*semverLibType) CompileOptions() []cel.EnvOption { + // Defined in this function to avoid an initialization order problem. + semverLibraryDecls := map[string][]cel.FunctionOpt{ + "semver": { + cel.Overload("string_to_semver", []*cel.Type{cel.StringType}, SemverType, cel.UnaryBinding((stringToSemver))), + }, + "isSemver": { + cel.Overload("is_semver_string", []*cel.Type{cel.StringType}, cel.BoolType, cel.UnaryBinding(isSemver)), + }, + "isGreaterThan": { + cel.MemberOverload("semver_is_greater_than", []*cel.Type{SemverType, SemverType}, cel.BoolType, cel.BinaryBinding(semverIsGreaterThan)), + }, + "isLessThan": { + cel.MemberOverload("semver_is_less_than", []*cel.Type{SemverType, SemverType}, cel.BoolType, cel.BinaryBinding(semverIsLessThan)), + }, + "compareTo": { + cel.MemberOverload("semver_compare_to", []*cel.Type{SemverType, SemverType}, cel.IntType, cel.BinaryBinding(semverCompareTo)), + }, + "major": { + cel.MemberOverload("semver_major", []*cel.Type{SemverType}, cel.IntType, cel.UnaryBinding(semverMajor)), + }, + "minor": { + cel.MemberOverload("semver_minor", []*cel.Type{SemverType}, cel.IntType, cel.UnaryBinding(semverMinor)), + }, + "patch": { + cel.MemberOverload("semver_patch", []*cel.Type{SemverType}, cel.IntType, cel.UnaryBinding(semverPatch)), + }, + } + + options := make([]cel.EnvOption, 0, len(semverLibraryDecls)) + for name, overloads := range semverLibraryDecls { + options = append(options, cel.Function(name, overloads...)) + } + return options +} + +func (*semverLibType) ProgramOptions() []cel.ProgramOption { + return []cel.ProgramOption{} +} + +func isSemver(arg ref.Val) ref.Val { + str, ok := arg.Value().(string) + if !ok { + return types.MaybeNoSuchOverloadErr(arg) + } + + // Using semver/v4 here is okay because this function isn't + // used to validate the Kubernetes API. In the CEL base library + // we would have to use the regular expression from + // pkg/apis/resource/structured/namedresources/validation/validation.go. + _, err := semver.Parse(str) + if err != nil { + return types.Bool(false) + } + + return types.Bool(true) +} + +func stringToSemver(arg ref.Val) ref.Val { + str, ok := arg.Value().(string) + if !ok { + return types.MaybeNoSuchOverloadErr(arg) + } + + // Using semver/v4 here is okay because this function isn't + // used to validate the Kubernetes API. In the CEL base library + // we would have to use the regular expression from + // pkg/apis/resource/structured/namedresources/validation/validation.go + // first before parsing. + v, err := semver.Parse(str) + if err != nil { + return types.WrapErr(err) + } + + return Semver{Version: v} +} + +func semverMajor(arg ref.Val) ref.Val { + v, ok := arg.Value().(semver.Version) + if !ok { + return types.MaybeNoSuchOverloadErr(arg) + } + return types.Int(v.Major) +} + +func semverMinor(arg ref.Val) ref.Val { + v, ok := arg.Value().(semver.Version) + if !ok { + return types.MaybeNoSuchOverloadErr(arg) + } + return types.Int(v.Minor) +} + +func semverPatch(arg ref.Val) ref.Val { + v, ok := arg.Value().(semver.Version) + if !ok { + return types.MaybeNoSuchOverloadErr(arg) + } + return types.Int(v.Patch) +} + +func semverIsGreaterThan(arg ref.Val, other ref.Val) ref.Val { + v, ok := arg.Value().(semver.Version) + if !ok { + return types.MaybeNoSuchOverloadErr(arg) + } + + v2, ok := other.Value().(semver.Version) + if !ok { + return types.MaybeNoSuchOverloadErr(arg) + } + + return types.Bool(v.Compare(v2) == 1) +} + +func semverIsLessThan(arg ref.Val, other ref.Val) ref.Val { + v, ok := arg.Value().(semver.Version) + if !ok { + return types.MaybeNoSuchOverloadErr(arg) + } + + v2, ok := other.Value().(semver.Version) + if !ok { + return types.MaybeNoSuchOverloadErr(arg) + } + + return types.Bool(v.Compare(v2) == -1) +} + +func semverCompareTo(arg ref.Val, other ref.Val) ref.Val { + v, ok := arg.Value().(semver.Version) + if !ok { + return types.MaybeNoSuchOverloadErr(arg) + } + + v2, ok := other.Value().(semver.Version) + if !ok { + return types.MaybeNoSuchOverloadErr(arg) + } + + return types.Int(v.Compare(v2)) +}