knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/webhook/resourcesemantics/defaulting/table_test.go (about) 1 /* 2 Copyright 2019 The Knative 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 defaulting 18 19 import ( 20 "context" 21 "testing" 22 "time" 23 24 kubeclient "knative.dev/pkg/client/injection/kube/client/fake" 25 _ "knative.dev/pkg/injection/clients/namespacedkube/informers/core/v1/secret/fake" 26 pkgreconciler "knative.dev/pkg/reconciler" 27 28 admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 29 corev1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/runtime/schema" 33 "k8s.io/apimachinery/pkg/types" 34 "k8s.io/apimachinery/pkg/util/wait" 35 clientgotesting "k8s.io/client-go/testing" 36 37 "knative.dev/pkg/configmap" 38 "knative.dev/pkg/controller" 39 "knative.dev/pkg/ptr" 40 "knative.dev/pkg/system" 41 "knative.dev/pkg/webhook" 42 certresources "knative.dev/pkg/webhook/certificates/resources" 43 "knative.dev/pkg/webhook/resourcesemantics" 44 45 . "knative.dev/pkg/reconciler/testing" 46 . "knative.dev/pkg/webhook/testing" 47 ) 48 49 func TestReconcile(t *testing.T) { 50 name, path := "foo.bar.baz", "/blah" 51 secretName := "webhook-secret" 52 53 secret := &corev1.Secret{ 54 ObjectMeta: metav1.ObjectMeta{ 55 Name: secretName, 56 Namespace: system.Namespace(), 57 }, 58 Data: map[string][]byte{ 59 certresources.ServerKey: []byte("present"), 60 certresources.ServerCert: []byte("present"), 61 certresources.CACert: []byte("present"), 62 }, 63 } 64 ns := &corev1.Namespace{ 65 ObjectMeta: metav1.ObjectMeta{ 66 Name: system.Namespace(), 67 }, 68 } 69 nsRef := *metav1.NewControllerRef(ns, corev1.SchemeGroupVersion.WithKind("Namespace")) 70 nsRef.Controller = ptr.Bool(false) 71 expectedOwnerReferences := []metav1.OwnerReference{nsRef} 72 73 // This is the namespace selector setup 74 namespaceSelector := &metav1.LabelSelector{ 75 MatchExpressions: []metav1.LabelSelectorRequirement{{ 76 Key: "webhooks.knative.dev/exclude", 77 Operator: metav1.LabelSelectorOpDoesNotExist, 78 }}, 79 } 80 81 // These are the rules we expect given the context of "handlers". 82 expectedRules := []admissionregistrationv1.RuleWithOperations{{ 83 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 84 Rule: admissionregistrationv1.Rule{ 85 APIGroups: []string{""}, 86 APIVersions: []string{"v1"}, 87 Resources: []string{"pods", "pods/status"}, 88 }, 89 }, { 90 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 91 Rule: admissionregistrationv1.Rule{ 92 APIGroups: []string{"pkg.knative.dev"}, 93 APIVersions: []string{"v1alpha1"}, 94 Resources: []string{"innerdefaultresources", "innerdefaultresources/status"}, 95 }, 96 }, { 97 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 98 Rule: admissionregistrationv1.Rule{ 99 APIGroups: []string{"pkg.knative.dev"}, 100 APIVersions: []string{"v1alpha1"}, 101 Resources: []string{"resources", "resources/status"}, 102 }, 103 }, { 104 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 105 Rule: admissionregistrationv1.Rule{ 106 APIGroups: []string{"pkg.knative.dev"}, 107 APIVersions: []string{"v1beta1"}, 108 Resources: []string{"resourcecallbackdefaultcreates", "resourcecallbackdefaultcreates/status"}, 109 }, 110 }, { 111 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 112 Rule: admissionregistrationv1.Rule{ 113 APIGroups: []string{"pkg.knative.dev"}, 114 APIVersions: []string{"v1beta1"}, 115 Resources: []string{"resourcecallbackdefaults", "resourcecallbackdefaults/status"}, 116 }, 117 }, { 118 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 119 Rule: admissionregistrationv1.Rule{ 120 APIGroups: []string{"pkg.knative.dev"}, 121 APIVersions: []string{"v1beta1"}, 122 Resources: []string{"resources", "resources/status"}, 123 }, 124 }, { 125 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 126 Rule: admissionregistrationv1.Rule{ 127 APIGroups: []string{"pkg.knative.io"}, 128 APIVersions: []string{"v1alpha1"}, 129 Resources: []string{"innerdefaultresources", "innerdefaultresources/status"}, 130 }, 131 }} 132 133 // The key to use, which for this singleton reconciler doesn't matter (although the 134 // namespace matters for namespace validation). 135 key := system.Namespace() + "/does not matter" 136 137 table := TableTest{{ 138 Name: "no secret", 139 Key: key, 140 WantErr: true, 141 }, { 142 Name: "secret missing CA Cert", 143 Key: key, 144 Objects: []runtime.Object{&corev1.Secret{ 145 ObjectMeta: metav1.ObjectMeta{ 146 Name: secretName, 147 Namespace: system.Namespace(), 148 }, 149 Data: map[string][]byte{ 150 certresources.ServerKey: []byte("present"), 151 certresources.ServerCert: []byte("present"), 152 // certresources.CACert: []byte("missing"), 153 }, 154 }}, 155 WantErr: true, 156 }, { 157 Name: "secret exists, but MWH does not", 158 Key: key, 159 Objects: []runtime.Object{secret}, 160 WantErr: true, 161 }, { 162 Name: "secret and MWH exist, missing service reference", 163 Key: key, 164 Objects: []runtime.Object{ 165 secret, ns, 166 &admissionregistrationv1.MutatingWebhookConfiguration{ 167 ObjectMeta: metav1.ObjectMeta{ 168 Name: name, 169 }, 170 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 171 Name: name, 172 }}, 173 }, 174 }, 175 WantErr: true, 176 }, { 177 Name: "secret and MWH exist, missing other stuff", 178 Key: key, 179 Objects: []runtime.Object{ 180 secret, ns, 181 &admissionregistrationv1.MutatingWebhookConfiguration{ 182 ObjectMeta: metav1.ObjectMeta{ 183 Name: name, 184 }, 185 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 186 Name: name, 187 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 188 Service: &admissionregistrationv1.ServiceReference{ 189 Namespace: system.Namespace(), 190 Name: "webhook", 191 }, 192 }, 193 }}, 194 }, 195 }, 196 WantUpdates: []clientgotesting.UpdateActionImpl{{ 197 Object: &admissionregistrationv1.MutatingWebhookConfiguration{ 198 ObjectMeta: metav1.ObjectMeta{ 199 Name: name, 200 OwnerReferences: expectedOwnerReferences, 201 }, 202 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 203 Name: name, 204 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 205 Service: &admissionregistrationv1.ServiceReference{ 206 Namespace: system.Namespace(), 207 Name: "webhook", 208 // Path is added. 209 Path: ptr.String(path), 210 }, 211 // CABundle is added. 212 CABundle: []byte("present"), 213 }, 214 // Rules are added. 215 Rules: expectedRules, 216 NamespaceSelector: namespaceSelector, 217 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 218 }}, 219 }, 220 }}, 221 }, { 222 Name: "secret and MWH exist, added fields are incorrect", 223 Key: key, 224 Objects: []runtime.Object{ 225 secret, ns, 226 &admissionregistrationv1.MutatingWebhookConfiguration{ 227 ObjectMeta: metav1.ObjectMeta{ 228 Name: name, 229 }, 230 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 231 Name: name, 232 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 233 Service: &admissionregistrationv1.ServiceReference{ 234 Namespace: system.Namespace(), 235 Name: "webhook", 236 // Incorrect 237 Path: ptr.String("incorrect"), 238 }, 239 // Incorrect 240 CABundle: []byte("incorrect"), 241 }, 242 // Incorrect (really just incomplete) 243 Rules: []admissionregistrationv1.RuleWithOperations{{ 244 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 245 Rule: admissionregistrationv1.Rule{ 246 APIGroups: []string{"pkg.knative.dev"}, 247 APIVersions: []string{"v1alpha1"}, 248 Resources: []string{"innerdefaultresources", "innerdefaultresources/status"}, 249 }, 250 }}, 251 // Incorrect 252 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.NeverReinvocationPolicy), 253 }}, 254 }, 255 }, 256 WantUpdates: []clientgotesting.UpdateActionImpl{{ 257 Object: &admissionregistrationv1.MutatingWebhookConfiguration{ 258 ObjectMeta: metav1.ObjectMeta{ 259 Name: name, 260 OwnerReferences: expectedOwnerReferences, 261 }, 262 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 263 Name: name, 264 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 265 Service: &admissionregistrationv1.ServiceReference{ 266 Namespace: system.Namespace(), 267 Name: "webhook", 268 // Path is fixed. 269 Path: ptr.String(path), 270 }, 271 // CABundle is fixed. 272 CABundle: []byte("present"), 273 }, 274 // Rules are fixed. 275 Rules: expectedRules, 276 NamespaceSelector: namespaceSelector, 277 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 278 }}, 279 }, 280 }}, 281 }, { 282 Name: "failure updating MWH", 283 Key: key, 284 WantErr: true, 285 WithReactors: []clientgotesting.ReactionFunc{ 286 InduceFailure("update", "mutatingwebhookconfigurations"), 287 }, 288 Objects: []runtime.Object{ 289 secret, ns, 290 &admissionregistrationv1.MutatingWebhookConfiguration{ 291 ObjectMeta: metav1.ObjectMeta{ 292 Name: name, 293 }, 294 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 295 Name: name, 296 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 297 Service: &admissionregistrationv1.ServiceReference{ 298 Namespace: system.Namespace(), 299 Name: "webhook", 300 // Incorrect 301 Path: ptr.String("incorrect"), 302 }, 303 // Incorrect 304 CABundle: []byte("incorrect"), 305 }, 306 // Incorrect (really just incomplete) 307 Rules: []admissionregistrationv1.RuleWithOperations{{ 308 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 309 Rule: admissionregistrationv1.Rule{ 310 APIGroups: []string{"pkg.knative.dev"}, 311 APIVersions: []string{"v1alpha1"}, 312 Resources: []string{"innerdefaultresources", "innerdefaultresources/status"}, 313 }, 314 }}, 315 }}, 316 }, 317 }, 318 WantUpdates: []clientgotesting.UpdateActionImpl{{ 319 Object: &admissionregistrationv1.MutatingWebhookConfiguration{ 320 ObjectMeta: metav1.ObjectMeta{ 321 Name: name, 322 OwnerReferences: expectedOwnerReferences, 323 }, 324 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 325 Name: name, 326 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 327 Service: &admissionregistrationv1.ServiceReference{ 328 Namespace: system.Namespace(), 329 Name: "webhook", 330 // Path is fixed. 331 Path: ptr.String(path), 332 }, 333 // CABundle is fixed. 334 CABundle: []byte("present"), 335 }, 336 // Rules are fixed. 337 Rules: expectedRules, 338 NamespaceSelector: namespaceSelector, 339 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 340 }}, 341 }, 342 }}, 343 }, { 344 Name: ":fire: everything is fine :fire:", 345 Key: key, 346 Objects: []runtime.Object{ 347 secret, ns, 348 &admissionregistrationv1.MutatingWebhookConfiguration{ 349 ObjectMeta: metav1.ObjectMeta{ 350 Name: name, 351 OwnerReferences: expectedOwnerReferences, 352 }, 353 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 354 Name: name, 355 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 356 Service: &admissionregistrationv1.ServiceReference{ 357 Namespace: system.Namespace(), 358 Name: "webhook", 359 // Path is fine. 360 Path: ptr.String(path), 361 }, 362 // CABundle is fine. 363 CABundle: []byte("present"), 364 }, 365 // Rules are fine. 366 Rules: expectedRules, 367 // A non-knative key in the namespace selector is fine. 368 NamespaceSelector: &metav1.LabelSelector{ 369 MatchExpressions: []metav1.LabelSelectorRequirement{{ 370 Key: "webhooks.knative.dev/exclude", 371 Operator: metav1.LabelSelectorOpDoesNotExist, 372 }, { 373 Key: "foo.bar/baz", 374 Operator: metav1.LabelSelectorOpDoesNotExist, 375 }}, 376 }, 377 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 378 }}, 379 }, 380 }, 381 }, { 382 Name: "secret and MWH exist, correcting namespaceSelector", 383 Key: key, 384 Objects: []runtime.Object{ 385 secret, ns, 386 &admissionregistrationv1.MutatingWebhookConfiguration{ 387 ObjectMeta: metav1.ObjectMeta{ 388 Name: name, 389 }, 390 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 391 Name: name, 392 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 393 Service: &admissionregistrationv1.ServiceReference{ 394 Namespace: system.Namespace(), 395 Name: "webhook", 396 // Path is fine. 397 Path: ptr.String(path), 398 }, 399 // CABundle is fine. 400 CABundle: []byte("present"), 401 }, 402 // Rules are fine. 403 Rules: expectedRules, 404 // NamespaceSelector contains non-knative things. 405 NamespaceSelector: &metav1.LabelSelector{ 406 MatchExpressions: []metav1.LabelSelectorRequirement{{ 407 Key: "foo.knative.dev/exclude", 408 Operator: metav1.LabelSelectorOpDoesNotExist, 409 }, { 410 Key: "foo.bar/baz", 411 Operator: metav1.LabelSelectorOpDoesNotExist, 412 }}, 413 }, 414 }}, 415 }, 416 }, 417 WantUpdates: []clientgotesting.UpdateActionImpl{{ 418 Object: &admissionregistrationv1.MutatingWebhookConfiguration{ 419 ObjectMeta: metav1.ObjectMeta{ 420 Name: name, 421 OwnerReferences: expectedOwnerReferences, 422 }, 423 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 424 Name: name, 425 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 426 Service: &admissionregistrationv1.ServiceReference{ 427 Namespace: system.Namespace(), 428 Name: "webhook", 429 Path: ptr.String(path), 430 }, 431 CABundle: []byte("present"), 432 }, 433 Rules: expectedRules, 434 NamespaceSelector: &metav1.LabelSelector{ 435 // The knative key is added while the non-knative key is kept. 436 // Old knative key is removed. 437 MatchExpressions: []metav1.LabelSelectorRequirement{{ 438 Key: "webhooks.knative.dev/exclude", 439 Operator: metav1.LabelSelectorOpDoesNotExist, 440 }, { 441 Key: "foo.bar/baz", 442 Operator: metav1.LabelSelectorOpDoesNotExist, 443 }}, 444 }, 445 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 446 }}, 447 }, 448 }}, 449 }} 450 451 table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler { 452 return &reconciler{ 453 key: types.NamespacedName{ 454 Name: name, 455 }, 456 path: path, 457 458 handlers: handlers, 459 callbacks: callbacks, 460 461 client: kubeclient.Get(ctx), 462 mwhlister: listers.GetMutatingWebhookConfigurationLister(), 463 secretlister: listers.GetSecretLister(), 464 465 secretName: secretName, 466 } 467 })) 468 } 469 470 func TestNew(t *testing.T) { 471 ctx, _ := SetupFakeContext(t) 472 ctx = webhook.WithOptions(ctx, webhook.Options{}) 473 474 c := NewAdmissionController(ctx, "foo", "/bar", 475 map[schema.GroupVersionKind]resourcesemantics.GenericCRD{}, 476 func(ctx context.Context) context.Context { 477 return ctx 478 }, true /* disallow unknown field */) 479 if c == nil { 480 t.Fatal("Expected NewController to return a non-nil value") 481 } 482 483 if want, got := 0, c.WorkQueue().Len(); want != got { 484 t.Errorf("WorkQueue.Len() = %d, wanted %d", got, want) 485 } 486 487 la, ok := c.Reconciler.(pkgreconciler.LeaderAware) 488 if !ok { 489 t.Fatalf("%T is not leader aware", c.Reconciler) 490 } 491 492 if err := la.Promote(pkgreconciler.UniversalBucket(), c.MaybeEnqueueBucketKey); err != nil { 493 t.Error("Promote() =", err) 494 } 495 496 // Queue has async moving parts so if we check at the wrong moment, this might still be 0. 497 if wait.PollUntilContextTimeout(ctx, 10*time.Millisecond, 250*time.Millisecond, true, func(ctx context.Context) (bool, error) { 498 return c.WorkQueue().Len() == 1, nil 499 }) != nil { 500 t.Error("Queue length was never 1") 501 } 502 }