• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubevirt / hyperconverged-cluster-operator / 21356570114

26 Jan 2026 11:48AM UTC coverage: 75.948% (-0.09%) from 76.038%
21356570114

Pull #3977

github

web-flow
Merge 335ae0e82 into 4ce018c73
Pull Request #3977: WIP: POC: add api v1

380 of 558 new or added lines in 8 files covered. (68.1%)

9 existing lines in 1 file now uncovered.

8753 of 11525 relevant lines covered (75.95%)

1.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.94
/controllers/webhooks/conversion-webhook-controller/controller.go
1
package conversion_webhook_controller
2

3
import (
4
        "context"
5
        "errors"
6
        "slices"
7

8
        corev1 "k8s.io/api/core/v1"
9
        apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
10
        apierrors "k8s.io/apimachinery/pkg/api/errors"
11
        "k8s.io/utils/ptr"
12
        "sigs.k8s.io/controller-runtime/pkg/client"
13
        "sigs.k8s.io/controller-runtime/pkg/controller"
14
        "sigs.k8s.io/controller-runtime/pkg/handler"
15
        logf "sigs.k8s.io/controller-runtime/pkg/log"
16
        "sigs.k8s.io/controller-runtime/pkg/manager"
17
        "sigs.k8s.io/controller-runtime/pkg/predicate"
18
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
19
        "sigs.k8s.io/controller-runtime/pkg/source"
20

21
        hcov1 "github.com/kubevirt/hyperconverged-cluster-operator/api/v1"
22
        hcov1beta1 "github.com/kubevirt/hyperconverged-cluster-operator/api/v1beta1"
23
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/ownresources"
24
        hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util"
25
)
26

27
const (
28
        k8sCertInjectionAnnotationName = "cert-manager.io/inject-ca-from-secret"
29

30
        openshiftCertInjectionAnnotationName = "service.beta.openshift.io/inject-cabundle"
31

32
        olmCaBundleKey = "olmCAKey"
33
)
34

35
var (
36
        setupLogger = logf.Log.WithName("conversion-webhook-controller")
37
)
38

39
// ReconcileConversionWebhook reconciles the HyperConverged CRD conversion webhook configuration
40
type ReconcileConversionWebhook struct {
41
        client             client.Client
42
        namespace          string
43
        secretName         string
44
        serviceName        string
45
        certAnnotationName string
46
        certAnnotationVal  string
47
        managedByOLM       bool
48
}
49

50
// RegisterReconciler creates a new conversion webhook controller and registers it into manager.
NEW
51
func RegisterReconciler(mgr manager.Manager) error {
×
NEW
52
        deploymentName := ownresources.GetDeploymentRef().Name
×
NEW
53
        if deploymentName == "" {
×
NEW
54
                setupLogger.Info("Deployment reference not available, skipping conversion webhook controller registration")
×
NEW
55
                return nil
×
NEW
56
        }
×
57

NEW
58
        secretName := deploymentName + "-service-cert"
×
NEW
59
        serviceName := deploymentName + "-service"
×
NEW
60
        namespace := hcoutil.GetOperatorNamespaceFromEnv()
×
NEW
61

×
NEW
62
        return add(mgr, newReconciler(mgr, namespace, secretName, serviceName))
×
63
}
64

65
func newReconciler(mgr manager.Manager, namespace, secretName, serviceName string) *ReconcileConversionWebhook {
1✔
66
        r := &ReconcileConversionWebhook{
1✔
67
                client:      mgr.GetClient(),
1✔
68
                namespace:   namespace,
1✔
69
                secretName:  secretName,
1✔
70
                serviceName: serviceName,
1✔
71
        }
1✔
72

1✔
73
        if hcoutil.GetClusterInfo().IsOpenshift() {
2✔
74
                r.certAnnotationName = openshiftCertInjectionAnnotationName
1✔
75
                r.certAnnotationVal = "true"
1✔
76
        } else {
2✔
77
                r.certAnnotationName = k8sCertInjectionAnnotationName
1✔
78
                r.certAnnotationVal = namespace + "/" + secretName
1✔
79
        }
1✔
80

81
        r.managedByOLM = hcoutil.GetClusterInfo().IsManagedByOLM()
1✔
82

1✔
83
        return r
1✔
84
}
85

