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 }