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  }