k8s.io/apiserver@v0.31.1/pkg/admission/plugin/webhook/predicates/rules/rules_test.go (about) 1 /* 2 Copyright 2017 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 rules 18 19 import ( 20 "fmt" 21 "testing" 22 23 adreg "k8s.io/api/admissionregistration/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/runtime" 26 "k8s.io/apimachinery/pkg/runtime/schema" 27 "k8s.io/apimachinery/pkg/util/sets" 28 "k8s.io/apiserver/pkg/admission" 29 ) 30 31 type ruleTest struct { 32 rule adreg.RuleWithOperations 33 match []admission.Attributes 34 noMatch []admission.Attributes 35 } 36 type tests map[string]ruleTest 37 38 func a(group, version, resource, subresource, name string, operation admission.Operation, operationOptions runtime.Object) admission.Attributes { 39 return admission.NewAttributesRecord( 40 nil, nil, 41 schema.GroupVersionKind{Group: group, Version: version, Kind: "k" + resource}, 42 "ns", name, 43 schema.GroupVersionResource{Group: group, Version: version, Resource: resource}, subresource, 44 operation, 45 operationOptions, 46 false, 47 nil, 48 ) 49 } 50 51 func namespacedAttributes(group, version, resource, subresource, name string, operation admission.Operation, operationOptions runtime.Object) admission.Attributes { 52 return admission.NewAttributesRecord( 53 nil, nil, 54 schema.GroupVersionKind{Group: group, Version: version, Kind: "k" + resource}, 55 "ns", name, 56 schema.GroupVersionResource{Group: group, Version: version, Resource: resource}, subresource, 57 operation, 58 operationOptions, 59 false, 60 nil, 61 ) 62 } 63 64 func clusterScopedAttributes(group, version, resource, subresource, name string, operation admission.Operation, operationOptions runtime.Object) admission.Attributes { 65 return admission.NewAttributesRecord( 66 nil, nil, 67 schema.GroupVersionKind{Group: group, Version: version, Kind: "k" + resource}, 68 "", name, 69 schema.GroupVersionResource{Group: group, Version: version, Resource: resource}, subresource, 70 operation, 71 operationOptions, 72 false, 73 nil, 74 ) 75 } 76 77 func attrList(a ...admission.Attributes) []admission.Attributes { 78 return a 79 } 80 81 func TestGroup(t *testing.T) { 82 table := tests{ 83 "wildcard": { 84 rule: adreg.RuleWithOperations{ 85 Rule: adreg.Rule{ 86 APIGroups: []string{"*"}, 87 }, 88 }, 89 match: attrList( 90 a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 91 ), 92 }, 93 "exact": { 94 rule: adreg.RuleWithOperations{ 95 Rule: adreg.Rule{ 96 APIGroups: []string{"g1", "g2"}, 97 }, 98 }, 99 match: attrList( 100 a("g1", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 101 a("g2", "v2", "r3", "", "name", admission.Create, &metav1.CreateOptions{}), 102 ), 103 noMatch: attrList( 104 a("g3", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 105 a("g4", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 106 ), 107 }, 108 } 109 110 for name, tt := range table { 111 for _, m := range tt.match { 112 r := Matcher{tt.rule, m} 113 if !r.group() { 114 t.Errorf("%v: expected match %#v", name, m) 115 } 116 } 117 for _, m := range tt.noMatch { 118 r := Matcher{tt.rule, m} 119 if r.group() { 120 t.Errorf("%v: expected no match %#v", name, m) 121 } 122 } 123 } 124 } 125 126 func TestVersion(t *testing.T) { 127 table := tests{ 128 "wildcard": { 129 rule: adreg.RuleWithOperations{ 130 Rule: adreg.Rule{ 131 APIVersions: []string{"*"}, 132 }, 133 }, 134 match: attrList( 135 a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 136 ), 137 }, 138 "exact": { 139 rule: adreg.RuleWithOperations{ 140 Rule: adreg.Rule{ 141 APIVersions: []string{"v1", "v2"}, 142 }, 143 }, 144 match: attrList( 145 a("g1", "v1", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 146 a("g2", "v2", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 147 ), 148 noMatch: attrList( 149 a("g1", "v3", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 150 a("g2", "v4", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 151 ), 152 }, 153 } 154 for name, tt := range table { 155 for _, m := range tt.match { 156 r := Matcher{tt.rule, m} 157 if !r.version() { 158 t.Errorf("%v: expected match %#v", name, m) 159 } 160 } 161 for _, m := range tt.noMatch { 162 r := Matcher{tt.rule, m} 163 if r.version() { 164 t.Errorf("%v: expected no match %#v", name, m) 165 } 166 } 167 } 168 } 169 170 func TestOperation(t *testing.T) { 171 table := tests{ 172 "wildcard": { 173 rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.OperationAll}}, 174 match: attrList( 175 a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 176 a("g", "v", "r", "", "name", admission.Update, &metav1.UpdateOptions{}), 177 a("g", "v", "r", "", "name", admission.Delete, &metav1.DeleteOptions{}), 178 a("g", "v", "r", "", "name", admission.Connect, nil), 179 ), 180 }, 181 "create": { 182 rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.Create}}, 183 match: attrList( 184 a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 185 ), 186 noMatch: attrList( 187 a("g", "v", "r", "", "name", admission.Update, &metav1.UpdateOptions{}), 188 a("g", "v", "r", "", "name", admission.Delete, &metav1.DeleteOptions{}), 189 a("g", "v", "r", "", "name", admission.Connect, nil), 190 ), 191 }, 192 "update": { 193 rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.Update}}, 194 match: attrList( 195 a("g", "v", "r", "", "name", admission.Update, &metav1.UpdateOptions{}), 196 ), 197 noMatch: attrList( 198 a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 199 a("g", "v", "r", "", "name", admission.Delete, &metav1.DeleteOptions{}), 200 a("g", "v", "r", "", "name", admission.Connect, nil), 201 ), 202 }, 203 "delete": { 204 rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.Delete}}, 205 match: attrList( 206 a("g", "v", "r", "", "name", admission.Delete, &metav1.DeleteOptions{}), 207 ), 208 noMatch: attrList( 209 a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 210 a("g", "v", "r", "", "name", admission.Update, &metav1.UpdateOptions{}), 211 a("g", "v", "r", "", "name", admission.Connect, nil), 212 ), 213 }, 214 "connect": { 215 rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.Connect}}, 216 match: attrList( 217 a("g", "v", "r", "", "name", admission.Connect, nil), 218 ), 219 noMatch: attrList( 220 a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 221 a("g", "v", "r", "", "name", admission.Update, &metav1.UpdateOptions{}), 222 a("g", "v", "r", "", "name", admission.Delete, &metav1.DeleteOptions{}), 223 ), 224 }, 225 "multiple": { 226 rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.Update, adreg.Delete}}, 227 match: attrList( 228 a("g", "v", "r", "", "name", admission.Update, &metav1.UpdateOptions{}), 229 a("g", "v", "r", "", "name", admission.Delete, &metav1.DeleteOptions{}), 230 ), 231 noMatch: attrList( 232 a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 233 a("g", "v", "r", "", "name", admission.Connect, nil), 234 ), 235 }, 236 } 237 for name, tt := range table { 238 for _, m := range tt.match { 239 r := Matcher{tt.rule, m} 240 if !r.operation() { 241 t.Errorf("%v: expected match %#v", name, m) 242 } 243 } 244 for _, m := range tt.noMatch { 245 r := Matcher{tt.rule, m} 246 if r.operation() { 247 t.Errorf("%v: expected no match %#v", name, m) 248 } 249 } 250 } 251 } 252 253 func TestResource(t *testing.T) { 254 table := tests{ 255 "no subresources": { 256 rule: adreg.RuleWithOperations{ 257 Rule: adreg.Rule{ 258 Resources: []string{"*"}, 259 }, 260 }, 261 match: attrList( 262 a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 263 a("2", "v", "r2", "", "name", admission.Create, &metav1.CreateOptions{}), 264 ), 265 noMatch: attrList( 266 a("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}), 267 a("2", "v", "r2", "proxy", "name", admission.Create, &metav1.CreateOptions{}), 268 ), 269 }, 270 "r & subresources": { 271 rule: adreg.RuleWithOperations{ 272 Rule: adreg.Rule{ 273 Resources: []string{"r/*"}, 274 }, 275 }, 276 match: attrList( 277 a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 278 a("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}), 279 ), 280 noMatch: attrList( 281 a("2", "v", "r2", "", "name", admission.Create, &metav1.CreateOptions{}), 282 a("2", "v", "r2", "proxy", "name", admission.Create, &metav1.CreateOptions{}), 283 ), 284 }, 285 "r & subresources or r2": { 286 rule: adreg.RuleWithOperations{ 287 Rule: adreg.Rule{ 288 Resources: []string{"r/*", "r2"}, 289 }, 290 }, 291 match: attrList( 292 a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 293 a("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}), 294 a("2", "v", "r2", "", "name", admission.Create, &metav1.CreateOptions{}), 295 ), 296 noMatch: attrList( 297 a("2", "v", "r2", "proxy", "name", admission.Create, &metav1.CreateOptions{}), 298 ), 299 }, 300 "proxy or exec": { 301 rule: adreg.RuleWithOperations{ 302 Rule: adreg.Rule{ 303 Resources: []string{"*/proxy", "*/exec"}, 304 }, 305 }, 306 match: attrList( 307 a("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}), 308 a("2", "v", "r2", "proxy", "name", admission.Create, &metav1.CreateOptions{}), 309 a("2", "v", "r3", "proxy", "name", admission.Create, &metav1.CreateOptions{}), 310 ), 311 noMatch: attrList( 312 a("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 313 a("2", "v", "r2", "", "name", admission.Create, &metav1.CreateOptions{}), 314 a("2", "v", "r4", "scale", "name", admission.Create, &metav1.CreateOptions{}), 315 ), 316 }, 317 } 318 for name, tt := range table { 319 for _, m := range tt.match { 320 r := Matcher{tt.rule, m} 321 if !r.resource() { 322 t.Errorf("%v: expected match %#v", name, m) 323 } 324 } 325 for _, m := range tt.noMatch { 326 r := Matcher{tt.rule, m} 327 if r.resource() { 328 t.Errorf("%v: expected no match %#v", name, m) 329 } 330 } 331 } 332 } 333 334 func TestScope(t *testing.T) { 335 cluster := adreg.ClusterScope 336 namespace := adreg.NamespacedScope 337 allscopes := adreg.AllScopes 338 table := tests{ 339 "cluster scope": { 340 rule: adreg.RuleWithOperations{ 341 Rule: adreg.Rule{ 342 Resources: []string{"*"}, 343 Scope: &cluster, 344 }, 345 }, 346 match: attrList( 347 clusterScopedAttributes("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 348 clusterScopedAttributes("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}), 349 clusterScopedAttributes("", "v1", "namespaces", "", "ns", admission.Create, &metav1.CreateOptions{}), 350 clusterScopedAttributes("", "v1", "namespaces", "finalize", "ns", admission.Create, &metav1.CreateOptions{}), 351 namespacedAttributes("", "v1", "namespaces", "", "ns", admission.Create, &metav1.CreateOptions{}), 352 namespacedAttributes("", "v1", "namespaces", "finalize", "ns", admission.Create, &metav1.CreateOptions{}), 353 ), 354 noMatch: attrList( 355 namespacedAttributes("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 356 namespacedAttributes("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}), 357 ), 358 }, 359 "namespace scope": { 360 rule: adreg.RuleWithOperations{ 361 Rule: adreg.Rule{ 362 Resources: []string{"*"}, 363 Scope: &namespace, 364 }, 365 }, 366 match: attrList( 367 namespacedAttributes("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 368 namespacedAttributes("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}), 369 ), 370 noMatch: attrList( 371 clusterScopedAttributes("", "v1", "namespaces", "", "ns", admission.Create, &metav1.CreateOptions{}), 372 clusterScopedAttributes("", "v1", "namespaces", "finalize", "ns", admission.Create, &metav1.CreateOptions{}), 373 namespacedAttributes("", "v1", "namespaces", "", "ns", admission.Create, &metav1.CreateOptions{}), 374 namespacedAttributes("", "v1", "namespaces", "finalize", "ns", admission.Create, &metav1.CreateOptions{}), 375 clusterScopedAttributes("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 376 clusterScopedAttributes("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}), 377 ), 378 }, 379 "all scopes": { 380 rule: adreg.RuleWithOperations{ 381 Rule: adreg.Rule{ 382 Resources: []string{"*"}, 383 Scope: &allscopes, 384 }, 385 }, 386 match: attrList( 387 namespacedAttributes("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 388 namespacedAttributes("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}), 389 clusterScopedAttributes("g", "v", "r", "", "name", admission.Create, &metav1.CreateOptions{}), 390 clusterScopedAttributes("g", "v", "r", "exec", "name", admission.Create, &metav1.CreateOptions{}), 391 clusterScopedAttributes("", "v1", "namespaces", "", "ns", admission.Create, &metav1.CreateOptions{}), 392 clusterScopedAttributes("", "v1", "namespaces", "finalize", "ns", admission.Create, &metav1.CreateOptions{}), 393 namespacedAttributes("", "v1", "namespaces", "", "ns", admission.Create, &metav1.CreateOptions{}), 394 namespacedAttributes("", "v1", "namespaces", "finalize", "ns", admission.Create, &metav1.CreateOptions{}), 395 ), 396 noMatch: attrList(), 397 }, 398 } 399 keys := sets.NewString() 400 for name := range table { 401 keys.Insert(name) 402 } 403 for _, name := range keys.List() { 404 tt := table[name] 405 for i, m := range tt.match { 406 t.Run(fmt.Sprintf("%s_match_%d", name, i), func(t *testing.T) { 407 r := Matcher{tt.rule, m} 408 if !r.scope() { 409 t.Errorf("%v: expected match %#v", name, m) 410 } 411 }) 412 } 413 for i, m := range tt.noMatch { 414 t.Run(fmt.Sprintf("%s_nomatch_%d", name, i), func(t *testing.T) { 415 r := Matcher{tt.rule, m} 416 if r.scope() { 417 t.Errorf("%v: expected no match %#v", name, m) 418 } 419 }) 420 } 421 } 422 }