NEW
86
func add(mgr manager.Manager, r *ReconcileConversionWebhook) error {
×
NEW
87
        setupLogger.Info("Setting up conversion webhook controller")
×
NEW
88

×
NEW
89
        c, err := controller.New("conversion-webhook-controller", mgr, controller.Options{Reconciler: r})
×
NEW
90
        if err != nil {
×
NEW
91
                return err
×
NEW
92
        }
×
93

NEW
94
        err = c.Watch(
×
NEW
95
                source.Kind[*apiextensionsv1.CustomResourceDefinition](
×
NEW
96
                        mgr.GetCache(),
×
NEW
97
                        &apiextensionsv1.CustomResourceDefinition{},
×
NEW
98
                        &handler.TypedEnqueueRequestForObject[*apiextensionsv1.CustomResourceDefinition]{},
×
NEW
99
                        predicate.And(
×
NEW
100
                                predicate.NewTypedPredicateFuncs(func(crd *apiextensionsv1.CustomResourceDefinition) bool {
×
NEW
101
                                        return crd.Name == hcoutil.HyperConvergedCRDName
×
NEW
102
                                }),
×
103
                                predicate.Or[*apiextensionsv1.CustomResourceDefinition](
104
                                        predicate.TypedGenerationChangedPredicate[*apiextensionsv1.CustomResourceDefinition]{},
105
                                        predicate.TypedAnnotationChangedPredicate[*apiextensionsv1.CustomResourceDefinition]{}),
106
                        ),
107
                ),
108
        )
109

NEW
110
        if err != nil {
×
NEW
111
                return err
×
NEW
112
        }
×
113

NEW
114
        if r.managedByOLM {
×
NEW
115
                return c.Watch(
×
NEW
116
                        source.Kind[*corev1.Secret](
×
NEW
117
                                mgr.GetCache(),
×
NEW
118
                                &corev1.Secret{},
×
NEW
119
                                &handler.TypedEnqueueRequestForObject[*corev1.Secret]{},
×
NEW
120
                                predicate.NewTypedPredicateFuncs(func(secret *corev1.Secret) bool {
×
NEW
121
                                        return secret.Name == r.secretName
×
NEW
122
                                }),
×
123
                        ),
124
                )
125
        }
126

NEW
127
        return nil
×
128
}
129

130
func (r *ReconcileConversionWebhook) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
1✔
131
        logger := logf.FromContext(ctx).WithName("conversion-webhook-controller").WithValues("Request.Name", request.Name)
1✔
132
        if request.Name == hcoutil.HyperConvergedCRDName {
2✔
133
                logger.Info("Reconciling CRD")
1✔
134
        } else {
1✔
NEW
135
                logger = logger.WithValues("Request.Namespace", request.Namespace)
×
NEW
136
                logger.Info("Reconciling Secret")
×
NEW
137
        }
×
138

139
        // Get the HyperConverged CRD
140
        crd := &apiextensionsv1.CustomResourceDefinition{}
1✔
141
        if err := r.client.Get(ctx, client.ObjectKey{Name: hcoutil.HyperConvergedCRDName}, crd); err != nil {
1✔
NEW
142
                if apierrors.IsNotFound(err) {
×
NEW
143
                        logger.Info("HyperConverged CRD not found, skipping reconciliation")
×
NEW
144
                        return reconcile.Result{}, nil
×
NEW
145
                }
×
NEW
146
                return reconcile.Result{}, err
×
147
        }
148

149
        var caBundle []byte
1✔
150
        var err error
1✔
151
        if r.managedByOLM {
2✔
152
                caBundle, err = r.getOLMCaBundle(ctx)
1✔
153
                if err != nil {
1✔
NEW
154
                        return reconcile.Result{}, err
×
NEW
155
                }
×
156
        }
157

158
        // Check if update is needed
159
        if !r.needsUpdate(crd, caBundle) {
2✔
160
                logger.Info("CRD conversion webhook configuration is up to date")
1✔
161
                return reconcile.Result{}, nil
1✔
162
        }
1✔
163

164
        // Update CRD with conversion webhook configuration
165
        if err = r.updateCRDConversion(ctx, crd, caBundle); err != nil {
1✔
NEW
166
                logger.Error(err, "Failed to update CRD conversion webhook configuration")
×
NEW
167
                return reconcile.Result{}, err
×
NEW
168
        }
