k8s.io/apiserver@v0.31.1/pkg/admission/plugin/webhook/predicates/object/matcher_test.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package object_test
    18  
    19  import (
    20  	"testing"
    21  
    22  	"k8s.io/api/admissionregistration/v1"
    23  	corev1 "k8s.io/api/core/v1"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/runtime/schema"
    26  	"k8s.io/apiserver/pkg/admission"
    27  	"k8s.io/apiserver/pkg/admission/plugin/webhook"
    28  	"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object"
    29  )
    30  
    31  func TestObjectSelector(t *testing.T) {
    32  	nodeLevel1 := &corev1.Node{
    33  		ObjectMeta: metav1.ObjectMeta{
    34  			Labels: map[string]string{
    35  				"runlevel": "1",
    36  			},
    37  		},
    38  	}
    39  	nodeLevel2 := &corev1.Node{
    40  		ObjectMeta: metav1.ObjectMeta{
    41  			Labels: map[string]string{
    42  				"runlevel": "2",
    43  			},
    44  		},
    45  	}
    46  	runLevel1Excluder := &metav1.LabelSelector{
    47  		MatchExpressions: []metav1.LabelSelectorRequirement{
    48  			{
    49  				Key:      "runlevel",
    50  				Operator: metav1.LabelSelectorOpNotIn,
    51  				Values:   []string{"1"},
    52  			},
    53  		},
    54  	}
    55  	matcher := &object.Matcher{}
    56  	allScopes := v1.AllScopes
    57  	testcases := []struct {
    58  		name string
    59  
    60  		objectSelector *metav1.LabelSelector
    61  		attrs          admission.Attributes
    62  
    63  		expectCall bool
    64  	}{
    65  		{
    66  			name:           "empty object selector matches everything",
    67  			objectSelector: &metav1.LabelSelector{},
    68  			attrs:          admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil),
    69  			expectCall:     true,
    70  		},
    71  		{
    72  			name:           "matches new object",
    73  			objectSelector: runLevel1Excluder,
    74  			attrs:          admission.NewAttributesRecord(nodeLevel2, nil, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil),
    75  			expectCall:     true,
    76  		},
    77  		{
    78  			name:           "matches old object",
    79  			objectSelector: runLevel1Excluder,
    80  			attrs:          admission.NewAttributesRecord(nil, nodeLevel2, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Delete, &metav1.DeleteOptions{}, false, nil),
    81  			expectCall:     true,
    82  		},
    83  		{
    84  			name:           "does not match new object",
    85  			objectSelector: runLevel1Excluder,
    86  			attrs:          admission.NewAttributesRecord(nodeLevel1, nil, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil),
    87  			expectCall:     false,
    88  		},
    89  		{
    90  			name:           "does not match old object",
    91  			objectSelector: runLevel1Excluder,
    92  			attrs:          admission.NewAttributesRecord(nil, nodeLevel1, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil),
    93  			expectCall:     false,
    94  		},
    95  		{
    96  			name:           "does not match object that does not implement Object interface",
    97  			objectSelector: runLevel1Excluder,
    98  			attrs:          admission.NewAttributesRecord(&corev1.NodeProxyOptions{}, nil, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil),
    99  			expectCall:     false,
   100  		},
   101  		{
   102  			name:           "empty selector matches everything, including object that does not implement Object interface",
   103  			objectSelector: &metav1.LabelSelector{},
   104  			attrs:          admission.NewAttributesRecord(&corev1.NodeProxyOptions{}, nil, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil),
   105  			expectCall:     true,
   106  		},
   107  	}
   108  
   109  	for _, testcase := range testcases {
   110  		hook := &v1.ValidatingWebhook{
   111  			NamespaceSelector: &metav1.LabelSelector{},
   112  			ObjectSelector:    testcase.objectSelector,
   113  			Rules: []v1.RuleWithOperations{{
   114  				Operations: []v1.OperationType{"*"},
   115  				Rule:       v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes},
   116  			}}}
   117  
   118  		t.Run(testcase.name, func(t *testing.T) {
   119  			match, err := matcher.MatchObjectSelector(webhook.NewValidatingWebhookAccessor("mock-hook", "mock-cfg", hook), testcase.attrs)
   120  			if err != nil {
   121  				t.Error(err)
   122  			}
   123  			if testcase.expectCall && !match {
   124  				t.Errorf("expected the webhook to be called")
   125  			}
   126  			if !testcase.expectCall && match {
   127  				t.Errorf("expected the webhook to be called")
   128  			}
   129  		})
   130  	}
   131  }