Merge pull request #74998 from mbohlool/pippin

Webhook configurations can choose which version of Review request they accept
This commit is contained in:
Kubernetes Prow Robot 2019-03-08 03:01:26 -08:00 committed by GitHub
commit e318642946
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1777 additions and 783 deletions

View File

@ -201,6 +201,13 @@
"io.k8s.api.admissionregistration.v1beta1.Webhook": { "io.k8s.api.admissionregistration.v1beta1.Webhook": {
"description": "Webhook describes an admission webhook and the resources and operations it applies to.", "description": "Webhook describes an admission webhook and the resources and operations it applies to.",
"properties": { "properties": {
"admissionReviewVersions": {
"description": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy. Default to `['v1beta1']`.",
"items": {
"type": "string"
},
"type": "array"
},
"clientConfig": { "clientConfig": {
"$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.WebhookClientConfig", "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.WebhookClientConfig",
"description": "ClientConfig defines how to communicate with the hook. Required" "description": "ClientConfig defines how to communicate with the hook. Required"
@ -16161,6 +16168,13 @@
"io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceConversion": { "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceConversion": {
"description": "CustomResourceConversion describes how to convert different versions of a CR.", "description": "CustomResourceConversion describes how to convert different versions of a CR.",
"properties": { "properties": {
"conversionReviewVersions": {
"description": "ConversionReviewVersions is an ordered list of preferred `ConversionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, conversion will fail for this object. If a persisted Webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail. Default to `['v1beta1']`.",
"items": {
"type": "string"
},
"type": "array"
},
"strategy": { "strategy": {
"description": "`strategy` specifies the conversion strategy. Allowed values are: - `None`: The converter only change the apiVersion and would not touch any other field in the CR. - `Webhook`: API Server will call to an external webhook to do the conversion. Additional information is needed for this option.", "description": "`strategy` specifies the conversion strategy. Allowed values are: - `None`: The converter only change the apiVersion and would not touch any other field in the CR. - `Webhook`: API Server will call to an external webhook to do the conversion. Additional information is needed for this option.",
"type": "string" "type": "string"

View File

@ -43,6 +43,7 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
i := int32(30) i := int32(30)
obj.TimeoutSeconds = &i obj.TimeoutSeconds = &i
} }
obj.AdmissionReviewVersions = []string{"v1beta1"}
}, },
} }
} }

View File

@ -239,6 +239,15 @@ type Webhook struct {
// The timeout value must be between 1 and 30 seconds. // The timeout value must be between 1 and 30 seconds.
// +optional // +optional
TimeoutSeconds *int32 TimeoutSeconds *int32
// AdmissionReviewVersions is an ordered list of preferred `AdmissionReview`
// versions the Webhook expects. API server will try to use first version in
// the list which it supports. If none of the versions specified in this list
// supported by API server, validation will fail for this object.
// If the webhook configuration has already been persisted with a version apiserver
// does not understand, calls to the webhook will fail and be subject to the failure policy.
// +optional
AdmissionReviewVersions []string
} }
// RuleWithOperations is a tuple of Operations and Resources. It is recommended to make // RuleWithOperations is a tuple of Operations and Resources. It is recommended to make

View File

@ -44,6 +44,10 @@ func SetDefaults_Webhook(obj *admissionregistrationv1beta1.Webhook) {
obj.TimeoutSeconds = new(int32) obj.TimeoutSeconds = new(int32)
*obj.TimeoutSeconds = 30 *obj.TimeoutSeconds = 30
} }
if len(obj.AdmissionReviewVersions) == 0 {
obj.AdmissionReviewVersions = []string{admissionregistrationv1beta1.SchemeGroupVersion.Version}
}
} }
func SetDefaults_Rule(obj *admissionregistrationv1beta1.Rule) { func SetDefaults_Rule(obj *admissionregistrationv1beta1.Rule) {

View File

@ -304,6 +304,7 @@ func autoConvert_v1beta1_Webhook_To_admissionregistration_Webhook(in *v1beta1.We
out.NamespaceSelector = (*v1.LabelSelector)(unsafe.Pointer(in.NamespaceSelector)) out.NamespaceSelector = (*v1.LabelSelector)(unsafe.Pointer(in.NamespaceSelector))
out.SideEffects = (*admissionregistration.SideEffectClass)(unsafe.Pointer(in.SideEffects)) out.SideEffects = (*admissionregistration.SideEffectClass)(unsafe.Pointer(in.SideEffects))
out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds)) out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds))
out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions))
return nil return nil
} }
@ -322,6 +323,7 @@ func autoConvert_admissionregistration_Webhook_To_v1beta1_Webhook(in *admissionr
out.NamespaceSelector = (*v1.LabelSelector)(unsafe.Pointer(in.NamespaceSelector)) out.NamespaceSelector = (*v1.LabelSelector)(unsafe.Pointer(in.NamespaceSelector))
out.SideEffects = (*v1beta1.SideEffectClass)(unsafe.Pointer(in.SideEffects)) out.SideEffects = (*v1beta1.SideEffectClass)(unsafe.Pointer(in.SideEffects))
out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds)) out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds))
out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions))
return nil return nil
} }

View File

@ -22,6 +22,7 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/apis/admissionregistration/validation", importpath = "k8s.io/kubernetes/pkg/apis/admissionregistration/validation",
deps = [ deps = [
"//pkg/apis/admissionregistration:go_default_library", "//pkg/apis/admissionregistration:go_default_library",
"//pkg/apis/admissionregistration/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/validation:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",

View File

@ -24,9 +24,11 @@ import (
metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation"
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/util/webhook" "k8s.io/apiserver/pkg/util/webhook"
"k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/admissionregistration"
"k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1"
) )
func hasWildcard(slice []string) bool { func hasWildcard(slice []string) bool {
@ -150,18 +152,71 @@ func validateRule(rule *admissionregistration.Rule, fldPath *field.Path, allowSu
return allErrors return allErrors
} }
var AcceptedAdmissionReviewVersions = []string{v1beta1.SchemeGroupVersion.Version}
func isAcceptedAdmissionReviewVersion(v string) bool {
for _, version := range AcceptedAdmissionReviewVersions {
if v == version {
return true
}
}
return false
}
func validateAdmissionReviewVersions(versions []string, requireRecognizedVersion bool, fldPath *field.Path) field.ErrorList {
allErrors := field.ErrorList{}
// Currently only v1beta1 accepted in AdmissionReviewVersions
if len(versions) < 1 {
allErrors = append(allErrors, field.Required(fldPath, ""))
} else {
seen := map[string]bool{}
hasAcceptedVersion := false
for i, v := range versions {
if seen[v] {
allErrors = append(allErrors, field.Invalid(fldPath.Index(i), v, "duplicate version"))
continue
}
seen[v] = true
for _, errString := range utilvalidation.IsDNS1035Label(v) {
allErrors = append(allErrors, field.Invalid(fldPath.Index(i), v, errString))
}
if isAcceptedAdmissionReviewVersion(v) {
hasAcceptedVersion = true
}
}
if requireRecognizedVersion && !hasAcceptedVersion {
allErrors = append(allErrors, field.Invalid(
fldPath, versions,
fmt.Sprintf("none of the versions accepted by this server. accepted version(s) are %v",
strings.Join(AcceptedAdmissionReviewVersions, ", "))))
}
}
return allErrors
}
func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList { func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList {
return validateValidatingWebhookConfiguration(e, true)
}
func validateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration, requireRecognizedVersion bool) field.ErrorList {
allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
for i, hook := range e.Webhooks { for i, hook := range e.Webhooks {
allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...) allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...)
allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, requireRecognizedVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...)
} }
return allErrors return allErrors
} }
func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration) field.ErrorList { func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
return validateMutatingWebhookConfiguration(e, true)
}
func validateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration, requireRecognizedVersion bool) field.ErrorList {
allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
for i, hook := range e.Webhooks { for i, hook := range e.Webhooks {
allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...) allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...)
allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, requireRecognizedVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...)
} }
return allErrors return allErrors
} }
@ -247,10 +302,28 @@ func validateRuleWithOperations(ruleWithOperations *admissionregistration.RuleWi
return allErrors return allErrors
} }
// hasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one
// admission review version this apiserver accepts.
func hasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.Webhook) bool {
for _, hook := range webhooks {
hasRecognizedVersion := false
for _, version := range hook.AdmissionReviewVersions {
if isAcceptedAdmissionReviewVersion(version) {
hasRecognizedVersion = true
break
}
}
if !hasRecognizedVersion && len(hook.AdmissionReviewVersions) > 0 {
return false
}
}
return true
}
func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList { func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList {
return ValidateValidatingWebhookConfiguration(newC) return validateValidatingWebhookConfiguration(newC, hasAcceptedAdmissionReviewVersions(oldC.Webhooks))
} }
func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList { func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
return ValidateMutatingWebhookConfiguration(newC) return validateMutatingWebhookConfiguration(newC, hasAcceptedAdmissionReviewVersions(oldC.Webhooks))
} }

