1 package apiext
2
3 import (
4 "context"
5 "reflect"
6
7
8 k8sTypesCoreV1 "k8s.io/api/core/v1"
9 k8sTypesAPIExtV1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
10 k8sTypesMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11
12
13 k8sClientAPIExtV1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
14
15
16 k8sErrors "k8s.io/apimachinery/pkg/api/errors"
17 k8sRuntime "k8s.io/apimachinery/pkg/runtime"
18 k8sSchema "k8s.io/apimachinery/pkg/runtime/schema"
19 k8sWatch "k8s.io/apimachinery/pkg/watch"
20 "k8s.io/client-go/rest"
21
22 "github.com/datawire/dlib/derror"
23 "github.com/datawire/dlib/dlog"
24 )
25
26
27
28
29 func ConfigureCRDs(
30 ctx context.Context,
31 restConfig *rest.Config,
32 serviceName, serviceNamespace string,
33 caSecret *k8sTypesCoreV1.Secret,
34 scheme *k8sRuntime.Scheme,
35 ) error {
36 ctx, cancel := context.WithCancel(ctx)
37 defer func() {
38 cancel()
39 }()
40 apiExtClient, err := k8sClientAPIExtV1.NewForConfig(restConfig)
41 if err != nil {
42 return err
43 }
44 crdsClient := apiExtClient.CustomResourceDefinitions()
45
46 crdList, err := crdsClient.List(ctx, k8sTypesMetaV1.ListOptions{})
47 if err != nil {
48 return err
49 }
50
51 webhookPath := pathWebhooksCrdConvert
52 webhookPort := int32(443)
53 conversionConfig := &k8sTypesAPIExtV1.CustomResourceConversion{
54 Strategy: k8sTypesAPIExtV1.WebhookConverter,
55 Webhook: &k8sTypesAPIExtV1.WebhookConversion{
56 ClientConfig: &k8sTypesAPIExtV1.WebhookClientConfig{
57 Service: &k8sTypesAPIExtV1.ServiceReference{
58 Name: serviceName,
59 Namespace: serviceNamespace,
60 Port: &webhookPort,
61 Path: &webhookPath,
62 },
63 CABundle: caSecret.Data[k8sTypesCoreV1.TLSCertKey],
64 },
65
66
67
68 ConversionReviewVersions: []string{
69 "v1beta1",
70 },
71 },
72 }
73
74 var errs derror.MultiError
75 for _, crd := range crdList.Items {
76 if err := updateCRD(ctx, scheme, crdsClient, crd, conversionConfig); err != nil {
77 errs = append(errs, err)
78 }
79 }
80 if len(errs) > 0 {
81 return errs
82 }
83
84
85
86 dlog.Infoln(ctx, "Initial configuration complete, now watching for further changes...")
87
88 crdWatch, err := crdsClient.Watch(ctx, k8sTypesMetaV1.ListOptions{
89 ResourceVersion: crdList.GetResourceVersion(),
90 })
91 if err != nil {
92 return err
93 }
94 go func() {
95 <-ctx.Done()
96 crdWatch.Stop()
97 }()
98 for event := range crdWatch.ResultChan() {
99 switch event.Type {
100 case k8sWatch.Added, k8sWatch.Modified:
101 crd := *(event.Object.(*k8sTypesAPIExtV1.CustomResourceDefinition))
102 if err := updateCRD(ctx, scheme, crdsClient, crd, conversionConfig); err != nil {
103 dlog.Errorln(ctx, err)
104 }
105 }
106 }
107
108 return nil
109 }
110
111 func updateCRD(
112 ctx context.Context,
113 scheme *k8sRuntime.Scheme,
114 crdsClient k8sClientAPIExtV1.CustomResourceDefinitionInterface,
115 crd k8sTypesAPIExtV1.CustomResourceDefinition,
116 conversionConfig *k8sTypesAPIExtV1.CustomResourceConversion,
117 ) error {
118 if len(crd.Spec.Versions) < 2 {
119
120 dlog.Debugf(ctx, "Skipping %q because it only has one version", crd.ObjectMeta.Name)
121 return nil
122 }
123 if !scheme.Recognizes(k8sSchema.GroupVersionKind{
124 Group: crd.Spec.Group,
125 Version: crd.Spec.Versions[0].Name,
126 Kind: crd.Spec.Names.Kind,
127 }) {
128
129 dlog.Debugf(ctx, "Skipping %q because it not a recognized type", crd.ObjectMeta.Name)
130 return nil
131 }
132 if reflect.DeepEqual(crd.Spec.Conversion, conversionConfig) {
133
134 dlog.Infof(ctx, "Skipping %q because it is already configured", crd.ObjectMeta.Name)
135 return nil
136 }
137 dlog.Infof(ctx, "Configuring conversion for %q", crd.ObjectMeta.Name)
138 crd.Spec.Conversion = conversionConfig
139 _, err := crdsClient.Update(ctx, &crd, k8sTypesMetaV1.UpdateOptions{})
140 if err != nil && !k8sErrors.IsConflict(err) {
141 return err
142 }
143 return nil
144 }
145
View as plain text