object matcher
This commit is contained in:
		@@ -40,6 +40,8 @@ type WebhookAccessor interface {
 | 
				
			|||||||
	GetMatchPolicy() *v1beta1.MatchPolicyType
 | 
						GetMatchPolicy() *v1beta1.MatchPolicyType
 | 
				
			||||||
	// GetNamespaceSelector gets the webhook NamespaceSelector field.
 | 
						// GetNamespaceSelector gets the webhook NamespaceSelector field.
 | 
				
			||||||
	GetNamespaceSelector() *metav1.LabelSelector
 | 
						GetNamespaceSelector() *metav1.LabelSelector
 | 
				
			||||||
 | 
						// GetObjectSelector gets the webhook ObjectSelector field.
 | 
				
			||||||
 | 
						GetObjectSelector() *metav1.LabelSelector
 | 
				
			||||||
	// GetSideEffects gets the webhook SideEffects field.
 | 
						// GetSideEffects gets the webhook SideEffects field.
 | 
				
			||||||
	GetSideEffects() *v1beta1.SideEffectClass
 | 
						GetSideEffects() *v1beta1.SideEffectClass
 | 
				
			||||||
	// GetTimeoutSeconds gets the webhook TimeoutSeconds field.
 | 
						// GetTimeoutSeconds gets the webhook TimeoutSeconds field.
 | 
				
			||||||
@@ -84,6 +86,9 @@ func (m mutatingWebhookAccessor) GetMatchPolicy() *v1beta1.MatchPolicyType {
 | 
				
			|||||||
func (m mutatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
 | 
					func (m mutatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
 | 
				
			||||||
	return m.NamespaceSelector
 | 
						return m.NamespaceSelector
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					func (m mutatingWebhookAccessor) GetObjectSelector() *metav1.LabelSelector {
 | 
				
			||||||
 | 
						return m.ObjectSelector
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
func (m mutatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass {
 | 
					func (m mutatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass {
 | 
				
			||||||
	return m.SideEffects
 | 
						return m.SideEffects
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -133,6 +138,9 @@ func (v validatingWebhookAccessor) GetMatchPolicy() *v1beta1.MatchPolicyType {
 | 
				
			|||||||
func (v validatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
 | 
					func (v validatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
 | 
				
			||||||
	return v.NamespaceSelector
 | 
						return v.NamespaceSelector
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					func (v validatingWebhookAccessor) GetObjectSelector() *metav1.LabelSelector {
 | 
				
			||||||
 | 
						return v.ObjectSelector
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
func (v validatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass {
 | 
					func (v validatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass {
 | 
				
			||||||
	return v.SideEffects
 | 
						return v.SideEffects
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,7 @@ go_library(
 | 
				
			|||||||
        "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library",
 | 
					        "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
 | 
					        "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library",
 | 
					        "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library",
 | 
				
			||||||
 | 
					        "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules:go_default_library",
 | 
					        "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library",
 | 
					        "//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/client-go/informers:go_default_library",
 | 
					        "//staging/src/k8s.io/client-go/informers:go_default_library",
 | 
				
			||||||
@@ -59,6 +60,7 @@ go_test(
 | 
				
			|||||||
        "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
 | 
					        "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library",
 | 
					        "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library",
 | 
					        "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library",
 | 
				
			||||||
 | 
					        "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiserver/pkg/apis/example:go_default_library",
 | 
					        "//staging/src/k8s.io/apiserver/pkg/apis/example:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiserver/pkg/apis/example/v1:go_default_library",
 | 
					        "//staging/src/k8s.io/apiserver/pkg/apis/example/v1:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiserver/pkg/apis/example2/v1:go_default_library",
 | 
					        "//staging/src/k8s.io/apiserver/pkg/apis/example2/v1:go_default_library",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,7 @@ type Source interface {
 | 
				
			|||||||
// variants of the object and old object.
 | 
					// variants of the object and old object.
 | 
				
			||||||
type VersionedAttributes struct {
 | 
					type VersionedAttributes struct {
 | 
				
			||||||
	// Attributes holds the original admission attributes
 | 
						// Attributes holds the original admission attributes
 | 
				
			||||||
	Attributes admission.Attributes
 | 
						admission.Attributes
 | 
				
			||||||
	// VersionedOldObject holds Attributes.OldObject (if non-nil), converted to VersionedKind.
 | 
						// VersionedOldObject holds Attributes.OldObject (if non-nil), converted to VersionedKind.
 | 
				
			||||||
	// It must never be mutated.
 | 
						// It must never be mutated.
 | 
				
			||||||
	VersionedOldObject runtime.Object
 | 
						VersionedOldObject runtime.Object
 | 
				
			||||||
@@ -48,6 +48,14 @@ type VersionedAttributes struct {
 | 
				
			|||||||
	Dirty bool
 | 
						Dirty bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetObject overrides the Attributes.GetObject()
 | 
				
			||||||
 | 
					func (v *VersionedAttributes) GetObject() runtime.Object {
 | 
				
			||||||
 | 
						if v.VersionedObject != nil {
 | 
				
			||||||
 | 
							return v.VersionedObject
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return v.Attributes.GetObject()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// WebhookInvocation describes how to call a webhook, including the resource and subresource the webhook registered for,
 | 
					// WebhookInvocation describes how to call a webhook, including the resource and subresource the webhook registered for,
 | 
				
			||||||
// and the kind that should be sent to the webhook.
 | 
					// and the kind that should be sent to the webhook.
 | 
				
			||||||
type WebhookInvocation struct {
 | 
					type WebhookInvocation struct {
 | 
				
			||||||
@@ -59,6 +67,9 @@ type WebhookInvocation struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Dispatcher dispatches webhook call to a list of webhooks with admission attributes as argument.
 | 
					// Dispatcher dispatches webhook call to a list of webhooks with admission attributes as argument.
 | 
				
			||||||
type Dispatcher interface {
 | 
					type Dispatcher interface {
 | 
				
			||||||
	// Dispatch a request to the webhooks using the given webhooks. A non-nil error means the request is rejected.
 | 
						// Dispatch a request to the webhooks. Dispatcher may choose not to
 | 
				
			||||||
	Dispatch(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, hooks []*WebhookInvocation) error
 | 
						// call a hook, either because the rules of the hook does not match, or
 | 
				
			||||||
 | 
						// the namespaceSelector or the objectSelector of the hook does not
 | 
				
			||||||
 | 
						// match. A non-nil error means the request is rejected.
 | 
				
			||||||
 | 
						Dispatch(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,7 @@ import (
 | 
				
			|||||||
	"k8s.io/apiserver/pkg/admission/plugin/webhook"
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook/object"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/admission/plugin/webhook/rules"
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook/rules"
 | 
				
			||||||
	webhookutil "k8s.io/apiserver/pkg/util/webhook"
 | 
						webhookutil "k8s.io/apiserver/pkg/util/webhook"
 | 
				
			||||||
	"k8s.io/client-go/informers"
 | 
						"k8s.io/client-go/informers"
 | 
				
			||||||
@@ -45,6 +46,7 @@ type Webhook struct {
 | 
				
			|||||||
	hookSource       Source
 | 
						hookSource       Source
 | 
				
			||||||
	clientManager    *webhookutil.ClientManager
 | 
						clientManager    *webhookutil.ClientManager
 | 
				
			||||||
	namespaceMatcher *namespace.Matcher
 | 
						namespaceMatcher *namespace.Matcher
 | 
				
			||||||
 | 
						objectMatcher    *object.Matcher
 | 
				
			||||||
	dispatcher       Dispatcher
 | 
						dispatcher       Dispatcher
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -80,6 +82,7 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory
 | 
				
			|||||||
		sourceFactory:    sourceFactory,
 | 
							sourceFactory:    sourceFactory,
 | 
				
			||||||
		clientManager:    &cm,
 | 
							clientManager:    &cm,
 | 
				
			||||||
		namespaceMatcher: &namespace.Matcher{},
 | 
							namespaceMatcher: &namespace.Matcher{},
 | 
				
			||||||
 | 
							objectMatcher:    &object.Matcher{},
 | 
				
			||||||
		dispatcher:       dispatcherFactory(&cm),
 | 
							dispatcher:       dispatcherFactory(&cm),
 | 
				
			||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -127,9 +130,9 @@ func (a *Webhook) ValidateInitialization() error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// shouldCallHook returns invocation details if the webhook should be called, nil if the webhook should not be called,
 | 
					// ShouldCallHook returns invocation details if the webhook should be called, nil if the webhook should not be called,
 | 
				
			||||||
// or an error if an error was encountered during evaluation.
 | 
					// or an error if an error was encountered during evaluation.
 | 
				
			||||||
func (a *Webhook) shouldCallHook(h webhook.WebhookAccessor, attr admission.Attributes, o admission.ObjectInterfaces) (*WebhookInvocation, *apierrors.StatusError) {
 | 
					func (a *Webhook) ShouldCallHook(h webhook.WebhookAccessor, attr admission.Attributes, o admission.ObjectInterfaces) (*WebhookInvocation, *apierrors.StatusError) {
 | 
				
			||||||
	var err *apierrors.StatusError
 | 
						var err *apierrors.StatusError
 | 
				
			||||||
	var invocation *WebhookInvocation
 | 
						var invocation *WebhookInvocation
 | 
				
			||||||
	for _, r := range h.GetRules() {
 | 
						for _, r := range h.GetRules() {
 | 
				
			||||||
@@ -184,6 +187,11 @@ func (a *Webhook) shouldCallHook(h webhook.WebhookAccessor, attr admission.Attri
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						matches, err = a.objectMatcher.MatchObjectSelector(h, attr)
 | 
				
			||||||
 | 
						if !matches || err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return invocation, nil
 | 
						return invocation, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -206,21 +214,5 @@ func (a *Webhook) Dispatch(attr admission.Attributes, o admission.ObjectInterfac
 | 
				
			|||||||
	// TODO: Figure out if adding one second timeout make sense here.
 | 
						// TODO: Figure out if adding one second timeout make sense here.
 | 
				
			||||||
	ctx := context.TODO()
 | 
						ctx := context.TODO()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var relevantHooks []*WebhookInvocation
 | 
						return a.dispatcher.Dispatch(ctx, attr, o, hooks)
 | 
				
			||||||
	for i := range hooks {
 | 
					 | 
				
			||||||
		invocation, err := a.shouldCallHook(hooks[i], attr, o)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if invocation != nil {
 | 
					 | 
				
			||||||
			relevantHooks = append(relevantHooks, invocation)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if len(relevantHooks) == 0 {
 | 
					 | 
				
			||||||
		// no matching hooks
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return a.dispatcher.Dispatch(ctx, attr, o, relevantHooks)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,10 +28,11 @@ import (
 | 
				
			|||||||
	"k8s.io/apiserver/pkg/admission"
 | 
						"k8s.io/apiserver/pkg/admission"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/admission/plugin/webhook"
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook/object"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestShouldCallHook(t *testing.T) {
 | 
					func TestShouldCallHook(t *testing.T) {
 | 
				
			||||||
	a := &Webhook{namespaceMatcher: &namespace.Matcher{}}
 | 
						a := &Webhook{namespaceMatcher: &namespace.Matcher{}, objectMatcher: &object.Matcher{}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	allScopes := v1beta1.AllScopes
 | 
						allScopes := v1beta1.AllScopes
 | 
				
			||||||
	exactMatch := v1beta1.Exact
 | 
						exactMatch := v1beta1.Exact
 | 
				
			||||||
@@ -82,6 +83,7 @@ func TestShouldCallHook(t *testing.T) {
 | 
				
			|||||||
			name: "invalid kind lookup",
 | 
								name: "invalid kind lookup",
 | 
				
			||||||
			webhook: &v1beta1.ValidatingWebhook{
 | 
								webhook: &v1beta1.ValidatingWebhook{
 | 
				
			||||||
				NamespaceSelector: &metav1.LabelSelector{},
 | 
									NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:    &metav1.LabelSelector{},
 | 
				
			||||||
				MatchPolicy:       &equivalentMatch,
 | 
									MatchPolicy:       &equivalentMatch,
 | 
				
			||||||
				Rules: []v1beta1.RuleWithOperations{{
 | 
									Rules: []v1beta1.RuleWithOperations{{
 | 
				
			||||||
					Operations: []v1beta1.OperationType{"*"},
 | 
										Operations: []v1beta1.OperationType{"*"},
 | 
				
			||||||
@@ -95,6 +97,7 @@ func TestShouldCallHook(t *testing.T) {
 | 
				
			|||||||
			name: "wildcard rule, match as requested",
 | 
								name: "wildcard rule, match as requested",
 | 
				
			||||||
			webhook: &v1beta1.ValidatingWebhook{
 | 
								webhook: &v1beta1.ValidatingWebhook{
 | 
				
			||||||
				NamespaceSelector: &metav1.LabelSelector{},
 | 
									NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:    &metav1.LabelSelector{},
 | 
				
			||||||
				Rules: []v1beta1.RuleWithOperations{{
 | 
									Rules: []v1beta1.RuleWithOperations{{
 | 
				
			||||||
					Operations: []v1beta1.OperationType{"*"},
 | 
										Operations: []v1beta1.OperationType{"*"},
 | 
				
			||||||
					Rule:       v1beta1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes},
 | 
										Rule:       v1beta1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes},
 | 
				
			||||||
@@ -109,6 +112,7 @@ func TestShouldCallHook(t *testing.T) {
 | 
				
			|||||||
			name: "specific rules, prefer exact match",
 | 
								name: "specific rules, prefer exact match",
 | 
				
			||||||
			webhook: &v1beta1.ValidatingWebhook{
 | 
								webhook: &v1beta1.ValidatingWebhook{
 | 
				
			||||||
				NamespaceSelector: &metav1.LabelSelector{},
 | 
									NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:    &metav1.LabelSelector{},
 | 
				
			||||||
				Rules: []v1beta1.RuleWithOperations{{
 | 
									Rules: []v1beta1.RuleWithOperations{{
 | 
				
			||||||
					Operations: []v1beta1.OperationType{"*"},
 | 
										Operations: []v1beta1.OperationType{"*"},
 | 
				
			||||||
					Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes},
 | 
										Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes},
 | 
				
			||||||
@@ -129,6 +133,7 @@ func TestShouldCallHook(t *testing.T) {
 | 
				
			|||||||
			name: "specific rules, match miss",
 | 
								name: "specific rules, match miss",
 | 
				
			||||||
			webhook: &v1beta1.ValidatingWebhook{
 | 
								webhook: &v1beta1.ValidatingWebhook{
 | 
				
			||||||
				NamespaceSelector: &metav1.LabelSelector{},
 | 
									NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:    &metav1.LabelSelector{},
 | 
				
			||||||
				Rules: []v1beta1.RuleWithOperations{{
 | 
									Rules: []v1beta1.RuleWithOperations{{
 | 
				
			||||||
					Operations: []v1beta1.OperationType{"*"},
 | 
										Operations: []v1beta1.OperationType{"*"},
 | 
				
			||||||
					Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes},
 | 
										Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes},
 | 
				
			||||||
@@ -144,6 +149,7 @@ func TestShouldCallHook(t *testing.T) {
 | 
				
			|||||||
			webhook: &v1beta1.ValidatingWebhook{
 | 
								webhook: &v1beta1.ValidatingWebhook{
 | 
				
			||||||
				MatchPolicy:       &exactMatch,
 | 
									MatchPolicy:       &exactMatch,
 | 
				
			||||||
				NamespaceSelector: &metav1.LabelSelector{},
 | 
									NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:    &metav1.LabelSelector{},
 | 
				
			||||||
				Rules: []v1beta1.RuleWithOperations{{
 | 
									Rules: []v1beta1.RuleWithOperations{{
 | 
				
			||||||
					Operations: []v1beta1.OperationType{"*"},
 | 
										Operations: []v1beta1.OperationType{"*"},
 | 
				
			||||||
					Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes},
 | 
										Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes},
 | 
				
			||||||
@@ -159,6 +165,7 @@ func TestShouldCallHook(t *testing.T) {
 | 
				
			|||||||
			webhook: &v1beta1.ValidatingWebhook{
 | 
								webhook: &v1beta1.ValidatingWebhook{
 | 
				
			||||||
				MatchPolicy:       &equivalentMatch,
 | 
									MatchPolicy:       &equivalentMatch,
 | 
				
			||||||
				NamespaceSelector: &metav1.LabelSelector{},
 | 
									NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:    &metav1.LabelSelector{},
 | 
				
			||||||
				Rules: []v1beta1.RuleWithOperations{{
 | 
									Rules: []v1beta1.RuleWithOperations{{
 | 
				
			||||||
					Operations: []v1beta1.OperationType{"*"},
 | 
										Operations: []v1beta1.OperationType{"*"},
 | 
				
			||||||
					Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes},
 | 
										Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes},
 | 
				
			||||||
@@ -177,6 +184,7 @@ func TestShouldCallHook(t *testing.T) {
 | 
				
			|||||||
			webhook: &v1beta1.ValidatingWebhook{
 | 
								webhook: &v1beta1.ValidatingWebhook{
 | 
				
			||||||
				MatchPolicy:       &equivalentMatch,
 | 
									MatchPolicy:       &equivalentMatch,
 | 
				
			||||||
				NamespaceSelector: &metav1.LabelSelector{},
 | 
									NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:    &metav1.LabelSelector{},
 | 
				
			||||||
				Rules: []v1beta1.RuleWithOperations{{
 | 
									Rules: []v1beta1.RuleWithOperations{{
 | 
				
			||||||
					Operations: []v1beta1.OperationType{"*"},
 | 
										Operations: []v1beta1.OperationType{"*"},
 | 
				
			||||||
					Rule:       v1beta1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes},
 | 
										Rule:       v1beta1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes},
 | 
				
			||||||
@@ -195,6 +203,7 @@ func TestShouldCallHook(t *testing.T) {
 | 
				
			|||||||
			name: "specific rules, subresource prefer exact match",
 | 
								name: "specific rules, subresource prefer exact match",
 | 
				
			||||||
			webhook: &v1beta1.ValidatingWebhook{
 | 
								webhook: &v1beta1.ValidatingWebhook{
 | 
				
			||||||
				NamespaceSelector: &metav1.LabelSelector{},
 | 
									NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:    &metav1.LabelSelector{},
 | 
				
			||||||
				Rules: []v1beta1.RuleWithOperations{{
 | 
									Rules: []v1beta1.RuleWithOperations{{
 | 
				
			||||||
					Operations: []v1beta1.OperationType{"*"},
 | 
										Operations: []v1beta1.OperationType{"*"},
 | 
				
			||||||
					Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes},
 | 
										Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes},
 | 
				
			||||||
@@ -215,6 +224,7 @@ func TestShouldCallHook(t *testing.T) {
 | 
				
			|||||||
			name: "specific rules, subresource match miss",
 | 
								name: "specific rules, subresource match miss",
 | 
				
			||||||
			webhook: &v1beta1.ValidatingWebhook{
 | 
								webhook: &v1beta1.ValidatingWebhook{
 | 
				
			||||||
				NamespaceSelector: &metav1.LabelSelector{},
 | 
									NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:    &metav1.LabelSelector{},
 | 
				
			||||||
				Rules: []v1beta1.RuleWithOperations{{
 | 
									Rules: []v1beta1.RuleWithOperations{{
 | 
				
			||||||
					Operations: []v1beta1.OperationType{"*"},
 | 
										Operations: []v1beta1.OperationType{"*"},
 | 
				
			||||||
					Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes},
 | 
										Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes},
 | 
				
			||||||
@@ -230,6 +240,7 @@ func TestShouldCallHook(t *testing.T) {
 | 
				
			|||||||
			webhook: &v1beta1.ValidatingWebhook{
 | 
								webhook: &v1beta1.ValidatingWebhook{
 | 
				
			||||||
				MatchPolicy:       &exactMatch,
 | 
									MatchPolicy:       &exactMatch,
 | 
				
			||||||
				NamespaceSelector: &metav1.LabelSelector{},
 | 
									NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:    &metav1.LabelSelector{},
 | 
				
			||||||
				Rules: []v1beta1.RuleWithOperations{{
 | 
									Rules: []v1beta1.RuleWithOperations{{
 | 
				
			||||||
					Operations: []v1beta1.OperationType{"*"},
 | 
										Operations: []v1beta1.OperationType{"*"},
 | 
				
			||||||
					Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes},
 | 
										Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes},
 | 
				
			||||||
@@ -245,6 +256,7 @@ func TestShouldCallHook(t *testing.T) {
 | 
				
			|||||||
			webhook: &v1beta1.ValidatingWebhook{
 | 
								webhook: &v1beta1.ValidatingWebhook{
 | 
				
			||||||
				MatchPolicy:       &equivalentMatch,
 | 
									MatchPolicy:       &equivalentMatch,
 | 
				
			||||||
				NamespaceSelector: &metav1.LabelSelector{},
 | 
									NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:    &metav1.LabelSelector{},
 | 
				
			||||||
				Rules: []v1beta1.RuleWithOperations{{
 | 
									Rules: []v1beta1.RuleWithOperations{{
 | 
				
			||||||
					Operations: []v1beta1.OperationType{"*"},
 | 
										Operations: []v1beta1.OperationType{"*"},
 | 
				
			||||||
					Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes},
 | 
										Rule:       v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes},
 | 
				
			||||||
@@ -263,6 +275,7 @@ func TestShouldCallHook(t *testing.T) {
 | 
				
			|||||||
			webhook: &v1beta1.ValidatingWebhook{
 | 
								webhook: &v1beta1.ValidatingWebhook{
 | 
				
			||||||
				MatchPolicy:       &equivalentMatch,
 | 
									MatchPolicy:       &equivalentMatch,
 | 
				
			||||||
				NamespaceSelector: &metav1.LabelSelector{},
 | 
									NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:    &metav1.LabelSelector{},
 | 
				
			||||||
				Rules: []v1beta1.RuleWithOperations{{
 | 
									Rules: []v1beta1.RuleWithOperations{{
 | 
				
			||||||
					Operations: []v1beta1.OperationType{"*"},
 | 
										Operations: []v1beta1.OperationType{"*"},
 | 
				
			||||||
					Rule:       v1beta1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes},
 | 
										Rule:       v1beta1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes},
 | 
				
			||||||
@@ -280,7 +293,7 @@ func TestShouldCallHook(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for i, testcase := range testcases {
 | 
						for i, testcase := range testcases {
 | 
				
			||||||
		t.Run(testcase.name, func(t *testing.T) {
 | 
							t.Run(testcase.name, func(t *testing.T) {
 | 
				
			||||||
			invocation, err := a.shouldCallHook(webhook.NewValidatingWebhookAccessor(fmt.Sprintf("webhook-%d", i), testcase.webhook), testcase.attrs, interfaces)
 | 
								invocation, err := a.ShouldCallHook(webhook.NewValidatingWebhookAccessor(fmt.Sprintf("webhook-%d", i), testcase.webhook), testcase.attrs, interfaces)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				if len(testcase.expectErr) == 0 {
 | 
									if len(testcase.expectErr) == 0 {
 | 
				
			||||||
					t.Fatal(err)
 | 
										t.Fatal(err)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,6 +36,7 @@ import (
 | 
				
			|||||||
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
						utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/admission"
 | 
						"k8s.io/apiserver/pkg/admission"
 | 
				
			||||||
	admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
 | 
						admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook"
 | 
				
			||||||
	webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
 | 
						webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/admission/plugin/webhook/request"
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook/request"
 | 
				
			||||||
@@ -56,7 +57,7 @@ func newMutatingDispatcher(p *Plugin) func(cm *webhookutil.ClientManager) generi
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var _ generic.Dispatcher = &mutatingDispatcher{}
 | 
					var _ generic.Dispatcher = &mutatingDispatcher{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, relevantHooks []*generic.WebhookInvocation) error {
 | 
					func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error {
 | 
				
			||||||
	reinvokeCtx := attr.GetReinvocationContext()
 | 
						reinvokeCtx := attr.GetReinvocationContext()
 | 
				
			||||||
	var webhookReinvokeCtx *webhookReinvokeContext
 | 
						var webhookReinvokeCtx *webhookReinvokeContext
 | 
				
			||||||
	if v := reinvokeCtx.Value(PluginName); v != nil {
 | 
						if v := reinvokeCtx.Value(PluginName); v != nil {
 | 
				
			||||||
@@ -75,14 +76,31 @@ func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attrib
 | 
				
			|||||||
		webhookReinvokeCtx.SetLastWebhookInvocationOutput(attr.GetObject())
 | 
							webhookReinvokeCtx.SetLastWebhookInvocationOutput(attr.GetObject())
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
	var versionedAttr *generic.VersionedAttributes
 | 
						var versionedAttr *generic.VersionedAttributes
 | 
				
			||||||
	for _, invocation := range relevantHooks {
 | 
						for _, hook := range hooks {
 | 
				
			||||||
 | 
							attrForCheck := attr
 | 
				
			||||||
 | 
							if versionedAttr != nil {
 | 
				
			||||||
 | 
								attrForCheck = versionedAttr
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							invocation, statusErr := a.plugin.ShouldCallHook(hook, attrForCheck, o)
 | 
				
			||||||
 | 
							if statusErr != nil {
 | 
				
			||||||
 | 
								return statusErr
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if invocation == nil {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		hook, ok := invocation.Webhook.GetMutatingWebhook()
 | 
							hook, ok := invocation.Webhook.GetMutatingWebhook()
 | 
				
			||||||
		if !ok {
 | 
							if !ok {
 | 
				
			||||||
			return fmt.Errorf("mutating webhook dispatch requires v1beta1.MutatingWebhook, but got %T", hook)
 | 
								return fmt.Errorf("mutating webhook dispatch requires v1beta1.MutatingWebhook, but got %T", hook)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							// This means that during reinvocation, a webhook will not be
 | 
				
			||||||
 | 
							// called for the first time. For example, if the webhook is
 | 
				
			||||||
 | 
							// skipped in the first round because of mismatching labels,
 | 
				
			||||||
 | 
							// even if the labels become matching, the webhook does not
 | 
				
			||||||
 | 
							// get called during reinvocation.
 | 
				
			||||||
		if reinvokeCtx.IsReinvoke() && !webhookReinvokeCtx.ShouldReinvokeWebhook(invocation.Webhook.GetUID()) {
 | 
							if reinvokeCtx.IsReinvoke() && !webhookReinvokeCtx.ShouldReinvokeWebhook(invocation.Webhook.GetUID()) {
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if versionedAttr == nil {
 | 
							if versionedAttr == nil {
 | 
				
			||||||
			// First webhook, create versioned attributes
 | 
								// First webhook, create versioned attributes
 | 
				
			||||||
			var err error
 | 
								var err error
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2019 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Package object defines the utilities that are used by the webhook plugin to
 | 
				
			||||||
 | 
					// decide if a webhook should run, as long as either the old object or the new
 | 
				
			||||||
 | 
					// object has labels matching the webhook config's objectSelector.
 | 
				
			||||||
 | 
					package object // import "k8s.io/apiserver/pkg/admission/plugin/webhook/object"
 | 
				
			||||||
@@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2019 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package object
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/api/meta"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/labels"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/runtime"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/admission"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook"
 | 
				
			||||||
 | 
						"k8s.io/klog"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Matcher decides if a request selected by the ObjectSelector.
 | 
				
			||||||
 | 
					type Matcher struct {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func matchObject(obj runtime.Object, selector labels.Selector) bool {
 | 
				
			||||||
 | 
						if obj == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						accessor, err := meta.Accessor(obj)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							klog.V(5).Infof("cannot access metadata of %v: %v", obj, err)
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return selector.Matches(labels.Set(accessor.GetLabels()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MatchObjectSelector decideds whether the request matches the ObjectSelector
 | 
				
			||||||
 | 
					// of the webhook. Only when they match, the webhook is called.
 | 
				
			||||||
 | 
					func (m *Matcher) MatchObjectSelector(h webhook.WebhookAccessor, attr admission.Attributes) (bool, *apierrors.StatusError) {
 | 
				
			||||||
 | 
						// TODO: adding an LRU cache to cache the translation
 | 
				
			||||||
 | 
						selector, err := metav1.LabelSelectorAsSelector(h.GetObjectSelector())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false, apierrors.NewInternalError(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if selector.Empty() {
 | 
				
			||||||
 | 
							return true, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return matchObject(attr.GetObject(), selector) || matchObject(attr.GetOldObject(), selector), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,130 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2019 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package object
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/api/admissionregistration/v1beta1"
 | 
				
			||||||
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/runtime/schema"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/admission"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestObjectSelector(t *testing.T) {
 | 
				
			||||||
 | 
						nodeLevel1 := &corev1.Node{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Labels: map[string]string{
 | 
				
			||||||
 | 
									"runlevel": "1",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						nodeLevel2 := &corev1.Node{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Labels: map[string]string{
 | 
				
			||||||
 | 
									"runlevel": "2",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						runLevel1Excluder := &metav1.LabelSelector{
 | 
				
			||||||
 | 
							MatchExpressions: []metav1.LabelSelectorRequirement{
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Key:      "runlevel",
 | 
				
			||||||
 | 
									Operator: metav1.LabelSelectorOpNotIn,
 | 
				
			||||||
 | 
									Values:   []string{"1"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						matcher := &Matcher{}
 | 
				
			||||||
 | 
						allScopes := v1beta1.AllScopes
 | 
				
			||||||
 | 
						testcases := []struct {
 | 
				
			||||||
 | 
							name string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							objectSelector *metav1.LabelSelector
 | 
				
			||||||
 | 
							attrs          admission.Attributes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expectCall bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "empty object selector matches everything",
 | 
				
			||||||
 | 
								objectSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
								attrs:          admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
				
			||||||
 | 
								expectCall:     true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "matches new object",
 | 
				
			||||||
 | 
								objectSelector: runLevel1Excluder,
 | 
				
			||||||
 | 
								attrs:          admission.NewAttributesRecord(nodeLevel2, nil, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
				
			||||||
 | 
								expectCall:     true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "matches old object",
 | 
				
			||||||
 | 
								objectSelector: runLevel1Excluder,
 | 
				
			||||||
 | 
								attrs:          admission.NewAttributesRecord(nil, nodeLevel2, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Delete, &metav1.DeleteOptions{}, false, nil),
 | 
				
			||||||
 | 
								expectCall:     true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "does not match new object",
 | 
				
			||||||
 | 
								objectSelector: runLevel1Excluder,
 | 
				
			||||||
 | 
								attrs:          admission.NewAttributesRecord(nodeLevel1, nil, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
				
			||||||
 | 
								expectCall:     false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "does not match old object",
 | 
				
			||||||
 | 
								objectSelector: runLevel1Excluder,
 | 
				
			||||||
 | 
								attrs:          admission.NewAttributesRecord(nil, nodeLevel1, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
				
			||||||
 | 
								expectCall:     false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "does not match object that does not implement Object interface",
 | 
				
			||||||
 | 
								objectSelector: runLevel1Excluder,
 | 
				
			||||||
 | 
								attrs:          admission.NewAttributesRecord(&corev1.NodeProxyOptions{}, nil, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
				
			||||||
 | 
								expectCall:     false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "empty selector matches everything, including object that does not implement Object interface",
 | 
				
			||||||
 | 
								objectSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
								attrs:          admission.NewAttributesRecord(&corev1.NodeProxyOptions{}, nil, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
				
			||||||
 | 
								expectCall:     true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, testcase := range testcases {
 | 
				
			||||||
 | 
							hook := &v1beta1.ValidatingWebhook{
 | 
				
			||||||
 | 
								NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
								ObjectSelector:    testcase.objectSelector,
 | 
				
			||||||
 | 
								Rules: []v1beta1.RuleWithOperations{{
 | 
				
			||||||
 | 
									Operations: []v1beta1.OperationType{"*"},
 | 
				
			||||||
 | 
									Rule:       v1beta1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes},
 | 
				
			||||||
 | 
								}}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							t.Run(testcase.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								match, err := matcher.MatchObjectSelector(webhook.NewValidatingWebhookAccessor("mock-hook", hook), testcase.attrs)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									t.Error(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if testcase.expectCall && !match {
 | 
				
			||||||
 | 
									t.Errorf("expected the webhook to be called")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !testcase.expectCall && match {
 | 
				
			||||||
 | 
									t.Errorf("expected the webhook to be called")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -245,7 +245,7 @@ func ConvertToMutatingTestCases(tests []ValidatingTest) []MutatingTest {
 | 
				
			|||||||
func ConvertToMutatingWebhooks(webhooks []registrationv1beta1.ValidatingWebhook) []registrationv1beta1.MutatingWebhook {
 | 
					func ConvertToMutatingWebhooks(webhooks []registrationv1beta1.ValidatingWebhook) []registrationv1beta1.MutatingWebhook {
 | 
				
			||||||
	mutating := make([]registrationv1beta1.MutatingWebhook, len(webhooks))
 | 
						mutating := make([]registrationv1beta1.MutatingWebhook, len(webhooks))
 | 
				
			||||||
	for i, h := range webhooks {
 | 
						for i, h := range webhooks {
 | 
				
			||||||
		mutating[i] = registrationv1beta1.MutatingWebhook{h.Name, h.ClientConfig, h.Rules, h.FailurePolicy, h.MatchPolicy, h.NamespaceSelector, h.SideEffects, h.TimeoutSeconds, h.AdmissionReviewVersions, nil}
 | 
							mutating[i] = registrationv1beta1.MutatingWebhook{h.Name, h.ClientConfig, h.Rules, h.FailurePolicy, h.MatchPolicy, h.NamespaceSelector, h.ObjectSelector, h.SideEffects, h.TimeoutSeconds, h.AdmissionReviewVersions, nil}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return mutating
 | 
						return mutating
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -552,6 +552,30 @@ func NewNonMutatingTestCases(url *url.URL) []ValidatingTest {
 | 
				
			|||||||
			}},
 | 
								}},
 | 
				
			||||||
			ExpectAllow: true,
 | 
								ExpectAllow: true,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name: "skip webhook whose objectSelector does not match",
 | 
				
			||||||
 | 
								Webhooks: []registrationv1beta1.ValidatingWebhook{{
 | 
				
			||||||
 | 
									Name:                    "allow.example.com",
 | 
				
			||||||
 | 
									ClientConfig:            ccfgSVC("allow"),
 | 
				
			||||||
 | 
									Rules:                   matchEverythingRules,
 | 
				
			||||||
 | 
									NamespaceSelector:       &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:          &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									AdmissionReviewVersions: []string{"v1beta1"},
 | 
				
			||||||
 | 
								}, {
 | 
				
			||||||
 | 
									Name:              "shouldNotBeCalled",
 | 
				
			||||||
 | 
									ClientConfig:      ccfgSVC("shouldNotBeCalled"),
 | 
				
			||||||
 | 
									NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector: &metav1.LabelSelector{
 | 
				
			||||||
 | 
										MatchLabels: map[string]string{
 | 
				
			||||||
 | 
											"label": "nonexistent",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Rules:                   matchEverythingRules,
 | 
				
			||||||
 | 
									AdmissionReviewVersions: []string{"v1beta1"},
 | 
				
			||||||
 | 
								}},
 | 
				
			||||||
 | 
								ExpectAllow:       true,
 | 
				
			||||||
 | 
								ExpectAnnotations: map[string]string{"allow.example.com/key1": "value1"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		// No need to test everything with the url case, since only the
 | 
							// No need to test everything with the url case, since only the
 | 
				
			||||||
		// connection is different.
 | 
							// connection is different.
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -642,6 +666,36 @@ func NewMutatingTestCases(url *url.URL) []MutatingTest {
 | 
				
			|||||||
			ExpectStatusCode: http.StatusBadRequest,
 | 
								ExpectStatusCode: http.StatusBadRequest,
 | 
				
			||||||
			ErrorContains:    "does not support dry run",
 | 
								ErrorContains:    "does not support dry run",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name: "first webhook remove labels, second webhook shouldn't be called",
 | 
				
			||||||
 | 
								Webhooks: []registrationv1beta1.MutatingWebhook{{
 | 
				
			||||||
 | 
									Name:              "removelabel.example.com",
 | 
				
			||||||
 | 
									ClientConfig:      ccfgSVC("removeLabel"),
 | 
				
			||||||
 | 
									Rules:             matchEverythingRules,
 | 
				
			||||||
 | 
									NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector: &metav1.LabelSelector{
 | 
				
			||||||
 | 
										MatchLabels: map[string]string{
 | 
				
			||||||
 | 
											"remove": "me",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									AdmissionReviewVersions: []string{"v1beta1"},
 | 
				
			||||||
 | 
								}, {
 | 
				
			||||||
 | 
									Name:              "shouldNotBeCalled",
 | 
				
			||||||
 | 
									ClientConfig:      ccfgSVC("shouldNotBeCalled"),
 | 
				
			||||||
 | 
									NamespaceSelector: &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector: &metav1.LabelSelector{
 | 
				
			||||||
 | 
										MatchLabels: map[string]string{
 | 
				
			||||||
 | 
											"remove": "me",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Rules:                   matchEverythingRules,
 | 
				
			||||||
 | 
									AdmissionReviewVersions: []string{"v1beta1"},
 | 
				
			||||||
 | 
								}},
 | 
				
			||||||
 | 
								ExpectAllow:       true,
 | 
				
			||||||
 | 
								AdditionalLabels:  map[string]string{"remove": "me"},
 | 
				
			||||||
 | 
								ExpectLabels:      map[string]string{"pod.name": "my-pod"},
 | 
				
			||||||
 | 
								ExpectAnnotations: map[string]string{"removelabel.example.com/key1": "value1"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		// No need to test everything with the url case, since only the
 | 
							// No need to test everything with the url case, since only the
 | 
				
			||||||
		// connection is different.
 | 
							// connection is different.
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
@@ -651,6 +705,7 @@ func NewMutatingTestCases(url *url.URL) []MutatingTest {
 | 
				
			|||||||
				ClientConfig:            ccfgSVC("addLabel"),
 | 
									ClientConfig:            ccfgSVC("addLabel"),
 | 
				
			||||||
				Rules:                   matchEverythingRules,
 | 
									Rules:                   matchEverythingRules,
 | 
				
			||||||
				NamespaceSelector:       &metav1.LabelSelector{},
 | 
									NamespaceSelector:       &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:          &metav1.LabelSelector{},
 | 
				
			||||||
				AdmissionReviewVersions: []string{"v1beta1"},
 | 
									AdmissionReviewVersions: []string{"v1beta1"},
 | 
				
			||||||
				ReinvocationPolicy:      &reinvokeIfNeeded,
 | 
									ReinvocationPolicy:      &reinvokeIfNeeded,
 | 
				
			||||||
			}, {
 | 
								}, {
 | 
				
			||||||
@@ -658,6 +713,7 @@ func NewMutatingTestCases(url *url.URL) []MutatingTest {
 | 
				
			|||||||
				ClientConfig:            ccfgSVC("removeLabel"),
 | 
									ClientConfig:            ccfgSVC("removeLabel"),
 | 
				
			||||||
				Rules:                   matchEverythingRules,
 | 
									Rules:                   matchEverythingRules,
 | 
				
			||||||
				NamespaceSelector:       &metav1.LabelSelector{},
 | 
									NamespaceSelector:       &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:          &metav1.LabelSelector{},
 | 
				
			||||||
				AdmissionReviewVersions: []string{"v1beta1"},
 | 
									AdmissionReviewVersions: []string{"v1beta1"},
 | 
				
			||||||
				ReinvocationPolicy:      &reinvokeIfNeeded,
 | 
									ReinvocationPolicy:      &reinvokeIfNeeded,
 | 
				
			||||||
			}},
 | 
								}},
 | 
				
			||||||
@@ -672,6 +728,7 @@ func NewMutatingTestCases(url *url.URL) []MutatingTest {
 | 
				
			|||||||
				ClientConfig:            ccfgSVC("addLabel"),
 | 
									ClientConfig:            ccfgSVC("addLabel"),
 | 
				
			||||||
				Rules:                   matchEverythingRules,
 | 
									Rules:                   matchEverythingRules,
 | 
				
			||||||
				NamespaceSelector:       &metav1.LabelSelector{},
 | 
									NamespaceSelector:       &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:          &metav1.LabelSelector{},
 | 
				
			||||||
				AdmissionReviewVersions: []string{"v1beta1"},
 | 
									AdmissionReviewVersions: []string{"v1beta1"},
 | 
				
			||||||
				ReinvocationPolicy:      &reinvokeNever,
 | 
									ReinvocationPolicy:      &reinvokeNever,
 | 
				
			||||||
			}},
 | 
								}},
 | 
				
			||||||
@@ -685,6 +742,7 @@ func NewMutatingTestCases(url *url.URL) []MutatingTest {
 | 
				
			|||||||
				ClientConfig:            ccfgSVC("addLabel"),
 | 
									ClientConfig:            ccfgSVC("addLabel"),
 | 
				
			||||||
				Rules:                   matchEverythingRules,
 | 
									Rules:                   matchEverythingRules,
 | 
				
			||||||
				NamespaceSelector:       &metav1.LabelSelector{},
 | 
									NamespaceSelector:       &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:          &metav1.LabelSelector{},
 | 
				
			||||||
				AdmissionReviewVersions: []string{"v1beta1"},
 | 
									AdmissionReviewVersions: []string{"v1beta1"},
 | 
				
			||||||
			}},
 | 
								}},
 | 
				
			||||||
			ExpectAllow:            true,
 | 
								ExpectAllow:            true,
 | 
				
			||||||
@@ -697,6 +755,7 @@ func NewMutatingTestCases(url *url.URL) []MutatingTest {
 | 
				
			|||||||
				ClientConfig:            ccfgSVC("noop"),
 | 
									ClientConfig:            ccfgSVC("noop"),
 | 
				
			||||||
				Rules:                   matchEverythingRules,
 | 
									Rules:                   matchEverythingRules,
 | 
				
			||||||
				NamespaceSelector:       &metav1.LabelSelector{},
 | 
									NamespaceSelector:       &metav1.LabelSelector{},
 | 
				
			||||||
 | 
									ObjectSelector:          &metav1.LabelSelector{},
 | 
				
			||||||
				AdmissionReviewVersions: []string{"v1beta1"},
 | 
									AdmissionReviewVersions: []string{"v1beta1"},
 | 
				
			||||||
			}},
 | 
								}},
 | 
				
			||||||
			ExpectAllow: true,
 | 
								ExpectAllow: true,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -82,6 +82,17 @@ func webhookHandler(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
						case "/shouldNotBeCalled":
 | 
				
			||||||
 | 
							w.Header().Set("Content-Type", "application/json")
 | 
				
			||||||
 | 
							json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{
 | 
				
			||||||
 | 
								Response: &v1beta1.AdmissionResponse{
 | 
				
			||||||
 | 
									Allowed: false,
 | 
				
			||||||
 | 
									Result: &metav1.Status{
 | 
				
			||||||
 | 
										Message: "doesn't expect labels to match object selector",
 | 
				
			||||||
 | 
										Code:    http.StatusForbidden,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	case "/allow":
 | 
						case "/allow":
 | 
				
			||||||
		w.Header().Set("Content-Type", "application/json")
 | 
							w.Header().Set("Content-Type", "application/json")
 | 
				
			||||||
		json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{
 | 
							json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,6 +29,7 @@ import (
 | 
				
			|||||||
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
						utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/admission"
 | 
						"k8s.io/apiserver/pkg/admission"
 | 
				
			||||||
	admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
 | 
						admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook"
 | 
				
			||||||
	webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
 | 
						webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/admission/plugin/webhook/request"
 | 
						"k8s.io/apiserver/pkg/admission/plugin/webhook/request"
 | 
				
			||||||
@@ -39,27 +40,44 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type validatingDispatcher struct {
 | 
					type validatingDispatcher struct {
 | 
				
			||||||
	cm     *webhookutil.ClientManager
 | 
						cm     *webhookutil.ClientManager
 | 
				
			||||||
 | 
						plugin *Plugin
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func newValidatingDispatcher(cm *webhookutil.ClientManager) generic.Dispatcher {
 | 
					func newValidatingDispatcher(p *Plugin) func(cm *webhookutil.ClientManager) generic.Dispatcher {
 | 
				
			||||||
	return &validatingDispatcher{cm}
 | 
						return func(cm *webhookutil.ClientManager) generic.Dispatcher {
 | 
				
			||||||
 | 
							return &validatingDispatcher{cm, p}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var _ generic.Dispatcher = &validatingDispatcher{}
 | 
					var _ generic.Dispatcher = &validatingDispatcher{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (d *validatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, relevantHooks []*generic.WebhookInvocation) error {
 | 
					func (d *validatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error {
 | 
				
			||||||
 | 
						var relevantHooks []*generic.WebhookInvocation
 | 
				
			||||||
	// Construct all the versions we need to call our webhooks
 | 
						// Construct all the versions we need to call our webhooks
 | 
				
			||||||
	versionedAttrs := map[schema.GroupVersionKind]*generic.VersionedAttributes{}
 | 
						versionedAttrs := map[schema.GroupVersionKind]*generic.VersionedAttributes{}
 | 
				
			||||||
	for _, call := range relevantHooks {
 | 
						for _, hook := range hooks {
 | 
				
			||||||
		// If we already have this version, continue
 | 
							invocation, statusError := d.plugin.ShouldCallHook(hook, attr, o)
 | 
				
			||||||
		if _, ok := versionedAttrs[call.Kind]; ok {
 | 
							if statusError != nil {
 | 
				
			||||||
 | 
								return statusError
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if invocation == nil {
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		versionedAttr, err := generic.NewVersionedAttributes(attr, call.Kind, o)
 | 
							relevantHooks = append(relevantHooks, invocation)
 | 
				
			||||||
 | 
							// If we already have this version, continue
 | 
				
			||||||
 | 
							if _, ok := versionedAttrs[invocation.Kind]; ok {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							versionedAttr, err := generic.NewVersionedAttributes(attr, invocation.Kind, o)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return apierrors.NewInternalError(err)
 | 
								return apierrors.NewInternalError(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		versionedAttrs[call.Kind] = versionedAttr
 | 
							versionedAttrs[invocation.Kind] = versionedAttr
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(relevantHooks) == 0 {
 | 
				
			||||||
 | 
							// no matching hooks
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	wg := sync.WaitGroup{}
 | 
						wg := sync.WaitGroup{}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,11 +51,13 @@ var _ admission.ValidationInterface = &Plugin{}
 | 
				
			|||||||
// NewValidatingAdmissionWebhook returns a generic admission webhook plugin.
 | 
					// NewValidatingAdmissionWebhook returns a generic admission webhook plugin.
 | 
				
			||||||
func NewValidatingAdmissionWebhook(configFile io.Reader) (*Plugin, error) {
 | 
					func NewValidatingAdmissionWebhook(configFile io.Reader) (*Plugin, error) {
 | 
				
			||||||
	handler := admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update)
 | 
						handler := admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update)
 | 
				
			||||||
	webhook, err := generic.NewWebhook(handler, configFile, configuration.NewValidatingWebhookConfigurationManager, newValidatingDispatcher)
 | 
						p := &Plugin{}
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						p.Webhook, err = generic.NewWebhook(handler, configFile, configuration.NewValidatingWebhookConfigurationManager, newValidatingDispatcher(p))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return &Plugin{webhook}, nil
 | 
						return p, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Validate makes an admission decision based on the request attributes.
 | 
					// Validate makes an admission decision based on the request attributes.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,6 +54,7 @@ func TestWebhookReinvocationPolicy(t *testing.T) {
 | 
				
			|||||||
	type testWebhook struct {
 | 
						type testWebhook struct {
 | 
				
			||||||
		path           string
 | 
							path           string
 | 
				
			||||||
		policy         *registrationv1beta1.ReinvocationPolicyType
 | 
							policy         *registrationv1beta1.ReinvocationPolicyType
 | 
				
			||||||
 | 
							objectSelector *metav1.LabelSelector
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	testCases := []struct {
 | 
						testCases := []struct {
 | 
				
			||||||
@@ -110,6 +111,16 @@ func TestWebhookReinvocationPolicy(t *testing.T) {
 | 
				
			|||||||
			expectLabels:      map[string]string{"x": "true", "fight": "false"},
 | 
								expectLabels:      map[string]string{"x": "true", "fight": "false"},
 | 
				
			||||||
			expectInvocations: map[string]int{"/settrue": 2, "/setfalse": 2},
 | 
								expectInvocations: map[string]int{"/settrue": 2, "/setfalse": 2},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{ // in-tree (mutation), webhook A is SKIPPED due to objectSelector not matching, webhook B (mutation), reinvoke in-tree (no-mutation), webhook A is SKIPPED even though the labels match now, because it's not called in the first round. No reinvocation of webhook B required
 | 
				
			||||||
 | 
								name:                 "no reinvocation of webhook B when in-tree or prior webhook mutations",
 | 
				
			||||||
 | 
								initialPriorityClass: "low-priority", // trigger initial in-tree mutation
 | 
				
			||||||
 | 
								webhooks: []testWebhook{
 | 
				
			||||||
 | 
									{path: "/conditionaladdlabel", policy: &reinvokeIfNeeded, objectSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
 | 
				
			||||||
 | 
									{path: "/addlabel", policy: &reinvokeIfNeeded},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectLabels:      map[string]string{"x": "true", "a": "true"},
 | 
				
			||||||
 | 
								expectInvocations: map[string]int{"/addlabel": 1, "/conditionaladdlabel": 0},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "invalid priority class set by webhook should result in error from in-tree priority plugin",
 | 
								name: "invalid priority class set by webhook should result in error from in-tree priority plugin",
 | 
				
			||||||
			webhooks: []testWebhook{
 | 
								webhooks: []testWebhook{
 | 
				
			||||||
@@ -193,7 +204,7 @@ func TestWebhookReinvocationPolicy(t *testing.T) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for i, webhook := range tt.webhooks {
 | 
								for i, webhook := range tt.webhooks {
 | 
				
			||||||
				defer registerWebhook(t, client, fmt.Sprintf("admission.integration.test%d", i), webhookServer.URL+webhook.path, webhook.policy)()
 | 
									defer registerWebhook(t, client, fmt.Sprintf("admission.integration.test%d", i), webhookServer.URL+webhook.path, webhook.policy, webhook.objectSelector)()
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			pod := &corev1.Pod{
 | 
								pod := &corev1.Pod{
 | 
				
			||||||
@@ -248,7 +259,7 @@ func TestWebhookReinvocationPolicy(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func registerWebhook(t *testing.T, client clientset.Interface, name, endpoint string, reinvocationPolicy *registrationv1beta1.ReinvocationPolicyType) func() {
 | 
					func registerWebhook(t *testing.T, client clientset.Interface, name, endpoint string, reinvocationPolicy *registrationv1beta1.ReinvocationPolicyType, objectSelector *metav1.LabelSelector) func() {
 | 
				
			||||||
	fail := admissionv1beta1.Fail
 | 
						fail := admissionv1beta1.Fail
 | 
				
			||||||
	hook, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&admissionv1beta1.MutatingWebhookConfiguration{
 | 
						hook, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&admissionv1beta1.MutatingWebhookConfiguration{
 | 
				
			||||||
		ObjectMeta: metav1.ObjectMeta{Name: name},
 | 
							ObjectMeta: metav1.ObjectMeta{Name: name},
 | 
				
			||||||
@@ -262,6 +273,7 @@ func registerWebhook(t *testing.T, client clientset.Interface, name, endpoint st
 | 
				
			|||||||
				Operations: []admissionv1beta1.OperationType{admissionv1beta1.OperationAll},
 | 
									Operations: []admissionv1beta1.OperationType{admissionv1beta1.OperationAll},
 | 
				
			||||||
				Rule:       admissionv1beta1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*/*"}},
 | 
									Rule:       admissionv1beta1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*/*"}},
 | 
				
			||||||
			}},
 | 
								}},
 | 
				
			||||||
 | 
								ObjectSelector:          objectSelector,
 | 
				
			||||||
			FailurePolicy:           &fail,
 | 
								FailurePolicy:           &fail,
 | 
				
			||||||
			ReinvocationPolicy:      reinvocationPolicy,
 | 
								ReinvocationPolicy:      reinvocationPolicy,
 | 
				
			||||||
			AdmissionReviewVersions: []string{"v1beta1"},
 | 
								AdmissionReviewVersions: []string{"v1beta1"},
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user