k8s.io/apiserver@v0.31.1/pkg/admission/plugin/policy/matching/matching_test.go (about) 1 /* 2 Copyright 2022 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 matching 18 19 import ( 20 "fmt" 21 "strings" 22 "testing" 23 24 v1 "k8s.io/api/admissionregistration/v1" 25 corev1 "k8s.io/api/core/v1" 26 "k8s.io/apimachinery/pkg/api/errors" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/labels" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/apimachinery/pkg/runtime/schema" 31 "k8s.io/apiserver/pkg/admission" 32 "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace" 33 "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object" 34 "k8s.io/apiserver/pkg/apis/example" 35 ) 36 37 var _ MatchCriteria = &fakeCriteria{} 38 39 type fakeCriteria struct { 40 matchResources v1.MatchResources 41 } 42 43 func (fc *fakeCriteria) GetMatchResources() v1.MatchResources { 44 return fc.matchResources 45 } 46 47 func (fc *fakeCriteria) GetParsedNamespaceSelector() (labels.Selector, error) { 48 return metav1.LabelSelectorAsSelector(fc.matchResources.NamespaceSelector) 49 } 50 51 func (fc *fakeCriteria) GetParsedObjectSelector() (labels.Selector, error) { 52 return metav1.LabelSelectorAsSelector(fc.matchResources.ObjectSelector) 53 } 54 55 func gvr(group, version, resource string) schema.GroupVersionResource { 56 return schema.GroupVersionResource{Group: group, Version: version, Resource: resource} 57 } 58 59 func gvk(group, version, kind string) schema.GroupVersionKind { 60 return schema.GroupVersionKind{Group: group, Version: version, Kind: kind} 61 } 62 63 func TestMatcher(t *testing.T) { 64 a := &Matcher{namespaceMatcher: &namespace.Matcher{}, objectMatcher: &object.Matcher{}} 65 66 allScopes := v1.AllScopes 67 exactMatch := v1.Exact 68 equivalentMatch := v1.Equivalent 69 70 mapper := runtime.NewEquivalentResourceRegistryWithIdentity(func(resource schema.GroupResource) string { 71 if resource.Resource == "deployments" { 72 // co-locate deployments in all API groups 73 return "/deployments" 74 } 75 return "" 76 }) 77 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "", gvk("extensions", "v1beta1", "Deployment")) 78 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "", gvk("apps", "v1", "Deployment")) 79 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "", gvk("apps", "v1beta1", "Deployment")) 80 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "", gvk("apps", "v1alpha1", "Deployment")) 81 82 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "scale", gvk("extensions", "v1beta1", "Scale")) 83 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "scale", gvk("autoscaling", "v1", "Scale")) 84 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "scale", gvk("apps", "v1beta1", "Scale")) 85 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "scale", gvk("apps", "v1alpha1", "Scale")) 86 87 // register invalid kinds to trigger an error 88 mapper.RegisterKindFor(gvr("example.com", "v1", "widgets"), "", gvk("", "", "")) 89 mapper.RegisterKindFor(gvr("example.com", "v2", "widgets"), "", gvk("", "", "")) 90 91 interfaces := &admission.RuntimeObjectInterfaces{EquivalentResourceMapper: mapper} 92 93 // TODO write test cases for name matching and exclude matching 94 testcases := []struct { 95 name string 96 97 criteria *v1.MatchResources 98 attrs admission.Attributes 99 100 expectMatches bool 101 expectMatchKind schema.GroupVersionKind 102 expectMatchResource schema.GroupVersionResource 103 expectErr string 104 }{ 105 { 106 name: "no rules (just write)", 107 criteria: &v1.MatchResources{NamespaceSelector: &metav1.LabelSelector{}, ResourceRules: []v1.NamedRuleWithOperations{}}, 108 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 109 expectMatches: false, 110 }, 111 { 112 name: "wildcard rule, match as requested", 113 criteria: &v1.MatchResources{ 114 NamespaceSelector: &metav1.LabelSelector{}, 115 ObjectSelector: &metav1.LabelSelector{}, 116 ResourceRules: []v1.NamedRuleWithOperations{{ 117 RuleWithOperations: v1.RuleWithOperations{ 118 Operations: []v1.OperationType{"*"}, 119 Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes}, 120 }, 121 }}}, 122 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 123 expectMatches: true, 124 expectMatchKind: gvk("apps", "v1", "Deployment"), 125 }, 126 { 127 name: "specific rules, prefer exact match", 128 criteria: &v1.MatchResources{ 129 NamespaceSelector: &metav1.LabelSelector{}, 130 ObjectSelector: &metav1.LabelSelector{}, 131 ResourceRules: []v1.NamedRuleWithOperations{{ 132 RuleWithOperations: v1.RuleWithOperations{ 133 Operations: []v1.OperationType{"*"}, 134 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 135 }, 136 }, { 137 RuleWithOperations: v1.RuleWithOperations{ 138 Operations: []v1.OperationType{"*"}, 139 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 140 }, 141 }, { 142 RuleWithOperations: v1.RuleWithOperations{ 143 Operations: []v1.OperationType{"*"}, 144 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 145 }, 146 }}}, 147 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 148 expectMatches: true, 149 expectMatchKind: gvk("apps", "v1", "Deployment"), 150 }, 151 { 152 name: "specific rules, match miss", 153 criteria: &v1.MatchResources{ 154 NamespaceSelector: &metav1.LabelSelector{}, 155 ObjectSelector: &metav1.LabelSelector{}, 156 ResourceRules: []v1.NamedRuleWithOperations{{ 157 RuleWithOperations: v1.RuleWithOperations{ 158 Operations: []v1.OperationType{"*"}, 159 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 160 }, 161 }, { 162 RuleWithOperations: v1.RuleWithOperations{ 163 Operations: []v1.OperationType{"*"}, 164 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 165 }, 166 }}}, 167 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 168 expectMatches: false, 169 }, 170 { 171 name: "specific rules, exact match miss", 172 criteria: &v1.MatchResources{ 173 MatchPolicy: &exactMatch, 174 NamespaceSelector: &metav1.LabelSelector{}, 175 ObjectSelector: &metav1.LabelSelector{}, 176 ResourceRules: []v1.NamedRuleWithOperations{{ 177 RuleWithOperations: v1.RuleWithOperations{ 178 Operations: []v1.OperationType{"*"}, 179 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 180 }, 181 }, { 182 RuleWithOperations: v1.RuleWithOperations{ 183 Operations: []v1.OperationType{"*"}, 184 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 185 }, 186 }}}, 187 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 188 expectMatches: false, 189 }, 190 { 191 name: "specific rules, equivalent match, prefer extensions", 192 criteria: &v1.MatchResources{ 193 MatchPolicy: &equivalentMatch, 194 NamespaceSelector: &metav1.LabelSelector{}, 195 ObjectSelector: &metav1.LabelSelector{}, 196 ResourceRules: []v1.NamedRuleWithOperations{{ 197 RuleWithOperations: v1.RuleWithOperations{ 198 Operations: []v1.OperationType{"*"}, 199 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 200 }, 201 }, { 202 RuleWithOperations: v1.RuleWithOperations{ 203 Operations: []v1.OperationType{"*"}, 204 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 205 }, 206 }}}, 207 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 208 expectMatches: true, 209 expectMatchResource: gvr("extensions", "v1beta1", "deployments"), 210 expectMatchKind: gvk("extensions", "v1beta1", "Deployment"), 211 }, 212 { 213 name: "specific rules, equivalent match, prefer apps", 214 criteria: &v1.MatchResources{ 215 MatchPolicy: &equivalentMatch, 216 NamespaceSelector: &metav1.LabelSelector{}, 217 ObjectSelector: &metav1.LabelSelector{}, 218 ResourceRules: []v1.NamedRuleWithOperations{{ 219 RuleWithOperations: v1.RuleWithOperations{ 220 Operations: []v1.OperationType{"*"}, 221 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 222 }, 223 }, { 224 RuleWithOperations: v1.RuleWithOperations{ 225 Operations: []v1.OperationType{"*"}, 226 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 227 }, 228 }}}, 229 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 230 expectMatches: true, 231 expectMatchResource: gvr("apps", "v1beta1", "deployments"), 232 expectMatchKind: gvk("apps", "v1beta1", "Deployment"), 233 }, 234 235 { 236 name: "specific rules, subresource prefer exact match", 237 criteria: &v1.MatchResources{ 238 NamespaceSelector: &metav1.LabelSelector{}, 239 ObjectSelector: &metav1.LabelSelector{}, 240 ResourceRules: []v1.NamedRuleWithOperations{{ 241 RuleWithOperations: v1.RuleWithOperations{ 242 Operations: []v1.OperationType{"*"}, 243 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 244 }, 245 }, { 246 RuleWithOperations: v1.RuleWithOperations{ 247 Operations: []v1.OperationType{"*"}, 248 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 249 }, 250 }, { 251 RuleWithOperations: v1.RuleWithOperations{ 252 Operations: []v1.OperationType{"*"}, 253 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 254 }, 255 }}}, 256 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil), 257 expectMatches: true, 258 expectMatchKind: gvk("autoscaling", "v1", "Scale"), 259 }, 260 { 261 name: "specific rules, subresource match miss", 262 criteria: &v1.MatchResources{ 263 NamespaceSelector: &metav1.LabelSelector{}, 264 ObjectSelector: &metav1.LabelSelector{}, 265 ResourceRules: []v1.NamedRuleWithOperations{{ 266 RuleWithOperations: v1.RuleWithOperations{ 267 Operations: []v1.OperationType{"*"}, 268 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 269 }, 270 }, { 271 RuleWithOperations: v1.RuleWithOperations{ 272 Operations: []v1.OperationType{"*"}, 273 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 274 }, 275 }}}, 276 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil), 277 expectMatches: false, 278 }, 279 { 280 name: "specific rules, subresource exact match miss", 281 criteria: &v1.MatchResources{ 282 MatchPolicy: &exactMatch, 283 NamespaceSelector: &metav1.LabelSelector{}, 284 ObjectSelector: &metav1.LabelSelector{}, 285 ResourceRules: []v1.NamedRuleWithOperations{{ 286 RuleWithOperations: v1.RuleWithOperations{ 287 Operations: []v1.OperationType{"*"}, 288 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 289 }, 290 }, { 291 RuleWithOperations: v1.RuleWithOperations{ 292 Operations: []v1.OperationType{"*"}, 293 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 294 }, 295 }}}, 296 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil), 297 expectMatches: false, 298 }, 299 { 300 name: "specific rules, subresource equivalent match, prefer extensions", 301 criteria: &v1.MatchResources{ 302 MatchPolicy: &equivalentMatch, 303 NamespaceSelector: &metav1.LabelSelector{}, 304 ObjectSelector: &metav1.LabelSelector{}, 305 ResourceRules: []v1.NamedRuleWithOperations{{ 306 RuleWithOperations: v1.RuleWithOperations{ 307 Operations: []v1.OperationType{"*"}, 308 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 309 }, 310 }, { 311 RuleWithOperations: v1.RuleWithOperations{ 312 Operations: []v1.OperationType{"*"}, 313 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 314 }, 315 }}}, 316 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil), 317 expectMatches: true, 318 expectMatchResource: gvr("extensions", "v1beta1", "deployments"), 319 expectMatchKind: gvk("extensions", "v1beta1", "Scale"), 320 }, 321 { 322 name: "specific rules, subresource equivalent match, prefer apps", 323 criteria: &v1.MatchResources{ 324 MatchPolicy: &equivalentMatch, 325 NamespaceSelector: &metav1.LabelSelector{}, 326 ObjectSelector: &metav1.LabelSelector{}, 327 ResourceRules: []v1.NamedRuleWithOperations{{ 328 RuleWithOperations: v1.RuleWithOperations{ 329 Operations: []v1.OperationType{"*"}, 330 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 331 }, 332 }, { 333 RuleWithOperations: v1.RuleWithOperations{ 334 Operations: []v1.OperationType{"*"}, 335 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 336 }, 337 }}}, 338 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil), 339 expectMatches: true, 340 expectMatchResource: gvr("apps", "v1beta1", "deployments"), 341 expectMatchKind: gvk("apps", "v1beta1", "Scale"), 342 }, 343 { 344 name: "specific rules, prefer exact match and name match", 345 criteria: &v1.MatchResources{ 346 NamespaceSelector: &metav1.LabelSelector{}, 347 ObjectSelector: &metav1.LabelSelector{}, 348 ResourceRules: []v1.NamedRuleWithOperations{{ 349 ResourceNames: []string{"name"}, 350 RuleWithOperations: v1.RuleWithOperations{ 351 Operations: []v1.OperationType{"*"}, 352 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 353 }, 354 }}}, 355 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 356 expectMatches: true, 357 expectMatchKind: gvk("autoscaling", "v1", "Scale"), 358 }, 359 { 360 name: "specific rules, prefer exact match and name match miss", 361 criteria: &v1.MatchResources{ 362 NamespaceSelector: &metav1.LabelSelector{}, 363 ObjectSelector: &metav1.LabelSelector{}, 364 ResourceRules: []v1.NamedRuleWithOperations{{ 365 ResourceNames: []string{"wrong-name"}, 366 RuleWithOperations: v1.RuleWithOperations{ 367 Operations: []v1.OperationType{"*"}, 368 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 369 }, 370 }}}, 371 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 372 expectMatches: false, 373 }, 374 { 375 name: "specific rules, subresource equivalent match, prefer extensions and name match", 376 criteria: &v1.MatchResources{ 377 MatchPolicy: &equivalentMatch, 378 NamespaceSelector: &metav1.LabelSelector{}, 379 ObjectSelector: &metav1.LabelSelector{}, 380 ResourceRules: []v1.NamedRuleWithOperations{{ 381 ResourceNames: []string{"name"}, 382 RuleWithOperations: v1.RuleWithOperations{ 383 Operations: []v1.OperationType{"*"}, 384 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 385 }, 386 }}}, 387 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("extensions", "v1beta1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil), 388 expectMatches: true, 389 expectMatchResource: gvr("apps", "v1", "deployments"), 390 expectMatchKind: gvk("autoscaling", "v1", "Scale"), 391 }, 392 { 393 name: "specific rules, subresource equivalent match, prefer extensions and name match miss", 394 criteria: &v1.MatchResources{ 395 MatchPolicy: &equivalentMatch, 396 NamespaceSelector: &metav1.LabelSelector{}, 397 ObjectSelector: &metav1.LabelSelector{}, 398 ResourceRules: []v1.NamedRuleWithOperations{{ 399 ResourceNames: []string{"wrong-name"}, 400 RuleWithOperations: v1.RuleWithOperations{ 401 Operations: []v1.OperationType{"*"}, 402 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 403 }, 404 }}}, 405 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("extensions", "v1beta1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil), 406 expectMatches: false, 407 }, 408 { 409 name: "exclude resource match on miss", 410 criteria: &v1.MatchResources{ 411 NamespaceSelector: &metav1.LabelSelector{}, 412 ObjectSelector: &metav1.LabelSelector{}, 413 ResourceRules: []v1.NamedRuleWithOperations{{ 414 RuleWithOperations: v1.RuleWithOperations{ 415 Operations: []v1.OperationType{"*"}, 416 Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes}, 417 }, 418 }}, 419 ExcludeResourceRules: []v1.NamedRuleWithOperations{{ 420 RuleWithOperations: v1.RuleWithOperations{ 421 Operations: []v1.OperationType{"*"}, 422 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 423 }, 424 }}, 425 }, 426 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 427 expectMatches: true, 428 expectMatchKind: gvk("autoscaling", "v1", "Scale"), 429 }, 430 { 431 name: "exclude resource miss on match", 432 criteria: &v1.MatchResources{ 433 NamespaceSelector: &metav1.LabelSelector{}, 434 ObjectSelector: &metav1.LabelSelector{}, 435 ResourceRules: []v1.NamedRuleWithOperations{{ 436 RuleWithOperations: v1.RuleWithOperations{ 437 Operations: []v1.OperationType{"*"}, 438 Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes}, 439 }, 440 }}, 441 ExcludeResourceRules: []v1.NamedRuleWithOperations{{ 442 RuleWithOperations: v1.RuleWithOperations{ 443 Operations: []v1.OperationType{"*"}, 444 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 445 }, 446 }}, 447 }, 448 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("extensions", "v1beta1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 449 expectMatches: false, 450 }, 451 { 452 name: "treat empty ResourceRules as match", 453 criteria: &v1.MatchResources{ 454 NamespaceSelector: &metav1.LabelSelector{}, 455 ObjectSelector: &metav1.LabelSelector{}, 456 ExcludeResourceRules: []v1.NamedRuleWithOperations{{ 457 RuleWithOperations: v1.RuleWithOperations{ 458 Operations: []v1.OperationType{"*"}, 459 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 460 }, 461 }}, 462 }, 463 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 464 expectMatches: true, 465 }, 466 { 467 name: "treat non-empty ResourceRules as no match", 468 criteria: &v1.MatchResources{ 469 NamespaceSelector: &metav1.LabelSelector{}, 470 ObjectSelector: &metav1.LabelSelector{}, 471 ResourceRules: []v1.NamedRuleWithOperations{{}}, 472 }, 473 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 474 expectMatches: false, 475 }, 476 { 477 name: "erroring namespace selector on otherwise non-matching rule doesn't error", 478 criteria: &v1.MatchResources{ 479 NamespaceSelector: &metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "key ", Operator: "In", Values: []string{"bad value"}}}}, 480 ObjectSelector: &metav1.LabelSelector{}, 481 ResourceRules: []v1.NamedRuleWithOperations{{ 482 RuleWithOperations: v1.RuleWithOperations{ 483 Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"deployments"}}, 484 Operations: []v1.OperationType{"*"}, 485 }, 486 }}, 487 }, 488 attrs: admission.NewAttributesRecord(&example.Pod{}, nil, gvk("example.apiserver.k8s.io", "v1", "Pod"), "ns", "name", gvr("example.apiserver.k8s.io", "v1", "pods"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 489 expectMatches: false, 490 expectErr: "", 491 }, 492 { 493 name: "erroring namespace selector on otherwise matching rule errors", 494 criteria: &v1.MatchResources{ 495 NamespaceSelector: &metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "key", Operator: "In", Values: []string{"bad value"}}}}, 496 ObjectSelector: &metav1.LabelSelector{}, 497 ResourceRules: []v1.NamedRuleWithOperations{{ 498 RuleWithOperations: v1.RuleWithOperations{ 499 Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"pods"}}, 500 Operations: []v1.OperationType{"*"}, 501 }, 502 }}, 503 }, 504 attrs: admission.NewAttributesRecord(&example.Pod{}, nil, gvk("example.apiserver.k8s.io", "v1", "Pod"), "ns", "name", gvr("example.apiserver.k8s.io", "v1", "pods"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 505 expectMatches: false, 506 expectErr: "bad value", 507 }, 508 { 509 name: "erroring object selector on otherwise non-matching rule doesn't error", 510 criteria: &v1.MatchResources{ 511 NamespaceSelector: &metav1.LabelSelector{}, 512 ObjectSelector: &metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "key", Operator: "In", Values: []string{"bad value"}}}}, 513 ResourceRules: []v1.NamedRuleWithOperations{{ 514 RuleWithOperations: v1.RuleWithOperations{ 515 Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"deployments"}}, 516 Operations: []v1.OperationType{"*"}, 517 }, 518 }}, 519 }, 520 attrs: admission.NewAttributesRecord(&example.Pod{}, nil, gvk("example.apiserver.k8s.io", "v1", "Pod"), "ns", "name", gvr("example.apiserver.k8s.io", "v1", "pods"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 521 expectMatches: false, 522 expectErr: "", 523 }, 524 { 525 name: "erroring object selector on otherwise matching rule errors", 526 criteria: &v1.MatchResources{ 527 NamespaceSelector: &metav1.LabelSelector{}, 528 ObjectSelector: &metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "key", Operator: "In", Values: []string{"bad value"}}}}, 529 ResourceRules: []v1.NamedRuleWithOperations{{ 530 RuleWithOperations: v1.RuleWithOperations{ 531 Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"pods"}}, 532 Operations: []v1.OperationType{"*"}, 533 }, 534 }}, 535 }, 536 attrs: admission.NewAttributesRecord(&example.Pod{}, nil, gvk("example.apiserver.k8s.io", "v1", "Pod"), "ns", "name", gvr("example.apiserver.k8s.io", "v1", "pods"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 537 expectMatches: false, 538 expectErr: "bad value", 539 }, 540 } 541 542 for _, testcase := range testcases { 543 t.Run(testcase.name, func(t *testing.T) { 544 matches, matchResource, matchKind, err := a.Matches(testcase.attrs, interfaces, &fakeCriteria{matchResources: *testcase.criteria}) 545 if err != nil { 546 if len(testcase.expectErr) == 0 { 547 t.Fatal(err) 548 } 549 if !strings.Contains(err.Error(), testcase.expectErr) { 550 t.Fatalf("expected error containing %q, got %s", testcase.expectErr, err.Error()) 551 } 552 return 553 } else if len(testcase.expectErr) > 0 { 554 t.Fatalf("expected error %q, got no error", testcase.expectErr) 555 } 556 var emptyGVK schema.GroupVersionKind 557 if testcase.expectMatchKind != emptyGVK { 558 if testcase.expectMatchKind != matchKind { 559 t.Fatalf("expected matchKind %v, got %v", testcase.expectMatchKind, matchKind) 560 } 561 } 562 563 if matches != testcase.expectMatches { 564 t.Fatalf("expected matches = %v; got %v", testcase.expectMatches, matches) 565 } 566 567 expectResource := testcase.expectMatchResource 568 if !expectResource.Empty() && !matches { 569 t.Fatalf("expectResource is non-empty, but did not match") 570 } else if expectResource.Empty() { 571 // Test for exact match by default. Tests that expect an equivalent 572 // resource to match should explicitly state so by supplying 573 // expectMatchResource 574 expectResource = testcase.attrs.GetResource() 575 } 576 577 if matches { 578 if matchResource != expectResource { 579 t.Fatalf("expected matchResource %v, got %v", expectResource, matchResource) 580 } 581 } 582 }) 583 } 584 } 585 586 type fakeNamespaceLister struct { 587 namespaces map[string]*corev1.Namespace 588 } 589 590 func (f fakeNamespaceLister) List(selector labels.Selector) (ret []*corev1.Namespace, err error) { 591 return nil, nil 592 } 593 func (f fakeNamespaceLister) Get(name string) (*corev1.Namespace, error) { 594 ns, ok := f.namespaces[name] 595 if ok { 596 return ns, nil 597 } 598 return nil, errors.NewNotFound(corev1.Resource("namespaces"), name) 599 } 600 601 func BenchmarkMatcher(b *testing.B) { 602 allScopes := v1.AllScopes 603 equivalentMatch := v1.Equivalent 604 605 namespace1Labels := map[string]string{"ns": "ns1"} 606 namespace1 := corev1.Namespace{ 607 ObjectMeta: metav1.ObjectMeta{ 608 Name: "ns1", 609 Labels: namespace1Labels, 610 }, 611 } 612 namespaceLister := fakeNamespaceLister{map[string]*corev1.Namespace{"ns": &namespace1}} 613 614 mapper := runtime.NewEquivalentResourceRegistryWithIdentity(func(resource schema.GroupResource) string { 615 if resource.Resource == "deployments" { 616 // co-locate deployments in all API groups 617 return "/deployments" 618 } 619 return "" 620 }) 621 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "", gvk("extensions", "v1beta1", "Deployment")) 622 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "", gvk("apps", "v1", "Deployment")) 623 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "", gvk("apps", "v1beta1", "Deployment")) 624 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "", gvk("apps", "v1alpha1", "Deployment")) 625 626 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "scale", gvk("extensions", "v1beta1", "Scale")) 627 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "scale", gvk("autoscaling", "v1", "Scale")) 628 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "scale", gvk("apps", "v1beta1", "Scale")) 629 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "scale", gvk("apps", "v1alpha1", "Scale")) 630 631 mapper.RegisterKindFor(gvr("apps", "v1", "statefulset"), "", gvk("apps", "v1", "StatefulSet")) 632 mapper.RegisterKindFor(gvr("apps", "v1beta1", "statefulset"), "", gvk("apps", "v1beta1", "StatefulSet")) 633 mapper.RegisterKindFor(gvr("apps", "v1beta2", "statefulset"), "", gvk("apps", "v1beta2", "StatefulSet")) 634 635 mapper.RegisterKindFor(gvr("apps", "v1", "statefulset"), "scale", gvk("apps", "v1", "Scale")) 636 mapper.RegisterKindFor(gvr("apps", "v1beta1", "statefulset"), "scale", gvk("apps", "v1beta1", "Scale")) 637 mapper.RegisterKindFor(gvr("apps", "v1alpha2", "statefulset"), "scale", gvk("apps", "v1beta2", "Scale")) 638 639 nsSelector := make(map[string]string) 640 for i := 0; i < 100; i++ { 641 nsSelector[fmt.Sprintf("key-%d", i)] = fmt.Sprintf("val-%d", i) 642 } 643 644 mr := v1.MatchResources{ 645 MatchPolicy: &equivalentMatch, 646 NamespaceSelector: &metav1.LabelSelector{MatchLabels: nsSelector}, 647 ObjectSelector: &metav1.LabelSelector{}, 648 ResourceRules: []v1.NamedRuleWithOperations{ 649 { 650 RuleWithOperations: v1.RuleWithOperations{ 651 Operations: []v1.OperationType{"*"}, 652 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 653 }, 654 }, 655 { 656 RuleWithOperations: v1.RuleWithOperations{ 657 Operations: []v1.OperationType{"*"}, 658 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 659 }, 660 }, 661 }, 662 } 663 664 criteria := &fakeCriteria{matchResources: mr} 665 attrs := admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil) 666 interfaces := &admission.RuntimeObjectInterfaces{EquivalentResourceMapper: mapper} 667 matcher := &Matcher{namespaceMatcher: &namespace.Matcher{NamespaceLister: namespaceLister}, objectMatcher: &object.Matcher{}} 668 669 for i := 0; i < b.N; i++ { 670 matcher.Matches(attrs, interfaces, criteria) 671 } 672 } 673 674 func BenchmarkShouldCallHookWithComplexRule(b *testing.B) { 675 allScopes := v1.AllScopes 676 equivalentMatch := v1.Equivalent 677 678 namespace1Labels := map[string]string{"ns": "ns1"} 679 namespace1 := corev1.Namespace{ 680 ObjectMeta: metav1.ObjectMeta{ 681 Name: "ns1", 682 Labels: namespace1Labels, 683 }, 684 } 685 namespaceLister := fakeNamespaceLister{map[string]*corev1.Namespace{"ns": &namespace1}} 686 687 mapper := runtime.NewEquivalentResourceRegistryWithIdentity(func(resource schema.GroupResource) string { 688 if resource.Resource == "deployments" { 689 // co-locate deployments in all API groups 690 return "/deployments" 691 } 692 return "" 693 }) 694 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "", gvk("extensions", "v1beta1", "Deployment")) 695 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "", gvk("apps", "v1", "Deployment")) 696 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "", gvk("apps", "v1beta1", "Deployment")) 697 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "", gvk("apps", "v1alpha1", "Deployment")) 698 699 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "scale", gvk("extensions", "v1beta1", "Scale")) 700 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "scale", gvk("autoscaling", "v1", "Scale")) 701 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "scale", gvk("apps", "v1beta1", "Scale")) 702 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "scale", gvk("apps", "v1alpha1", "Scale")) 703 704 mapper.RegisterKindFor(gvr("apps", "v1", "statefulset"), "", gvk("apps", "v1", "StatefulSet")) 705 mapper.RegisterKindFor(gvr("apps", "v1beta1", "statefulset"), "", gvk("apps", "v1beta1", "StatefulSet")) 706 mapper.RegisterKindFor(gvr("apps", "v1beta2", "statefulset"), "", gvk("apps", "v1beta2", "StatefulSet")) 707 708 mapper.RegisterKindFor(gvr("apps", "v1", "statefulset"), "scale", gvk("apps", "v1", "Scale")) 709 mapper.RegisterKindFor(gvr("apps", "v1beta1", "statefulset"), "scale", gvk("apps", "v1beta1", "Scale")) 710 mapper.RegisterKindFor(gvr("apps", "v1alpha2", "statefulset"), "scale", gvk("apps", "v1beta2", "Scale")) 711 712 mr := v1.MatchResources{ 713 MatchPolicy: &equivalentMatch, 714 NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, 715 ObjectSelector: &metav1.LabelSelector{}, 716 ResourceRules: []v1.NamedRuleWithOperations{}, 717 } 718 719 for i := 0; i < 100; i++ { 720 rule := v1.NamedRuleWithOperations{ 721 RuleWithOperations: v1.RuleWithOperations{ 722 Operations: []v1.OperationType{"*"}, 723 Rule: v1.Rule{ 724 APIGroups: []string{fmt.Sprintf("app-%d", i)}, 725 APIVersions: []string{fmt.Sprintf("v%d", i)}, 726 Resources: []string{fmt.Sprintf("resource%d", i), fmt.Sprintf("resource%d/scale", i)}, 727 Scope: &allScopes, 728 }, 729 }, 730 } 731 mr.ResourceRules = append(mr.ResourceRules, rule) 732 } 733 734 criteria := &fakeCriteria{matchResources: mr} 735 attrs := admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil) 736 interfaces := &admission.RuntimeObjectInterfaces{EquivalentResourceMapper: mapper} 737 matcher := &Matcher{namespaceMatcher: &namespace.Matcher{NamespaceLister: namespaceLister}, objectMatcher: &object.Matcher{}} 738 739 for i := 0; i < b.N; i++ { 740 matcher.Matches(attrs, interfaces, criteria) 741 } 742 } 743 744 func BenchmarkShouldCallHookWithComplexSelectorAndRule(b *testing.B) { 745 allScopes := v1.AllScopes 746 equivalentMatch := v1.Equivalent 747 748 namespace1Labels := map[string]string{"ns": "ns1"} 749 namespace1 := corev1.Namespace{ 750 ObjectMeta: metav1.ObjectMeta{ 751 Name: "ns1", 752 Labels: namespace1Labels, 753 }, 754 } 755 namespaceLister := fakeNamespaceLister{map[string]*corev1.Namespace{"ns": &namespace1}} 756 757 mapper := runtime.NewEquivalentResourceRegistryWithIdentity(func(resource schema.GroupResource) string { 758 if resource.Resource == "deployments" { 759 // co-locate deployments in all API groups 760 return "/deployments" 761 } 762 return "" 763 }) 764 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "", gvk("extensions", "v1beta1", "Deployment")) 765 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "", gvk("apps", "v1", "Deployment")) 766 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "", gvk("apps", "v1beta1", "Deployment")) 767 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "", gvk("apps", "v1alpha1", "Deployment")) 768 769 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "scale", gvk("extensions", "v1beta1", "Scale")) 770 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "scale", gvk("autoscaling", "v1", "Scale")) 771 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "scale", gvk("apps", "v1beta1", "Scale")) 772 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "scale", gvk("apps", "v1alpha1", "Scale")) 773 774 mapper.RegisterKindFor(gvr("apps", "v1", "statefulset"), "", gvk("apps", "v1", "StatefulSet")) 775 mapper.RegisterKindFor(gvr("apps", "v1beta1", "statefulset"), "", gvk("apps", "v1beta1", "StatefulSet")) 776 mapper.RegisterKindFor(gvr("apps", "v1beta2", "statefulset"), "", gvk("apps", "v1beta2", "StatefulSet")) 777 778 mapper.RegisterKindFor(gvr("apps", "v1", "statefulset"), "scale", gvk("apps", "v1", "Scale")) 779 mapper.RegisterKindFor(gvr("apps", "v1beta1", "statefulset"), "scale", gvk("apps", "v1beta1", "Scale")) 780 mapper.RegisterKindFor(gvr("apps", "v1alpha2", "statefulset"), "scale", gvk("apps", "v1beta2", "Scale")) 781 782 nsSelector := make(map[string]string) 783 for i := 0; i < 100; i++ { 784 nsSelector[fmt.Sprintf("key-%d", i)] = fmt.Sprintf("val-%d", i) 785 } 786 787 mr := v1.MatchResources{ 788 MatchPolicy: &equivalentMatch, 789 NamespaceSelector: &metav1.LabelSelector{MatchLabels: nsSelector}, 790 ObjectSelector: &metav1.LabelSelector{}, 791 ResourceRules: []v1.NamedRuleWithOperations{}, 792 } 793 794 for i := 0; i < 100; i++ { 795 rule := v1.NamedRuleWithOperations{ 796 RuleWithOperations: v1.RuleWithOperations{ 797 Operations: []v1.OperationType{"*"}, 798 Rule: v1.Rule{ 799 APIGroups: []string{fmt.Sprintf("app-%d", i)}, 800 APIVersions: []string{fmt.Sprintf("v%d", i)}, 801 Resources: []string{fmt.Sprintf("resource%d", i), fmt.Sprintf("resource%d/scale", i)}, 802 Scope: &allScopes, 803 }, 804 }, 805 } 806 mr.ResourceRules = append(mr.ResourceRules, rule) 807 } 808 809 criteria := &fakeCriteria{matchResources: mr} 810 attrs := admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil) 811 interfaces := &admission.RuntimeObjectInterfaces{EquivalentResourceMapper: mapper} 812 matcher := &Matcher{namespaceMatcher: &namespace.Matcher{NamespaceLister: namespaceLister}, objectMatcher: &object.Matcher{}} 813 814 for i := 0; i < b.N; i++ { 815 matcher.Matches(attrs, interfaces, criteria) 816 } 817 }