knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/webhook/psbinding/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 psbinding 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "testing" 25 "time" 26 27 "github.com/google/go-cmp/cmp" 28 jsonpatch "gomodules.xyz/jsonpatch/v2" 29 fakek8s "k8s.io/client-go/kubernetes/fake" 30 "knative.dev/pkg/apis" 31 "knative.dev/pkg/client/injection/ducks/duck/v1/podspecable" 32 kubeclient "knative.dev/pkg/client/injection/kube/client/fake" 33 mwhinformer "knative.dev/pkg/client/injection/kube/informers/admissionregistration/v1/mutatingwebhookconfiguration" 34 _ "knative.dev/pkg/client/injection/kube/informers/admissionregistration/v1/mutatingwebhookconfiguration/fake" 35 dynamicclient "knative.dev/pkg/injection/clients/dynamicclient/fake" 36 secretinformer "knative.dev/pkg/injection/clients/namespacedkube/informers/core/v1/secret" 37 _ "knative.dev/pkg/injection/clients/namespacedkube/informers/core/v1/secret/fake" 38 pkgreconciler "knative.dev/pkg/reconciler" 39 "knative.dev/pkg/tracker" 40 41 admissionv1 "k8s.io/api/admission/v1" 42 admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 43 appsv1 "k8s.io/api/apps/v1" 44 corev1 "k8s.io/api/core/v1" 45 apierrs "k8s.io/apimachinery/pkg/api/errors" 46 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 47 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 48 "k8s.io/apimachinery/pkg/runtime" 49 "k8s.io/apimachinery/pkg/runtime/schema" 50 "k8s.io/apimachinery/pkg/types" 51 "k8s.io/apimachinery/pkg/util/wait" 52 clientgotesting "k8s.io/client-go/testing" 53 "k8s.io/client-go/tools/cache" 54 "k8s.io/client-go/tools/record" 55 "knative.dev/pkg/apis/duck" 56 duckv1 "knative.dev/pkg/apis/duck/v1" 57 duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1" 58 "knative.dev/pkg/configmap" 59 "knative.dev/pkg/controller" 60 "knative.dev/pkg/ptr" 61 "knative.dev/pkg/system" 62 "knative.dev/pkg/webhook" 63 certresources "knative.dev/pkg/webhook/certificates/resources" 64 65 . "knative.dev/pkg/reconciler/testing" 66 . "knative.dev/pkg/testing/duck" 67 . "knative.dev/pkg/webhook/testing" 68 ) 69 70 func checkDeploymentIsPatched(t *testing.T, r *TableRow) { 71 t.Helper() 72 ac := r.Reconciler.(webhook.AdmissionController) 73 d := &appsv1.Deployment{ 74 ObjectMeta: metav1.ObjectMeta{ 75 Namespace: "foo", 76 Name: "on-it", 77 Labels: map[string]string{ 78 "foo": "bar", 79 }, 80 }, 81 Spec: appsv1.DeploymentSpec{ 82 Template: corev1.PodTemplateSpec{ 83 Spec: corev1.PodSpec{ 84 Containers: []corev1.Container{{ 85 Name: "foo", 86 Image: "busybox", 87 }}, 88 }, 89 }, 90 }, 91 } 92 b, err := json.Marshal(d) 93 if err != nil { 94 t.Fatal("Unable to serialize deployment:", err) 95 } 96 97 req := &admissionv1.AdmissionRequest{ 98 Operation: admissionv1.Create, 99 Kind: metav1.GroupVersionKind{ 100 Group: "apps", 101 Version: "v1", 102 Kind: "Deployment", 103 }, 104 Namespace: d.Namespace, 105 Object: runtime.RawExtension{Raw: b}, 106 } 107 108 // It is allowed, and patched to include the environment variable. 109 resp := ac.Admit(r.Ctx, req) 110 ExpectAllowed(t, resp) 111 ExpectPatches(t, resp.Patch, []jsonpatch.JsonPatchOperation{{ 112 Operation: "add", 113 Path: "/spec/template/spec/containers/0/env", 114 Value: []interface{}{map[string]interface{}{ 115 "name": "FOO", 116 "value": "the-value", 117 }}, 118 }}) 119 } 120 121 func checkDeploymentIsPatchedBack(t *testing.T, r *TableRow) { 122 t.Helper() 123 ac := r.Reconciler.(webhook.AdmissionController) 124 d := &appsv1.Deployment{ 125 ObjectMeta: metav1.ObjectMeta{ 126 Namespace: "foo", 127 Name: "on-it", 128 Labels: map[string]string{ 129 "foo": "bar", 130 }, 131 }, 132 Spec: appsv1.DeploymentSpec{ 133 Template: corev1.PodTemplateSpec{ 134 Spec: corev1.PodSpec{ 135 Containers: []corev1.Container{{ 136 Name: "foo", 137 Image: "busybox", 138 Env: []corev1.EnvVar{{ 139 Name: "FOO", 140 Value: "the-value", 141 }}, 142 }}, 143 }, 144 }, 145 }, 146 } 147 b, err := json.Marshal(d) 148 if err != nil { 149 t.Fatal("Unable to serialize deployment:", err) 150 } 151 152 req := &admissionv1.AdmissionRequest{ 153 Operation: admissionv1.Create, 154 Kind: metav1.GroupVersionKind{ 155 Group: "apps", 156 Version: "v1", 157 Kind: "Deployment", 158 }, 159 Namespace: d.Namespace, 160 Object: runtime.RawExtension{Raw: b}, 161 } 162 163 // It is allowed, and patched to REMOVE the environment variable. 164 resp := ac.Admit(r.Ctx, req) 165 ExpectAllowed(t, resp) 166 ExpectPatches(t, resp.Patch, []jsonpatch.JsonPatchOperation{{ 167 Operation: "remove", 168 Path: "/spec/template/spec/containers/0/env", 169 }}) 170 } 171 172 func checkDeploymentIsNotPatched(t *testing.T, r *TableRow) { 173 t.Helper() 174 ac := r.Reconciler.(webhook.AdmissionController) 175 d := &appsv1.Deployment{ 176 ObjectMeta: metav1.ObjectMeta{ 177 Namespace: "foo", 178 Name: "off-it", 179 Labels: map[string]string{ 180 "foo": "baz", 181 }, 182 }, 183 Spec: appsv1.DeploymentSpec{ 184 Template: corev1.PodTemplateSpec{ 185 Spec: corev1.PodSpec{ 186 Containers: []corev1.Container{{ 187 Name: "foo", 188 Image: "busybox", 189 }}, 190 }, 191 }, 192 }, 193 } 194 b, err := json.Marshal(d) 195 if err != nil { 196 t.Fatal("Unable to serialize deployment:", err) 197 } 198 199 req := &admissionv1.AdmissionRequest{ 200 Operation: admissionv1.Create, 201 Kind: metav1.GroupVersionKind{ 202 Group: "apps", 203 Version: "v1", 204 Kind: "Deployment", 205 }, 206 Namespace: d.Namespace, 207 Object: runtime.RawExtension{Raw: b}, 208 } 209 210 // It is allowed, but not patched. 211 resp := ac.Admit(r.Ctx, req) 212 ExpectAllowed(t, resp) 213 if want, got := "", string(resp.Patch); want != got { 214 t.Errorf("Admit() = %s, got %s", got, want) 215 } 216 } 217 218 func checkDeleteIgnored(t *testing.T, r *TableRow) { 219 t.Helper() 220 ac := r.Reconciler.(webhook.AdmissionController) 221 d := &appsv1.Deployment{ 222 ObjectMeta: metav1.ObjectMeta{ 223 Namespace: "foo", 224 Name: "on-it", 225 Labels: map[string]string{ 226 "foo": "bar", 227 }, 228 }, 229 Spec: appsv1.DeploymentSpec{ 230 Template: corev1.PodTemplateSpec{ 231 Spec: corev1.PodSpec{ 232 Containers: []corev1.Container{{ 233 Name: "foo", 234 Image: "busybox", 235 }}, 236 }, 237 }, 238 }, 239 } 240 b, err := json.Marshal(d) 241 if err != nil { 242 t.Fatal("Unable to serialize deployment:", err) 243 } 244 245 req := &admissionv1.AdmissionRequest{ 246 Operation: admissionv1.Delete, 247 Kind: metav1.GroupVersionKind{ 248 Group: "apps", 249 Version: "v1", 250 Kind: "Deployment", 251 }, 252 Namespace: d.Namespace, 253 Object: runtime.RawExtension{Raw: b}, 254 } 255 256 // It is allowed, and patched to include the environment variable. 257 resp := ac.Admit(r.Ctx, req) 258 ExpectAllowed(t, resp) 259 if want, got := "", string(resp.Patch); want != got { 260 t.Errorf("Admit() = %s, got %s", got, want) 261 } 262 } 263 264 func TestWebhookReconcile(t *testing.T) { 265 name, path := "foo.bar.baz", "/blah" 266 secretName := "webhook-secret" 267 268 secret := &corev1.Secret{ 269 ObjectMeta: metav1.ObjectMeta{ 270 Name: secretName, 271 Namespace: system.Namespace(), 272 }, 273 Data: map[string][]byte{ 274 certresources.ServerKey: []byte("present"), 275 certresources.ServerCert: []byte("present"), 276 certresources.CACert: []byte("present"), 277 }, 278 } 279 280 equivalent := admissionregistrationv1.Equivalent 281 282 // The key to use, which for this singleton reconciler doesn't matter (although the 283 // namespace matters for namespace validation). 284 key := system.Namespace() + "/does not matter" 285 286 table := TableTest{{ 287 Name: "no secret", 288 Key: key, 289 WantErr: true, 290 }, { 291 Name: "secret missing CA Cert", 292 Key: key, 293 Objects: []runtime.Object{&corev1.Secret{ 294 ObjectMeta: metav1.ObjectMeta{ 295 Name: secretName, 296 Namespace: system.Namespace(), 297 }, 298 Data: map[string][]byte{ 299 certresources.ServerKey: []byte("present"), 300 certresources.ServerCert: []byte("present"), 301 // certresources.CACert: []byte("missing"), 302 }, 303 }}, 304 WantErr: true, 305 }, { 306 Name: "secret exists, but MWH does not", 307 Key: key, 308 Objects: []runtime.Object{secret}, 309 WantErr: true, 310 }, { 311 Name: "secret and MWH exist, missing service reference", 312 Key: key, 313 Objects: []runtime.Object{ 314 secret, 315 &admissionregistrationv1.MutatingWebhookConfiguration{ 316 ObjectMeta: metav1.ObjectMeta{ 317 Name: name, 318 }, 319 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 320 Name: name, 321 }}, 322 }, 323 }, 324 WantErr: true, 325 }, { 326 Name: "secret and MWH exist, missing other stuff", 327 Key: key, 328 Objects: []runtime.Object{ 329 secret, 330 &admissionregistrationv1.MutatingWebhookConfiguration{ 331 ObjectMeta: metav1.ObjectMeta{ 332 Name: name, 333 }, 334 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 335 Name: name, 336 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 337 Service: &admissionregistrationv1.ServiceReference{ 338 Namespace: system.Namespace(), 339 Name: "webhook", 340 }, 341 }, 342 }}, 343 }, 344 }, 345 WantUpdates: []clientgotesting.UpdateActionImpl{{ 346 Object: &admissionregistrationv1.MutatingWebhookConfiguration{ 347 ObjectMeta: metav1.ObjectMeta{ 348 Name: name, 349 }, 350 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 351 Name: name, 352 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 353 Service: &admissionregistrationv1.ServiceReference{ 354 Namespace: system.Namespace(), 355 Name: "webhook", 356 // Path is added. 357 Path: ptr.String(path), 358 }, 359 // CABundle is added. 360 CABundle: []byte("present"), 361 }, 362 // Rules are added. 363 Rules: nil, 364 // MatchPolicy is added. 365 MatchPolicy: &equivalent, 366 // Selectors are added. 367 NamespaceSelector: &ExclusionSelector, 368 ObjectSelector: &ExclusionSelector, 369 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 370 }}, 371 }, 372 }}, 373 }, { 374 Name: "secret and MWH exist, added fields are incorrect", 375 Key: key, 376 Objects: []runtime.Object{ 377 secret, 378 &admissionregistrationv1.MutatingWebhookConfiguration{ 379 ObjectMeta: metav1.ObjectMeta{ 380 Name: name, 381 }, 382 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 383 Name: name, 384 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 385 Service: &admissionregistrationv1.ServiceReference{ 386 Namespace: system.Namespace(), 387 Name: "webhook", 388 // Incorrect 389 Path: ptr.String("incorrect"), 390 }, 391 // Incorrect 392 CABundle: []byte("incorrect"), 393 }, 394 // Incorrect (really just incomplete) 395 Rules: []admissionregistrationv1.RuleWithOperations{{ 396 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 397 Rule: admissionregistrationv1.Rule{ 398 APIGroups: []string{"pkg.knative.dev"}, 399 APIVersions: []string{"v1alpha1"}, 400 Resources: []string{"innerdefaultresources/*"}, 401 }, 402 }}, 403 // Incorrect 404 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.NeverReinvocationPolicy), 405 }}, 406 }, 407 }, 408 WantUpdates: []clientgotesting.UpdateActionImpl{{ 409 Object: &admissionregistrationv1.MutatingWebhookConfiguration{ 410 ObjectMeta: metav1.ObjectMeta{ 411 Name: name, 412 }, 413 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 414 Name: name, 415 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 416 Service: &admissionregistrationv1.ServiceReference{ 417 Namespace: system.Namespace(), 418 Name: "webhook", 419 // Path is fixed. 420 Path: ptr.String(path), 421 }, 422 // CABundle is fixed. 423 CABundle: []byte("present"), 424 }, 425 // Rules are fixed. 426 Rules: nil, 427 // MatchPolicy is added. 428 MatchPolicy: &equivalent, 429 // Selectors are added. 430 NamespaceSelector: &ExclusionSelector, 431 ObjectSelector: &ExclusionSelector, 432 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 433 }}, 434 }, 435 }}, 436 }, { 437 Name: "failure updating MWH", 438 Key: key, 439 WantErr: true, 440 WithReactors: []clientgotesting.ReactionFunc{ 441 InduceFailure("update", "mutatingwebhookconfigurations"), 442 }, 443 Objects: []runtime.Object{ 444 secret, 445 &admissionregistrationv1.MutatingWebhookConfiguration{ 446 ObjectMeta: metav1.ObjectMeta{ 447 Name: name, 448 }, 449 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 450 Name: name, 451 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 452 Service: &admissionregistrationv1.ServiceReference{ 453 Namespace: system.Namespace(), 454 Name: "webhook", 455 // Incorrect 456 Path: ptr.String("incorrect"), 457 }, 458 // Incorrect 459 CABundle: []byte("incorrect"), 460 }, 461 // Incorrect (really just incomplete) 462 Rules: []admissionregistrationv1.RuleWithOperations{{ 463 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 464 Rule: admissionregistrationv1.Rule{ 465 APIGroups: []string{"pkg.knative.dev"}, 466 APIVersions: []string{"v1alpha1"}, 467 Resources: []string{"innerdefaultresources/*"}, 468 }, 469 }}, 470 }}, 471 }, 472 }, 473 WantUpdates: []clientgotesting.UpdateActionImpl{{ 474 Object: &admissionregistrationv1.MutatingWebhookConfiguration{ 475 ObjectMeta: metav1.ObjectMeta{ 476 Name: name, 477 }, 478 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 479 Name: name, 480 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 481 Service: &admissionregistrationv1.ServiceReference{ 482 Namespace: system.Namespace(), 483 Name: "webhook", 484 // Path is fixed. 485 Path: ptr.String(path), 486 }, 487 // CABundle is fixed. 488 CABundle: []byte("present"), 489 }, 490 // Rules are fixed. 491 Rules: nil, 492 // MatchPolicy is added. 493 MatchPolicy: &equivalent, 494 // Selectors are added. 495 NamespaceSelector: &ExclusionSelector, 496 ObjectSelector: &ExclusionSelector, 497 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 498 }}, 499 }, 500 }}, 501 }, { 502 Name: ":fire: everything is fine :fire:", 503 Key: key, 504 Objects: []runtime.Object{ 505 secret, 506 &admissionregistrationv1.MutatingWebhookConfiguration{ 507 ObjectMeta: metav1.ObjectMeta{ 508 Name: name, 509 }, 510 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 511 Name: name, 512 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 513 Service: &admissionregistrationv1.ServiceReference{ 514 Namespace: system.Namespace(), 515 Name: "webhook", 516 // Path is fine. 517 Path: ptr.String(path), 518 }, 519 // CABundle is fine. 520 CABundle: []byte("present"), 521 }, 522 // Rules are fine. 523 Rules: nil, 524 // MatchPolicy is fine. 525 MatchPolicy: &equivalent, 526 // Selectors are fine. 527 NamespaceSelector: &ExclusionSelector, 528 ObjectSelector: &ExclusionSelector, 529 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 530 }}, 531 }, 532 }, 533 }, { 534 Name: "a new binding has entered the match", 535 Key: key, 536 Objects: []runtime.Object{ 537 secret, 538 &TestBindable{ 539 ObjectMeta: metav1.ObjectMeta{ 540 Namespace: "foo", 541 Name: "bar", 542 }, 543 Spec: TestBindableSpec{ 544 BindingSpec: duckv1alpha1.BindingSpec{ 545 Subject: tracker.Reference{ 546 APIVersion: "random.knative.dev/v2beta3", 547 Kind: "Knoodle", 548 Namespace: "foo", 549 Name: "on-it", 550 }, 551 }, 552 }, 553 }, 554 &admissionregistrationv1.MutatingWebhookConfiguration{ 555 ObjectMeta: metav1.ObjectMeta{ 556 Name: name, 557 }, 558 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 559 Name: name, 560 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 561 Service: &admissionregistrationv1.ServiceReference{ 562 Namespace: system.Namespace(), 563 Name: "webhook", 564 // Path is fine. 565 Path: ptr.String(path), 566 }, 567 // CABundle is fine. 568 CABundle: []byte("present"), 569 }, 570 // Rules are fine. 571 Rules: nil, 572 // MatchPolicy is fine. 573 MatchPolicy: &equivalent, 574 // Selectors are fine. 575 NamespaceSelector: &ExclusionSelector, 576 ObjectSelector: &ExclusionSelector, 577 }}, 578 }, 579 }, 580 WantUpdates: []clientgotesting.UpdateActionImpl{{ 581 Object: &admissionregistrationv1.MutatingWebhookConfiguration{ 582 ObjectMeta: metav1.ObjectMeta{ 583 Name: name, 584 }, 585 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 586 Name: name, 587 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 588 Service: &admissionregistrationv1.ServiceReference{ 589 Namespace: system.Namespace(), 590 Name: "webhook", 591 Path: ptr.String(path), 592 }, 593 CABundle: []byte("present"), 594 }, 595 // A new rule is added to intercept the new type. 596 Rules: []admissionregistrationv1.RuleWithOperations{{ 597 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 598 Rule: admissionregistrationv1.Rule{ 599 APIGroups: []string{"random.knative.dev"}, 600 APIVersions: []string{"v2beta3"}, 601 Resources: []string{"knoodles/*"}, 602 }, 603 }}, 604 MatchPolicy: &equivalent, 605 NamespaceSelector: &ExclusionSelector, 606 ObjectSelector: &ExclusionSelector, 607 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 608 }}, 609 }, 610 }}, 611 }, { 612 Name: "steady state direct bindings", 613 Key: key, 614 Objects: []runtime.Object{ 615 secret, 616 &TestBindable{ 617 ObjectMeta: metav1.ObjectMeta{ 618 Namespace: "foo", 619 Name: "bar1", 620 }, 621 Spec: TestBindableSpec{ 622 BindingSpec: duckv1alpha1.BindingSpec{ 623 Subject: tracker.Reference{ 624 APIVersion: "random.knative.dev/v2beta3", 625 Kind: "Knoodle", 626 Namespace: "foo", 627 Name: "on-it", 628 }, 629 }, 630 Foo: "one-value", 631 }, 632 }, 633 &TestBindable{ 634 ObjectMeta: metav1.ObjectMeta{ 635 Namespace: "foo", 636 Name: "bar2", 637 }, 638 Spec: TestBindableSpec{ 639 BindingSpec: duckv1alpha1.BindingSpec{ 640 Subject: tracker.Reference{ 641 APIVersion: "apps/v1", 642 Kind: "Deployment", 643 Namespace: "foo", 644 Name: "on-it", 645 }, 646 }, 647 Foo: "the-value", 648 }, 649 }, 650 &admissionregistrationv1.MutatingWebhookConfiguration{ 651 ObjectMeta: metav1.ObjectMeta{ 652 Name: name, 653 }, 654 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 655 Name: name, 656 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 657 Service: &admissionregistrationv1.ServiceReference{ 658 Namespace: system.Namespace(), 659 Name: "webhook", 660 Path: ptr.String(path), 661 }, 662 CABundle: []byte("present"), 663 }, 664 // A new rule is added to intercept the new type. 665 Rules: []admissionregistrationv1.RuleWithOperations{{ 666 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 667 Rule: admissionregistrationv1.Rule{ 668 APIGroups: []string{"apps"}, 669 APIVersions: []string{"v1"}, 670 Resources: []string{"deployments/*"}, 671 }, 672 }, { 673 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 674 Rule: admissionregistrationv1.Rule{ 675 APIGroups: []string{"random.knative.dev"}, 676 APIVersions: []string{"v2beta3"}, 677 Resources: []string{"knoodles/*"}, 678 }, 679 }}, 680 MatchPolicy: &equivalent, 681 NamespaceSelector: &ExclusionSelector, 682 ObjectSelector: &ExclusionSelector, 683 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 684 }}, 685 }, 686 }, 687 // Verify that Admit properly patches deployments after being programmed 688 // with the binding. 689 PostConditions: []func(*testing.T, *TableRow){ 690 checkDeploymentIsPatched, 691 checkDeploymentIsNotPatched, 692 checkDeleteIgnored, 693 }, 694 }, { 695 Name: "steady state selector", 696 Key: key, 697 Objects: []runtime.Object{ 698 secret, 699 &TestBindable{ 700 ObjectMeta: metav1.ObjectMeta{ 701 Namespace: "foo", 702 Name: "bar1", 703 }, 704 Spec: TestBindableSpec{ 705 BindingSpec: duckv1alpha1.BindingSpec{ 706 Subject: tracker.Reference{ 707 APIVersion: "random.knative.dev/v2beta3", 708 Kind: "Knoodle", 709 Namespace: "foo", 710 Selector: &metav1.LabelSelector{ 711 // Match everything. 712 MatchLabels: map[string]string{}, 713 }, 714 }, 715 }, 716 Foo: "one-value", 717 }, 718 }, 719 &TestBindable{ 720 ObjectMeta: metav1.ObjectMeta{ 721 Namespace: "foo", 722 Name: "bar2", 723 }, 724 Spec: TestBindableSpec{ 725 BindingSpec: duckv1alpha1.BindingSpec{ 726 Subject: tracker.Reference{ 727 APIVersion: "apps/v1", 728 Kind: "Deployment", 729 Namespace: "foo", 730 Selector: &metav1.LabelSelector{ 731 MatchLabels: map[string]string{ 732 "foo": "bar", 733 }, 734 }, 735 }, 736 }, 737 Foo: "the-value", 738 }, 739 }, 740 &admissionregistrationv1.MutatingWebhookConfiguration{ 741 ObjectMeta: metav1.ObjectMeta{ 742 Name: name, 743 }, 744 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 745 Name: name, 746 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 747 Service: &admissionregistrationv1.ServiceReference{ 748 Namespace: system.Namespace(), 749 Name: "webhook", 750 Path: ptr.String(path), 751 }, 752 CABundle: []byte("present"), 753 }, 754 // A new rule is added to intercept the new type. 755 Rules: []admissionregistrationv1.RuleWithOperations{{ 756 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 757 Rule: admissionregistrationv1.Rule{ 758 APIGroups: []string{"apps"}, 759 APIVersions: []string{"v1"}, 760 Resources: []string{"deployments/*"}, 761 }, 762 }, { 763 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 764 Rule: admissionregistrationv1.Rule{ 765 APIGroups: []string{"random.knative.dev"}, 766 APIVersions: []string{"v2beta3"}, 767 Resources: []string{"knoodles/*"}, 768 }, 769 }}, 770 MatchPolicy: &equivalent, 771 NamespaceSelector: &ExclusionSelector, 772 ObjectSelector: &ExclusionSelector, 773 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 774 }}, 775 }, 776 }, 777 // Verify that Admit properly patches deployments after being programmed 778 // with the binding. 779 PostConditions: []func(*testing.T, *TableRow){ 780 checkDeploymentIsPatched, 781 checkDeploymentIsNotPatched, 782 checkDeleteIgnored, 783 }, 784 }, { 785 Name: "tombstoned binding undoes patch", 786 Key: key, 787 Objects: []runtime.Object{ 788 secret, 789 &TestBindable{ 790 ObjectMeta: metav1.ObjectMeta{ 791 Namespace: "foo", 792 Name: "bar2", 793 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 794 }, 795 Spec: TestBindableSpec{ 796 BindingSpec: duckv1alpha1.BindingSpec{ 797 Subject: tracker.Reference{ 798 APIVersion: "apps/v1", 799 Kind: "Deployment", 800 Namespace: "foo", 801 Selector: &metav1.LabelSelector{ 802 MatchLabels: map[string]string{ 803 "foo": "bar", 804 }, 805 }, 806 }, 807 }, 808 Foo: "the-value", 809 }, 810 }, 811 &admissionregistrationv1.MutatingWebhookConfiguration{ 812 ObjectMeta: metav1.ObjectMeta{ 813 Name: name, 814 }, 815 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 816 Name: name, 817 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 818 Service: &admissionregistrationv1.ServiceReference{ 819 Namespace: system.Namespace(), 820 Name: "webhook", 821 Path: ptr.String(path), 822 }, 823 CABundle: []byte("present"), 824 }, 825 Rules: []admissionregistrationv1.RuleWithOperations{{ 826 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 827 Rule: admissionregistrationv1.Rule{ 828 APIGroups: []string{"apps"}, 829 APIVersions: []string{"v1"}, 830 Resources: []string{"deployments/*"}, 831 }, 832 }}, 833 MatchPolicy: &equivalent, 834 NamespaceSelector: &ExclusionSelector, 835 ObjectSelector: &ExclusionSelector, 836 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 837 }}, 838 }, 839 }, 840 // Verify that Admit properly patches deployments after being programmed 841 // with the binding. 842 PostConditions: []func(*testing.T, *TableRow){ 843 checkDeploymentIsPatchedBack, 844 checkDeploymentIsNotPatched, 845 checkDeleteIgnored, 846 }, 847 }, { 848 Name: "multiple new bindings have entered the match", 849 Key: key, 850 Objects: []runtime.Object{ 851 secret, 852 &TestBindable{ 853 ObjectMeta: metav1.ObjectMeta{ 854 Namespace: "foo", 855 Name: "bar", 856 }, 857 Spec: TestBindableSpec{ 858 BindingSpec: duckv1alpha1.BindingSpec{ 859 Subject: tracker.Reference{ 860 APIVersion: "random.knative.dev/v2beta3", 861 Kind: "Knoodle", 862 Namespace: "foo", 863 Name: "on-it", 864 }, 865 }, 866 }, 867 }, 868 &TestBindable{ 869 ObjectMeta: metav1.ObjectMeta{ 870 Namespace: "blah", 871 Name: "bazinga", 872 }, 873 Spec: TestBindableSpec{ 874 BindingSpec: duckv1alpha1.BindingSpec{ 875 Subject: tracker.Reference{ 876 APIVersion: "pseudorandom.knative.dev/v3beta1", 877 Kind: "Knoogle", 878 Namespace: "blah", 879 Name: "oh-yeah", 880 }, 881 }, 882 }, 883 }, 884 &admissionregistrationv1.MutatingWebhookConfiguration{ 885 ObjectMeta: metav1.ObjectMeta{ 886 Name: name, 887 }, 888 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 889 Name: name, 890 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 891 Service: &admissionregistrationv1.ServiceReference{ 892 Namespace: system.Namespace(), 893 Name: "webhook", 894 // Path is fine. 895 Path: ptr.String(path), 896 }, 897 // CABundle is fine. 898 CABundle: []byte("present"), 899 }, 900 // Rules are fine. 901 Rules: nil, 902 // MatchPolicy is fine. 903 MatchPolicy: &equivalent, 904 // Selectors are fine. 905 NamespaceSelector: &ExclusionSelector, 906 ObjectSelector: &ExclusionSelector, 907 }}, 908 }, 909 }, 910 WantUpdates: []clientgotesting.UpdateActionImpl{{ 911 Object: &admissionregistrationv1.MutatingWebhookConfiguration{ 912 ObjectMeta: metav1.ObjectMeta{ 913 Name: name, 914 }, 915 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 916 Name: name, 917 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 918 Service: &admissionregistrationv1.ServiceReference{ 919 Namespace: system.Namespace(), 920 Name: "webhook", 921 Path: ptr.String(path), 922 }, 923 CABundle: []byte("present"), 924 }, 925 // New rules are added to intercept the new types. 926 Rules: []admissionregistrationv1.RuleWithOperations{{ 927 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 928 Rule: admissionregistrationv1.Rule{ 929 APIGroups: []string{"pseudorandom.knative.dev"}, 930 APIVersions: []string{"v3beta1"}, 931 Resources: []string{"knoogles/*"}, 932 }, 933 }, { 934 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 935 Rule: admissionregistrationv1.Rule{ 936 APIGroups: []string{"random.knative.dev"}, 937 APIVersions: []string{"v2beta3"}, 938 Resources: []string{"knoodles/*"}, 939 }, 940 }}, 941 MatchPolicy: &equivalent, 942 NamespaceSelector: &ExclusionSelector, 943 ObjectSelector: &ExclusionSelector, 944 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 945 }}, 946 }, 947 }}, 948 }, { 949 Name: "a new selector-based binding has entered the match", 950 Key: key, 951 Objects: []runtime.Object{ 952 secret, 953 &TestBindable{ 954 ObjectMeta: metav1.ObjectMeta{ 955 Namespace: "foo", 956 Name: "bar", 957 }, 958 Spec: TestBindableSpec{ 959 BindingSpec: duckv1alpha1.BindingSpec{ 960 Subject: tracker.Reference{ 961 APIVersion: "random.knative.dev/v2beta3", 962 Kind: "Knoodle", 963 Namespace: "foo", 964 Selector: &metav1.LabelSelector{ 965 MatchLabels: map[string]string{ 966 "foo": "bar", 967 }, 968 }, 969 }, 970 }, 971 }, 972 }, 973 &admissionregistrationv1.MutatingWebhookConfiguration{ 974 ObjectMeta: metav1.ObjectMeta{ 975 Name: name, 976 }, 977 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 978 Name: name, 979 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 980 Service: &admissionregistrationv1.ServiceReference{ 981 Namespace: system.Namespace(), 982 Name: "webhook", 983 // Path is fine. 984 Path: ptr.String(path), 985 }, 986 // CABundle is fine. 987 CABundle: []byte("present"), 988 }, 989 // Rules are fine. 990 Rules: nil, 991 // MatchPolicy is fine. 992 MatchPolicy: &equivalent, 993 // Selectors are fine. 994 NamespaceSelector: &ExclusionSelector, 995 ObjectSelector: &ExclusionSelector, 996 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 997 }}, 998 }, 999 }, 1000 WantUpdates: []clientgotesting.UpdateActionImpl{{ 1001 Object: &admissionregistrationv1.MutatingWebhookConfiguration{ 1002 ObjectMeta: metav1.ObjectMeta{ 1003 Name: name, 1004 }, 1005 Webhooks: []admissionregistrationv1.MutatingWebhook{{ 1006 Name: name, 1007 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 1008 Service: &admissionregistrationv1.ServiceReference{ 1009 Namespace: system.Namespace(), 1010 Name: "webhook", 1011 Path: ptr.String(path), 1012 }, 1013 CABundle: []byte("present"), 1014 }, 1015 // A new rule is added to intercept the new type. 1016 Rules: []admissionregistrationv1.RuleWithOperations{{ 1017 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 1018 Rule: admissionregistrationv1.Rule{ 1019 APIGroups: []string{"random.knative.dev"}, 1020 APIVersions: []string{"v2beta3"}, 1021 Resources: []string{"knoodles/*"}, 1022 }, 1023 }}, 1024 MatchPolicy: &equivalent, 1025 NamespaceSelector: &ExclusionSelector, 1026 ObjectSelector: &ExclusionSelector, 1027 ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy), 1028 }}, 1029 }, 1030 }}, 1031 }} 1032 1033 table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler { 1034 r := NewReconciler(name, path, secretName, kubeclient.Get(ctx), listers.GetMutatingWebhookConfigurationLister(), listers.GetSecretLister(), nil) 1035 r.ListAll = func() ([]Bindable, error) { 1036 bl := make([]Bindable, 0) 1037 for _, elt := range listers.GetDuckObjects() { 1038 b, ok := elt.(Bindable) 1039 if !ok { 1040 continue 1041 } 1042 bl = append(bl, b) 1043 } 1044 return bl, nil 1045 } 1046 return r 1047 })) 1048 } 1049 1050 func TestNew(t *testing.T) { 1051 ctx, _ := SetupFakeContext(t) 1052 ctx = webhook.WithOptions(ctx, webhook.Options{}) 1053 1054 c := NewAdmissionController(ctx, "foo", "/bar", 1055 func(context.Context, cache.ResourceEventHandler) ListAll { 1056 return func() ([]Bindable, error) { 1057 return nil, nil 1058 } 1059 }, 1060 func(ctx context.Context, b Bindable) (context.Context, error) { 1061 return ctx, nil 1062 }) 1063 if c == nil { 1064 t.Fatal("Expected NewController to return a non-nil value") 1065 } 1066 1067 if want, got := 0, c.WorkQueue().Len(); want != got { 1068 t.Errorf("WorkQueue.Len() = %d, wanted %d", got, want) 1069 } 1070 1071 la, ok := c.Reconciler.(pkgreconciler.LeaderAware) 1072 if !ok { 1073 t.Fatalf("%T is not leader aware", c.Reconciler) 1074 } 1075 1076 if err := la.Promote(pkgreconciler.UniversalBucket(), c.MaybeEnqueueBucketKey); err != nil { 1077 t.Error("Promote() =", err) 1078 } 1079 1080 // Queue has async moving parts so if we check at the wrong moment, this might still be 0. 1081 if wait.PollUntilContextTimeout(ctx, 10*time.Millisecond, 250*time.Millisecond, true, func(ctx context.Context) (bool, error) { 1082 return c.WorkQueue().Len() == 1, nil 1083 }) != nil { 1084 t.Error("Queue length was never 1") 1085 } 1086 } 1087 1088 func TestNewReconciler(t *testing.T) { 1089 ctx, _ := SetupFakeContext(t) 1090 ctx = webhook.WithOptions(ctx, webhook.Options{}) 1091 tests := []struct { 1092 name string 1093 selectorOption ReconcilerOption 1094 wantSelector metav1.LabelSelector 1095 }{ 1096 { 1097 name: "no selector, use default", 1098 wantSelector: ExclusionSelector, 1099 }, 1100 { 1101 name: "ExclusionSelector option", 1102 selectorOption: WithSelector(ExclusionSelector), 1103 wantSelector: ExclusionSelector, 1104 }, 1105 { 1106 name: "InclusionSelector option", 1107 selectorOption: WithSelector(InclusionSelector), 1108 wantSelector: InclusionSelector, 1109 }, 1110 } 1111 1112 for _, tt := range tests { 1113 t.Run(tt.name, func(t *testing.T) { 1114 client := kubeclient.Get(ctx) 1115 mwhInformer := mwhinformer.Get(ctx) 1116 secretInformer := secretinformer.Get(ctx) 1117 withContext := func(ctx context.Context, b Bindable) (context.Context, error) { 1118 return ctx, nil 1119 } 1120 var r *Reconciler 1121 if tt.selectorOption == nil { 1122 r = NewReconciler("foo", "/bar", "foosec", client, mwhInformer.Lister(), secretInformer.Lister(), withContext) 1123 } else { 1124 r = NewReconciler("foo", "/bar", "foosec", client, mwhInformer.Lister(), secretInformer.Lister(), withContext, tt.selectorOption) 1125 } 1126 if diff := cmp.Diff(r.selector, tt.wantSelector); diff != "" { 1127 t.Errorf("Wrong selector configured. Got: %+v, want: %+v, diff: %v", r.selector, tt.wantSelector, diff) 1128 } 1129 }) 1130 } 1131 } 1132 1133 func TestBaseReconcile(t *testing.T) { 1134 table := TableTest{{ 1135 Name: "bad key", 1136 Key: "this/is/a/bad/key", 1137 }, { 1138 Name: "not found", 1139 Key: "its/missing", 1140 }, { 1141 Name: "add finalizer, add env var", 1142 Key: "foo/bar", 1143 Objects: []runtime.Object{ 1144 &TestBindable{ 1145 ObjectMeta: metav1.ObjectMeta{ 1146 Namespace: "foo", 1147 Name: "bar", 1148 }, 1149 Spec: TestBindableSpec{ 1150 BindingSpec: duckv1alpha1.BindingSpec{ 1151 Subject: tracker.Reference{ 1152 APIVersion: "apps/v1", 1153 Kind: "Deployment", 1154 Namespace: "foo", 1155 Name: "on-it", 1156 }, 1157 }, 1158 Foo: "asdfasdfasdfasdf", 1159 }, 1160 }, 1161 &appsv1.Deployment{ 1162 ObjectMeta: metav1.ObjectMeta{ 1163 Namespace: "foo", 1164 Name: "on-it", 1165 }, 1166 Spec: appsv1.DeploymentSpec{ 1167 Template: corev1.PodTemplateSpec{ 1168 Spec: corev1.PodSpec{ 1169 Containers: []corev1.Container{{ 1170 Name: "foo", 1171 Image: "busybox", 1172 }}, 1173 }, 1174 }, 1175 }, 1176 }, 1177 &corev1.Namespace{ 1178 ObjectMeta: metav1.ObjectMeta{ 1179 Name: "foo", 1180 }, 1181 }, 1182 }, 1183 WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ 1184 Object: mustTU(t, &TestBindable{ 1185 ObjectMeta: metav1.ObjectMeta{ 1186 Namespace: "foo", 1187 Name: "bar", 1188 }, 1189 Spec: TestBindableSpec{ 1190 BindingSpec: duckv1alpha1.BindingSpec{ 1191 Subject: tracker.Reference{ 1192 APIVersion: "apps/v1", 1193 Kind: "Deployment", 1194 Namespace: "foo", 1195 Name: "on-it", 1196 }, 1197 }, 1198 Foo: "asdfasdfasdfasdf", 1199 }, 1200 Status: TestBindableStatus{ 1201 Status: duckv1.Status{ 1202 Conditions: []apis.Condition{{ 1203 Type: "Ready", 1204 Status: "True", 1205 }}, 1206 }, 1207 }, 1208 }), 1209 }}, 1210 WantPatches: []clientgotesting.PatchActionImpl{ 1211 patchAddFinalizer("foo", "bar", "" /* resource version */), 1212 patchAddLabel("foo"), 1213 patchAddEnv("foo", "on-it", "asdfasdfasdfasdf"), 1214 }, 1215 }, { 1216 Name: "failure adding finalizer", 1217 Key: "foo/bar", 1218 WantErr: true, 1219 WithReactors: []clientgotesting.ReactionFunc{ 1220 InduceFailure("patch", "testbindables"), 1221 }, 1222 Objects: []runtime.Object{ 1223 &TestBindable{ 1224 ObjectMeta: metav1.ObjectMeta{ 1225 Namespace: "foo", 1226 Name: "bar", 1227 }, 1228 Spec: TestBindableSpec{ 1229 BindingSpec: duckv1alpha1.BindingSpec{ 1230 Subject: tracker.Reference{ 1231 APIVersion: "apps/v1", 1232 Kind: "Deployment", 1233 Namespace: "foo", 1234 Name: "on-it", 1235 }, 1236 }, 1237 Foo: "asdfasdfasdfasdf", 1238 }, 1239 Status: TestBindableStatus{ 1240 Status: duckv1.Status{ 1241 Conditions: []apis.Condition{{ 1242 Type: "Ready", 1243 Status: "True", 1244 }}, 1245 }, 1246 }, 1247 }, 1248 }, 1249 WantPatches: []clientgotesting.PatchActionImpl{ 1250 patchAddFinalizer("foo", "bar", "" /* resource version */), 1251 }, 1252 }, { 1253 Name: "failure patching deployment", 1254 Key: "foo/bar", 1255 WantErr: true, 1256 WithReactors: []clientgotesting.ReactionFunc{ 1257 InduceFailure("patch", "deployments"), 1258 }, 1259 Objects: []runtime.Object{ 1260 &TestBindable{ 1261 ObjectMeta: metav1.ObjectMeta{ 1262 Namespace: "foo", 1263 Name: "bar", 1264 Finalizers: []string{"testbindables.duck.knative.dev"}, 1265 }, 1266 Spec: TestBindableSpec{ 1267 BindingSpec: duckv1alpha1.BindingSpec{ 1268 Subject: tracker.Reference{ 1269 APIVersion: "apps/v1", 1270 Kind: "Deployment", 1271 Namespace: "foo", 1272 Name: "on-it", 1273 }, 1274 }, 1275 Foo: "asdfasdfasdfasdf", 1276 }, 1277 Status: TestBindableStatus{ 1278 Status: duckv1.Status{ 1279 Conditions: []apis.Condition{{ 1280 Type: "Ready", 1281 Status: "True", 1282 }}, 1283 }, 1284 }, 1285 }, 1286 &corev1.Namespace{ 1287 ObjectMeta: metav1.ObjectMeta{ 1288 Name: "foo", 1289 Labels: map[string]string{duck.BindingIncludeLabel: "true"}, 1290 }, 1291 }, 1292 &appsv1.Deployment{ 1293 ObjectMeta: metav1.ObjectMeta{ 1294 Namespace: "foo", 1295 Name: "on-it", 1296 }, 1297 Spec: appsv1.DeploymentSpec{ 1298 Template: corev1.PodTemplateSpec{ 1299 Spec: corev1.PodSpec{ 1300 Containers: []corev1.Container{{ 1301 Name: "foo", 1302 Image: "busybox", 1303 }}, 1304 }, 1305 }, 1306 }, 1307 }, 1308 }, 1309 WantPatches: []clientgotesting.PatchActionImpl{ 1310 patchAddEnv("foo", "on-it", "asdfasdfasdfasdf"), 1311 }, 1312 WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ 1313 Object: mustTU(t, &TestBindable{ 1314 ObjectMeta: metav1.ObjectMeta{ 1315 Namespace: "foo", 1316 Name: "bar", 1317 Finalizers: []string{"testbindables.duck.knative.dev"}, 1318 }, 1319 Spec: TestBindableSpec{ 1320 BindingSpec: duckv1alpha1.BindingSpec{ 1321 Subject: tracker.Reference{ 1322 APIVersion: "apps/v1", 1323 Kind: "Deployment", 1324 Namespace: "foo", 1325 Name: "on-it", 1326 }, 1327 }, 1328 Foo: "asdfasdfasdfasdf", 1329 }, 1330 Status: TestBindableStatus{ 1331 Status: duckv1.Status{ 1332 Conditions: []apis.Condition{{ 1333 Type: "Ready", 1334 Status: "False", 1335 Reason: "BindingFailed", 1336 Message: "failed binding subject on-it: inducing failure for patch deployments", 1337 }}, 1338 }, 1339 }, 1340 }), 1341 }}, 1342 }, { 1343 Name: "steady state", 1344 Key: "foo/bar", 1345 Objects: []runtime.Object{ 1346 &TestBindable{ 1347 ObjectMeta: metav1.ObjectMeta{ 1348 Namespace: "foo", 1349 Name: "bar", 1350 Finalizers: []string{"testbindables.duck.knative.dev"}, 1351 }, 1352 Spec: TestBindableSpec{ 1353 BindingSpec: duckv1alpha1.BindingSpec{ 1354 Subject: tracker.Reference{ 1355 APIVersion: "apps/v1", 1356 Kind: "Deployment", 1357 Namespace: "foo", 1358 Name: "on-it", 1359 }, 1360 }, 1361 Foo: "asdfasdfasdfasdf", 1362 }, 1363 Status: TestBindableStatus{ 1364 Status: duckv1.Status{ 1365 Conditions: []apis.Condition{{ 1366 Type: "Ready", 1367 Status: "True", 1368 }}, 1369 }, 1370 }, 1371 }, 1372 &appsv1.Deployment{ 1373 ObjectMeta: metav1.ObjectMeta{ 1374 Namespace: "foo", 1375 Name: "on-it", 1376 }, 1377 Spec: appsv1.DeploymentSpec{ 1378 Template: corev1.PodTemplateSpec{ 1379 Spec: corev1.PodSpec{ 1380 Containers: []corev1.Container{{ 1381 Name: "foo", 1382 Image: "busybox", 1383 Env: []corev1.EnvVar{{ 1384 Name: "FOO", 1385 Value: "asdfasdfasdfasdf", 1386 }}, 1387 }}, 1388 }, 1389 }, 1390 }, 1391 }, 1392 &corev1.Namespace{ 1393 ObjectMeta: metav1.ObjectMeta{ 1394 Name: "foo", 1395 }, 1396 }, 1397 }, 1398 WantPatches: []clientgotesting.PatchActionImpl{ 1399 patchAddLabel("foo"), 1400 }, 1401 }, { 1402 Name: "finalizing, but not our turn.", 1403 Key: "foo/bar", 1404 Objects: []runtime.Object{ 1405 &TestBindable{ 1406 ObjectMeta: metav1.ObjectMeta{ 1407 Namespace: "foo", 1408 Name: "bar", 1409 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 1410 Finalizers: []string{ 1411 "slow.your.role", 1412 "testbindables.duck.knative.dev", 1413 }, 1414 }, 1415 Spec: TestBindableSpec{ 1416 BindingSpec: duckv1alpha1.BindingSpec{ 1417 Subject: tracker.Reference{ 1418 APIVersion: "apps/v1", 1419 Kind: "Deployment", 1420 Namespace: "foo", 1421 Name: "on-it", 1422 }, 1423 }, 1424 Foo: "new value", 1425 }, 1426 Status: TestBindableStatus{ 1427 Status: duckv1.Status{ 1428 Conditions: []apis.Condition{{ 1429 Type: "Ready", 1430 Status: "True", 1431 }}, 1432 }, 1433 }, 1434 }, 1435 }, 1436 }, { 1437 Name: "finalizing, missing subject (remove the finalizer).", 1438 Key: "foo/bar", 1439 Objects: []runtime.Object{ 1440 &TestBindable{ 1441 ObjectMeta: metav1.ObjectMeta{ 1442 Namespace: "foo", 1443 Name: "bar", 1444 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 1445 Finalizers: []string{ 1446 "testbindables.duck.knative.dev", 1447 }, 1448 }, 1449 Spec: TestBindableSpec{ 1450 BindingSpec: duckv1alpha1.BindingSpec{ 1451 Subject: tracker.Reference{ 1452 APIVersion: "apps/v1", 1453 Kind: "Deployment", 1454 Namespace: "foo", 1455 Name: "on-it", 1456 }, 1457 }, 1458 Foo: "new value", 1459 }, 1460 Status: TestBindableStatus{ 1461 Status: duckv1.Status{ 1462 Conditions: []apis.Condition{{ 1463 Type: "Ready", 1464 Status: "False", 1465 Reason: "SubjectMissing", 1466 Message: `deployments.apps "on-it" not found`, 1467 }}, 1468 }, 1469 }, 1470 }, 1471 }, 1472 WantPatches: []clientgotesting.PatchActionImpl{ 1473 patchRemoveFinalizer("foo", "bar", "" /* resource version */), 1474 }, 1475 }, { 1476 Name: "finalizing forbidden subject", 1477 Key: "foo/bar", 1478 WithReactors: []clientgotesting.ReactionFunc{ 1479 // This will cause the duck informer factory to return a Forbidden error on Get(gvr) 1480 // The informer calls list to ensure the type exists - this will 1481 func(a clientgotesting.Action) (handled bool, ret runtime.Object, err error) { 1482 if a.Matches("list", "deployments") { 1483 return true, nil, apierrs.NewForbidden(schema.GroupResource{}, "", errors.New("some-error")) 1484 } 1485 return false, nil, nil 1486 }, 1487 }, 1488 Objects: []runtime.Object{ 1489 &TestBindable{ 1490 ObjectMeta: metav1.ObjectMeta{ 1491 Namespace: "foo", 1492 Name: "bar", 1493 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 1494 Finalizers: []string{"testbindables.duck.knative.dev"}, 1495 }, 1496 Spec: TestBindableSpec{ 1497 BindingSpec: duckv1alpha1.BindingSpec{ 1498 Subject: tracker.Reference{ 1499 APIVersion: "apps/v1", 1500 Kind: "Deployment", 1501 Namespace: "foo", 1502 Name: "on-it", 1503 }, 1504 }, 1505 Foo: "new value", 1506 }, 1507 Status: TestBindableStatus{ 1508 Status: duckv1.Status{ 1509 Conditions: []apis.Condition{{ 1510 Type: "Ready", 1511 Status: "True", 1512 }}, 1513 }, 1514 }, 1515 }, 1516 }, 1517 WantPatches: []clientgotesting.PatchActionImpl{ 1518 patchRemoveFinalizer("foo", "bar", "" /* resource version */), 1519 }, 1520 WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ 1521 Object: mustTU(t, &TestBindable{ 1522 ObjectMeta: metav1.ObjectMeta{ 1523 Namespace: "foo", 1524 Name: "bar", 1525 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 1526 Finalizers: []string{"testbindables.duck.knative.dev"}, 1527 }, 1528 Spec: TestBindableSpec{ 1529 BindingSpec: duckv1alpha1.BindingSpec{ 1530 Subject: tracker.Reference{ 1531 APIVersion: "apps/v1", 1532 Kind: "Deployment", 1533 Namespace: "foo", 1534 Name: "on-it", 1535 }, 1536 }, 1537 Foo: "new value", 1538 }, 1539 Status: TestBindableStatus{ 1540 Status: duckv1.Status{ 1541 Conditions: []apis.Condition{{ 1542 Type: "Ready", 1543 Status: "False", 1544 Reason: "SubjectUnavailable", 1545 // prefix comes from apierrs.NewForbidden 1546 Message: "forbidden: some-error", 1547 }}, 1548 }, 1549 }, 1550 }), 1551 }}, 1552 }, { 1553 Name: "finalizing (unbind, and remove the finalizer)", 1554 Key: "foo/bar", 1555 Objects: []runtime.Object{ 1556 &TestBindable{ 1557 ObjectMeta: metav1.ObjectMeta{ 1558 Namespace: "foo", 1559 Name: "bar", 1560 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 1561 Finalizers: []string{ 1562 "testbindables.duck.knative.dev", 1563 }, 1564 }, 1565 Spec: TestBindableSpec{ 1566 BindingSpec: duckv1alpha1.BindingSpec{ 1567 Subject: tracker.Reference{ 1568 APIVersion: "apps/v1", 1569 Kind: "Deployment", 1570 Namespace: "foo", 1571 Name: "on-it", 1572 }, 1573 }, 1574 Foo: "value", 1575 }, 1576 Status: TestBindableStatus{ 1577 Status: duckv1.Status{ 1578 Conditions: []apis.Condition{{ 1579 Type: "Ready", 1580 Status: "True", 1581 }}, 1582 }, 1583 }, 1584 }, 1585 &appsv1.Deployment{ 1586 ObjectMeta: metav1.ObjectMeta{ 1587 Namespace: "foo", 1588 Name: "on-it", 1589 }, 1590 Spec: appsv1.DeploymentSpec{ 1591 Template: corev1.PodTemplateSpec{ 1592 Spec: corev1.PodSpec{ 1593 Containers: []corev1.Container{{ 1594 Name: "foo", 1595 Image: "busybox", 1596 Env: []corev1.EnvVar{{ 1597 Name: "FOO", 1598 Value: "value", 1599 }}, 1600 }}, 1601 }, 1602 }, 1603 }, 1604 }, 1605 &corev1.Namespace{ 1606 ObjectMeta: metav1.ObjectMeta{ 1607 Name: "foo", 1608 }, 1609 }, 1610 }, 1611 WantPatches: []clientgotesting.PatchActionImpl{ 1612 patchAddLabel("foo"), 1613 patchRemoveEnv("foo", "on-it"), 1614 patchRemoveFinalizer("foo", "bar", "" /* resource version */), 1615 }, 1616 }, { 1617 Name: "add finalizer, add env var (via selector)", 1618 Key: "foo/bar", 1619 Objects: []runtime.Object{ 1620 &TestBindable{ 1621 ObjectMeta: metav1.ObjectMeta{ 1622 Namespace: "foo", 1623 Name: "bar", 1624 }, 1625 Spec: TestBindableSpec{ 1626 BindingSpec: duckv1alpha1.BindingSpec{ 1627 Subject: tracker.Reference{ 1628 APIVersion: "apps/v1", 1629 Kind: "Deployment", 1630 Namespace: "foo", 1631 Selector: &metav1.LabelSelector{ 1632 MatchLabels: map[string]string{}, 1633 }, 1634 }, 1635 }, 1636 Foo: "asdfasdfasdfasdf", 1637 }, 1638 Status: TestBindableStatus{ 1639 Status: duckv1.Status{ 1640 Conditions: []apis.Condition{{ 1641 Type: "Ready", 1642 Status: "True", 1643 }}, 1644 }, 1645 }, 1646 }, 1647 &appsv1.Deployment{ 1648 ObjectMeta: metav1.ObjectMeta{ 1649 Namespace: "foo", 1650 Name: "on-it", 1651 }, 1652 Spec: appsv1.DeploymentSpec{ 1653 Template: corev1.PodTemplateSpec{ 1654 Spec: corev1.PodSpec{ 1655 Containers: []corev1.Container{{ 1656 Name: "foo", 1657 Image: "busybox", 1658 }}, 1659 }, 1660 }, 1661 }, 1662 }, 1663 &corev1.Namespace{ 1664 ObjectMeta: metav1.ObjectMeta{ 1665 Name: "foo", 1666 }, 1667 }, 1668 }, 1669 WantPatches: []clientgotesting.PatchActionImpl{ 1670 patchAddFinalizer("foo", "bar", "" /* resource version */), 1671 patchAddLabel("foo"), 1672 patchAddEnv("foo", "on-it", "asdfasdfasdfasdf"), 1673 }, 1674 }, { 1675 Name: "steady state (via selector)", 1676 Key: "foo/bar", 1677 Objects: []runtime.Object{ 1678 &TestBindable{ 1679 ObjectMeta: metav1.ObjectMeta{ 1680 Namespace: "foo", 1681 Name: "bar", 1682 Finalizers: []string{"testbindables.duck.knative.dev"}, 1683 }, 1684 Spec: TestBindableSpec{ 1685 BindingSpec: duckv1alpha1.BindingSpec{ 1686 Subject: tracker.Reference{ 1687 APIVersion: "apps/v1", 1688 Kind: "Deployment", 1689 Namespace: "foo", 1690 Selector: &metav1.LabelSelector{ 1691 MatchLabels: map[string]string{}, 1692 }, 1693 }, 1694 }, 1695 Foo: "asdfasdfasdfasdf", 1696 }, 1697 Status: TestBindableStatus{ 1698 Status: duckv1.Status{ 1699 Conditions: []apis.Condition{{ 1700 Type: "Ready", 1701 Status: "True", 1702 }}, 1703 }, 1704 }, 1705 }, 1706 &appsv1.Deployment{ 1707 ObjectMeta: metav1.ObjectMeta{ 1708 Namespace: "foo", 1709 Name: "on-it", 1710 }, 1711 Spec: appsv1.DeploymentSpec{ 1712 Template: corev1.PodTemplateSpec{ 1713 Spec: corev1.PodSpec{ 1714 Containers: []corev1.Container{{ 1715 Name: "foo", 1716 Image: "busybox", 1717 Env: []corev1.EnvVar{{ 1718 Name: "FOO", 1719 Value: "asdfasdfasdfasdf", 1720 }}, 1721 }}, 1722 }, 1723 }, 1724 }, 1725 }, 1726 &corev1.Namespace{ 1727 ObjectMeta: metav1.ObjectMeta{ 1728 Name: "foo", 1729 }, 1730 }, 1731 }, 1732 WantPatches: []clientgotesting.PatchActionImpl{ 1733 patchAddLabel("foo"), 1734 }, 1735 }, { 1736 Name: "finalizing, missing subject (remove the finalizer, via selector)", 1737 Key: "foo/bar", 1738 Objects: []runtime.Object{ 1739 &TestBindable{ 1740 ObjectMeta: metav1.ObjectMeta{ 1741 Namespace: "foo", 1742 Name: "bar", 1743 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 1744 Finalizers: []string{ 1745 "testbindables.duck.knative.dev", 1746 }, 1747 }, 1748 Spec: TestBindableSpec{ 1749 BindingSpec: duckv1alpha1.BindingSpec{ 1750 Subject: tracker.Reference{ 1751 APIVersion: "apps/v1", 1752 Kind: "Deployment", 1753 Namespace: "foo", 1754 Selector: &metav1.LabelSelector{ 1755 MatchLabels: map[string]string{}, 1756 }, 1757 }, 1758 }, 1759 Foo: "new value", 1760 }, 1761 Status: TestBindableStatus{ 1762 Status: duckv1.Status{ 1763 Conditions: []apis.Condition{{ 1764 Type: "Ready", 1765 Status: "True", 1766 }}, 1767 }, 1768 }, 1769 }, 1770 &corev1.Namespace{ 1771 ObjectMeta: metav1.ObjectMeta{ 1772 Name: "foo", 1773 }, 1774 }, 1775 }, 1776 WantPatches: []clientgotesting.PatchActionImpl{ 1777 patchAddLabel("foo"), 1778 patchRemoveFinalizer("foo", "bar", "" /* resource version */), 1779 }, 1780 }, { 1781 Name: "finalizing (unbind, and remove the finalizer, via selector)", 1782 Key: "foo/bar", 1783 Objects: []runtime.Object{ 1784 &TestBindable{ 1785 ObjectMeta: metav1.ObjectMeta{ 1786 Namespace: "foo", 1787 Name: "bar", 1788 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 1789 Finalizers: []string{ 1790 "testbindables.duck.knative.dev", 1791 }, 1792 }, 1793 Spec: TestBindableSpec{ 1794 BindingSpec: duckv1alpha1.BindingSpec{ 1795 Subject: tracker.Reference{ 1796 APIVersion: "apps/v1", 1797 Kind: "Deployment", 1798 Namespace: "foo", 1799 Selector: &metav1.LabelSelector{ 1800 MatchLabels: map[string]string{}, 1801 }, 1802 }, 1803 }, 1804 Foo: "value", 1805 }, 1806 Status: TestBindableStatus{ 1807 Status: duckv1.Status{ 1808 Conditions: []apis.Condition{{ 1809 Type: "Ready", 1810 Status: "True", 1811 }}, 1812 }, 1813 }, 1814 }, 1815 &appsv1.Deployment{ 1816 ObjectMeta: metav1.ObjectMeta{ 1817 Namespace: "foo", 1818 Name: "on-it", 1819 }, 1820 Spec: appsv1.DeploymentSpec{ 1821 Template: corev1.PodTemplateSpec{ 1822 Spec: corev1.PodSpec{ 1823 Containers: []corev1.Container{{ 1824 Name: "foo", 1825 Image: "busybox", 1826 Env: []corev1.EnvVar{{ 1827 Name: "FOO", 1828 Value: "value", 1829 }}, 1830 }}, 1831 }, 1832 }, 1833 }, 1834 }, 1835 &corev1.Namespace{ 1836 ObjectMeta: metav1.ObjectMeta{ 1837 Name: "foo", 1838 }, 1839 }, 1840 }, 1841 WantPatches: []clientgotesting.PatchActionImpl{ 1842 patchAddLabel("foo"), 1843 patchRemoveEnv("foo", "on-it"), 1844 patchRemoveFinalizer("foo", "bar", "" /* resource version */), 1845 }, 1846 }, { 1847 Name: "failure updating status", 1848 Key: "foo/bar", 1849 WantErr: true, 1850 WithReactors: []clientgotesting.ReactionFunc{ 1851 InduceFailure("update", "testbindables"), 1852 }, 1853 Objects: []runtime.Object{ 1854 &TestBindable{ 1855 ObjectMeta: metav1.ObjectMeta{ 1856 Namespace: "foo", 1857 Name: "bar", 1858 Finalizers: []string{"testbindables.duck.knative.dev"}, 1859 }, 1860 Spec: TestBindableSpec{ 1861 BindingSpec: duckv1alpha1.BindingSpec{ 1862 Subject: tracker.Reference{ 1863 APIVersion: "apps/v1", 1864 Kind: "Deployment", 1865 Namespace: "foo", 1866 Name: "on-it", 1867 }, 1868 }, 1869 Foo: "asdfasdfasdfasdf", 1870 }, 1871 }, 1872 &appsv1.Deployment{ 1873 ObjectMeta: metav1.ObjectMeta{ 1874 Namespace: "foo", 1875 Name: "on-it", 1876 }, 1877 Spec: appsv1.DeploymentSpec{ 1878 Template: corev1.PodTemplateSpec{ 1879 Spec: corev1.PodSpec{ 1880 Containers: []corev1.Container{{ 1881 Name: "foo", 1882 Image: "busybox", 1883 Env: []corev1.EnvVar{{ 1884 Name: "FOO", 1885 Value: "asdfasdfasdfasdf", 1886 }}, 1887 }}, 1888 }, 1889 }, 1890 }, 1891 }, 1892 &corev1.Namespace{ 1893 ObjectMeta: metav1.ObjectMeta{ 1894 Name: "foo", 1895 }, 1896 }, 1897 }, 1898 WantPatches: []clientgotesting.PatchActionImpl{ 1899 patchAddLabel("foo"), 1900 }, 1901 WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ 1902 Object: mustTU(t, &TestBindable{ 1903 ObjectMeta: metav1.ObjectMeta{ 1904 Namespace: "foo", 1905 Name: "bar", 1906 Finalizers: []string{"testbindables.duck.knative.dev"}, 1907 }, 1908 Spec: TestBindableSpec{ 1909 BindingSpec: duckv1alpha1.BindingSpec{ 1910 Subject: tracker.Reference{ 1911 APIVersion: "apps/v1", 1912 Kind: "Deployment", 1913 Namespace: "foo", 1914 Name: "on-it", 1915 }, 1916 }, 1917 Foo: "asdfasdfasdfasdf", 1918 }, 1919 Status: TestBindableStatus{ 1920 Status: duckv1.Status{ 1921 Conditions: []apis.Condition{{ 1922 Type: "Ready", 1923 Status: "True", 1924 }}, 1925 }, 1926 }, 1927 }), 1928 }}, 1929 }, { 1930 Name: "finalizing (error during unbind)", 1931 Key: "foo/bar", 1932 WantErr: true, 1933 WithReactors: []clientgotesting.ReactionFunc{ 1934 InduceFailure("patch", "deployments"), 1935 }, 1936 Objects: []runtime.Object{ 1937 &TestBindable{ 1938 ObjectMeta: metav1.ObjectMeta{ 1939 Namespace: "foo", 1940 Name: "bar", 1941 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 1942 Finalizers: []string{ 1943 "testbindables.duck.knative.dev", 1944 }, 1945 }, 1946 Spec: TestBindableSpec{ 1947 BindingSpec: duckv1alpha1.BindingSpec{ 1948 Subject: tracker.Reference{ 1949 APIVersion: "apps/v1", 1950 Kind: "Deployment", 1951 Namespace: "foo", 1952 Selector: &metav1.LabelSelector{ 1953 MatchLabels: map[string]string{}, 1954 }, 1955 }, 1956 }, 1957 Foo: "value", 1958 }, 1959 Status: TestBindableStatus{ 1960 Status: duckv1.Status{ 1961 Conditions: []apis.Condition{{ 1962 Type: "Ready", 1963 Status: "False", 1964 Reason: "BindingFailed", 1965 Message: "failed binding subject on-it: inducing failure for patch deployments", 1966 }}, 1967 }, 1968 }, 1969 }, 1970 &appsv1.Deployment{ 1971 ObjectMeta: metav1.ObjectMeta{ 1972 Namespace: "foo", 1973 Name: "on-it", 1974 }, 1975 Spec: appsv1.DeploymentSpec{ 1976 Template: corev1.PodTemplateSpec{ 1977 Spec: corev1.PodSpec{ 1978 Containers: []corev1.Container{{ 1979 Name: "foo", 1980 Image: "busybox", 1981 Env: []corev1.EnvVar{{ 1982 Name: "FOO", 1983 Value: "value", 1984 }}, 1985 }}, 1986 }, 1987 }, 1988 }, 1989 }, 1990 &corev1.Namespace{ 1991 ObjectMeta: metav1.ObjectMeta{ 1992 Name: "foo", 1993 }, 1994 }, 1995 }, 1996 WantPatches: []clientgotesting.PatchActionImpl{ 1997 patchAddLabel("foo"), 1998 patchRemoveEnv("foo", "on-it"), 1999 }, 2000 }} 2001 2002 table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler { 2003 gvr := SchemeGroupVersion.WithResource("testbindables") 2004 ctx = podspecable.WithDuck(ctx) 2005 2006 dc := dynamicclient.Get(ctx) 2007 2008 return &BaseReconciler{ 2009 GVR: gvr, 2010 2011 DynamicClient: dc, 2012 Factory: podspecable.Get(ctx), 2013 2014 Tracker: &FakeTracker{}, 2015 2016 Recorder: record.NewFakeRecorder(20), 2017 2018 Get: func(namespace, name string) (Bindable, error) { 2019 for _, elt := range listers.GetDuckObjects() { 2020 b, ok := elt.(*TestBindable) 2021 if !ok { 2022 continue 2023 } 2024 if b.Namespace != namespace || b.Name != name { 2025 continue 2026 } 2027 return b, nil 2028 } 2029 return nil, apierrs.NewNotFound(gvr.GroupResource(), name) 2030 }, 2031 NamespaceLister: listers.GetNamespaceLister(), 2032 } 2033 })) 2034 } 2035 2036 func TestBaseReconcileWithSubResourcesReconciler(t *testing.T) { 2037 table := TableTest{ 2038 { 2039 Name: "create new subresource", 2040 Key: "foo/bar", 2041 Objects: []runtime.Object{ 2042 &TestBindable{ 2043 ObjectMeta: metav1.ObjectMeta{ 2044 Namespace: "foo", 2045 Name: "bar", 2046 Finalizers: []string{"testbindables.duck.knative.dev"}, 2047 }, 2048 Spec: TestBindableSpec{ 2049 BindingSpec: duckv1alpha1.BindingSpec{ 2050 Subject: tracker.Reference{ 2051 APIVersion: "apps/v1", 2052 Kind: "Deployment", 2053 Namespace: "foo", 2054 Name: "on-it", 2055 }, 2056 }, 2057 Foo: "asdfasdfasdfasdf", 2058 }, 2059 Status: TestBindableStatus{ 2060 Status: duckv1.Status{ 2061 Conditions: []apis.Condition{{ 2062 Type: "Ready", 2063 Status: "True", 2064 }}, 2065 }, 2066 }, 2067 }, 2068 &appsv1.Deployment{ 2069 ObjectMeta: metav1.ObjectMeta{ 2070 Namespace: "foo", 2071 Name: "on-it", 2072 }, 2073 Spec: appsv1.DeploymentSpec{ 2074 Template: corev1.PodTemplateSpec{ 2075 Spec: corev1.PodSpec{ 2076 Containers: []corev1.Container{{ 2077 Name: "foo", 2078 Image: "busybox", 2079 Env: []corev1.EnvVar{{ 2080 Name: "FOO", 2081 Value: "asdfasdfasdfasdf", 2082 }}, 2083 }}, 2084 }, 2085 }, 2086 }, 2087 }, 2088 &corev1.Namespace{ 2089 ObjectMeta: metav1.ObjectMeta{ 2090 Name: "foo", 2091 }, 2092 }, 2093 }, 2094 WantPatches: []clientgotesting.PatchActionImpl{ 2095 patchAddLabel("foo"), 2096 }, 2097 WantCreates: []runtime.Object{ 2098 &corev1.ConfigMap{ 2099 ObjectMeta: metav1.ObjectMeta{ 2100 Name: "bar", 2101 }, 2102 }, 2103 }, 2104 }, 2105 { 2106 Name: "delete resource and subresource", 2107 Key: "foo/bar", 2108 Objects: []runtime.Object{ 2109 &TestBindable{ 2110 ObjectMeta: metav1.ObjectMeta{ 2111 Namespace: "foo", 2112 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 2113 Name: "bar", 2114 Finalizers: []string{"testbindables.duck.knative.dev"}, 2115 }, 2116 Spec: TestBindableSpec{ 2117 BindingSpec: duckv1alpha1.BindingSpec{ 2118 Subject: tracker.Reference{ 2119 APIVersion: "apps/v1", 2120 Kind: "Deployment", 2121 Namespace: "foo", 2122 Name: "on-it", 2123 }, 2124 }, 2125 Foo: "asdfasdfasdfasdf", 2126 }, 2127 Status: TestBindableStatus{ 2128 Status: duckv1.Status{ 2129 Conditions: []apis.Condition{{ 2130 Type: "Ready", 2131 Status: "True", 2132 }}, 2133 }, 2134 }, 2135 }, 2136 &appsv1.Deployment{ 2137 ObjectMeta: metav1.ObjectMeta{ 2138 Namespace: "foo", 2139 Name: "on-it", 2140 }, 2141 Spec: appsv1.DeploymentSpec{ 2142 Template: corev1.PodTemplateSpec{ 2143 Spec: corev1.PodSpec{ 2144 Containers: []corev1.Container{{ 2145 Name: "foo", 2146 Image: "busybox", 2147 Env: []corev1.EnvVar{{ 2148 Name: "FOO", 2149 Value: "asdfasdfasdfasdf", 2150 }}, 2151 }}, 2152 }, 2153 }, 2154 }, 2155 }, 2156 &corev1.Namespace{ 2157 ObjectMeta: metav1.ObjectMeta{ 2158 Name: "foo", 2159 }, 2160 }, 2161 &corev1.ConfigMap{ 2162 ObjectMeta: metav1.ObjectMeta{ 2163 Name: "bar", 2164 Namespace: "foo", 2165 }, 2166 }, 2167 }, 2168 WantPatches: []clientgotesting.PatchActionImpl{ 2169 patchAddLabel("foo"), 2170 patchRemoveEnv("foo", "on-it"), 2171 patchRemoveFinalizer("foo", "bar", "" /* resource version */), 2172 }, 2173 WantDeletes: []clientgotesting.DeleteActionImpl{ 2174 deletedConfigmap("foo", "bar"), 2175 }, 2176 }, 2177 } 2178 2179 table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler { 2180 gvr := SchemeGroupVersion.WithResource("testbindables") 2181 ctx = podspecable.WithDuck(ctx) 2182 2183 dc := dynamicclient.Get(ctx) 2184 2185 kc := kubeclient.Get(ctx) 2186 srr := &fakeSubResourcesReconciler{ 2187 client: kc, 2188 } 2189 return &BaseReconciler{ 2190 GVR: gvr, 2191 2192 DynamicClient: dc, 2193 Factory: podspecable.Get(ctx), 2194 2195 Tracker: &FakeTracker{}, 2196 2197 Recorder: record.NewFakeRecorder(20), 2198 2199 Get: func(namespace, name string) (Bindable, error) { 2200 for _, elt := range listers.GetDuckObjects() { 2201 b, ok := elt.(*TestBindable) 2202 if !ok { 2203 continue 2204 } 2205 if b.Namespace != namespace || b.Name != name { 2206 continue 2207 } 2208 return b, nil 2209 } 2210 return nil, apierrs.NewNotFound(gvr.GroupResource(), name) 2211 }, 2212 NamespaceLister: listers.GetNamespaceLister(), 2213 2214 SubResourcesReconciler: srr, 2215 } 2216 })) 2217 } 2218 2219 func mustTU(t *testing.T, ro duck.OneOfOurs) *unstructured.Unstructured { 2220 u, err := duck.ToUnstructured(ro) 2221 if err != nil { 2222 t.Fatalf("ToUnstructured(%+v) = %v", ro, err) 2223 } 2224 return u 2225 } 2226 2227 func patchAddLabel(namespace string) clientgotesting.PatchActionImpl { 2228 action := clientgotesting.PatchActionImpl{} 2229 resource := schema.GroupVersionResource{ 2230 Group: "", 2231 Version: "v1", 2232 Resource: "namespaces", 2233 } 2234 actionImpl := clientgotesting.ActionImpl{ 2235 Namespace: namespace, 2236 Verb: "patch", 2237 Resource: resource, 2238 Subresource: "", 2239 } 2240 action.ActionImpl = actionImpl 2241 action.Name = namespace 2242 action.PatchType = types.MergePatchType 2243 2244 patch, _ := json.Marshal(jsonLabelPatch) 2245 action.Patch = patch 2246 return action 2247 } 2248 2249 func patchAddFinalizer(namespace, name, resourceVersion string) clientgotesting.PatchActionImpl { 2250 action := clientgotesting.PatchActionImpl{} 2251 action.Name = name 2252 action.Namespace = namespace 2253 2254 patch := fmt.Sprintf(`{"metadata":{"finalizers":["testbindables.duck.knative.dev"],"resourceVersion":%q}}`, resourceVersion) 2255 2256 action.Patch = []byte(patch) 2257 return action 2258 } 2259 2260 func patchRemoveFinalizer(namespace, name, resourceVersion string) clientgotesting.PatchActionImpl { 2261 action := clientgotesting.PatchActionImpl{} 2262 action.Name = name 2263 action.Namespace = namespace 2264 2265 patch := fmt.Sprintf(`{"metadata":{"finalizers":[],"resourceVersion":%q}}`, resourceVersion) 2266 2267 action.Patch = []byte(patch) 2268 return action 2269 } 2270 2271 func patchAddEnv(namespace, name, value string) clientgotesting.PatchActionImpl { 2272 action := clientgotesting.PatchActionImpl{} 2273 action.Name = name 2274 action.Namespace = namespace 2275 2276 patch := fmt.Sprintf(`[{"op":"add","path":"/spec/template/spec/containers/0/env","value":[{"name":"FOO","value":%q}]}]`, value) 2277 2278 action.Patch = []byte(patch) 2279 return action 2280 } 2281 2282 func patchRemoveEnv(namespace, name string) clientgotesting.PatchActionImpl { 2283 action := clientgotesting.PatchActionImpl{} 2284 action.Name = name 2285 action.Namespace = namespace 2286 2287 patch := `[{"op":"remove","path":"/spec/template/spec/containers/0/env"}]` 2288 2289 action.Patch = []byte(patch) 2290 return action 2291 } 2292 2293 func deletedConfigmap(namespace string, name string) clientgotesting.DeleteActionImpl { 2294 action := clientgotesting.DeleteActionImpl{} 2295 resource := schema.GroupVersionResource{ 2296 Group: "core", 2297 Version: "v1", 2298 Resource: "configmaps", 2299 } 2300 actionImpl := clientgotesting.ActionImpl{ 2301 Namespace: namespace, 2302 Verb: "delete", 2303 Resource: resource, 2304 Subresource: "", 2305 } 2306 action.ActionImpl = actionImpl 2307 action.Name = name 2308 return action 2309 } 2310 2311 type fakeSubResourcesReconciler struct { 2312 client *fakek8s.Clientset 2313 } 2314 2315 func (d *fakeSubResourcesReconciler) Reconcile(ctx context.Context, fb Bindable) error { 2316 cfg := corev1.ConfigMap{ 2317 ObjectMeta: metav1.ObjectMeta{ 2318 Name: fb.GetName(), 2319 }, 2320 } 2321 2322 _, err := d.client.CoreV1().ConfigMaps(fb.GetNamespace()).Create(ctx, &cfg, metav1.CreateOptions{}) 2323 return err 2324 } 2325 2326 func (d *fakeSubResourcesReconciler) ReconcileDeletion(ctx context.Context, fb Bindable) error { 2327 return d.client.CoreV1().ConfigMaps(fb.GetNamespace()).Delete(ctx, fb.GetName(), metav1.DeleteOptions{}) 2328 }