×
169

170
        logger.Info("Successfully updated CRD conversion webhook configuration")
1✔
171
        return reconcile.Result{}, nil
1✔
172
}
173

174
func (r *ReconcileConversionWebhook) needsUpdate(crd *apiextensionsv1.CustomResourceDefinition, caBundle []byte) bool {
1✔
175
        if !r.managedByOLM && crd.Annotations[r.certAnnotationName] != r.certAnnotationVal {
2✔
176
                return true
1✔
177
        }
1✔
178

179
        if crd.Spec.Conversion == nil {
1✔
NEW
180
                return true
×
NEW
181
        }
×
182

183
        if crd.Spec.Conversion.Strategy != apiextensionsv1.WebhookConverter {
2✔
184
                return true
1✔
185
        }
1✔
186

187
        if crd.Spec.Conversion.Webhook == nil {
2✔
188
                return true
1✔
189
        }
1✔
190

191
        webhook := crd.Spec.Conversion.Webhook
1✔
192
        if webhook.ClientConfig == nil {
2✔
193
                return true
1✔
194
        }
1✔
195

196
        clientConfig := webhook.ClientConfig
1✔
197
        if clientConfig.Service == nil {
2✔
198
                return true
1✔
199
        }
1✔
200

201
        if r.managedByOLM && slices.Compare(caBundle, clientConfig.CABundle) != 0 {
2✔
202
                return true
1✔
203
        }
1✔
204

205
        service := clientConfig.Service
1✔
206
        return service.Name != r.serviceName ||
1✔
207
                service.Namespace != r.namespace ||
1✔
208
                ptr.Deref(service.Path, "") != hcoutil.HCOConversionWebhookPath ||
1✔
209
                ptr.Deref(service.Port, 0) != int32(hcoutil.WebhookPort)
1✔
210
}
211

212
func (r *ReconcileConversionWebhook) updateCRDConversion(ctx context.Context, crd *apiextensionsv1.CustomResourceDefinition, caBundle []byte) error {
1✔
213
        if !r.managedByOLM {
2✔
214
                if crd.Annotations == nil {
2✔
215
                        crd.Annotations = map[string]string{}
1✔
216
                }
1✔
217

218
                crd.Annotations[r.certAnnotationName] = r.certAnnotationVal
1✔
219
        }
220

221
        crd.Spec.Conversion = &apiextensionsv1.CustomResourceConversion{
1✔
222
                Strategy: apiextensionsv1.WebhookConverter,
1✔
223
                Webhook: &apiextensionsv1.WebhookConversion{
1✔
224
                        ClientConfig: &apiextensionsv1.WebhookClientConfig{
1✔
225
                                Service: &apiextensionsv1.ServiceReference{
1✔
226
                                        Namespace: r.namespace,
1✔
227
                                        Name:      r.serviceName,
1✔
228
                                        Path:      ptr.To(hcoutil.HCOConversionWebhookPath),
1✔
229
                                        Port:      ptr.To(int32(hcoutil.WebhookPort)),
1✔
230
                                },
1✔
231
                        },
1✔
232
                        ConversionReviewVersions: []string{hcov1.APIVersionV1, hcov1beta1.APIVersionBeta},
1✔
233
                },
1✔
234
        }
1✔
235

1✔
236
        if caBundle != nil {
2✔
237
                crd.Spec.Conversion.Webhook.ClientConfig.CABundle = caBundle
1✔
238
        }
1✔
239

240
        return r.client.Update(ctx, crd)
1✔
241
}
242

243
func (r *ReconcileConversionWebhook) getOLMCaBundle(ctx context.Context) ([]byte, error) {
1✔
244
        secret := &corev1.Secret{}
1✔
245
        err := r.client.Get(ctx, client.ObjectKey{Name: r.secretName, Namespace: r.namespace}, secret)
1✔
246
        if err != nil {
1✔
NEW
247
                return nil, err
×
NEW
248
        }
×
249

250
        caBundle, ok := secret.Data[olmCaBundleKey]
1✔
251
        if !ok {
1✔
NEW
252
                return nil, errors.New("can't find caBundle")
×
NEW
253
        }
×
254

255
        return caBundle, nil
1✔
256
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc