@@ -18,15 +18,27 @@ package controller_test
18
18
19
19
import (
20
20
"context"
21
+ goerrors "errors"
22
+ "path/filepath"
23
+ "time"
21
24
22
25
appsv1 "k8s.io/api/apps/v1"
23
26
corev1 "k8s.io/api/core/v1"
27
+ apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
28
+ "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
29
+ "k8s.io/apimachinery/pkg/api/errors"
30
+ "k8s.io/apimachinery/pkg/api/meta"
24
31
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32
+ "k8s.io/apimachinery/pkg/runtime"
25
33
"k8s.io/apimachinery/pkg/runtime/schema"
26
34
"k8s.io/apimachinery/pkg/types"
35
+ "k8s.io/apimachinery/pkg/util/wait"
27
36
"sigs.k8s.io/controller-runtime/pkg/cache"
37
+ "sigs.k8s.io/controller-runtime/pkg/client"
28
38
"sigs.k8s.io/controller-runtime/pkg/controller"
29
39
"sigs.k8s.io/controller-runtime/pkg/controller/controllertest"
40
+ foo "sigs.k8s.io/controller-runtime/pkg/controller/testdata/foo/v1"
41
+ "sigs.k8s.io/controller-runtime/pkg/envtest"
30
42
"sigs.k8s.io/controller-runtime/pkg/handler"
31
43
"sigs.k8s.io/controller-runtime/pkg/reconcile"
32
44
"sigs.k8s.io/controller-runtime/pkg/source"
@@ -173,6 +185,198 @@ var _ = Describe("controller", func() {
173
185
close (done )
174
186
}, 5 )
175
187
})
188
+
189
+ It ("should reconcile when the CRD is installed, uninstalled, reinstalled" , func (done Done ) {
190
+ By ("Initializing the scheme and crd" )
191
+ s := runtime .NewScheme ()
192
+ err := v1beta1 .AddToScheme (s )
193
+ Expect (err ).NotTo (HaveOccurred ())
194
+ err = apiextensionsv1 .AddToScheme (s )
195
+ Expect (err ).NotTo (HaveOccurred ())
196
+ err = foo .AddToScheme (s )
197
+ Expect (err ).NotTo (HaveOccurred ())
198
+ options := manager.Options {Scheme : s }
199
+
200
+ By ("Creating the Manager" )
201
+ cm , err := manager .New (cfg , options )
202
+ Expect (err ).NotTo (HaveOccurred ())
203
+
204
+ By ("Creating the Controller" )
205
+ instance , err := controller .New ("foo-controller" , cm , controller.Options {
206
+ Reconciler : reconcile .Func (
207
+ func (_ context.Context , request reconcile.Request ) (reconcile.Result , error ) {
208
+ reconciled <- request
209
+ return reconcile.Result {}, nil
210
+ }),
211
+ })
212
+ Expect (err ).NotTo (HaveOccurred ())
213
+
214
+ By ("Watching foo CRD as conditional kinds" )
215
+ f := & foo.Foo {}
216
+ gvk := schema.GroupVersionKind {
217
+ Group : "bar.example.com" ,
218
+ Version : "v1" ,
219
+ Kind : "Foo" ,
220
+ }
221
+ Expect (err ).NotTo (HaveOccurred ())
222
+ existsInDiscovery := func () bool {
223
+ resources , err := clientset .Discovery ().ServerResourcesForGroupVersion (gvk .GroupVersion ().String ())
224
+ if err != nil {
225
+ return false
226
+ }
227
+ for _ , res := range resources .APIResources {
228
+ if res .Kind == gvk .Kind {
229
+ return true
230
+ }
231
+ }
232
+ return false
233
+ }
234
+ err = instance .Watch (& source.ConditionalKind {Kind : source.Kind {Type : f }, DiscoveryCheck : existsInDiscovery }, & handler.EnqueueRequestForObject {})
235
+ Expect (err ).NotTo (HaveOccurred ())
236
+
237
+ By ("Starting the Manager" )
238
+ ctx , cancel := context .WithCancel (context .Background ())
239
+ defer cancel ()
240
+ go func () {
241
+ defer GinkgoRecover ()
242
+ Expect (cm .Start (ctx )).NotTo (HaveOccurred ())
243
+ }()
244
+
245
+ testFoo := & foo.Foo {
246
+ TypeMeta : metav1.TypeMeta {Kind : gvk .Kind , APIVersion : gvk .GroupVersion ().String ()},
247
+ ObjectMeta : metav1.ObjectMeta {Name : "test-foo" , Namespace : "default" },
248
+ }
249
+
250
+ expectedReconcileRequest := reconcile.Request {NamespacedName : types.NamespacedName {
251
+ Name : "test-foo" ,
252
+ Namespace : "default" ,
253
+ }}
254
+ _ = expectedReconcileRequest
255
+
256
+ By ("Failing to create a foo object if the crd isn't installed" )
257
+ kindMatchErr := & meta.NoKindMatchError {}
258
+ err = cm .GetClient ().Create (ctx , testFoo )
259
+ Expect (goerrors .As (err , & kindMatchErr )).To (BeTrue ())
260
+
261
+ By ("Installing the CRD" )
262
+ crdPath := filepath .Join ("." , "testdata" , "foo" , "foocrd.yaml" )
263
+ crdOpts := envtest.CRDInstallOptions {
264
+ Paths : []string {crdPath },
265
+ MaxTime : 50 * time .Millisecond ,
266
+ PollInterval : 15 * time .Millisecond ,
267
+ }
268
+ crds , err := envtest .InstallCRDs (cfg , crdOpts )
269
+ Expect (err ).NotTo (HaveOccurred ())
270
+ Expect (len (crds )).To (Equal (1 ))
271
+
272
+ By ("Expecting to find the CRD" )
273
+ crdv1 := & apiextensionsv1.CustomResourceDefinition {}
274
+ err = cm .GetClient ().Get (context .TODO (), types.NamespacedName {Name : "foos.bar.example.com" }, crdv1 )
275
+ Expect (err ).NotTo (HaveOccurred ())
276
+ Expect (crdv1 .Spec .Names .Kind ).To (Equal ("Foo" ))
277
+
278
+ err = envtest .WaitForCRDs (cfg , []client.Object {
279
+ & v1beta1.CustomResourceDefinition {
280
+ Spec : v1beta1.CustomResourceDefinitionSpec {
281
+ Group : "bar.example.com" ,
282
+ Names : v1beta1.CustomResourceDefinitionNames {
283
+ Kind : "Foo" ,
284
+ Plural : "foos" ,
285
+ },
286
+ Versions : []v1beta1.CustomResourceDefinitionVersion {
287
+ {
288
+ Name : "v1" ,
289
+ Storage : true ,
290
+ Served : true ,
291
+ },
292
+ }},
293
+ },
294
+ },
295
+ crdOpts ,
296
+ )
297
+ Expect (err ).NotTo (HaveOccurred ())
298
+
299
+ By ("Invoking Reconcile for foo Create" )
300
+ err = cm .GetClient ().Create (ctx , testFoo )
301
+ Expect (err ).NotTo (HaveOccurred ())
302
+ Expect (<- reconciled ).To (Equal (expectedReconcileRequest ))
303
+
304
+ By ("Uninstalling the CRD" )
305
+ err = envtest .UninstallCRDs (cfg , crdOpts )
306
+ Expect (err ).NotTo (HaveOccurred ())
307
+ // wait for discovery to not recognize the resource after uninstall
308
+ wait .PollImmediate (15 * time .Millisecond , 50 * time .Millisecond , func () (bool , error ) {
309
+ if _ , err := clientset .Discovery ().ServerResourcesForGroupVersion (gvk .Group + "/" + gvk .Version ); err != nil {
310
+ if err .Error () == "the server could not find the requested resource" {
311
+ return true , nil
312
+ }
313
+ }
314
+ return false , nil
315
+ })
316
+
317
+ By ("Failing create foo object if the crd isn't installed" )
318
+ errNotFound := errors .NewGenericServerResponse (404 , "POST" , schema.GroupResource {Group : "bar.example.com" , Resource : "foos" }, "" , "404 page not found" , 0 , true )
319
+ err = cm .GetClient ().Create (ctx , testFoo )
320
+ Expect (err ).To (Equal (errNotFound ))
321
+
322
+ By ("Reinstalling the CRD" )
323
+ crds , err = envtest .InstallCRDs (cfg , crdOpts )
324
+ Expect (err ).NotTo (HaveOccurred ())
325
+ Expect (len (crds )).To (Equal (1 ))
326
+
327
+ By ("Expecting to find the CRD" )
328
+ crdv1 = & apiextensionsv1.CustomResourceDefinition {}
329
+ err = cm .GetClient ().Get (context .TODO (), types.NamespacedName {Name : "foos.bar.example.com" }, crdv1 )
330
+ Expect (err ).NotTo (HaveOccurred ())
331
+ Expect (crdv1 .Spec .Names .Kind ).To (Equal ("Foo" ))
332
+
333
+ err = envtest .WaitForCRDs (cfg , []client.Object {
334
+ & v1beta1.CustomResourceDefinition {
335
+ Spec : v1beta1.CustomResourceDefinitionSpec {
336
+ Group : "bar.example.com" ,
337
+ Names : v1beta1.CustomResourceDefinitionNames {
338
+ Kind : "Foo" ,
339
+ Plural : "foos" ,
340
+ },
341
+ Versions : []v1beta1.CustomResourceDefinitionVersion {
342
+ {
343
+ Name : "v1" ,
344
+ Storage : true ,
345
+ Served : true ,
346
+ },
347
+ }},
348
+ },
349
+ },
350
+ crdOpts ,
351
+ )
352
+ Expect (err ).NotTo (HaveOccurred ())
353
+
354
+ By ("Invoking Reconcile for foo Create" )
355
+ testFoo .ResourceVersion = ""
356
+ err = cm .GetClient ().Create (ctx , testFoo )
357
+ Expect (err ).NotTo (HaveOccurred ())
358
+ Expect (<- reconciled ).To (Equal (expectedReconcileRequest ))
359
+
360
+ By ("Uninstalling the CRD" )
361
+ err = envtest .UninstallCRDs (cfg , crdOpts )
362
+ Expect (err ).NotTo (HaveOccurred ())
363
+ // wait for discovery to not recognize the resource after uninstall
364
+ wait .PollImmediate (15 * time .Millisecond , 50 * time .Millisecond , func () (bool , error ) {
365
+ if _ , err := clientset .Discovery ().ServerResourcesForGroupVersion (gvk .Group + "/" + gvk .Version ); err != nil {
366
+ if err .Error () == "the server could not find the requested resource" {
367
+ return true , nil
368
+ }
369
+ }
370
+ return false , nil
371
+ })
372
+
373
+ By ("Failing create foo object if the crd isn't installed" )
374
+ err = cm .GetClient ().Create (ctx , testFoo )
375
+ Expect (err ).To (Equal (errNotFound ))
376
+
377
+ close (done )
378
+ }, 10 )
379
+
176
380
})
177
381
178
382
func truePtr () * bool {
0 commit comments