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

     1  /*
     2  Copyright 2023 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 matchconditions
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"strings"
    23  	"testing"
    24  
    25  	api "k8s.io/api/core/v1"
    26  
    27  	v1 "k8s.io/api/admissionregistration/v1"
    28  
    29  	celtypes "github.com/google/cel-go/common/types"
    30  	"github.com/stretchr/testify/require"
    31  
    32  	admissionv1 "k8s.io/api/admission/v1"
    33  	"k8s.io/apimachinery/pkg/runtime/schema"
    34  	"k8s.io/apiserver/pkg/admission"
    35  	"k8s.io/apiserver/pkg/admission/plugin/cel"
    36  )
    37  
    38  var _ cel.Filter = &fakeCelFilter{}
    39  
    40  type fakeCelFilter struct {
    41  	evaluations []cel.EvaluationResult
    42  	throwError  bool
    43  }
    44  
    45  func (f *fakeCelFilter) ForInput(context.Context, *admission.VersionedAttributes, *admissionv1.AdmissionRequest, cel.OptionalVariableBindings, *api.Namespace, int64) ([]cel.EvaluationResult, int64, error) {
    46  	if f.throwError {
    47  		return nil, 0, errors.New("test error")
    48  	}
    49  	return f.evaluations, 0, nil
    50  }
    51  
    52  func (f *fakeCelFilter) CompilationErrors() []error {
    53  	return []error{}
    54  }
    55  
    56  func TestMatch(t *testing.T) {
    57  	fakeAttr := admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "default", "foo", schema.GroupVersionResource{}, "", admission.Create, nil, false, nil)
    58  	fakeVersionedAttr, _ := admission.NewVersionedAttributes(fakeAttr, schema.GroupVersionKind{}, nil)
    59  	fail := v1.Fail
    60  	ignore := v1.Ignore
    61  
    62  	cases := []struct {
    63  		name         string
    64  		evaluations  []cel.EvaluationResult
    65  		throwError   bool
    66  		shouldMatch  bool
    67  		returnedName string
    68  		failPolicy   *v1.FailurePolicyType
    69  		expectError  string
    70  	}{
    71  		{
    72  			name: "test single matches",
    73  			evaluations: []cel.EvaluationResult{
    74  				{
    75  					EvalResult:         celtypes.True,
    76  					ExpressionAccessor: &MatchCondition{},
    77  				},
    78  			},
    79  			shouldMatch: true,
    80  		},
    81  		{
    82  			name: "test multiple match",
    83  			evaluations: []cel.EvaluationResult{
    84  				{
    85  					EvalResult:         celtypes.True,
    86  					ExpressionAccessor: &MatchCondition{},
    87  				},
    88  				{
    89  					EvalResult:         celtypes.True,
    90  					ExpressionAccessor: &MatchCondition{},
    91  				},
    92  			},
    93  			shouldMatch: true,
    94  		},
    95  		{
    96  			name:        "test empty evals",
    97  			evaluations: []cel.EvaluationResult{},
    98  			shouldMatch: true,
    99  		},
   100  		{
   101  			name: "test single no match",
   102  			evaluations: []cel.EvaluationResult{
   103  				{
   104  					EvalResult: celtypes.False,
   105  					ExpressionAccessor: &MatchCondition{
   106  						Name: "test1",
   107  					},
   108  				},
   109  			},
   110  			shouldMatch:  false,
   111  			returnedName: "test1",
   112  		},
   113  		{
   114  			name: "test multiple no match",
   115  			evaluations: []cel.EvaluationResult{
   116  				{
   117  					EvalResult: celtypes.False,
   118  					ExpressionAccessor: &MatchCondition{
   119  						Name: "test1",
   120  					},
   121  				},
   122  				{
   123  					EvalResult: celtypes.False,
   124  					ExpressionAccessor: &MatchCondition{
   125  						Name: "test2",
   126  					},
   127  				},
   128  			},
   129  			shouldMatch:  false,
   130  			returnedName: "test1",
   131  		},
   132  		{
   133  			name: "test mixed with no match first",
   134  			evaluations: []cel.EvaluationResult{
   135  				{
   136  					EvalResult: celtypes.False,
   137  					ExpressionAccessor: &MatchCondition{
   138  						Name: "test1",
   139  					},
   140  				},
   141  				{
   142  					EvalResult: celtypes.True,
   143  					ExpressionAccessor: &MatchCondition{
   144  						Name: "test2",
   145  					},
   146  				},
   147  			},
   148  			shouldMatch:  false,
   149  			returnedName: "test1",
   150  		},
   151  		{
   152  			name: "test mixed with no match last",
   153  			evaluations: []cel.EvaluationResult{
   154  				{
   155  					EvalResult: celtypes.True,
   156  					ExpressionAccessor: &MatchCondition{
   157  						Name: "test2",
   158  					},
   159  				},
   160  				{
   161  					EvalResult: celtypes.False,
   162  					ExpressionAccessor: &MatchCondition{
   163  						Name: "test1",
   164  					},
   165  				},
   166  			},
   167  			shouldMatch:  false,
   168  			returnedName: "test1",
   169  		},
   170  		{
   171  			name: "test mixed with no match middle",
   172  			evaluations: []cel.EvaluationResult{
   173  				{
   174  					EvalResult: celtypes.True,
   175  					ExpressionAccessor: &MatchCondition{
   176  						Name: "test2",
   177  					},
   178  				},
   179  				{
   180  					EvalResult: celtypes.False,
   181  					ExpressionAccessor: &MatchCondition{
   182  						Name: "test1",
   183  					},
   184  				},
   185  				{
   186  					EvalResult: celtypes.True,
   187  					ExpressionAccessor: &MatchCondition{
   188  						Name: "test2",
   189  					},
   190  				},
   191  			},
   192  			shouldMatch:  false,
   193  			returnedName: "test1",
   194  		},
   195  		{
   196  			name: "test error, no fail policy",
   197  			evaluations: []cel.EvaluationResult{
   198  				{
   199  					EvalResult:         celtypes.True,
   200  					ExpressionAccessor: &MatchCondition{},
   201  				},
   202  			},
   203  			shouldMatch: true,
   204  			throwError:  true,
   205  			expectError: "test error",
   206  		},
   207  		{
   208  			name: "test error, fail policy fail",
   209  			evaluations: []cel.EvaluationResult{
   210  				{
   211  					EvalResult:         celtypes.True,
   212  					ExpressionAccessor: &MatchCondition{},
   213  				},
   214  			},
   215  			failPolicy:  &fail,
   216  			shouldMatch: true,
   217  			throwError:  true,
   218  			expectError: "test error",
   219  		},
   220  		{
   221  			name: "test error, fail policy ignore",
   222  			evaluations: []cel.EvaluationResult{
   223  				{
   224  					EvalResult:         celtypes.True,
   225  					ExpressionAccessor: &MatchCondition{},
   226  				},
   227  			},
   228  			failPolicy:  &ignore,
   229  			shouldMatch: false,
   230  			throwError:  true,
   231  		},
   232  		{
   233  			name: "test mix of true, errors and false",
   234  			evaluations: []cel.EvaluationResult{
   235  				{
   236  					EvalResult:         celtypes.True,
   237  					ExpressionAccessor: &MatchCondition{},
   238  				},
   239  				{
   240  					Error:              errors.New("test error"),
   241  					ExpressionAccessor: &MatchCondition{},
   242  				},
   243  				{
   244  					EvalResult:         celtypes.False,
   245  					ExpressionAccessor: &MatchCondition{},
   246  				},
   247  				{
   248  					EvalResult:         celtypes.True,
   249  					ExpressionAccessor: &MatchCondition{},
   250  				},
   251  				{
   252  					Error:              errors.New("test error"),
   253  					ExpressionAccessor: &MatchCondition{},
   254  				},
   255  			},
   256  			shouldMatch: false,
   257  			throwError:  false,
   258  		},
   259  		{
   260  			name: "test mix of true, errors and fail policy not set",
   261  			evaluations: []cel.EvaluationResult{
   262  				{
   263  					EvalResult:         celtypes.True,
   264  					ExpressionAccessor: &MatchCondition{},
   265  				},
   266  				{
   267  					Error:              errors.New("test error"),
   268  					ExpressionAccessor: &MatchCondition{},
   269  				},
   270  				{
   271  					EvalResult:         celtypes.True,
   272  					ExpressionAccessor: &MatchCondition{},
   273  				},
   274  				{
   275  					Error:              errors.New("test error"),
   276  					ExpressionAccessor: &MatchCondition{},
   277  				},
   278  			},
   279  			shouldMatch: false,
   280  			throwError:  false,
   281  			expectError: "test error",
   282  		},
   283  		{
   284  			name: "test mix of true, errors and fail policy fail",
   285  			evaluations: []cel.EvaluationResult{
   286  				{
   287  					EvalResult:         celtypes.True,
   288  					ExpressionAccessor: &MatchCondition{},
   289  				},
   290  				{
   291  					Error:              errors.New("test error"),
   292  					ExpressionAccessor: &MatchCondition{},
   293  				},
   294  				{
   295  					EvalResult:         celtypes.True,
   296  					ExpressionAccessor: &MatchCondition{},
   297  				},
   298  				{
   299  					Error:              errors.New("test error"),
   300  					ExpressionAccessor: &MatchCondition{},
   301  				},
   302  			},
   303  			failPolicy:  &fail,
   304  			shouldMatch: false,
   305  			throwError:  false,
   306  			expectError: "test error",
   307  		},
   308  		{
   309  			name: "test mix of true, errors and fail policy ignore",
   310  			evaluations: []cel.EvaluationResult{
   311  				{
   312  					EvalResult:         celtypes.True,
   313  					ExpressionAccessor: &MatchCondition{},
   314  				},
   315  				{
   316  					Error:              errors.New("test error"),
   317  					ExpressionAccessor: &MatchCondition{},
   318  				},
   319  				{
   320  					EvalResult:         celtypes.True,
   321  					ExpressionAccessor: &MatchCondition{},
   322  				},
   323  				{
   324  					Error:              errors.New("test error"),
   325  					ExpressionAccessor: &MatchCondition{},
   326  				},
   327  			},
   328  			failPolicy:  &ignore,
   329  			shouldMatch: false,
   330  			throwError:  false,
   331  		},
   332  	}
   333  
   334  	for _, tc := range cases {
   335  		t.Run(tc.name, func(t *testing.T) {
   336  			m := NewMatcher(&fakeCelFilter{
   337  				evaluations: tc.evaluations,
   338  				throwError:  tc.throwError,
   339  			}, tc.failPolicy, "webhook", "test", "testhook")
   340  			ctx := context.TODO()
   341  			matchResult := m.Match(ctx, fakeVersionedAttr, nil, nil)
   342  
   343  			if matchResult.Error != nil {
   344  				if len(tc.expectError) == 0 {
   345  					t.Fatal(matchResult.Error)
   346  				}
   347  				if !strings.Contains(matchResult.Error.Error(), tc.expectError) {
   348  					t.Fatalf("expected error containing %q, got %s", tc.expectError, matchResult.Error.Error())
   349  				}
   350  				return
   351  			} else if len(tc.expectError) > 0 {
   352  				t.Fatal("expected error but did not get one")
   353  			}
   354  			if len(tc.expectError) > 0 && matchResult.Error == nil {
   355  				t.Errorf("expected error thrown when filter errors")
   356  			}
   357  
   358  			require.Equal(t, tc.shouldMatch, matchResult.Matches)
   359  			require.Equal(t, tc.returnedName, matchResult.FailedConditionName)
   360  		})
   361  	}
   362  }