View File

@ -28,7 +28,14 @@ func strPtr(s string) *string { return &s }
func int32Ptr(i int32) *int32 { return &i } func int32Ptr(i int32) *int32 { return &i }
func newValidatingWebhookConfiguration(hooks []admissionregistration.Webhook) *admissionregistration.ValidatingWebhookConfiguration { func newValidatingWebhookConfiguration(hooks []admissionregistration.Webhook, defaultAdmissionReviewVersions bool) *admissionregistration.ValidatingWebhookConfiguration {
// If the test case did not specify an AdmissionReviewVersions, default it so the test passes as
// this field will be defaulted in production code.
for i := range hooks {
if defaultAdmissionReviewVersions && len(hooks[i].AdmissionReviewVersions) == 0 {
hooks[i].AdmissionReviewVersions = []string{"v1beta1"}
}
}
return &admissionregistration.ValidatingWebhookConfiguration{ return &admissionregistration.ValidatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "config", Name: "config",
@ -48,10 +55,64 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
config *admissionregistration.ValidatingWebhookConfiguration config *admissionregistration.ValidatingWebhookConfiguration
expectedError string expectedError string
}{ }{
{
name: "should fail on bad AdmissionReviewVersion value",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
{
Name: "webhook.k8s.io",
ClientConfig: validClientConfig,
AdmissionReviewVersions: []string{"0v"},
},
}, true),
expectedError: `Invalid value: "0v": a DNS-1035 label`,
},
{
name: "should pass on valid AdmissionReviewVersion",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
{
Name: "webhook.k8s.io",
ClientConfig: validClientConfig,
AdmissionReviewVersions: []string{"v1beta1"},
},
}, true),
expectedError: ``,
},
{
name: "should pass on mix of accepted and unaccepted AdmissionReviewVersion",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
{
Name: "webhook.k8s.io",
ClientConfig: validClientConfig,
AdmissionReviewVersions: []string{"v1beta1", "invalid-version"},
},
}, true),
expectedError: ``,
},
{
name: "should fail on invalid AdmissionReviewVersion",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
{
Name: "webhook.k8s.io",
ClientConfig: validClientConfig,
AdmissionReviewVersions: []string{"invalidVersion"},
},
}, true),
expectedError: `Invalid value: []string{"invalidVersion"}`,
},
{
name: "should fail on duplicate AdmissionReviewVersion",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
{
Name: "webhook.k8s.io",
ClientConfig: validClientConfig,
AdmissionReviewVersions: []string{"v1beta1", "v1beta1"},
},
}, true),
expectedError: `Invalid value: "v1beta1": duplicate version`,
},
{ {
name: "all Webhooks must have a fully qualified name", name: "all Webhooks must have a fully qualified name",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -64,13 +125,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
Name: "", Name: "",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
}, },
}), }, true),
expectedError: `webhooks[1].name: Invalid value: "k8s.io": should be a domain with at least three segments separated by dots, webhooks[2].name: Required value`, expectedError: `webhooks[1].name: Invalid value: "k8s.io": should be a domain with at least three segments separated by dots, webhooks[2].name: Required value`,
}, },
{ {
name: "Operations must not be empty or nil", name: "Operations must not be empty or nil",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
Rules: []admissionregistration.RuleWithOperations{ Rules: []admissionregistration.RuleWithOperations{
@ -92,13 +152,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
expectedError: `webhooks[0].rules[0].operations: Required value, webhooks[0].rules[1].operations: Required value`, expectedError: `webhooks[0].rules[0].operations: Required value, webhooks[0].rules[1].operations: Required value`,
}, },
{ {
name: "\"\" is NOT a valid operation", name: "\"\" is NOT a valid operation",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
Rules: []admissionregistration.RuleWithOperations{ Rules: []admissionregistration.RuleWithOperations{
@ -112,13 +171,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
expectedError: `Unsupported value: ""`, expectedError: `Unsupported value: ""`,
}, },
{ {
name: "operation must be either create/update/delete/connect", name: "operation must be either create/update/delete/connect",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
Rules: []admissionregistration.RuleWithOperations{ Rules: []admissionregistration.RuleWithOperations{
@ -132,13 +190,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
expectedError: `Unsupported value: "PATCH"`, expectedError: `Unsupported value: "PATCH"`,
}, },
{ {
name: "wildcard operation cannot be mixed with other strings", name: "wildcard operation cannot be mixed with other strings",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
Rules: []admissionregistration.RuleWithOperations{ Rules: []admissionregistration.RuleWithOperations{
@ -152,13 +209,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
expectedError: `if '*' is present, must not specify other operations`, expectedError: `if '*' is present, must not specify other operations`,
}, },
{ {
name: `resource "*" can co-exist with resources that have subresources`, name: `resource "*" can co-exist with resources that have subresources`,
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -173,12 +229,11 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
}, },
{ {
name: `resource "*" cannot mix with resources that don't have subresources`, name: `resource "*" cannot mix with resources that don't have subresources`,
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -193,13 +248,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
expectedError: `if '*' is present, must not specify other resources without subresources`, expectedError: `if '*' is present, must not specify other resources without subresources`,
}, },
{ {
name: "resource a/* cannot mix with a/x", name: "resource a/* cannot mix with a/x",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -214,13 +268,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`, expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`,
}, },
{ {
name: "resource a/* can mix with a", name: "resource a/* can mix with a",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -235,12 +288,11 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
}, },
{ {
name: "resource */a cannot mix with x/a", name: "resource */a cannot mix with x/a",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -255,13 +307,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`, expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`,
}, },
{ {
name: "resource */* cannot mix with other resources", name: "resource */* cannot mix with other resources",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -276,13 +327,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
expectedError: `webhooks[0].rules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`, expectedError: `webhooks[0].rules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`,
}, },
{ {
name: "FailurePolicy can only be \"Ignore\" or \"Fail\"", name: "FailurePolicy can only be \"Ignore\" or \"Fail\"",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -291,13 +341,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
return &r return &r
}(), }(),
}, },
}), }, true),
expectedError: `webhooks[0].failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`, expectedError: `webhooks[0].failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`,
}, },
{ {
name: "SideEffects can only be \"Unknown\", \"None\", \"Some\", or \"NoneOnDryRun\"", name: "SideEffects can only be \"Unknown\", \"None\", \"Some\", or \"NoneOnDryRun\"",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -306,24 +355,22 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
return &r return &r
}(), }(),
}, },
}), }, true),
expectedError: `webhooks[0].sideEffects: Unsupported value: "other": supported values: "None", "NoneOnDryRun", "Some", "Unknown"`, expectedError: `webhooks[0].sideEffects: Unsupported value: "other": supported values: "None", "NoneOnDryRun", "Some", "Unknown"`,
}, },
{ {
name: "both service and URL missing", name: "both service and URL missing",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{}, ClientConfig: admissionregistration.WebhookClientConfig{},
}, },
}), }, true),
expectedError: `exactly one of`, expectedError: `exactly one of`,
}, },
{ {
name: "both service and URL provided", name: "both service and URL provided",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -334,104 +381,96 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
URL: strPtr("example.com/k8s/webhook"), URL: strPtr("example.com/k8s/webhook"),
}, },
}, },
}), }, true),
expectedError: `[0].clientConfig: Required value: exactly one of url or service is required`, expectedError: `[0].clientConfig: Required value: exactly one of url or service is required`,
}, },
{ {
name: "blank URL", name: "blank URL",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
URL: strPtr(""), URL: strPtr(""),
}, },
}, },
}), }, true),
expectedError: `[0].clientConfig.url: Invalid value: "": host must be provided`, expectedError: `[0].clientConfig.url: Invalid value: "": host must be provided`,
}, },
{ {
name: "wrong scheme", name: "wrong scheme",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
URL: strPtr("http://example.com"), URL: strPtr("http://example.com"),
}, },
}, },
}), }, true),
expectedError: `https`, expectedError: `https`,
}, },
{ {
name: "missing host", name: "missing host",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
URL: strPtr("https:///fancy/webhook"), URL: strPtr("https:///fancy/webhook"),
}, },
}, },
}), }, true),
expectedError: `host must be provided`, expectedError: `host must be provided`,
}, },
{ {
name: "fragment", name: "fragment",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
URL: strPtr("https://example.com/#bookmark"), URL: strPtr("https://example.com/#bookmark"),
}, },
}, },
}), }, true),
expectedError: `"bookmark": fragments are not permitted`, expectedError: `"bookmark": fragments are not permitted`,
}, },
{ {
name: "query", name: "query",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
URL: strPtr("https://example.com?arg=value"), URL: strPtr("https://example.com?arg=value"),
}, },
}, },
}), }, true),
expectedError: `"arg=value": query parameters are not permitted`, expectedError: `"arg=value": query parameters are not permitted`,
}, },
{ {
name: "user", name: "user",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
URL: strPtr("https://harry.potter@example.com/"), URL: strPtr("https://harry.potter@example.com/"),
}, },
}, },
}), }, true),
expectedError: `"harry.potter": user information is not permitted`, expectedError: `"harry.potter": user information is not permitted`,
}, },
{ {
name: "just totally wrong", name: "just totally wrong",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
URL: strPtr("arg#backwards=thisis?html.index/port:host//:https"), URL: strPtr("arg#backwards=thisis?html.index/port:host//:https"),
}, },
}, },
}), }, true),
expectedError: `host must be provided`, expectedError: `host must be provided`,
}, },
{ {
name: "path must start with slash", name: "path must start with slash",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -442,13 +481,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
expectedError: `clientConfig.service.path: Invalid value: "foo/": must start with a '/'`, expectedError: `clientConfig.service.path: Invalid value: "foo/": must start with a '/'`,
}, },
{ {
name: "path accepts slash", name: "path accepts slash",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -459,13 +497,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
expectedError: ``, expectedError: ``,
}, },
{ {
name: "path accepts no trailing slash", name: "path accepts no trailing slash",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -476,13 +513,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
expectedError: ``, expectedError: ``,
}, },
{ {
name: "path fails //", name: "path fails //",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -493,13 +529,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
expectedError: `clientConfig.service.path: Invalid value: "//": segment[0] may not be empty`, expectedError: `clientConfig.service.path: Invalid value: "//": segment[0] may not be empty`,
}, },
{ {
name: "path no empty step", name: "path no empty step",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -510,12 +545,11 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`, expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`,
}, { }, {
name: "path no empty step 2", name: "path no empty step 2",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -526,13 +560,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
expectedError: `clientConfig.service.path: Invalid value: "/foo/bar//": segment[2] may not be empty`, expectedError: `clientConfig.service.path: Invalid value: "/foo/bar//": segment[2] may not be empty`,
}, },
{ {
name: "path no non-subdomain", name: "path no non-subdomain",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -543,49 +576,45 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}, },
}), }, true),
expectedError: `clientConfig.service.path: Invalid value: "/apis/foo.bar/v1alpha1/--bad": segment[3]: a DNS-1123 subdomain`, expectedError: `clientConfig.service.path: Invalid value: "/apis/foo.bar/v1alpha1/--bad": segment[3]: a DNS-1123 subdomain`,
}, },
{ {
name: "timeout seconds cannot be greater than 30", name: "timeout seconds cannot be greater than 30",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
TimeoutSeconds: int32Ptr(31), TimeoutSeconds: int32Ptr(31),
}, },
}), }, true),
expectedError: `webhooks[0].timeoutSeconds: Invalid value: 31: the timeout value must be between 1 and 30 seconds`, expectedError: `webhooks[0].timeoutSeconds: Invalid value: 31: the timeout value must be between 1 and 30 seconds`,
}, },
{ {
name: "timeout seconds cannot be smaller than 1", name: "timeout seconds cannot be smaller than 1",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
TimeoutSeconds: int32Ptr(0), TimeoutSeconds: int32Ptr(0),
}, },
}), }, true),
expectedError: `webhooks[0].timeoutSeconds: Invalid value: 0: the timeout value must be between 1 and 30 seconds`, expectedError: `webhooks[0].timeoutSeconds: Invalid value: 0: the timeout value must be between 1 and 30 seconds`,
}, },
{ {
name: "timeout seconds must be positive", name: "timeout seconds must be positive",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
TimeoutSeconds: int32Ptr(-1), TimeoutSeconds: int32Ptr(-1),
}, },
}), }, true),
expectedError: `webhooks[0].timeoutSeconds: Invalid value: -1: the timeout value must be between 1 and 30 seconds`, expectedError: `webhooks[0].timeoutSeconds: Invalid value: -1: the timeout value must be between 1 and 30 seconds`,
}, },
{ {
name: "valid timeout seconds", name: "valid timeout seconds",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
[]admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -601,7 +630,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
TimeoutSeconds: int32Ptr(30), TimeoutSeconds: int32Ptr(30),
}, },
}), }, true),
}, },
} }
for _, test := range tests { for _, test := range tests {
@ -621,3 +650,102 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
} }
} }
func TestValidateValidatingWebhookConfigurationUpdate(t *testing.T) {
validClientConfig := admissionregistration.WebhookClientConfig{
URL: strPtr("https://example.com"),
}
tests := []struct {
name string
oldconfig *admissionregistration.ValidatingWebhookConfiguration
config *admissionregistration.ValidatingWebhookConfiguration
expectedError string
}{
{
name: "should pass on valid new AdmissionReviewVersion",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
{
Name: "webhook.k8s.io",
ClientConfig: validClientConfig,
AdmissionReviewVersions: []string{"v1beta1"},
},
}, true),
oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
{
Name: "webhook.k8s.io",
ClientConfig: validClientConfig,
},
}, true),
expectedError: ``,
},
{
name: "should pass on invalid AdmissionReviewVersion with invalid previous versions",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
{
Name: "webhook.k8s.io",
ClientConfig: validClientConfig,
AdmissionReviewVersions: []string{"invalid-v1", "invalid-v2"},
},
}, true),
oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
{
Name: "webhook.k8s.io",
ClientConfig: validClientConfig,
AdmissionReviewVersions: []string{"invalid-v0"},
},
}, true),
expectedError: ``,
},
{
name: "should fail on invalid AdmissionReviewVersion with valid previous versions",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
{
Name: "webhook.k8s.io",
ClientConfig: validClientConfig,
AdmissionReviewVersions: []string{"invalid-v1"},
},
}, true),
oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
{
Name: "webhook.k8s.io",
ClientConfig: validClientConfig,
AdmissionReviewVersions: []string{"v1beta1", "invalid-v1"},
},
}, true),
expectedError: `Invalid value: []string{"invalid-v1"}`,
},
{
name: "should fail on invalid AdmissionReviewVersion with missing previous versions",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
{
Name: "webhook.k8s.io",
ClientConfig: validClientConfig,
AdmissionReviewVersions: []string{"invalid-v1"},
},
}, true),
oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
{
Name: "webhook.k8s.io",
ClientConfig: validClientConfig,
},
}, false),
expectedError: `Invalid value: []string{"invalid-v1"}`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
errs := ValidateValidatingWebhookConfigurationUpdate(test.config, test.oldconfig)
err := errs.ToAggregate()
if err != nil {
if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" {
t.Errorf("expected to contain %s, got %s", e, a)
}
} else {
if test.expectedError != "" {
t.Errorf("unexpected no error, expected to contain %s", test.expectedError)
}
}
})
}
}

