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

kubevirt / containerized-data-importer / #5747

31 Dec 2025 04:35PM UTC coverage: 49.418% (+0.04%) from 49.382%
#5747

push

travis-ci

web-flow
stop opinionating webhook namespaceselector field (#3841)

CDI opinionates the namespace selector field
which makes it play badly in case something is mutating
this field.

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

19 of 25 new or added lines in 1 file covered. (76.0%)

14610 of 29564 relevant lines covered (49.42%)

0.55 hits per line

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

55.48
/pkg/operator/controller/callbacks.go
1
/*
2
Copyright 2018 The CDI Authors.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package controller
18

19
import (
20
        "context"
21
        "fmt"
22
        "reflect"
23

24
        "github.com/go-logr/logr"
25

26
        admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
27
        appsv1 "k8s.io/api/apps/v1"
28
        corev1 "k8s.io/api/core/v1"
29
        extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
30
        "k8s.io/apimachinery/pkg/api/errors"
31
        "k8s.io/apimachinery/pkg/api/meta"
32
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33
        "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
34
        "k8s.io/apimachinery/pkg/labels"
35
        "k8s.io/apimachinery/pkg/runtime"
36
        "k8s.io/apimachinery/pkg/runtime/schema"
37

38
        "sigs.k8s.io/controller-runtime/pkg/client"
39
        "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
40

41
        cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
42
        "kubevirt.io/containerized-data-importer/pkg/common"
43
        cdicontroller "kubevirt.io/containerized-data-importer/pkg/controller"
44
        cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
45
        featuregates "kubevirt.io/containerized-data-importer/pkg/feature-gates"
46
        "kubevirt.io/containerized-data-importer/pkg/operator/resources/cluster"
47
        "kubevirt.io/containerized-data-importer/pkg/operator/resources/utils"
48
        sdk "kubevirt.io/controller-lifecycle-operator-sdk/pkg/sdk"
49
        "kubevirt.io/controller-lifecycle-operator-sdk/pkg/sdk/callbacks"
50
)
51

52
func addReconcileCallbacks(r *ReconcileCDI) {
1✔
53
        r.reconciler.AddCallback(&appsv1.Deployment{}, reconcileDeleteControllerDeployment)
1✔
54
        r.reconciler.AddCallback(&appsv1.Deployment{}, reconcileUpdateControllerDeploymentSelector)
1✔
55
        r.reconciler.AddCallback(&corev1.ServiceAccount{}, reconcileSCC)
1✔
56
        r.reconciler.AddCallback(&appsv1.Deployment{}, reconcileCreatePrometheusInfra)
1✔
57
        r.reconciler.AddCallback(&appsv1.Deployment{}, reconcileRemainingRelationshipLabels)
1✔
58
        r.reconciler.AddCallback(&appsv1.Deployment{}, reconcileDeleteDeprecatedResources)
1✔
59
        r.reconciler.AddCallback(&appsv1.Deployment{}, reconcileCDICRD)
1✔
60
        r.reconciler.AddCallback(&appsv1.Deployment{}, reconcilePvcMutatingWebhook)
1✔
61
        r.reconciler.AddCallback(&extv1.CustomResourceDefinition{}, reconcileSetConfigAuthority)
1✔
62
        r.reconciler.AddCallback(&extv1.CustomResourceDefinition{}, reconcileHandleOldVersion)
1✔
63
        r.reconciler.AddCallback(&admissionregistrationv1.ValidatingWebhookConfiguration{}, reconcileAnyNamespaceSelector)
1✔
64
        r.reconciler.AddCallback(&admissionregistrationv1.MutatingWebhookConfiguration{}, reconcileAnyNamespaceSelector)
1✔
65
        if r.haveRoutes {
2✔
66
                r.reconciler.AddCallback(&appsv1.Deployment{}, reconcileRoute)
1✔
67
        }
1✔
68
        if r.haveVolumeDataSourceValidator {
2✔
69
                r.reconciler.AddCallback(&appsv1.Deployment{}, reconcileVolumePopulators)
1✔
70
        }
1✔
71
}
72

73
func isControllerDeployment(d *appsv1.Deployment) bool {
1✔
74
        return d.Name == "cdi-deployment"
1✔
75
}
1✔
76

77
func reconcileAnyNamespaceSelector(args *callbacks.ReconcileCallbackArgs) error {
1✔
78
        // NamespaceSelector gets defaulted to the empty LabelSelector but we want to allow any value
1✔
79
        // For certain cloud providers mutators
1✔
80
        if args.State != callbacks.ReconcileStatePostRead {
2✔
81
                return nil
1✔
82
        }
1✔
83

84
        if args.CurrentObject == nil || args.DesiredObject == nil {
1✔
NEW
85
                return nil
×
NEW
86
        }
×
87

88
        switch whc := args.DesiredObject.(type) {
1✔
89
        case *admissionregistrationv1.ValidatingWebhookConfiguration:
1✔
90
                current := args.CurrentObject.(*admissionregistrationv1.ValidatingWebhookConfiguration)
1✔
91
                if len(whc.Webhooks) != 1 || len(current.Webhooks) != 1 {
1✔
NEW
92
                        return nil
×
NEW
93
                }
×
94
                whc.Webhooks[0].NamespaceSelector = current.Webhooks[0].NamespaceSelector
1✔
95
        case *admissionregistrationv1.MutatingWebhookConfiguration:
1✔
96
                current := args.CurrentObject.(*admissionregistrationv1.MutatingWebhookConfiguration)
1✔
97
                if len(whc.Webhooks) != 1 || len(current.Webhooks) != 1 {
1✔
NEW
98
                        return nil
×
NEW
99
                }
×
100
                whc.Webhooks[0].NamespaceSelector = current.Webhooks[0].NamespaceSelector
1✔
101
        }
102

103
        return nil
1✔
104
}
105

106
func reconcileUpdateControllerDeploymentSelector(args *callbacks.ReconcileCallbackArgs) error {
1✔
107
        if args.State != callbacks.ReconcileStatePostRead {
2✔
108
                return nil
1✔
109
        }
1✔
110

111
        if args.CurrentObject == nil || args.DesiredObject == nil {
1✔
112
                return nil
×
113
        }
×
114

115
        deployment := args.DesiredObject.(*appsv1.Deployment)
1✔
116

1✔
117
        if !isControllerDeployment(deployment) {
2✔
118
                return nil
1✔
119
        }
1✔
120

121
        if !reflect.DeepEqual(args.CurrentObject.(*appsv1.Deployment).Spec.Selector.MatchLabels, deployment.Spec.Selector.MatchLabels) {
2✔
122
                args.Logger.Info("Mismatching selector detected for CDI deployment, cleaning up")
1✔
123
                if err := deleteControllerDeployment(deployment, args); err != nil {
1✔
124
                        return err
×
125
                }
×
126
        }
127

128
        return nil
1✔
129
}
130

131
func reconcileDeleteControllerDeployment(args *callbacks.ReconcileCallbackArgs) error {
1✔
132
        switch args.State {
1✔
133
        case callbacks.ReconcileStatePostDelete, callbacks.ReconcileStateOperatorDelete:
1✔
134
        default:
1✔
135
                return nil
1✔
136
        }
137

138
        var deployment *appsv1.Deployment
1✔
139
        if args.DesiredObject != nil {
2✔
140
                deployment = args.DesiredObject.(*appsv1.Deployment)
1✔
141
        } else if args.CurrentObject != nil {
3✔
142
                deployment = args.CurrentObject.(*appsv1.Deployment)
1✔
143
        } else {
1✔
144
                args.Logger.Info("Received callback with no desired/current object")
×
145
                return nil
×
146
        }
×
147

148
        if !isControllerDeployment(deployment) {
2✔
149
                return nil
1✔
150
        }
1✔
151

152
        if err := deleteControllerDeployment(deployment, args); err != nil {
1✔
153
                return err
×
154
        }
×
155

156
        cr := args.Resource.(runtime.Object)
1✔
157
        if err := deleteWorkerResources(args.Logger, args.Client); err != nil {
1✔
158
                args.Logger.Error(err, "Error deleting worker resources")
×
159
                args.Recorder.Event(cr, corev1.EventTypeWarning, deleteResourceFailed, fmt.Sprintf("Failed to deleted worker resources %v", err))
×
160
                return err
×
161
        }
×
162
        args.Recorder.Event(cr, corev1.EventTypeNormal, deleteResourceSuccess, "Deleted worker resources successfully")
1✔
163

1✔
164
        return nil
1✔
165
}
166

167
func deleteControllerDeployment(deployment *appsv1.Deployment, args *callbacks.ReconcileCallbackArgs) error {
1✔
168
        args.Logger.Info("Deleting CDI deployment and all import/upload/clone pods/services")
1✔
169
        err := args.Client.Delete(context.TODO(), deployment, &client.DeleteOptions{
1✔
170
                PropagationPolicy: &[]metav1.DeletionPropagation{metav1.DeletePropagationForeground}[0],
1✔
171
        })
1✔
172
        cr := args.Resource.(runtime.Object)
1✔
173
        if err != nil && !errors.IsNotFound(err) {
1✔
174
                args.Logger.Error(err, "Error deleting cdi controller deployment")
×
175
                args.Recorder.Event(cr, corev1.EventTypeWarning, deleteResourceFailed, fmt.Sprintf("Failed to delete deployment %s, %v", deployment.Name, err))
×
176
                return err
×
177
        }
×
178
        args.Recorder.Event(cr, corev1.EventTypeNormal, deleteResourceSuccess, fmt.Sprintf("Deleted deployment %s successfully", deployment.Name))
1✔
179

1✔
180
        return nil
1✔
181
}
182

183
func reconcileRoute(args *callbacks.ReconcileCallbackArgs) error {
1✔
184
        if args.State != callbacks.ReconcileStatePostRead {
2✔
185
                return nil
1✔
186
        }
1✔
187

188
        deployment := args.CurrentObject.(*appsv1.Deployment)
1✔
189
        if !isControllerDeployment(deployment) || !sdk.CheckDeploymentReady(deployment) {
2✔
190
                return nil
1✔
191
        }
1✔
192

193
        cr := args.Resource.(runtime.Object)
1✔
194
        updated, err := ensureUploadProxyRouteExists(context.TODO(), args.Logger, args.Client, args.Scheme, deployment)
1✔
195
        if err != nil {
1✔
196
                args.Recorder.Event(cr, corev1.EventTypeWarning, createResourceFailed, fmt.Sprintf("Failed to ensure upload proxy route exists, %v", err))
×
197
                return err
×
198
        }
×
199

200
        if updated {
2✔
201
                args.Recorder.Event(cr, corev1.EventTypeNormal, createResourceSuccess, "Successfully ensured upload proxy route exists")
1✔
202
        }
1✔
203

204
        if err := updateUserRoutes(context.TODO(), args.Logger, args.Client, args.Recorder); err != nil {
1✔
205
                return err
×
206
        }
×
207

208
        return nil
1✔
209
}
210

211
func reconcileVolumePopulators(args *callbacks.ReconcileCallbackArgs) error {
1✔
212
        if args.State != callbacks.ReconcileStatePostRead {
2✔
213
                return nil
1✔
214
        }
1✔
215

216
        deployment := args.CurrentObject.(*appsv1.Deployment)
1✔
217
        if !isControllerDeployment(deployment) || !sdk.CheckDeploymentReady(deployment) {
2✔
218
                return nil
1✔
219
        }
1✔
220

221
        cr := args.Resource.(runtime.Object)
1✔
222
        updated, err := ensureVolumePopulatorsExist(context.TODO(), args.Client, args.Scheme)
1✔
223
        if err != nil {
1✔
224
                args.Recorder.Event(cr, corev1.EventTypeWarning, createResourceFailed, fmt.Sprintf("Failed to ensure volume populators exist, %v", err))
×
225
                return err
×
226
        }
×
227

228
        if updated {
2✔
229
                args.Recorder.Event(cr, corev1.EventTypeNormal, createResourceSuccess, "Successfully ensured volume populators exist")
1✔
230
        }
1✔
231

232
        return nil
1✔
233
}
234

235
func reconcileSCC(args *callbacks.ReconcileCallbackArgs) error {
1✔
236
        switch args.State {
1✔
237
        case callbacks.ReconcileStatePreCreate, callbacks.ReconcileStatePostRead:
1✔
238
        default:
1✔
239
                return nil
1✔
240
        }
241

242
        sa := args.DesiredObject.(*corev1.ServiceAccount)
1✔
243
        if sa.Name != common.ControllerServiceAccountName {
2✔
244
                return nil
1✔
245
        }
1✔
246

247
        cr := args.Resource.(runtime.Object)
1✔
248
        updated, err := ensureSCCExists(context.TODO(), args.Logger, args.Client, args.Namespace, common.ControllerServiceAccountName, common.CronJobServiceAccountName)
1✔
249
        if err != nil {
1✔
250
                args.Recorder.Event(cr, corev1.EventTypeWarning, createResourceFailed, fmt.Sprintf("Failed to ensure SecurityContextConstraint exists, %v", err))
×
251
                return err
×
252
        }
×
253

254
        if updated {
2✔
255
                args.Recorder.Event(cr, corev1.EventTypeNormal, createResourceSuccess, "Successfully ensured SecurityContextConstraint exists")
1✔
256
        }
1✔
257

258
        return nil
1✔
259
}
260

261
func reconcileCreatePrometheusInfra(args *callbacks.ReconcileCallbackArgs) error {
1✔
262
        if args.State != callbacks.ReconcileStatePostRead {
2✔
263
                return nil
1✔
264
        }
1✔
265

266
        deployment := args.CurrentObject.(*appsv1.Deployment)
1✔
267
        // we don't check sdk.CheckDeploymentReady(deployment) since we want Prometheus to cover NotReady state as well
1✔
268
        if !isControllerDeployment(deployment) {
2✔
269
                return nil
1✔
270
        }
1✔
271

272
        cr := args.Resource.(runtime.Object)
1✔
273
        namespace := deployment.GetNamespace()
1✔
274
        if namespace == "" {
1✔
275
                return fmt.Errorf("cluster scoped owner not supported")
×
276
        }
×
277

278
        if deployed, err := isPrometheusDeployed(args.Logger, args.Client, namespace); err != nil {
1✔
279
                return err
×
280
        } else if !deployed {
1✔
281
                return nil
×
282
        }
×
283
        if err := ensurePrometheusResourcesExist(context.TODO(), args.Client, args.Scheme, deployment); err != nil {
1✔
284
                args.Recorder.Event(cr, corev1.EventTypeWarning, createResourceFailed, fmt.Sprintf("Failed to ensure prometheus resources exists, %v", err))
×
285
                return err
×
286
        }
×
287

288
        return nil
1✔
289
}
290

291
func deleteWorkerResources(l logr.Logger, c client.Client) error {
1✔
292
        listTypes := []client.ObjectList{&corev1.PodList{}, &corev1.ServiceList{}}
1✔
293

1✔
294
        ls, err := labels.Parse(fmt.Sprintf("cdi.kubevirt.io in (%s, %s, %s)",
1✔
295
                common.ImporterPodName, common.UploadServerCDILabel, common.ClonerSourcePodName))
1✔
296
        if err != nil {
1✔
297
                return err
×
298
        }
×
299

300
        for _, lt := range listTypes {
2✔
301
                lo := &client.ListOptions{
1✔
302
                        LabelSelector: ls,
1✔
303
                }
1✔
304

1✔
305
                l.V(1).Info("Deleting worker resources", "type", reflect.TypeOf(lt).Elem().Name())
1✔
306

1✔
307
                if err := cc.BulkDeleteResources(context.TODO(), c, lt, lo); err != nil {
1✔
308
                        return err
×
309
                }
×
310
        }
311

312
        return nil
1✔
313
}
314

315
func reconcileSetConfigAuthority(args *callbacks.ReconcileCallbackArgs) error {
1✔
316
        if args.State != callbacks.ReconcileStatePostRead {
2✔
317
                return nil
1✔
318
        }
1✔
319

320
        crd := args.CurrentObject.(*extv1.CustomResourceDefinition)
1✔
321
        if crd.Name != "cdiconfigs.cdi.kubevirt.io" {
2✔
322
                return nil
1✔
323
        }
1✔
324

325
        cdi, ok := args.Resource.(*cdiv1.CDI)
1✔
326
        if !ok {
1✔
327
                return nil
×
328
        }
×
329

330
        if _, ok = cdi.Annotations[cdicontroller.AnnConfigAuthority]; ok {
2✔
331
                return nil
1✔
332
        }
1✔
333

334
        if cdi.Spec.Config == nil {
2✔
335
                cl := &cdiv1.CDIConfigList{}
1✔
336
                err := args.Client.List(context.TODO(), cl)
1✔
337
                if err != nil {
1✔
338
                        if meta.IsNoMatchError(err) {
×
339
                                return nil
×
340
                        }
×
341

342
                        return err
×
343
                }
344

345
                if len(cl.Items) != 1 {
2✔
346
                        return nil
1✔
347
                }
1✔
348

349
                cs := cl.Items[0].Spec.DeepCopy()
1✔
350
                if !reflect.DeepEqual(cs, &cdiv1.CDIConfigSpec{}) {
2✔
351
                        cdi.Spec.Config = cs
1✔
352
                }
1✔
353
        }
354

355
        if cdi.Annotations == nil {
2✔
356
                cdi.Annotations = map[string]string{}
1✔
357
        }
1✔
358
        cdi.Annotations[cdicontroller.AnnConfigAuthority] = ""
1✔
359

1✔
360
        return args.Client.Update(context.TODO(), cdi)
1✔
361
}
362

363
func getSpecVersion(version string, crd *extv1.CustomResourceDefinition) *extv1.CustomResourceDefinitionVersion {
1✔
364
        for _, v := range crd.Spec.Versions {
2✔
365
                if v.Name == version {
2✔
366
                        return &v
1✔
367
                }
1✔
368
        }
369
        return nil
1✔
370
}
371

372
func rewriteOldObjects(args *callbacks.ReconcileCallbackArgs, version string, crd *extv1.CustomResourceDefinition) error {
×
373
        args.Logger.Info("Rewriting old objects")
×
374
        kind := crd.Spec.Names.Kind
×
375
        gvk := schema.GroupVersionKind{
×
376
                Group:   crd.Spec.Group,
×
377
                Version: version,
×
378
                Kind:    kind,
×
379
        }
×
380
        ul := &unstructured.UnstructuredList{}
×
381
        ul.SetGroupVersionKind(gvk)
×
382
        err := args.Client.List(context.TODO(), ul, &client.ListOptions{})
×
383
        if err != nil {
×
384
                return err
×
385
        }
×
386
        for _, item := range ul.Items {
×
387
                nn := client.ObjectKey{Namespace: item.GetNamespace(), Name: item.GetName()}
×
388
                u := &unstructured.Unstructured{}
×
389
                u.SetGroupVersionKind(item.GetObjectKind().GroupVersionKind())
×
390
                err = args.Client.Get(context.TODO(), nn, u)
×
391
                if err != nil {
×
392
                        return err
×
393
                }
×
394
                err = args.Client.Update(context.TODO(), u)
×
395
                if err != nil {
×
396
                        return err
×
397
                }
×
398
        }
399
        return nil
×
400
}
401

402
func removeStoredVersion(args *callbacks.ReconcileCallbackArgs, desiredVersion string, crd *extv1.CustomResourceDefinition) error {
×
403
        args.Logger.Info("Removing stored version")
×
404
        crd.Status.StoredVersions = []string{desiredVersion}
×
405
        return args.Client.Status().Update(context.TODO(), crd)
×
406
}
×
407

408
// Handle upgrade from clusters that had v1alpha1 as a storage version
409
// and remove it from all CRDs managed by us
410
func reconcileHandleOldVersion(args *callbacks.ReconcileCallbackArgs) error {
1✔
411
        if args.State != callbacks.ReconcileStatePostRead {
2✔
412
                return nil
1✔
413
        }
1✔
414
        currentCrd := args.CurrentObject.(*extv1.CustomResourceDefinition)
1✔
415
        desiredCrd := args.DesiredObject.(*extv1.CustomResourceDefinition)
1✔
416
        desiredVersion := newestVersion(desiredCrd)
1✔
417

1✔
418
        if olderVersionsExist(desiredVersion, currentCrd) {
1✔
419
                restoreOlderVersions(currentCrd, desiredCrd)
×
420
                if !desiredIsStorage(desiredVersion, currentCrd) {
×
421
                        // Let kubernetes add it
×
422
                        return nil
×
423
                }
×
424
                if err := rewriteOldObjects(args, desiredVersion, currentCrd); err != nil {
×
425
                        return err
×
426
                }
×
427
                if err := removeStoredVersion(args, desiredVersion, currentCrd); err != nil {
×
428
                        return err
×
429
                }
×
430
        }
431
        return nil
1✔
432
}
433

434
func olderVersionsExist(desiredVersion string, crd *extv1.CustomResourceDefinition) bool {
1✔
435
        for _, version := range crd.Status.StoredVersions {
1✔
436
                if version != desiredVersion {
×
437
                        return true
×
438
                }
×
439
        }
440
        return false
1✔
441
}
442

443
func desiredIsStorage(desiredVersion string, crd *extv1.CustomResourceDefinition) bool {
×
444
        specVersion := getSpecVersion(desiredVersion, crd)
×
445
        return specVersion != nil && specVersion.Storage
×
446
}
×
447

448
func newestVersion(crd *extv1.CustomResourceDefinition) string {
1✔
449
        orderedVersions := []string{"v1", "v1beta1", "v1alpha1"}
1✔
450
        for _, version := range orderedVersions {
2✔
451
                specVersion := getSpecVersion(version, crd)
1✔
452
                if specVersion != nil {
2✔
453
                        return version
1✔
454
                }
1✔
455
        }
456
        return ""
×
457
}
458

459
// Merge both old and new versions into new CRD, so we have both in the desiredCrd object
460
func restoreOlderVersions(currentCrd, desiredCrd *extv1.CustomResourceDefinition) *extv1.CustomResourceDefinition {
×
461
        for _, version := range currentCrd.Status.StoredVersions {
×
462
                specVersion := getSpecVersion(version, desiredCrd)
×
463
                if specVersion == nil {
×
464
                        // Not available in desired CRD, restore from current CRD
×
465
                        specVersion := getSpecVersion(version, currentCrd)
×
466
                        // We are only allowed one storage version
×
467
                        // The desired CRD already has one
×
468
                        specVersion.Storage = false
×
469

×
470
                        desiredCrd.Spec.Versions = append(desiredCrd.Spec.Versions, *specVersion)
×
471
                }
×
472
        }
473
        return desiredCrd
×
474
}
475

476
func reconcilePvcMutatingWebhook(args *callbacks.ReconcileCallbackArgs) error {
1✔
477
        if args.State != callbacks.ReconcileStatePostRead {
2✔
478
                return nil
1✔
479
        }
1✔
480

481
        deployment, ok := args.DesiredObject.(*appsv1.Deployment)
1✔
482
        if !ok || deployment.Name != common.CDIApiServerResourceName {
2✔
483
                return nil
1✔
484
        }
1✔
485

486
        enabled, err := featuregates.IsWebhookPvcRenderingEnabled(args.Client)
1✔
487
        if err != nil {
2✔
488
                return cc.IgnoreNotFound(err)
1✔
489
        }
1✔
490

491
        whc := &admissionregistrationv1.MutatingWebhookConfiguration{}
1✔
492
        key := client.ObjectKey{Name: "cdi-api-pvc-mutate"}
1✔
493
        err = args.Client.Get(context.TODO(), key, whc)
1✔
494
        if err != nil && !errors.IsNotFound(err) {
1✔
495
                return err
×
496
        }
×
497

498
        exists := err == nil
1✔
499
        if !enabled {
2✔
500
                if !exists {
2✔
501
                        return nil
1✔
502
                }
1✔
503
                err = args.Client.Delete(context.TODO(), whc)
×
504
                return client.IgnoreNotFound(err)
×
505
        }
506

507
        if !exists {
×
508
                if err := initPvcMutatingWebhook(whc, args); err != nil {
×
509
                        return err
×
510
                }
×
511
                return args.Client.Create(context.TODO(), whc)
×
512
        }
513

514
        whcCopy := whc.DeepCopy()
×
515
        if err := initPvcMutatingWebhook(whc, args); err != nil {
×
516
                return err
×
517
        }
×
518
        if !reflect.DeepEqual(whc, whcCopy) {
×
519
                return args.Client.Update(context.TODO(), whc)
×
520
        }
×
521

522
        return nil
×
523
}
524

525
func initPvcMutatingWebhook(whc *admissionregistrationv1.MutatingWebhookConfiguration, args *callbacks.ReconcileCallbackArgs) error {
×
526
        path := "/pvc-mutate"
×
527
        defaultServicePort := int32(443)
×
528
        allScopes := admissionregistrationv1.AllScopes
×
529
        exactPolicy := admissionregistrationv1.Exact
×
530
        failurePolicy := admissionregistrationv1.Fail
×
531
        defaultTimeoutSeconds := int32(10)
×
532
        reinvocationNever := admissionregistrationv1.NeverReinvocationPolicy
×
533
        sideEffect := admissionregistrationv1.SideEffectClassNone
×
534
        bundle := cluster.GetAPIServerCABundle(args.Namespace, args.Client, args.Logger)
×
535

×
536
        whc.Name = "cdi-api-pvc-mutate"
×
537
        whc.Labels = map[string]string{utils.CDILabel: cluster.APIServerServiceName}
×
538
        whc.Webhooks = []admissionregistrationv1.MutatingWebhook{
×
539
                {
×
540
                        Name: "pvc-mutate.cdi.kubevirt.io",
×
541
                        Rules: []admissionregistrationv1.RuleWithOperations{{
×
542
                                Operations: []admissionregistrationv1.OperationType{
×
543
                                        admissionregistrationv1.Create,
×
544
                                },
×
545
                                Rule: admissionregistrationv1.Rule{
×
546
                                        APIGroups:   []string{corev1.SchemeGroupVersion.Group},
×
547
                                        APIVersions: []string{corev1.SchemeGroupVersion.Version},
×
548
                                        Resources:   []string{"persistentvolumeclaims"},
×
549
                                        Scope:       &allScopes,
×
550
                                },
×
551
                        }},
×
552
                        ClientConfig: admissionregistrationv1.WebhookClientConfig{
×
553
                                Service: &admissionregistrationv1.ServiceReference{
×
554
                                        Namespace: args.Namespace,
×
555
                                        Name:      cluster.APIServerServiceName,
×
556
                                        Path:      &path,
×
557
                                        Port:      &defaultServicePort,
×
558
                                },
×
559
                                CABundle: bundle,
×
560
                        },
×
561
                        FailurePolicy:     &failurePolicy,
×
562
                        SideEffects:       &sideEffect,
×
563
                        MatchPolicy:       &exactPolicy,
×
564
                        NamespaceSelector: &metav1.LabelSelector{},
×
565
                        TimeoutSeconds:    &defaultTimeoutSeconds,
×
566
                        AdmissionReviewVersions: []string{
×
567
                                "v1",
×
568
                        },
×
569
                        ObjectSelector: &metav1.LabelSelector{
×
570
                                MatchLabels: map[string]string{
×
571
                                        common.PvcApplyStorageProfileLabel: "true",
×
572
                                },
×
573
                        },
×
574
                        ReinvocationPolicy: &reinvocationNever,
×
575
                },
×
576
        }
×
577

×
578
        cdi, err := cc.GetActiveCDI(context.TODO(), args.Client)
×
579
        if err != nil {
×
580
                return err
×
581
        }
×
582

583
        return controllerutil.SetControllerReference(cdi, whc, args.Scheme)
×
584
}
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