Skip to content

Commit 7863ed6

Browse files
authored
Merge pull request kubernetes-sigs#1532 from vincepri/modify-crd-registered
🐛 Envtest should enable conversion only for convertible GVKs
2 parents d8dd31f + 33a6b8f commit 7863ed6

File tree

2 files changed

+117
-4
lines changed

2 files changed

+117
-4
lines changed

pkg/envtest/crd.go

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,31 @@ import (
3333
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
3434
apierrors "k8s.io/apimachinery/pkg/api/errors"
3535
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
36+
"k8s.io/apimachinery/pkg/runtime"
3637
"k8s.io/apimachinery/pkg/runtime/schema"
3738
"k8s.io/apimachinery/pkg/util/sets"
3839
"k8s.io/apimachinery/pkg/util/wait"
3940
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
41+
"k8s.io/client-go/kubernetes/scheme"
4042
"k8s.io/client-go/rest"
4143
"k8s.io/client-go/util/retry"
4244
"k8s.io/utils/pointer"
4345
"sigs.k8s.io/controller-runtime/pkg/client"
46+
"sigs.k8s.io/controller-runtime/pkg/webhook/conversion"
4447
"sigs.k8s.io/yaml"
4548
)
4649

4750
// CRDInstallOptions are the options for installing CRDs
4851
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+
4961
// Paths is a list of paths to the directories or files containing CRDs
5062
Paths []string
5163

@@ -86,7 +98,7 @@ func InstallCRDs(config *rest.Config, options CRDInstallOptions) ([]client.Objec
8698
return nil, err
8799
}
88100

89-
if err := modifyConversionWebhooks(options.CRDs, options.WebhookOptions); err != nil {
101+
if err := modifyConversionWebhooks(options.CRDs, options.Scheme, options.WebhookOptions); err != nil {
90102
return nil, err
91103
}
92104

@@ -118,6 +130,9 @@ func readCRDFiles(options *CRDInstallOptions) error {
118130

119131
// defaultCRDOptions sets the default values for CRDs
120132
func defaultCRDOptions(o *CRDInstallOptions) {
133+
if o.Scheme == nil {
134+
o.Scheme = scheme.Scheme
135+
}
121136
if o.MaxTime == 0 {
122137
o.MaxTime = defaultMaxWait
123138
}
@@ -356,11 +371,30 @@ func renderCRDs(options *CRDInstallOptions) ([]client.Object, error) {
356371
return res, nil
357372
}
358373

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
360382
if len(webhookOptions.LocalServingCAData) == 0 {
361383
return nil
362384
}
363385

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+
364398
// generate host port.
365399
hostPort, err := webhookOptions.generateHostPort()
366400
if err != nil {
@@ -371,10 +405,19 @@ func modifyConversionWebhooks(crds []client.Object, webhookOptions WebhookInstal
371405
for _, crd := range crds {
372406
switch c := crd.(type) {
373407
case *apiextensionsv1beta1.CustomResourceDefinition:
408+
// Continue if we're preserving unknown fields.
409+
//
374410
// preserveUnknownFields defaults to true if `nil` in v1beta1.
375411
if c.Spec.PreserveUnknownFields == nil || *c.Spec.PreserveUnknownFields {
376412
continue
377413
}
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+
}
378421
c.Spec.Conversion.Strategy = apiextensionsv1beta1.WebhookConverter
379422
c.Spec.Conversion.WebhookClientConfig.Service = nil
380423
c.Spec.Conversion.WebhookClientConfig = &apiextensionsv1beta1.WebhookClientConfig{
@@ -383,9 +426,17 @@ func modifyConversionWebhooks(crds []client.Object, webhookOptions WebhookInstal
383426
CABundle: webhookOptions.LocalServingCAData,
384427
}
385428
case *apiextensionsv1.CustomResourceDefinition:
429+
// Continue if we're preserving unknown fields.
386430
if c.Spec.PreserveUnknownFields {
387431
continue
388432
}
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+
}
389440
c.Spec.Conversion.Strategy = apiextensionsv1.WebhookConverter
390441
c.Spec.Conversion.Webhook.ClientConfig.Service = nil
391442
c.Spec.Conversion.Webhook.ClientConfig = &apiextensionsv1.WebhookClientConfig{
@@ -401,8 +452,32 @@ func modifyConversionWebhooks(crds []client.Object, webhookOptions WebhookInstal
401452

402453
switch c.GroupVersionKind().Version {
403454
case "v1beta1":
455+
// Continue if we're preserving unknown fields.
456+
//
404457
// 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 {
406481
continue
407482
}
408483

@@ -428,7 +503,29 @@ func modifyConversionWebhooks(crds []client.Object, webhookOptions WebhookInstal
428503
return err
429504
}
430505
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 {
432529
continue
433530
}
434531

pkg/envtest/server.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"strings"
2424
"time"
2525

26+
"k8s.io/apimachinery/pkg/runtime"
27+
"k8s.io/client-go/kubernetes/scheme"
2628
"k8s.io/client-go/rest"
2729
"sigs.k8s.io/controller-runtime/pkg/client"
2830
"sigs.k8s.io/controller-runtime/pkg/client/config"
@@ -95,6 +97,15 @@ type Environment struct {
9597
// ControlPlane is the ControlPlane including the apiserver and etcd
9698
ControlPlane integration.ControlPlane
9799

100+
// Scheme is used to determine if conversion webhooks should be enabled
101+
// for a particular CRD / object.
102+
//
103+
// Conversion webhooks are going to be enabled if an object in the scheme
104+
// implements Hub and Spoke conversions.
105+
//
106+
// If nil, scheme.Scheme is used.
107+
Scheme *runtime.Scheme
108+
98109
// Config can be used to talk to the apiserver. It's automatically
99110
// populated if not set using the standard controller-runtime config
100111
// loading.
@@ -263,6 +274,11 @@ func (te *Environment) Start() (*rest.Config, error) {
263274
}
264275
}
265276

277+
// Set the default scheme if nil.
278+
if te.Scheme == nil {
279+
te.Scheme = scheme.Scheme
280+
}
281+
266282
// Call PrepWithoutInstalling to setup certificates first
267283
// and have them available to patch CRD conversion webhook as well.
268284
if err := te.WebhookInstallOptions.PrepWithoutInstalling(); err != nil {

0 commit comments

Comments
 (0)