View File

@ -267,6 +267,11 @@ func (in *Webhook) DeepCopyInto(out *Webhook) {
*out = new(int32) *out = new(int32)
**out = **in **out = **in
} }
if in.AdmissionReviewVersions != nil {
in, out := &in.AdmissionReviewVersions, &out.AdmissionReviewVersions
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }

View File

@ -78,9 +78,7 @@ func (mutatingWebhookConfigurationStrategy) AllowCreateOnUpdate() bool {
// ValidateUpdate is the default update validation for an end user. // ValidateUpdate is the default update validation for an end user.
func (mutatingWebhookConfigurationStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { func (mutatingWebhookConfigurationStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
validationErrorList := validation.ValidateMutatingWebhookConfiguration(obj.(*admissionregistration.MutatingWebhookConfiguration)) return validation.ValidateMutatingWebhookConfigurationUpdate(obj.(*admissionregistration.MutatingWebhookConfiguration), old.(*admissionregistration.MutatingWebhookConfiguration))
updateErrorList := validation.ValidateMutatingWebhookConfigurationUpdate(obj.(*admissionregistration.MutatingWebhookConfiguration), old.(*admissionregistration.MutatingWebhookConfiguration))
return append(validationErrorList, updateErrorList...)
} }
// AllowUnconditionalUpdate is the default update policy for mutatingWebhookConfiguration objects. Status update should // AllowUnconditionalUpdate is the default update policy for mutatingWebhookConfiguration objects. Status update should

View File

@ -63,8 +63,7 @@ func (validatingWebhookConfigurationStrategy) PrepareForUpdate(ctx context.Conte
// Validate validates a new validatingWebhookConfiguration. // Validate validates a new validatingWebhookConfiguration.
func (validatingWebhookConfigurationStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { func (validatingWebhookConfigurationStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
ic := obj.(*admissionregistration.ValidatingWebhookConfiguration) return validation.ValidateValidatingWebhookConfiguration(obj.(*admissionregistration.ValidatingWebhookConfiguration))
return validation.ValidateValidatingWebhookConfiguration(ic)
} }
// Canonicalize normalizes the object after validation. // Canonicalize normalizes the object after validation.
@ -78,9 +77,7 @@ func (validatingWebhookConfigurationStrategy) AllowCreateOnUpdate() bool {
// ValidateUpdate is the default update validation for an end user. // ValidateUpdate is the default update validation for an end user.
func (validatingWebhookConfigurationStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { func (validatingWebhookConfigurationStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
validationErrorList := validation.ValidateValidatingWebhookConfiguration(obj.(*admissionregistration.ValidatingWebhookConfiguration)) return validation.ValidateValidatingWebhookConfigurationUpdate(obj.(*admissionregistration.ValidatingWebhookConfiguration), old.(*admissionregistration.ValidatingWebhookConfiguration))
updateErrorList := validation.ValidateValidatingWebhookConfigurationUpdate(obj.(*admissionregistration.ValidatingWebhookConfiguration), old.(*admissionregistration.ValidatingWebhookConfiguration))
return append(validationErrorList, updateErrorList...)
} }
// AllowUnconditionalUpdate is the default update policy for validatingWebhookConfiguration objects. Status update should // AllowUnconditionalUpdate is the default update policy for validatingWebhookConfiguration objects. Status update should

View File

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

View File

@ -237,6 +237,17 @@ message Webhook {
// Default to 30 seconds. // Default to 30 seconds.
// +optional // +optional
optional int32 timeoutSeconds = 7; optional int32 timeoutSeconds = 7;
// AdmissionReviewVersions is an ordered list of preferred `AdmissionReview`
// versions the Webhook expects. API server will try to use first version in
// the list which it supports. If none of the versions specified in this list
// supported by API server, validation will fail for this object.
// If a persisted webhook configuration specifies allowed versions and does not
// include any versions known to the API Server, calls to the webhook will fail
// and be subject to the failure policy.
// Default to `['v1beta1']`.
// +optional
repeated string admissionReviewVersions = 8;
} }
// WebhookClientConfig contains the information to make a TLS // WebhookClientConfig contains the information to make a TLS

View File

@ -248,6 +248,17 @@ type Webhook struct {
// Default to 30 seconds. // Default to 30 seconds.
// +optional // +optional
TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty" protobuf:"varint,7,opt,name=timeoutSeconds"` TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty" protobuf:"varint,7,opt,name=timeoutSeconds"`
// AdmissionReviewVersions is an ordered list of preferred `AdmissionReview`
// versions the Webhook expects. API server will try to use first version in
// the list which it supports. If none of the versions specified in this list
// supported by API server, validation will fail for this object.
// If a persisted webhook configuration specifies allowed versions and does not
// include any versions known to the API Server, calls to the webhook will fail
// and be subject to the failure policy.
// Default to `['v1beta1']`.
// +optional
AdmissionReviewVersions []string `json:"admissionReviewVersions,omitempty" protobuf:"bytes,8,rep,name=admissionReviewVersions"`
} }
// RuleWithOperations is a tuple of Operations and Resources. It is recommended to make // RuleWithOperations is a tuple of Operations and Resources. It is recommended to make

View File

@ -108,6 +108,7 @@ var map_Webhook = map[string]string{
"namespaceSelector": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", "namespaceSelector": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.",
"sideEffects": "SideEffects states whether this webhookk has side effects. Acceptable values are: Unknown, None, Some, NoneOnDryRun Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission change and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some. Defaults to Unknown.", "sideEffects": "SideEffects states whether this webhookk has side effects. Acceptable values are: Unknown, None, Some, NoneOnDryRun Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission change and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some. Defaults to Unknown.",
"timeoutSeconds": "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 30 seconds.", "timeoutSeconds": "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 30 seconds.",
"admissionReviewVersions": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy. Default to `['v1beta1']`.",
} }
func (Webhook) SwaggerDoc() map[string]string { func (Webhook) SwaggerDoc() map[string]string {

View File

@ -267,6 +267,11 @@ func (in *Webhook) DeepCopyInto(out *Webhook) {
*out = new(int32) *out = new(int32)
**out = **in **out = **in
} }
if in.AdmissionReviewVersions != nil {
in, out := &in.AdmissionReviewVersions, &out.AdmissionReviewVersions
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }

View File

@ -66,6 +66,9 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} {
Strategy: apiextensions.NoneConverter, Strategy: apiextensions.NoneConverter,
} }
} }
if obj.Conversion.Strategy == apiextensions.WebhookConverter && len(obj.Conversion.ConversionReviewVersions) == 0 {
obj.Conversion.ConversionReviewVersions = []string{"v1beta1"}
}
}, },
func(obj *apiextensions.CustomResourceDefinition, c fuzz.Continue) { func(obj *apiextensions.CustomResourceDefinition, c fuzz.Continue) {
c.FuzzNoCustom(obj) c.FuzzNoCustom(obj)

View File

@ -84,6 +84,15 @@ type CustomResourceConversion struct {
// `webhookClientConfig` is the instructions for how to call the webhook if strategy is `Webhook`. // `webhookClientConfig` is the instructions for how to call the webhook if strategy is `Webhook`.
WebhookClientConfig *WebhookClientConfig WebhookClientConfig *WebhookClientConfig
// ConversionReviewVersions is an ordered list of preferred `ConversionReview`
// versions the Webhook expects. API server will try to use first version in
// the list which it supports. If none of the versions specified in this list
// supported by API server, conversion will fail for this object.
// If a persisted Webhook configuration specifies allowed versions and does not
// include any versions known to the API Server, calls to the webhook will fail.
// +optional
ConversionReviewVersions []string
} }
// WebhookClientConfig contains the information to make a TLS // WebhookClientConfig contains the information to make a TLS

View File

@ -68,6 +68,9 @@ func SetDefaults_CustomResourceDefinitionSpec(obj *CustomResourceDefinitionSpec)
Strategy: NoneConverter, Strategy: NoneConverter,
} }
} }
if obj.Conversion.Strategy == WebhookConverter && len(obj.Conversion.ConversionReviewVersions) == 0 {
obj.Conversion.ConversionReviewVersions = []string{SchemeGroupVersion.Version}
}
} }
// hasPerVersionColumns returns true if a CRD uses per-version columns. // hasPerVersionColumns returns true if a CRD uses per-version columns.

View File

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

View File

@ -113,6 +113,16 @@ message CustomResourceConversion {
// alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature. // alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature.
// +optional // +optional
optional WebhookClientConfig webhookClientConfig = 2; optional WebhookClientConfig webhookClientConfig = 2;
// ConversionReviewVersions is an ordered list of preferred `ConversionReview`
// versions the Webhook expects. API server will try to use first version in
// the list which it supports. If none of the versions specified in this list
// supported by API server, conversion will fail for this object.
// If a persisted Webhook configuration specifies allowed versions and does not
// include any versions known to the API Server, calls to the webhook will fail.
// Default to `['v1beta1']`.
// +optional
repeated string conversionReviewVersions = 3;
} }
// CustomResourceDefinition represents a resource that should be exposed on the API server. Its name MUST be in the format // CustomResourceDefinition represents a resource that should be exposed on the API server. Its name MUST be in the format

View File

@ -91,6 +91,16 @@ type CustomResourceConversion struct {
// alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature. // alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature.
// +optional // +optional
WebhookClientConfig *WebhookClientConfig `json:"webhookClientConfig,omitempty" protobuf:"bytes,2,name=webhookClientConfig"` WebhookClientConfig *WebhookClientConfig `json:"webhookClientConfig,omitempty" protobuf:"bytes,2,name=webhookClientConfig"`
// ConversionReviewVersions is an ordered list of preferred `ConversionReview`
// versions the Webhook expects. API server will try to use first version in
// the list which it supports. If none of the versions specified in this list
// supported by API server, conversion will fail for this object.
// If a persisted Webhook configuration specifies allowed versions and does not
// include any versions known to the API Server, calls to the webhook will fail.
// Default to `['v1beta1']`.
// +optional
ConversionReviewVersions []string `json:"conversionReviewVersions,omitempty" protobuf:"bytes,3,rep,name=conversionReviewVersions"`
} }
// WebhookClientConfig contains the information to make a TLS // WebhookClientConfig contains the information to make a TLS

View File

@ -296,6 +296,7 @@ func Convert_apiextensions_CustomResourceColumnDefinition_To_v1beta1_CustomResou
func autoConvert_v1beta1_CustomResourceConversion_To_apiextensions_CustomResourceConversion(in *CustomResourceConversion, out *apiextensions.CustomResourceConversion, s conversion.Scope) error { func autoConvert_v1beta1_CustomResourceConversion_To_apiextensions_CustomResourceConversion(in *CustomResourceConversion, out *apiextensions.CustomResourceConversion, s conversion.Scope) error {
out.Strategy = apiextensions.ConversionStrategyType(in.Strategy) out.Strategy = apiextensions.ConversionStrategyType(in.Strategy)
out.WebhookClientConfig = (*apiextensions.WebhookClientConfig)(unsafe.Pointer(in.WebhookClientConfig)) out.WebhookClientConfig = (*apiextensions.WebhookClientConfig)(unsafe.Pointer(in.WebhookClientConfig))
out.ConversionReviewVersions = *(*[]string)(unsafe.Pointer(&in.ConversionReviewVersions))
return nil return nil
} }
@ -307,6 +308,7 @@ func Convert_v1beta1_CustomResourceConversion_To_apiextensions_CustomResourceCon
func autoConvert_apiextensions_CustomResourceConversion_To_v1beta1_CustomResourceConversion(in *apiextensions.CustomResourceConversion, out *CustomResourceConversion, s conversion.Scope) error { func autoConvert_apiextensions_CustomResourceConversion_To_v1beta1_CustomResourceConversion(in *apiextensions.CustomResourceConversion, out *CustomResourceConversion, s conversion.Scope) error {
out.Strategy = ConversionStrategyType(in.Strategy) out.Strategy = ConversionStrategyType(in.Strategy)
out.WebhookClientConfig = (*WebhookClientConfig)(unsafe.Pointer(in.WebhookClientConfig)) out.WebhookClientConfig = (*WebhookClientConfig)(unsafe.Pointer(in.WebhookClientConfig))
out.ConversionReviewVersions = *(*[]string)(unsafe.Pointer(&in.ConversionReviewVersions))
return nil return nil
} }

View File

@ -130,6 +130,11 @@ func (in *CustomResourceConversion) DeepCopyInto(out *CustomResourceConversion)
*out = new(WebhookClientConfig) *out = new(WebhookClientConfig)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
if in.ConversionReviewVersions != nil {
in, out := &in.ConversionReviewVersions, &out.ConversionReviewVersions
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }

View File

@ -13,6 +13,7 @@ go_library(
importpath = "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation", importpath = "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation",
deps = [ deps = [
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/features:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",

View File

@ -24,12 +24,14 @@ import (
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
genericvalidation "k8s.io/apimachinery/pkg/api/validation" genericvalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
validationutil "k8s.io/apimachinery/pkg/util/validation" validationutil "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/apiserver/pkg/util/webhook" "k8s.io/apiserver/pkg/util/webhook"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation" apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features" apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
) )
@ -113,6 +115,10 @@ func ValidateCustomResourceDefinitionVersion(version *apiextensions.CustomResour
// ValidateCustomResourceDefinitionSpec statically validates // ValidateCustomResourceDefinitionSpec statically validates
func ValidateCustomResourceDefinitionSpec(spec *apiextensions.CustomResourceDefinitionSpec, fldPath *field.Path) field.ErrorList { func ValidateCustomResourceDefinitionSpec(spec *apiextensions.CustomResourceDefinitionSpec, fldPath *field.Path) field.ErrorList {
return validateCustomResourceDefinitionSpec(spec, true, fldPath)
}
func validateCustomResourceDefinitionSpec(spec *apiextensions.CustomResourceDefinitionSpec, requireRecognizedVersion bool, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
if len(spec.Group) == 0 { if len(spec.Group) == 0 {
@ -205,7 +211,7 @@ func ValidateCustomResourceDefinitionSpec(spec *apiextensions.CustomResourceDefi
} }
} }
allErrs = append(allErrs, ValidateCustomResourceConversion(spec.Conversion, fldPath.Child("conversion"))...) allErrs = append(allErrs, validateCustomResourceConversion(spec.Conversion, requireRecognizedVersion, fldPath.Child("conversion"))...)
return allErrs return allErrs
} }
@ -225,8 +231,66 @@ func validateEnumStrings(fldPath *field.Path, value string, accepted []string, r
return field.ErrorList{field.NotSupported(fldPath, value, accepted)} return field.ErrorList{field.NotSupported(fldPath, value, accepted)}
} }
var acceptedConversionReviewVersion = []string{v1beta1.SchemeGroupVersion.Version}
func isAcceptedConversionReviewVersion(v string) bool {
for _, version := range acceptedConversionReviewVersion {
if v == version {
return true
}
}
return false
}
func validateConversionReviewVersions(versions []string, requireRecognizedVersion bool, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(versions) < 1 {
allErrs = append(allErrs, field.Required(fldPath, ""))
} else {
seen := map[string]bool{}
hasAcceptedVersion := false
for i, v := range versions {
if seen[v] {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i), v, "duplicate version"))
continue
}
seen[v] = true
for _, errString := range utilvalidation.IsDNS1035Label(v) {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i), v, errString))
}
if isAcceptedConversionReviewVersion(v) {
hasAcceptedVersion = true
}
}
if requireRecognizedVersion && !hasAcceptedVersion {
allErrs = append(allErrs, field.Invalid(
fldPath, versions,
fmt.Sprintf("none of the versions accepted by this server. accepted version(s) are %v",
strings.Join(acceptedConversionReviewVersion, ", "))))
}
}
return allErrs
}
// hasValidConversionReviewVersion return true if there is a valid version or if the list is empty.
func hasValidConversionReviewVersionOrEmpty(versions []string) bool {
if len(versions) < 1 {
return true
}
for _, v := range versions {
if isAcceptedConversionReviewVersion(v) {
return true
}
}
return false
}
// ValidateCustomResourceConversion statically validates // ValidateCustomResourceConversion statically validates
func ValidateCustomResourceConversion(conversion *apiextensions.CustomResourceConversion, fldPath *field.Path) field.ErrorList { func ValidateCustomResourceConversion(conversion *apiextensions.CustomResourceConversion, fldPath *field.Path) field.ErrorList {
return validateCustomResourceConversion(conversion, true, fldPath)
}
func validateCustomResourceConversion(conversion *apiextensions.CustomResourceConversion, requireRecognizedVersion bool, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
if conversion == nil { if conversion == nil {
return allErrs return allErrs
@ -250,15 +314,22 @@ func ValidateCustomResourceConversion(conversion *apiextensions.CustomResourceCo
allErrs = append(allErrs, webhook.ValidateWebhookService(fldPath.Child("webhookClientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path)...) allErrs = append(allErrs, webhook.ValidateWebhookService(fldPath.Child("webhookClientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path)...)
} }
} }
} else if conversion.WebhookClientConfig != nil { allErrs = append(allErrs, validateConversionReviewVersions(conversion.ConversionReviewVersions, requireRecognizedVersion, fldPath.Child("conversionReviewVersions"))...)
} else {
if conversion.WebhookClientConfig != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("webhookClientConfig"), "should not be set when strategy is not set to Webhook")) allErrs = append(allErrs, field.Forbidden(fldPath.Child("webhookClientConfig"), "should not be set when strategy is not set to Webhook"))
} }
if len(conversion.ConversionReviewVersions) > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("conversionReviewVersions"), "should not be set when strategy is not set to Webhook"))
}
}
return allErrs return allErrs
} }
// ValidateCustomResourceDefinitionSpecUpdate statically validates // ValidateCustomResourceDefinitionSpecUpdate statically validates
func ValidateCustomResourceDefinitionSpecUpdate(spec, oldSpec *apiextensions.CustomResourceDefinitionSpec, established bool, fldPath *field.Path) field.ErrorList { func ValidateCustomResourceDefinitionSpecUpdate(spec, oldSpec *apiextensions.CustomResourceDefinitionSpec, established bool, fldPath *field.Path) field.ErrorList {
allErrs := ValidateCustomResourceDefinitionSpec(spec, fldPath) requireRecognizedVersion := oldSpec.Conversion == nil || hasValidConversionReviewVersionOrEmpty(oldSpec.Conversion.ConversionReviewVersions)
allErrs := validateCustomResourceDefinitionSpec(spec, requireRecognizedVersion, fldPath)
if established { if established {
// these effect the storage and cannot be changed therefore // these effect the storage and cannot be changed therefore

View File

@ -35,6 +35,9 @@ func required(path ...string) validationMatch {
func invalid(path ...string) validationMatch { func invalid(path ...string) validationMatch {
return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeInvalid} return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeInvalid}
} }
func invalidIndex(index int, path ...string) validationMatch {
return validationMatch{path: field.NewPath(path[0], path[1:]...).Index(index), errorType: field.ErrorTypeInvalid}
}
func unsupported(path ...string) validationMatch { func unsupported(path ...string) validationMatch {
return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeNotSupported} return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeNotSupported}
} }
@ -65,7 +68,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
errors []validationMatch errors []validationMatch
}{ }{
{ {
name: "webhookconfig: blank URL", name: "webhookconfig: both service and URL provided",
resource: &apiextensions.CustomResourceDefinition{ resource: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"}, ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
Spec: apiextensions.CustomResourceDefinitionSpec{ Spec: apiextensions.CustomResourceDefinitionSpec{
@ -109,7 +112,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
}, },
}, },
{ {
name: "webhookconfig: both service and URL provided", name: "webhookconfig: blank URL",
resource: &apiextensions.CustomResourceDefinition{ resource: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"}, ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
Spec: apiextensions.CustomResourceDefinitionSpec{ Spec: apiextensions.CustomResourceDefinitionSpec{
@ -189,6 +192,207 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
forbidden("spec", "conversion", "webhookClientConfig"), forbidden("spec", "conversion", "webhookClientConfig"),
}, },
}, },
{
name: "ConversionReviewVersions_should_not_be_set",
resource: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
Spec: apiextensions.CustomResourceDefinitionSpec{
Group: "group.com",
Scope: apiextensions.ResourceScope("Cluster"),
Names: apiextensions.CustomResourceDefinitionNames{
Plural: "plural",
Singular: "singular",
Kind: "Plural",
ListKind: "PluralList",
},
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "version",
Served: true,
Storage: true,
},
{
Name: "version2",
Served: true,
Storage: false,
},
},
Conversion: &apiextensions.CustomResourceConversion{
Strategy: apiextensions.ConversionStrategyType("None"),
ConversionReviewVersions: []string{"v1beta1"},
},
},
Status: apiextensions.CustomResourceDefinitionStatus{
StoredVersions: []string{"version"},
},
},
errors: []validationMatch{
forbidden("spec", "conversion", "conversionReviewVersions"),
},
},
{
name: "webhookconfig: invalid ConversionReviewVersion",
resource: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
Spec: apiextensions.CustomResourceDefinitionSpec{
Group: "group.com",
Scope: apiextensions.ResourceScope("Cluster"),
Names: apiextensions.CustomResourceDefinitionNames{
Plural: "plural",
Singular: "singular",
Kind: "Plural",
ListKind: "PluralList",
},
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "version",
Served: true,
Storage: true,
},
{
Name: "version2",
Served: true,
Storage: false,
},
},
Conversion: &apiextensions.CustomResourceConversion{
Strategy: apiextensions.ConversionStrategyType("Webhook"),
WebhookClientConfig: &apiextensions.WebhookClientConfig{
URL: strPtr("https://example.com/webhook"),
},
ConversionReviewVersions: []string{"invalid-version"},
},
},
Status: apiextensions.CustomResourceDefinitionStatus{
StoredVersions: []string{"version"},
},
},
errors: []validationMatch{
invalid("spec", "conversion", "conversionReviewVersions"),
},
},
{
name: "webhookconfig: invalid ConversionReviewVersion version string",
resource: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
Spec: apiextensions.CustomResourceDefinitionSpec{
Group: "group.com",
Scope: apiextensions.ResourceScope("Cluster"),
Names: apiextensions.CustomResourceDefinitionNames{
Plural: "plural",
Singular: "singular",
Kind: "Plural",
ListKind: "PluralList",
},
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "version",
Served: true,
Storage: true,
},
{
Name: "version2",
Served: true,
Storage: false,
},
},
Conversion: &apiextensions.CustomResourceConversion{
Strategy: apiextensions.ConversionStrategyType("Webhook"),
WebhookClientConfig: &apiextensions.WebhookClientConfig{
URL: strPtr("https://example.com/webhook"),
},
ConversionReviewVersions: []string{"0v"},
},
},
Status: apiextensions.CustomResourceDefinitionStatus{
StoredVersions: []string{"version"},
},
},
errors: []validationMatch{
invalidIndex(0, "spec", "conversion", "conversionReviewVersions"),
invalid("spec", "conversion", "conversionReviewVersions"),
},
},
{
name: "webhookconfig: at least one valid ConversionReviewVersion",
resource: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
Spec: apiextensions.CustomResourceDefinitionSpec{
Group: "group.com",
Scope: apiextensions.ResourceScope("Cluster"),
Names: apiextensions.CustomResourceDefinitionNames{
Plural: "plural",
Singular: "singular",
Kind: "Plural",
ListKind: "PluralList",
},
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "version",
Served: true,
Storage: true,
},
{
Name: "version2",
Served: true,
Storage: false,
},
},
Conversion: &apiextensions.CustomResourceConversion{
Strategy: apiextensions.ConversionStrategyType("Webhook"),
WebhookClientConfig: &apiextensions.WebhookClientConfig{
URL: strPtr("https://example.com/webhook"),
},
ConversionReviewVersions: []string{"invalid-version", "v1beta1"},
},
},
Status: apiextensions.CustomResourceDefinitionStatus{
StoredVersions: []string{"version"},
},
},
errors: []validationMatch{},
},
{
name: "webhookconfig: duplicate ConversionReviewVersion",
resource: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
Spec: apiextensions.CustomResourceDefinitionSpec{
Group: "group.com",
Scope: apiextensions.ResourceScope("Cluster"),
Names: apiextensions.CustomResourceDefinitionNames{
Plural: "plural",
Singular: "singular",
Kind: "Plural",
ListKind: "PluralList",
},
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "version",
Served: true,
Storage: true,
},
{
Name: "version2",
Served: true,
Storage: false,
},
},
Conversion: &apiextensions.CustomResourceConversion{
Strategy: apiextensions.ConversionStrategyType("Webhook"),
WebhookClientConfig: &apiextensions.WebhookClientConfig{
URL: strPtr("https://example.com/webhook"),
},
ConversionReviewVersions: []string{"v1beta1", "v1beta1"},
},
},
Status: apiextensions.CustomResourceDefinitionStatus{
StoredVersions: []string{"version"},
},
},
errors: []validationMatch{
invalidIndex(1, "spec", "conversion", "conversionReviewVersions"),
},
},
{ {
name: "missing_webhookconfig", name: "missing_webhookconfig",
resource: &apiextensions.CustomResourceDefinition{ resource: &apiextensions.CustomResourceDefinition{
@ -646,6 +850,10 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
} }
for _, tc := range tests { for _, tc := range tests {
// duplicate defaulting behaviour
if tc.resource.Spec.Conversion != nil && tc.resource.Spec.Conversion.Strategy == apiextensions.WebhookConverter && len(tc.resource.Spec.Conversion.ConversionReviewVersions) == 0 {
tc.resource.Spec.Conversion.ConversionReviewVersions = []string{"v1beta1"}
}
errs := ValidateCustomResourceDefinition(tc.resource) errs := ValidateCustomResourceDefinition(tc.resource)
seenErrs := make([]bool, len(errs)) seenErrs := make([]bool, len(errs))
@ -679,6 +887,231 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
resource *apiextensions.CustomResourceDefinition resource *apiextensions.CustomResourceDefinition
errors []validationMatch errors []validationMatch
}{ }{
{
name: "webhookconfig: should pass on invalid ConversionReviewVersion with old invalid versions",
resource: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
Spec: apiextensions.CustomResourceDefinitionSpec{
Group: "group.com",
Scope: apiextensions.ResourceScope("Cluster"),
Names: apiextensions.CustomResourceDefinitionNames{
Plural: "plural",
Singular: "singular",
Kind: "Plural",
ListKind: "PluralList",
},
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "version",
Served: true,
Storage: true,
},
{
Name: "version2",
Served: true,
Storage: false,
},
},
Conversion: &apiextensions.CustomResourceConversion{
Strategy: apiextensions.ConversionStrategyType("Webhook"),
WebhookClientConfig: &apiextensions.WebhookClientConfig{
URL: strPtr("https://example.com/webhook"),
},
ConversionReviewVersions: []string{"invalid-version"},
},
},
Status: apiextensions.CustomResourceDefinitionStatus{
StoredVersions: []string{"version"},
},
},
old: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
Spec: apiextensions.CustomResourceDefinitionSpec{
Group: "group.com",
Scope: apiextensions.ResourceScope("Cluster"),
Names: apiextensions.CustomResourceDefinitionNames{
Plural: "plural",
Singular: "singular",
Kind: "Plural",
ListKind: "PluralList",
},
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "version",
Served: true,
Storage: true,
},
{
Name: "version2",
Served: true,
Storage: false,
},
},
Conversion: &apiextensions.CustomResourceConversion{
Strategy: apiextensions.ConversionStrategyType("Webhook"),
WebhookClientConfig: &apiextensions.WebhookClientConfig{
URL: strPtr("https://example.com/webhook"),
},
ConversionReviewVersions: []string{"invalid-version_0, invalid-version"},
},
},
Status: apiextensions.CustomResourceDefinitionStatus{
StoredVersions: []string{"version"},
},
},
errors: []validationMatch{},
},
{
name: "webhookconfig: should fail on invalid ConversionReviewVersion with old valid versions",
resource: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
Spec: apiextensions.CustomResourceDefinitionSpec{
Group: "group.com",
Scope: apiextensions.ResourceScope("Cluster"),
Names: apiextensions.CustomResourceDefinitionNames{
Plural: "plural",
Singular: "singular",
Kind: "Plural",
ListKind: "PluralList",
},
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "version",
Served: true,
Storage: true,
},
{
Name: "version2",
Served: true,
Storage: false,
},
},
Conversion: &apiextensions.CustomResourceConversion{
Strategy: apiextensions.ConversionStrategyType("Webhook"),
WebhookClientConfig: &apiextensions.WebhookClientConfig{
URL: strPtr("https://example.com/webhook"),
},
ConversionReviewVersions: []string{"invalid-version"},
},
},
Status: apiextensions.CustomResourceDefinitionStatus{
StoredVersions: []string{"version"},
},
},
old: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
Spec: apiextensions.CustomResourceDefinitionSpec{
Group: "group.com",
Scope: apiextensions.ResourceScope("Cluster"),
Names: apiextensions.CustomResourceDefinitionNames{
Plural: "plural",
Singular: "singular",
Kind: "Plural",
ListKind: "PluralList",
},
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "version",
Served: true,
Storage: true,
},
{
Name: "version2",
Served: true,
Storage: false,
},
},
Conversion: &apiextensions.CustomResourceConversion{
Strategy: apiextensions.ConversionStrategyType("Webhook"),
WebhookClientConfig: &apiextensions.WebhookClientConfig{
URL: strPtr("https://example.com/webhook"),
},
ConversionReviewVersions: []string{"v1beta1", "invalid-version"},
},
},
Status: apiextensions.CustomResourceDefinitionStatus{
StoredVersions: []string{"version"},
},
},
errors: []validationMatch{
invalid("spec", "conversion", "conversionReviewVersions"),
},
},
{
name: "webhookconfig: should fail on invalid ConversionReviewVersion with missing old versions",
resource: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
Spec: apiextensions.CustomResourceDefinitionSpec{
Group: "group.com",
Scope: apiextensions.ResourceScope("Cluster"),
Names: apiextensions.CustomResourceDefinitionNames{
Plural: "plural",
Singular: "singular",
Kind: "Plural",
ListKind: "PluralList",
},
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "version",
Served: true,
Storage: true,
},
{
Name: "version2",
Served: true,
Storage: false,
},
},
Conversion: &apiextensions.CustomResourceConversion{
Strategy: apiextensions.ConversionStrategyType("Webhook"),
WebhookClientConfig: &apiextensions.WebhookClientConfig{
URL: strPtr("https://example.com/webhook"),
},
ConversionReviewVersions: []string{"invalid-version"},
},
},
Status: apiextensions.CustomResourceDefinitionStatus{
StoredVersions: []string{"version"},
},
},
old: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
Spec: apiextensions.CustomResourceDefinitionSpec{
Group: "group.com",
Scope: apiextensions.ResourceScope("Cluster"),
Names: apiextensions.CustomResourceDefinitionNames{
Plural: "plural",
Singular: "singular",
Kind: "Plural",
ListKind: "PluralList",
},
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "version",
Served: true,
Storage: true,
},
{
Name: "version2",
Served: true,
Storage: false,
},
},
Conversion: &apiextensions.CustomResourceConversion{
Strategy: apiextensions.ConversionStrategyType("Webhook"),
WebhookClientConfig: &apiextensions.WebhookClientConfig{
URL: strPtr("https://example.com/webhook"),
},
},
},
Status: apiextensions.CustomResourceDefinitionStatus{
StoredVersions: []string{"version"},
},
},
errors: []validationMatch{
invalid("spec", "conversion", "conversionReviewVersions"),
},
},
{ {
name: "unchanged", name: "unchanged",
old: &apiextensions.CustomResourceDefinition{ old: &apiextensions.CustomResourceDefinition{

View File

@ -48,6 +48,11 @@ func (in *CustomResourceConversion) DeepCopyInto(out *CustomResourceConversion)
*out = new(WebhookClientConfig) *out = new(WebhookClientConfig)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
if in.ConversionReviewVersions != nil {
in, out := &in.ConversionReviewVersions, &out.ConversionReviewVersions
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }

View File

@ -60,6 +60,8 @@ type webhookConverter struct {
restClient *rest.RESTClient restClient *rest.RESTClient
name string name string
nopConverter nopConverter nopConverter nopConverter
conversionReviewVersions []string
} }
func webhookClientConfigForCRD(crd *internal.CustomResourceDefinition) *webhook.ClientConfig { func webhookClientConfigForCRD(crd *internal.CustomResourceDefinition) *webhook.ClientConfig {
@ -96,6 +98,8 @@ func (f *webhookConverterFactory) NewWebhookConverter(validVersions map[schema.G
restClient: restClient, restClient: restClient,
name: crd.Name, name: crd.Name,
nopConverter: nopConverter{validVersions: validVersions}, nopConverter: nopConverter{validVersions: validVersions},
conversionReviewVersions: crd.Spec.Conversion.ConversionReviewVersions,
}, nil }, nil
} }
@ -136,6 +140,16 @@ func (c *webhookConverter) Convert(in, out, context interface{}) error {
return nil return nil
} }
// hasConversionReviewVersion check whether a version is accepted by a given webhook.
func (c *webhookConverter) hasConversionReviewVersion(v string) bool {
for _, b := range c.conversionReviewVersions {
if b == v {
return true
}
}
return false
}
func createConversionReview(obj runtime.Object, apiVersion string) *v1beta1.ConversionReview { func createConversionReview(obj runtime.Object, apiVersion string) *v1beta1.ConversionReview {
listObj, isList := obj.(*unstructured.UnstructuredList) listObj, isList := obj.(*unstructured.UnstructuredList)
var objects []runtime.RawExtension var objects []runtime.RawExtension
@ -216,6 +230,12 @@ func (c *webhookConverter) ConvertToVersion(in runtime.Object, target runtime.Gr
} }
} }
// Currently converter only supports `v1beta1` ConversionReview
// TODO: Make CRD webhooks caller capable of sending/receiving multiple ConversionReview versions
if !c.hasConversionReviewVersion(v1beta1.SchemeGroupVersion.Version) {
return nil, fmt.Errorf("webhook does not accept v1beta1 ConversionReview")
}
request := createConversionReview(in, toGV.String()) request := createConversionReview(in, toGV.String())
if len(request.Request.Objects) == 0 { if len(request.Request.Objects) == 0 {
if !isList { if !isList {

View File

@ -94,6 +94,12 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta
} }
} }
// Currently dispatcher only supports `v1beta1` AdmissionReview
// TODO: Make the dispatcher capable of sending multiple AdmissionReview versions
if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, h) {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReview")}
}
// Make the webhook request // Make the webhook request
request := request.CreateAdmissionReview(attr) request := request.CreateAdmissionReview(attr)
client, err := a.cm.HookClient(util.HookClientConfigForWebhook(h)) client, err := a.cm.HookClient(util.HookClientConfigForWebhook(h))

View File

@ -212,6 +212,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
Operations: []registrationv1beta1.OperationType{registrationv1beta1.Create}, Operations: []registrationv1beta1.OperationType{registrationv1beta1.Create},
}}, }},
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: true, ExpectAllow: true,
}, },
@ -222,6 +223,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
ClientConfig: ccfgSVC("allow"), ClientConfig: ccfgSVC("allow"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: true, ExpectAllow: true,
ExpectAnnotations: map[string]string{"allow.example.com/key1": "value1"}, ExpectAnnotations: map[string]string{"allow.example.com/key1": "value1"},
@ -233,6 +235,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
ClientConfig: ccfgSVC("disallow"), ClientConfig: ccfgSVC("disallow"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ErrorContains: "without explanation", ErrorContains: "without explanation",
}, },
@ -243,6 +246,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
ClientConfig: ccfgSVC("disallowReason"), ClientConfig: ccfgSVC("disallowReason"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ErrorContains: "you shall not pass", ErrorContains: "you shall not pass",
@ -260,6 +264,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
Operator: metav1.LabelSelectorOpIn, Operator: metav1.LabelSelectorOpIn,
}}, }},
}, },
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: true, ExpectAllow: true,
@ -277,6 +282,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
Operator: metav1.LabelSelectorOpNotIn, Operator: metav1.LabelSelectorOpNotIn,
}}, }},
}, },
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: true, ExpectAllow: true,
}, },
@ -288,18 +294,21 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
FailurePolicy: &policyIgnore, FailurePolicy: &policyIgnore,
AdmissionReviewVersions: []string{"v1beta1"},
}, { }, {
Name: "internalErr B", Name: "internalErr B",
ClientConfig: ccfgSVC("internalErr"), ClientConfig: ccfgSVC("internalErr"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
FailurePolicy: &policyIgnore, FailurePolicy: &policyIgnore,
AdmissionReviewVersions: []string{"v1beta1"},
}, { }, {
Name: "internalErr C", Name: "internalErr C",
ClientConfig: ccfgSVC("internalErr"), ClientConfig: ccfgSVC("internalErr"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
FailurePolicy: &policyIgnore, FailurePolicy: &policyIgnore,
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: true, ExpectAllow: true,
@ -311,16 +320,19 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
ClientConfig: ccfgSVC("internalErr"), ClientConfig: ccfgSVC("internalErr"),
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
Rules: matchEverythingRules, Rules: matchEverythingRules,
AdmissionReviewVersions: []string{"v1beta1"},
}, { }, {
Name: "internalErr B", Name: "internalErr B",
ClientConfig: ccfgSVC("internalErr"), ClientConfig: ccfgSVC("internalErr"),
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
Rules: matchEverythingRules, Rules: matchEverythingRules,
AdmissionReviewVersions: []string{"v1beta1"},
}, { }, {
Name: "internalErr C", Name: "internalErr C",
ClientConfig: ccfgSVC("internalErr"), ClientConfig: ccfgSVC("internalErr"),
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
Rules: matchEverythingRules, Rules: matchEverythingRules,
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: false, ExpectAllow: false,
}, },
@ -332,18 +344,21 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
FailurePolicy: &policyFail, FailurePolicy: &policyFail,
AdmissionReviewVersions: []string{"v1beta1"},
}, { }, {
Name: "internalErr B", Name: "internalErr B",
ClientConfig: ccfgSVC("internalErr"), ClientConfig: ccfgSVC("internalErr"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
FailurePolicy: &policyFail, FailurePolicy: &policyFail,
AdmissionReviewVersions: []string{"v1beta1"},
}, { }, {
Name: "internalErr C", Name: "internalErr C",
ClientConfig: ccfgSVC("internalErr"), ClientConfig: ccfgSVC("internalErr"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
FailurePolicy: &policyFail, FailurePolicy: &policyFail,
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: false, ExpectAllow: false,
}, },
@ -354,6 +369,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
ClientConfig: ccfgURL("allow"), ClientConfig: ccfgURL("allow"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: true, ExpectAllow: true,
ExpectAnnotations: map[string]string{"allow.example.com/key1": "value1"}, ExpectAnnotations: map[string]string{"allow.example.com/key1": "value1"},
@ -365,6 +381,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
ClientConfig: ccfgURL("disallow"), ClientConfig: ccfgURL("disallow"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ErrorContains: "without explanation", ErrorContains: "without explanation",
}, { }, {
@ -375,6 +392,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
FailurePolicy: &policyIgnore, FailurePolicy: &policyIgnore,
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: true, ExpectAllow: true,
}, },
@ -386,6 +404,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
FailurePolicy: &policyFail, FailurePolicy: &policyFail,
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ErrorContains: "Webhook response was absent", ErrorContains: "Webhook response was absent",
}, },
@ -399,6 +418,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}}, }},
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
SideEffects: &sideEffectsSome, SideEffects: &sideEffectsSome,
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
IsDryRun: true, IsDryRun: true,
ExpectAllow: true, ExpectAllow: true,
@ -411,6 +431,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
SideEffects: &sideEffectsUnknown, SideEffects: &sideEffectsUnknown,
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
IsDryRun: true, IsDryRun: true,
ErrorContains: "does not support dry run", ErrorContains: "does not support dry run",
@ -423,6 +444,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
SideEffects: &sideEffectsNone, SideEffects: &sideEffectsNone,
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
IsDryRun: true, IsDryRun: true,
ExpectAllow: true, ExpectAllow: true,
@ -436,6 +458,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
SideEffects: &sideEffectsSome, SideEffects: &sideEffectsSome,
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
IsDryRun: true, IsDryRun: true,
ErrorContains: "does not support dry run", ErrorContains: "does not support dry run",
@ -448,6 +471,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
SideEffects: &sideEffectsNoneOnDryRun, SideEffects: &sideEffectsNoneOnDryRun,
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
IsDryRun: true, IsDryRun: true,
ExpectAllow: true, ExpectAllow: true,
@ -460,6 +484,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
ClientConfig: ccfgURL("invalidAnnotation"), ClientConfig: ccfgURL("invalidAnnotation"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: true, ExpectAllow: true,
}, },
@ -480,6 +505,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
ClientConfig: ccfgSVC("removeLabel"), ClientConfig: ccfgSVC("removeLabel"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: true, ExpectAllow: true,
AdditionalLabels: map[string]string{"remove": "me"}, AdditionalLabels: map[string]string{"remove": "me"},
@ -493,6 +519,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
ClientConfig: ccfgSVC("addLabel"), ClientConfig: ccfgSVC("addLabel"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: true, ExpectAllow: true,
ExpectLabels: map[string]string{"pod.name": "my-pod", "added": "test"}, ExpectLabels: map[string]string{"pod.name": "my-pod", "added": "test"},
@ -504,6 +531,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
ClientConfig: ccfgSVC("addLabel"), ClientConfig: ccfgSVC("addLabel"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
IsCRD: true, IsCRD: true,
ExpectAllow: true, ExpectAllow: true,
@ -516,6 +544,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
ClientConfig: ccfgSVC("removeLabel"), ClientConfig: ccfgSVC("removeLabel"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
IsCRD: true, IsCRD: true,
ExpectAllow: true, ExpectAllow: true,
@ -530,6 +559,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
ClientConfig: ccfgSVC("invalidMutation"), ClientConfig: ccfgSVC("invalidMutation"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ErrorContains: "invalid character", ErrorContains: "invalid character",
}, },
@ -541,6 +571,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
Rules: matchEverythingRules, Rules: matchEverythingRules,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
SideEffects: &sideEffectsUnknown, SideEffects: &sideEffectsUnknown,
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
IsDryRun: true, IsDryRun: true,
ErrorContains: "does not support dry run", ErrorContains: "does not support dry run",
@ -572,6 +603,7 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest {
Rules: newMatchEverythingRules(), Rules: newMatchEverythingRules(),
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
FailurePolicy: &policyIgnore, FailurePolicy: &policyIgnore,
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: true, ExpectAllow: true,
ExpectCacheMiss: true, ExpectCacheMiss: true,
@ -584,6 +616,7 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest {
Rules: newMatchEverythingRules(), Rules: newMatchEverythingRules(),
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
FailurePolicy: &policyIgnore, FailurePolicy: &policyIgnore,
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: true, ExpectAllow: true,
ExpectCacheMiss: true, ExpectCacheMiss: true,
@ -596,6 +629,7 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest {
Rules: newMatchEverythingRules(), Rules: newMatchEverythingRules(),
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
FailurePolicy: &policyIgnore, FailurePolicy: &policyIgnore,
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: true, ExpectAllow: true,
ExpectCacheMiss: false, ExpectCacheMiss: false,
@ -608,6 +642,7 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest {
Rules: newMatchEverythingRules(), Rules: newMatchEverythingRules(),
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
FailurePolicy: &policyIgnore, FailurePolicy: &policyIgnore,
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: true, ExpectAllow: true,
ExpectCacheMiss: true, ExpectCacheMiss: true,
@ -620,6 +655,7 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest {
Rules: newMatchEverythingRules(), Rules: newMatchEverythingRules(),
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
FailurePolicy: &policyIgnore, FailurePolicy: &policyIgnore,
AdmissionReviewVersions: []string{"v1beta1"},
}}, }},
ExpectAllow: true, ExpectAllow: true,
ExpectCacheMiss: false, ExpectCacheMiss: false,

View File

@ -40,3 +40,13 @@ func HookClientConfigForWebhook(w *v1beta1.Webhook) webhook.ClientConfig {
} }
return ret return ret
} }
// HasAdmissionReviewVersion check whether a version is accepted by a given webhook.
func HasAdmissionReviewVersion(a string, w *v1beta1.Webhook) bool {
for _, b := range w.AdmissionReviewVersions {
if b == a {
return true
}
}
return false
}

View File

@ -108,6 +108,12 @@ func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.Webhook,
} }
} }
// Currently dispatcher only supports `v1beta1` AdmissionReview
// TODO: Make the dispatcher capable of sending multiple AdmissionReview versions
if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, h) {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReviewRequest")}
}
// Make the webhook request // Make the webhook request
request := request.CreateAdmissionReview(attr) request := request.CreateAdmissionReview(attr)
client, err := d.cm.HookClient(util.HookClientConfigForWebhook(h)) client, err := d.cm.HookClient(util.HookClientConfigForWebhook(h))