mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			392 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			392 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2017 The Kubernetes Authors.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package validation
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	genericvalidation "k8s.io/apimachinery/pkg/api/validation"
 | 
						|
	metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
 | 
						|
	"k8s.io/apimachinery/pkg/util/sets"
 | 
						|
	utilvalidation "k8s.io/apimachinery/pkg/util/validation"
 | 
						|
	"k8s.io/apimachinery/pkg/util/validation/field"
 | 
						|
	"k8s.io/apiserver/pkg/util/webhook"
 | 
						|
	"k8s.io/kubernetes/pkg/apis/admissionregistration"
 | 
						|
	"k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1"
 | 
						|
)
 | 
						|
 | 
						|
func hasWildcard(slice []string) bool {
 | 
						|
	for _, s := range slice {
 | 
						|
		if s == "*" {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func validateResources(resources []string, fldPath *field.Path) field.ErrorList {
 | 
						|
	var allErrors field.ErrorList
 | 
						|
	if len(resources) == 0 {
 | 
						|
		allErrors = append(allErrors, field.Required(fldPath, ""))
 | 
						|
	}
 | 
						|
 | 
						|
	// */x
 | 
						|
	resourcesWithWildcardSubresoures := sets.String{}
 | 
						|
	// x/*
 | 
						|
	subResourcesWithWildcardResource := sets.String{}
 | 
						|
	// */*
 | 
						|
	hasDoubleWildcard := false
 | 
						|
	// *
 | 
						|
	hasSingleWildcard := false
 | 
						|
	// x
 | 
						|
	hasResourceWithoutSubresource := false
 | 
						|
 | 
						|
	for i, resSub := range resources {
 | 
						|
		if resSub == "" {
 | 
						|
			allErrors = append(allErrors, field.Required(fldPath.Index(i), ""))
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if resSub == "*/*" {
 | 
						|
			hasDoubleWildcard = true
 | 
						|
		}
 | 
						|
		if resSub == "*" {
 | 
						|
			hasSingleWildcard = true
 | 
						|
		}
 | 
						|
		parts := strings.SplitN(resSub, "/", 2)
 | 
						|
		if len(parts) == 1 {
 | 
						|
			hasResourceWithoutSubresource = resSub != "*"
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		res, sub := parts[0], parts[1]
 | 
						|
		if _, ok := resourcesWithWildcardSubresoures[res]; ok {
 | 
						|
			allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resSub, fmt.Sprintf("if '%s/*' is present, must not specify %s", res, resSub)))
 | 
						|
		}
 | 
						|
		if _, ok := subResourcesWithWildcardResource[sub]; ok {
 | 
						|
			allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resSub, fmt.Sprintf("if '*/%s' is present, must not specify %s", sub, resSub)))
 | 
						|
		}
 | 
						|
		if sub == "*" {
 | 
						|
			resourcesWithWildcardSubresoures[res] = struct{}{}
 | 
						|
		}
 | 
						|
		if res == "*" {
 | 
						|
			subResourcesWithWildcardResource[sub] = struct{}{}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if len(resources) > 1 && hasDoubleWildcard {
 | 
						|
		allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*/*' is present, must not specify other resources"))
 | 
						|
	}
 | 
						|
	if hasSingleWildcard && hasResourceWithoutSubresource {
 | 
						|
		allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*' is present, must not specify other resources without subresources"))
 | 
						|
	}
 | 
						|
	return allErrors
 | 
						|
}
 | 
						|
 | 
						|
func validateResourcesNoSubResources(resources []string, fldPath *field.Path) field.ErrorList {
 | 
						|
	var allErrors field.ErrorList
 | 
						|
	if len(resources) == 0 {
 | 
						|
		allErrors = append(allErrors, field.Required(fldPath, ""))
 | 
						|
	}
 | 
						|
	for i, resource := range resources {
 | 
						|
		if resource == "" {
 | 
						|
			allErrors = append(allErrors, field.Required(fldPath.Index(i), ""))
 | 
						|
		}
 | 
						|
		if strings.Contains(resource, "/") {
 | 
						|
			allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resource, "must not specify subresources"))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if len(resources) > 1 && hasWildcard(resources) {
 | 
						|
		allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*' is present, must not specify other resources"))
 | 
						|
	}
 | 
						|
	return allErrors
 | 
						|
}
 | 
						|
 | 
						|
var validScopes = sets.NewString(
 | 
						|
	string(admissionregistration.ClusterScope),
 | 
						|
	string(admissionregistration.NamespacedScope),
 | 
						|
	string(admissionregistration.AllScopes),
 | 
						|
)
 | 
						|
 | 
						|
func validateRule(rule *admissionregistration.Rule, fldPath *field.Path, allowSubResource bool) field.ErrorList {
 | 
						|
	var allErrors field.ErrorList
 | 
						|
	if len(rule.APIGroups) == 0 {
 | 
						|
		allErrors = append(allErrors, field.Required(fldPath.Child("apiGroups"), ""))
 | 
						|
	}
 | 
						|
	if len(rule.APIGroups) > 1 && hasWildcard(rule.APIGroups) {
 | 
						|
		allErrors = append(allErrors, field.Invalid(fldPath.Child("apiGroups"), rule.APIGroups, "if '*' is present, must not specify other API groups"))
 | 
						|
	}
 | 
						|
	// Note: group could be empty, e.g., the legacy "v1" API
 | 
						|
	if len(rule.APIVersions) == 0 {
 | 
						|
		allErrors = append(allErrors, field.Required(fldPath.Child("apiVersions"), ""))
 | 
						|
	}
 | 
						|
	if len(rule.APIVersions) > 1 && hasWildcard(rule.APIVersions) {
 | 
						|
		allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersions"), rule.APIVersions, "if '*' is present, must not specify other API versions"))
 | 
						|
	}
 | 
						|
	for i, version := range rule.APIVersions {
 | 
						|
		if version == "" {
 | 
						|
			allErrors = append(allErrors, field.Required(fldPath.Child("apiVersions").Index(i), ""))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if allowSubResource {
 | 
						|
		allErrors = append(allErrors, validateResources(rule.Resources, fldPath.Child("resources"))...)
 | 
						|
	} else {
 | 
						|
		allErrors = append(allErrors, validateResourcesNoSubResources(rule.Resources, fldPath.Child("resources"))...)
 | 
						|
	}
 | 
						|
	if rule.Scope != nil && !validScopes.Has(string(*rule.Scope)) {
 | 
						|
		allErrors = append(allErrors, field.NotSupported(fldPath.Child("scope"), *rule.Scope, validScopes.List()))
 | 
						|
	}
 | 
						|
	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 {
 | 
						|
	return validateValidatingWebhookConfiguration(e, true)
 | 
						|
}
 | 
						|
 | 
						|
func validateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration, requireRecognizedVersion bool) field.ErrorList {
 | 
						|
	allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
 | 
						|
	for i, hook := range e.Webhooks {
 | 
						|
		allErrors = append(allErrors, validateValidatingWebhook(&hook, field.NewPath("webhooks").Index(i))...)
 | 
						|
		allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, requireRecognizedVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...)
 | 
						|
	}
 | 
						|
	return allErrors
 | 
						|
}
 | 
						|
 | 
						|
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"))
 | 
						|
	for i, hook := range e.Webhooks {
 | 
						|
		allErrors = append(allErrors, validateMutatingWebhook(&hook, field.NewPath("webhooks").Index(i))...)
 | 
						|
		allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, requireRecognizedVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...)
 | 
						|
	}
 | 
						|
	return allErrors
 | 
						|
}
 | 
						|
 | 
						|
func validateValidatingWebhook(hook *admissionregistration.ValidatingWebhook, fldPath *field.Path) field.ErrorList {
 | 
						|
	var allErrors field.ErrorList
 | 
						|
	// hook.Name must be fully qualified
 | 
						|
	allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...)
 | 
						|
 | 
						|
	for i, rule := range hook.Rules {
 | 
						|
		allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...)
 | 
						|
	}
 | 
						|
	if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) {
 | 
						|
		allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List()))
 | 
						|
	}
 | 
						|
	if hook.MatchPolicy != nil && !supportedMatchPolicies.Has(string(*hook.MatchPolicy)) {
 | 
						|
		allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *hook.MatchPolicy, supportedMatchPolicies.List()))
 | 
						|
	}
 | 
						|
	if hook.SideEffects != nil && !supportedSideEffectClasses.Has(string(*hook.SideEffects)) {
 | 
						|
		allErrors = append(allErrors, field.NotSupported(fldPath.Child("sideEffects"), *hook.SideEffects, supportedSideEffectClasses.List()))
 | 
						|
	}
 | 
						|
	if hook.TimeoutSeconds != nil && (*hook.TimeoutSeconds > 30 || *hook.TimeoutSeconds < 1) {
 | 
						|
		allErrors = append(allErrors, field.Invalid(fldPath.Child("timeoutSeconds"), *hook.TimeoutSeconds, "the timeout value must be between 1 and 30 seconds"))
 | 
						|
	}
 | 
						|
 | 
						|
	if hook.NamespaceSelector != nil {
 | 
						|
		allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, fldPath.Child("namespaceSelector"))...)
 | 
						|
	}
 | 
						|
 | 
						|
	cc := hook.ClientConfig
 | 
						|
	switch {
 | 
						|
	case (cc.URL == nil) == (cc.Service == nil):
 | 
						|
		allErrors = append(allErrors, field.Required(fldPath.Child("clientConfig"), "exactly one of url or service is required"))
 | 
						|
	case cc.URL != nil:
 | 
						|
		allErrors = append(allErrors, webhook.ValidateWebhookURL(fldPath.Child("clientConfig").Child("url"), *cc.URL, true)...)
 | 
						|
	case cc.Service != nil:
 | 
						|
		allErrors = append(allErrors, webhook.ValidateWebhookService(fldPath.Child("clientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...)
 | 
						|
	}
 | 
						|
	return allErrors
 | 
						|
}
 | 
						|
 | 
						|
func validateMutatingWebhook(hook *admissionregistration.MutatingWebhook, fldPath *field.Path) field.ErrorList {
 | 
						|
	var allErrors field.ErrorList
 | 
						|
	// hook.Name must be fully qualified
 | 
						|
	allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...)
 | 
						|
 | 
						|
	for i, rule := range hook.Rules {
 | 
						|
		allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...)
 | 
						|
	}
 | 
						|
	if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) {
 | 
						|
		allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List()))
 | 
						|
	}
 | 
						|
	if hook.MatchPolicy != nil && !supportedMatchPolicies.Has(string(*hook.MatchPolicy)) {
 | 
						|
		allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *hook.MatchPolicy, supportedMatchPolicies.List()))
 | 
						|
	}
 | 
						|
	if hook.SideEffects != nil && !supportedSideEffectClasses.Has(string(*hook.SideEffects)) {
 | 
						|
		allErrors = append(allErrors, field.NotSupported(fldPath.Child("sideEffects"), *hook.SideEffects, supportedSideEffectClasses.List()))
 | 
						|
	}
 | 
						|
	if hook.TimeoutSeconds != nil && (*hook.TimeoutSeconds > 30 || *hook.TimeoutSeconds < 1) {
 | 
						|
		allErrors = append(allErrors, field.Invalid(fldPath.Child("timeoutSeconds"), *hook.TimeoutSeconds, "the timeout value must be between 1 and 30 seconds"))
 | 
						|
	}
 | 
						|
 | 
						|
	if hook.NamespaceSelector != nil {
 | 
						|
		allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, fldPath.Child("namespaceSelector"))...)
 | 
						|
	}
 | 
						|
 | 
						|
	cc := hook.ClientConfig
 | 
						|
	switch {
 | 
						|
	case (cc.URL == nil) == (cc.Service == nil):
 | 
						|
		allErrors = append(allErrors, field.Required(fldPath.Child("clientConfig"), "exactly one of url or service is required"))
 | 
						|
	case cc.URL != nil:
 | 
						|
		allErrors = append(allErrors, webhook.ValidateWebhookURL(fldPath.Child("clientConfig").Child("url"), *cc.URL, true)...)
 | 
						|
	case cc.Service != nil:
 | 
						|
		allErrors = append(allErrors, webhook.ValidateWebhookService(fldPath.Child("clientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...)
 | 
						|
	}
 | 
						|
	return allErrors
 | 
						|
}
 | 
						|
 | 
						|
var supportedFailurePolicies = sets.NewString(
 | 
						|
	string(admissionregistration.Ignore),
 | 
						|
	string(admissionregistration.Fail),
 | 
						|
)
 | 
						|
 | 
						|
var supportedMatchPolicies = sets.NewString(
 | 
						|
	string(admissionregistration.Exact),
 | 
						|
	string(admissionregistration.Equivalent),
 | 
						|
)
 | 
						|
 | 
						|
var supportedSideEffectClasses = sets.NewString(
 | 
						|
	string(admissionregistration.SideEffectClassUnknown),
 | 
						|
	string(admissionregistration.SideEffectClassNone),
 | 
						|
	string(admissionregistration.SideEffectClassSome),
 | 
						|
	string(admissionregistration.SideEffectClassNoneOnDryRun),
 | 
						|
)
 | 
						|
 | 
						|
var supportedOperations = sets.NewString(
 | 
						|
	string(admissionregistration.OperationAll),
 | 
						|
	string(admissionregistration.Create),
 | 
						|
	string(admissionregistration.Update),
 | 
						|
	string(admissionregistration.Delete),
 | 
						|
	string(admissionregistration.Connect),
 | 
						|
)
 | 
						|
 | 
						|
func hasWildcardOperation(operations []admissionregistration.OperationType) bool {
 | 
						|
	for _, o := range operations {
 | 
						|
		if o == admissionregistration.OperationAll {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func validateRuleWithOperations(ruleWithOperations *admissionregistration.RuleWithOperations, fldPath *field.Path) field.ErrorList {
 | 
						|
	var allErrors field.ErrorList
 | 
						|
	if len(ruleWithOperations.Operations) == 0 {
 | 
						|
		allErrors = append(allErrors, field.Required(fldPath.Child("operations"), ""))
 | 
						|
	}
 | 
						|
	if len(ruleWithOperations.Operations) > 1 && hasWildcardOperation(ruleWithOperations.Operations) {
 | 
						|
		allErrors = append(allErrors, field.Invalid(fldPath.Child("operations"), ruleWithOperations.Operations, "if '*' is present, must not specify other operations"))
 | 
						|
	}
 | 
						|
	for i, operation := range ruleWithOperations.Operations {
 | 
						|
		if !supportedOperations.Has(string(operation)) {
 | 
						|
			allErrors = append(allErrors, field.NotSupported(fldPath.Child("operations").Index(i), operation, supportedOperations.List()))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	allowSubResource := true
 | 
						|
	allErrors = append(allErrors, validateRule(&ruleWithOperations.Rule, fldPath, allowSubResource)...)
 | 
						|
	return allErrors
 | 
						|
}
 | 
						|
 | 
						|
// mutatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one
 | 
						|
// admission review version this apiserver accepts.
 | 
						|
func mutatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.MutatingWebhook) 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
 | 
						|
}
 | 
						|
 | 
						|
// validatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one
 | 
						|
// admission review version this apiserver accepts.
 | 
						|
func validatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.ValidatingWebhook) 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 {
 | 
						|
	return validateValidatingWebhookConfiguration(newC, validatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks))
 | 
						|
}
 | 
						|
 | 
						|
func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
 | 
						|
	return validateMutatingWebhookConfiguration(newC, mutatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks))
 | 
						|
}
 |