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