@@ -33,19 +33,31 @@ import (
33
33
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
34
34
apierrors "k8s.io/apimachinery/pkg/api/errors"
35
35
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
36
+ "k8s.io/apimachinery/pkg/runtime"
36
37
"k8s.io/apimachinery/pkg/runtime/schema"
37
38
"k8s.io/apimachinery/pkg/util/sets"
38
39
"k8s.io/apimachinery/pkg/util/wait"
39
40
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
41
+ "k8s.io/client-go/kubernetes/scheme"
40
42
"k8s.io/client-go/rest"
41
43
"k8s.io/client-go/util/retry"
42
44
"k8s.io/utils/pointer"
43
45
"sigs.k8s.io/controller-runtime/pkg/client"
46
+ "sigs.k8s.io/controller-runtime/pkg/webhook/conversion"
44
47
"sigs.k8s.io/yaml"
45
48
)
46
49
47
50
// CRDInstallOptions are the options for installing CRDs
48
51
type CRDInstallOptions struct {
52
+ // Scheme is used to determine if conversion webhooks should be enabled
53
+ // for a particular CRD / object.
54
+ //
55
+ // Conversion webhooks are going to be enabled if an object in the scheme
56
+ // implements Hub and Spoke conversions.
57
+ //
58
+ // If nil, scheme.Scheme is used.
59
+ Scheme * runtime.Scheme
60
+
49
61
// Paths is a list of paths to the directories or files containing CRDs
50
62
Paths []string
51
63
@@ -86,7 +98,7 @@ func InstallCRDs(config *rest.Config, options CRDInstallOptions) ([]client.Objec
86
98
return nil , err
87
99
}
88
100
89
- if err := modifyConversionWebhooks (options .CRDs , options .WebhookOptions ); err != nil {
101
+ if err := modifyConversionWebhooks (options .CRDs , options .Scheme , options . WebhookOptions ); err != nil {
90
102
return nil , err
91
103
}
92
104
@@ -118,6 +130,9 @@ func readCRDFiles(options *CRDInstallOptions) error {
118
130
119
131
// defaultCRDOptions sets the default values for CRDs
120
132
func defaultCRDOptions (o * CRDInstallOptions ) {
133
+ if o .Scheme == nil {
134
+ o .Scheme = scheme .Scheme
135
+ }
121
136
if o .MaxTime == 0 {
122
137
o .MaxTime = defaultMaxWait
123
138
}
@@ -356,11 +371,30 @@ func renderCRDs(options *CRDInstallOptions) ([]client.Object, error) {
356
371
return res , nil
357
372
}
358
373
359
- func modifyConversionWebhooks (crds []client.Object , webhookOptions WebhookInstallOptions ) error {
374
+ // modifyConversionWebhooks takes all the registered CustomResourceDefinitions and applies modifications
375
+ // to conditionally enable webhooks if the type is registered within the scheme.
376
+ //
377
+ // The complexity of this function is high mostly due to all the edge cases that we need to handle:
378
+ // CRDv1beta1, CRDv1, and their unstructured counterpart.
379
+ //
380
+ // We should be able to simplify this code once we drop support for v1beta1 and standardize around the typed CRDv1 object.
381
+ func modifyConversionWebhooks (crds []client.Object , scheme * runtime.Scheme , webhookOptions WebhookInstallOptions ) error { //nolint:gocyclo
360
382
if len (webhookOptions .LocalServingCAData ) == 0 {
361
383
return nil
362
384
}
363
385
386
+ // Determine all registered convertible types.
387
+ convertibles := map [schema.GroupKind ]struct {}{}
388
+ for gvk := range scheme .AllKnownTypes () {
389
+ obj , err := scheme .New (gvk )
390
+ if err != nil {
391
+ return err
392
+ }
393
+ if ok , err := conversion .IsConvertible (scheme , obj ); ok && err == nil {
394
+ convertibles [gvk .GroupKind ()] = struct {}{}
395
+ }
396
+ }
397
+
364
398
// generate host port.
365
399
hostPort , err := webhookOptions .generateHostPort ()
366
400
if err != nil {
@@ -371,10 +405,19 @@ func modifyConversionWebhooks(crds []client.Object, webhookOptions WebhookInstal
371
405
for _ , crd := range crds {
372
406
switch c := crd .(type ) {
373
407
case * apiextensionsv1beta1.CustomResourceDefinition :
408
+ // Continue if we're preserving unknown fields.
409
+ //
374
410
// preserveUnknownFields defaults to true if `nil` in v1beta1.
375
411
if c .Spec .PreserveUnknownFields == nil || * c .Spec .PreserveUnknownFields {
376
412
continue
377
413
}
414
+ // Continue if the GroupKind isn't registered as being convertible.
415
+ if _ , ok := convertibles [schema.GroupKind {
416
+ Group : c .Spec .Group ,
417
+ Kind : c .Spec .Names .Kind ,
418
+ }]; ! ok {
419
+ continue
420
+ }
378
421
c .Spec .Conversion .Strategy = apiextensionsv1beta1 .WebhookConverter
379
422
c .Spec .Conversion .WebhookClientConfig .Service = nil
380
423
c .Spec .Conversion .WebhookClientConfig = & apiextensionsv1beta1.WebhookClientConfig {
@@ -383,9 +426,17 @@ func modifyConversionWebhooks(crds []client.Object, webhookOptions WebhookInstal
383
426
CABundle : webhookOptions .LocalServingCAData ,
384
427
}
385
428
case * apiextensionsv1.CustomResourceDefinition :
429
+ // Continue if we're preserving unknown fields.
386
430
if c .Spec .PreserveUnknownFields {
387
431
continue
388
432
}
433
+ // Continue if the GroupKind isn't registered as being convertible.
434
+ if _ , ok := convertibles [schema.GroupKind {
435
+ Group : c .Spec .Group ,
436
+ Kind : c .Spec .Names .Kind ,
437
+ }]; ! ok {
438
+ continue
439
+ }
389
440
c .Spec .Conversion .Strategy = apiextensionsv1 .WebhookConverter
390
441
c .Spec .Conversion .Webhook .ClientConfig .Service = nil
391
442
c .Spec .Conversion .Webhook .ClientConfig = & apiextensionsv1.WebhookClientConfig {
@@ -401,8 +452,32 @@ func modifyConversionWebhooks(crds []client.Object, webhookOptions WebhookInstal
401
452
402
453
switch c .GroupVersionKind ().Version {
403
454
case "v1beta1" :
455
+ // Continue if we're preserving unknown fields.
456
+ //
404
457
// preserveUnknownFields defaults to true if `nil` in v1beta1.
405
- if preserve , found , _ := unstructured .NestedBool (c .Object , "spec" , "preserveUnknownFields" ); preserve || ! found {
458
+ if preserve , found , err := unstructured .NestedBool (c .Object , "spec" , "preserveUnknownFields" ); preserve || ! found {
459
+ continue
460
+ } else if err != nil {
461
+ return err
462
+ }
463
+
464
+ // Continue if the GroupKind isn't registered as being convertible.
465
+ group , found , err := unstructured .NestedString (c .Object , "spec" , "group" )
466
+ if ! found {
467
+ continue
468
+ } else if err != nil {
469
+ return err
470
+ }
471
+ kind , found , err := unstructured .NestedString (c .Object , "spec" , "names" , "kind" )
472
+ if ! found {
473
+ continue
474
+ } else if err != nil {
475
+ return err
476
+ }
477
+ if _ , ok := convertibles [schema.GroupKind {
478
+ Group : group ,
479
+ Kind : kind ,
480
+ }]; ! ok {
406
481
continue
407
482
}
408
483
@@ -428,7 +503,29 @@ func modifyConversionWebhooks(crds []client.Object, webhookOptions WebhookInstal
428
503
return err
429
504
}
430
505
case "v1" :
431
- if preserve , _ , _ := unstructured .NestedBool (c .Object , "spec" , "preserveUnknownFields" ); preserve {
506
+ if preserve , _ , err := unstructured .NestedBool (c .Object , "spec" , "preserveUnknownFields" ); preserve {
507
+ continue
508
+ } else if err != nil {
509
+ return err
510
+ }
511
+
512
+ // Continue if the GroupKind isn't registered as being convertible.
513
+ group , found , err := unstructured .NestedString (c .Object , "spec" , "group" )
514
+ if ! found {
515
+ continue
516
+ } else if err != nil {
517
+ return err
518
+ }
519
+ kind , found , err := unstructured .NestedString (c .Object , "spec" , "names" , "kind" )
520
+ if ! found {
521
+ continue
522
+ } else if err != nil {
523
+ return err
524
+ }
525
+ if _ , ok := convertibles [schema.GroupKind {
526
+ Group : group ,
527
+ Kind : kind ,
528
+ }]; ! ok {
432
529
continue
433
530
}
434
531
0 commit comments