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

kubevirt / containerized-data-importer / #5925

06 Feb 2026 05:59AM UTC coverage: 49.529% (+0.03%) from 49.499%
#5925

Pull #4010

travis-ci

halfcrazy
feat: Add checksum validation for HTTP/HTTPS DataVolume sources

Introduces cryptographic hash validation for HTTP/HTTPS
import sources to prevent data tampering during download.

**Usage Example:**

```yaml
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
  name: fedora-dv
spec:
  source:
    http:
      url: "https://download.fedoraproject.org/pub/fedora/linux/releases/39/Cloud/x86_64/images/Fedora-Cloud-Base-39-1.5.x86_64.qcow2"
      checksum: "sha256:c5b50f903e39b3c5d3b7c7bb9a4c5e4f3"
  pvc:
    accessModes:
      - ReadWriteOnce
    resources:
      requests:
        storage: 10Gi
```

Signed-off-by: Yan Zhu <hackzhuyan@gmail.com>
Pull Request #4010: feat: Add checksum validation for HTTP/HTTPS DataVolume sources

158 of 247 new or added lines in 14 files covered. (63.97%)

1342 existing lines in 22 files now uncovered.

14926 of 30136 relevant lines covered (49.53%)

0.55 hits per line

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

69.89
/pkg/controller/storageprofile-controller.go
1
package controller
2

3
import (
4
        "context"
5
        "errors"
6
        "fmt"
7
        "reflect"
8
        "strconv"
9

10
        "github.com/go-logr/logr"
11
        snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
12
        ocpconfigv1 "github.com/openshift/api/config/v1"
13
        "github.com/prometheus/client_golang/prometheus"
14

15
        v1 "k8s.io/api/core/v1"
16
        storagev1 "k8s.io/api/storage/v1"
17
        apiequality "k8s.io/apimachinery/pkg/api/equality"
18
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
19
        "k8s.io/apimachinery/pkg/api/meta"
20
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21
        "k8s.io/apimachinery/pkg/runtime"
22
        "k8s.io/apimachinery/pkg/types"
23
        "k8s.io/client-go/tools/record"
24
        storagehelpers "k8s.io/component-helpers/storage/volume"
25

26
        "sigs.k8s.io/controller-runtime/pkg/client"
27
        "sigs.k8s.io/controller-runtime/pkg/controller"
28
        "sigs.k8s.io/controller-runtime/pkg/event"
29
        "sigs.k8s.io/controller-runtime/pkg/handler"
30
        "sigs.k8s.io/controller-runtime/pkg/manager"
31
        "sigs.k8s.io/controller-runtime/pkg/predicate"
32
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
33
        "sigs.k8s.io/controller-runtime/pkg/source"
34

35
        cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
36
        "kubevirt.io/containerized-data-importer/pkg/common"
37
        cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
38
        metrics "kubevirt.io/containerized-data-importer/pkg/monitoring/metrics/cdi-controller"
39
        "kubevirt.io/containerized-data-importer/pkg/operator"
40
        "kubevirt.io/containerized-data-importer/pkg/storagecapabilities"
41
        "kubevirt.io/containerized-data-importer/pkg/util"
42
)
43

44
const (
45
        storageProfileControllerName = "storageprofile-controller"
46
        counterLabelStorageClass     = "storageclass"
47
        counterLabelProvisioner      = "provisioner"
48
        counterLabelComplete         = "complete"
49
        counterLabelDefault          = "default"
50
        counterLabelVirtDefault      = "virtdefault"
51
        counterLabelRWX              = "rwx"
52
        counterLabelSmartClone       = "smartclone"
53
        counterLabelDegraded         = "degraded"
54
)
55

56
// StorageProfileReconciler members
57
type StorageProfileReconciler struct {
58
        client client.Client
59
        // use this for getting any resources not in the install namespace or cluster scope
60
        uncachedClient  client.Client
61
        recorder        record.EventRecorder
62
        scheme          *runtime.Scheme
63
        log             logr.Logger
64
        installerLabels map[string]string
65
}
66

67
// Reconcile the reconcile.Reconciler implementation for the StorageProfileReconciler object.
68
func (r *StorageProfileReconciler) Reconcile(_ context.Context, req reconcile.Request) (reconcile.Result, error) {
69
        log := r.log.WithValues("StorageProfile", req.NamespacedName)
70
        log.Info("reconciling StorageProfile")
71

72
        storageClass := &storagev1.StorageClass{}
73
        if err := r.client.Get(context.TODO(), req.NamespacedName, storageClass); err != nil {
1✔
74
                if k8serrors.IsNotFound(err) {
1✔
75
                        return reconcile.Result{}, r.deleteStorageProfile(req.NamespacedName.Name, log)
1✔
76
                }
1✔
77
                return reconcile.Result{}, err
1✔
78
        } else if storageClass.GetDeletionTimestamp() != nil {
2✔
79
                return reconcile.Result{}, r.deleteStorageProfile(req.NamespacedName.Name, log)
2✔
80
        }
1✔
81

1✔
UNCOV
82
        return r.reconcileStorageProfile(storageClass)
×
83
}
1✔
UNCOV
84

×
UNCOV
85
func (r *StorageProfileReconciler) reconcileStorageProfile(sc *storagev1.StorageClass) (reconcile.Result, error) {
×
86
        log := r.log.WithValues("StorageProfile", sc.Name)
87

1✔
88
        storageProfile, prevStorageProfile, err := r.getStorageProfile(sc)
89
        if err != nil {
90
                log.Error(err, "Unable to create StorageProfile")
1✔
91
                return reconcile.Result{}, err
1✔
92
        }
1✔
93

1✔
94
        storageProfile.Status.StorageClass = &sc.Name
1✔
UNCOV
95
        storageProfile.Status.Provisioner = &sc.Provisioner
×
UNCOV
96
        snapClass, err := cc.GetSnapshotClassForSmartClone(nil, &sc.Name, storageProfile.Spec.SnapshotClass, r.log, r.client, r.recorder)
×
UNCOV
97
        if err != nil {
×
98
                return reconcile.Result{}, err
99
        }
1✔
100
        if snapClass != "" {
1✔
101
                storageProfile.Status.SnapshotClass = &snapClass
1✔
102
        }
2✔
103
        storageProfile.Status.CloneStrategy = r.reconcileCloneStrategy(sc, storageProfile.Spec.CloneStrategy, snapClass)
1✔
104
        storageProfile.Status.DataImportCronSourceFormat = r.reconcileDataImportCronSourceFormat(sc, storageProfile.Spec.DataImportCronSourceFormat, snapClass)
1✔
105
        r.reconcileMinimumSupportedPVCSize(sc, storageProfile)
2✔
106

1✔
107
        var claimPropertySets []cdiv1.ClaimPropertySet
1✔
108

1✔
109
        if len(storageProfile.Spec.ClaimPropertySets) > 0 {
1✔
110
                for _, cps := range storageProfile.Spec.ClaimPropertySets {
1✔
111
                        if cps.VolumeMode == nil || len(cps.AccessModes) == 0 {
1✔
112
                                err = errors.New("each ClaimPropertySet must provide both volume mode and access modes")
1✔
113
                                log.Error(err, "Unable to update StorageProfile")
1✔
114
                                return reconcile.Result{}, err
1✔
115
                        }
1✔
116
                }
1✔
117
                claimPropertySets = storageProfile.Spec.ClaimPropertySets
1✔
118
        } else {
2✔
119
                claimPropertySets = r.reconcilePropertySets(sc)
2✔
120
        }
2✔
121

1✔
122
        storageProfile.Status.ClaimPropertySets = claimPropertySets
1✔
123

1✔
124
        util.SetRecommendedLabels(storageProfile, r.installerLabels, "cdi-controller")
1✔
125
        if err := r.updateStorageProfile(prevStorageProfile, storageProfile, log); err != nil {
126
                return reconcile.Result{}, err
1✔
127
        }
1✔
128

1✔
129
        return reconcile.Result{}, r.computeMetrics(storageProfile, sc)
1✔
130
}
131

1✔
132
func (r *StorageProfileReconciler) updateStorageProfile(prevStorageProfile runtime.Object, storageProfile *cdiv1.StorageProfile, log logr.Logger) error {
1✔
133
        var prevSP *cdiv1.StorageProfile
1✔
134
        if p, ok := prevStorageProfile.(*cdiv1.StorageProfile); ok {
1✔
135
                prevSP = p
1✔
UNCOV
136
        }
×
UNCOV
137

×
138
        if prevSP == nil {
139
                return r.client.Create(context.TODO(), storageProfile)
1✔
140
        }
141

142
        if storageProfileSpecMetaChanged(prevSP, storageProfile) {
1✔
143
                log.Info("Updating StorageProfile", "StorageProfile.Name", storageProfile.Name, "storageProfile", storageProfile)
1✔
144
                if err := r.client.Update(context.TODO(), storageProfile); err != nil {
2✔
145
                        return err
1✔
146
                }
1✔
147
        }
148

2✔
149
        if !reflect.DeepEqual(prevSP.Status, storageProfile.Status) {
1✔
150
                log.Info("Updating StorageProfile Status", "StorageProfile.Name", storageProfile.Name, "storageProfile", storageProfile)
1✔
151
                if err := r.client.Status().Update(context.TODO(), storageProfile); err != nil {
152
                        return err
2✔
153
                }
1✔
154
        }
1✔
UNCOV
155

×
UNCOV
156
        return nil
×
157
}
158

159
// storageProfileSpecMetaChanged returns true if Spec, Labels, or Annotations differ
2✔
160
func storageProfileSpecMetaChanged(previous, desired *cdiv1.StorageProfile) bool {
1✔
161
        if previous == nil || desired == nil {
1✔
UNCOV
162
                return previous != desired
×
UNCOV
163
        }
×
164
        if !apiequality.Semantic.DeepEqual(previous.Spec, desired.Spec) {
165
                return true
166
        }
1✔
167
        if !apiequality.Semantic.DeepEqual(previous.GetLabels(), desired.GetLabels()) {
168
                return true
169
        }
170
        if !apiequality.Semantic.DeepEqual(previous.GetAnnotations(), desired.GetAnnotations()) {
1✔
171
                return true
1✔
UNCOV
172
        }
×
UNCOV
173
        return false
×
174
}
1✔
UNCOV
175

×
176
func (r *StorageProfileReconciler) getStorageProfile(sc *storagev1.StorageClass) (*cdiv1.StorageProfile, runtime.Object, error) {
×
177
        var prevStorageProfile runtime.Object
2✔
178
        storageProfile := &cdiv1.StorageProfile{}
1✔
179

1✔
180
        if err := r.client.Get(context.TODO(), types.NamespacedName{Name: sc.Name}, storageProfile); err != nil {
1✔
UNCOV
181
                if k8serrors.IsNotFound(err) {
×
UNCOV
182
                        storageProfile, err = r.createEmptyStorageProfile(sc)
×
183
                        if err != nil {
1✔
184
                                return nil, nil, err
185
                        }
186
                } else {
1✔
187
                        return nil, nil, err
1✔
188
                }
1✔
189
        } else {
1✔
190
                prevStorageProfile = storageProfile.DeepCopyObject()
2✔
191
        }
2✔
192

1✔
193
        return storageProfile, prevStorageProfile, nil
1✔
UNCOV
194
}
×
UNCOV
195

×
UNCOV
196
func (r *StorageProfileReconciler) reconcilePropertySets(sc *storagev1.StorageClass) []cdiv1.ClaimPropertySet {
×
UNCOV
197
        claimPropertySets := []cdiv1.ClaimPropertySet{}
×
UNCOV
198
        capabilities, found := storagecapabilities.GetCapabilities(r.client, sc)
×
199
        if found {
1✔
200
                for i := range capabilities {
1✔
201
                        claimPropertySet := cdiv1.ClaimPropertySet{
1✔
202
                                AccessModes: []v1.PersistentVolumeAccessMode{capabilities[i].AccessMode},
203
                                VolumeMode:  &capabilities[i].VolumeMode,
1✔
204
                        }
205
                        claimPropertySets = append(claimPropertySets, claimPropertySet)
206
                }
1✔
207
        }
1✔
208
        return claimPropertySets
2✔
209
}
1✔
210

1✔
211
func (r *StorageProfileReconciler) reconcileCloneStrategy(sc *storagev1.StorageClass, desiredCloneStrategy *cdiv1.CDICloneStrategy, snapClass string) *cdiv1.CDICloneStrategy {
1✔
212
        if desiredCloneStrategy != nil {
213
                return desiredCloneStrategy
1✔
214
        }
1✔
215

1✔
216
        if annStrategyVal, ok := sc.Annotations["cdi.kubevirt.io/clone-strategy"]; ok {
1✔
217
                return r.getCloneStrategyFromStorageClass(annStrategyVal)
1✔
218
        }
1✔
219

1✔
220
        // Default to trying snapshot clone unless volume snapshot class missing
221
        hostAssistedStrategy := cdiv1.CloneStrategyHostAssisted
222
        strategy := hostAssistedStrategy
223
        if snapClass != "" {
1✔
224
                strategy = cdiv1.CloneStrategySnapshot
2✔
225
        }
2✔
226

1✔
227
        if knownStrategy, ok := storagecapabilities.GetAdvisedCloneStrategy(sc); ok {
1✔
228
                strategy = knownStrategy
229
        }
1✔
230

231
        if strategy == cdiv1.CloneStrategySnapshot && snapClass == "" {
232
                r.log.Info("No VolumeSnapshotClass found for storage class, falling back to host assisted cloning", "StorageClass.Name", sc.Name)
1✔
233
                return &hostAssistedStrategy
1✔
234
        }
1✔
235

2✔
236
        return &strategy
2✔
237
}
1✔
238

1✔
239
func (r *StorageProfileReconciler) getCloneStrategyFromStorageClass(annStrategyVal string) *cdiv1.CDICloneStrategy {
1✔
240
        var strategy cdiv1.CDICloneStrategy
1✔
241

1✔
242
        switch annStrategyVal {
1✔
243
        case "copy":
244
                strategy = cdiv1.CloneStrategyHostAssisted
1✔
245
        case "snapshot":
246
                strategy = cdiv1.CloneStrategySnapshot
247
        case "csi-clone":
1✔
248
                strategy = cdiv1.CloneStrategyCsiClone
2✔
249
        }
1✔
250

1✔
251
        return &strategy
252
}
2✔
253

1✔
254
func (r *StorageProfileReconciler) reconcileDataImportCronSourceFormat(sc *storagev1.StorageClass, desiredFormat *cdiv1.DataImportCronSourceFormat, snapClass string) *cdiv1.DataImportCronSourceFormat {
1✔
255
        if desiredFormat != nil {
256
                return desiredFormat
257
        }
1✔
258

1✔
259
        // This can be changed later on
2✔
260
        // for example, if at some point we're confident snapshot sources should be the default
1✔
261
        pvcFormat := cdiv1.DataImportCronSourceFormatPvc
1✔
262
        format := pvcFormat
263

2✔
264
        if knownFormat, ok := storagecapabilities.GetAdvisedSourceFormat(sc); ok {
1✔
265
                format = knownFormat
1✔
266
        }
267

2✔
268
        if format == cdiv1.DataImportCronSourceFormatSnapshot && snapClass == "" {
1✔
269
                // No point using snapshots without a corresponding snapshot class
1✔
270
                r.log.Info("No VolumeSnapshotClass found for storage class, falling back to pvc sources for DataImportCrons", "StorageClass.Name", sc.Name)
1✔
271
                return &pvcFormat
272
        }
1✔
273

274
        return &format
275
}
1✔
276

1✔
277
func (r *StorageProfileReconciler) reconcileMinimumSupportedPVCSize(sc *storagev1.StorageClass, sp *cdiv1.StorageProfile) {
1✔
278
        if size, hasSize := storagecapabilities.GetMinimumSupportedPVCSize(sc); hasSize {
1✔
279
                if _, isAnnotated := sp.Annotations[cc.AnnMinimumSupportedPVCSize]; !isAnnotated {
1✔
280
                        if sp.Annotations == nil {
1✔
281
                                sp.Annotations = make(map[string]string)
1✔
282
                        }
1✔
283
                        sp.Annotations[cc.AnnMinimumSupportedPVCSize] = size
1✔
284
                }
1✔
285
        }
286
}
287

1✔
288
func (r *StorageProfileReconciler) createEmptyStorageProfile(sc *storagev1.StorageClass) (*cdiv1.StorageProfile, error) {
289
        storageProfile := MakeEmptyStorageProfileSpec(sc.Name)
290
        util.SetRecommendedLabels(storageProfile, r.installerLabels, "cdi-controller")
1✔
291
        // uncachedClient is used to directly get the config map
1✔
UNCOV
292
        // the controller runtime client caches objects that are read once, and thus requires a list/watch
×
UNCOV
293
        // should be cheaper than watching
×
294
        if err := operator.SetOwnerRuntime(r.uncachedClient, storageProfile); err != nil {
295
                return nil, err
296
        }
297
        return storageProfile, nil
1✔
298
}
1✔
299

1✔
300
func (r *StorageProfileReconciler) deleteStorageProfile(name string, log logr.Logger) error {
2✔
301
        log.Info("Cleaning up StorageProfile that corresponds to deleted StorageClass", "StorageClass.Name", name)
1✔
302
        profile := &cdiv1.StorageProfile{
1✔
303
                ObjectMeta: metav1.ObjectMeta{
304
                        Name: name,
2✔
305
                },
1✔
306
        }
1✔
307

1✔
308
        if err := r.client.Delete(context.TODO(), profile); cc.IgnoreNotFound(err) != nil {
1✔
309
                return err
310
        }
1✔
311

312
        labels := prometheus.Labels{
313
                counterLabelStorageClass: name,
1✔
314
        }
2✔
315
        metrics.DeleteStorageProfileStatus(labels)
2✔
316
        return nil
2✔
317
}
1✔
318

1✔
319
func isNoProvisioner(name string, cl client.Client) bool {
1✔
320
        storageClass := &storagev1.StorageClass{}
321
        if err := cl.Get(context.TODO(), types.NamespacedName{Name: name}, storageClass); err != nil {
322
                return false
323
        }
324
        return storageClass.Provisioner == storagehelpers.NotSupportedProvisioner
1✔
325
}
2✔
326

1✔
327
func (r *StorageProfileReconciler) computeMetrics(profile *cdiv1.StorageProfile, sc *storagev1.StorageClass) error {
1✔
328
        if profile.Status.StorageClass == nil || profile.Status.Provisioner == nil {
329
                return nil
2✔
330
        }
1✔
331

1✔
332
        storageClass := *profile.Status.StorageClass
333
        provisioner := *profile.Status.Provisioner
2✔
334

1✔
335
        // We don't count explicitly unsupported provisioners as incomplete
1✔
336
        _, found := storagecapabilities.UnsupportedProvisioners[*profile.Status.Provisioner]
1✔
337
        isComplete := found || !isIncomplete(profile.Status.ClaimPropertySets)
338
        isDefault := sc.Annotations[cc.AnnDefaultStorageClass] == "true"
339
        isVirtDefault := sc.Annotations[cc.AnnDefaultVirtStorageClass] == "true"
1✔
340
        isRWX := hasRWX(profile.Status.ClaimPropertySets)
1✔
341
        isSmartClone, err := r.hasSmartClone(profile)
1✔
UNCOV
342
        if err != nil {
×
343
                return err
×
344
        }
×
345

346
        isSNO := false
2✔
347
        clusterInfra := &ocpconfigv1.Infrastructure{}
1✔
348
        if err := r.client.Get(context.TODO(), types.NamespacedName{Name: "cluster"}, clusterInfra); err != nil {
1✔
349
                if !meta.IsNoMatchError(err) && !k8serrors.IsNotFound(err) {
1✔
350
                        return err
351
                }
1✔
UNCOV
352
        } else {
×
UNCOV
353
                isSNO = clusterInfra.Status.ControlPlaneTopology == ocpconfigv1.SingleReplicaTopologyMode &&
×
354
                        clusterInfra.Status.InfrastructureTopology == ocpconfigv1.SingleReplicaTopologyMode
1✔
355
        }
356

357
        isDegraded := (!isSNO && !isRWX) || !isSmartClone
358

1✔
359
        // Setting the labeled Gauge to 1 will not delete older metric, so we need to explicitly delete them
1✔
360
        scLabels := prometheus.Labels{counterLabelStorageClass: storageClass, counterLabelProvisioner: provisioner}
1✔
UNCOV
361
        metricsDeleted := metrics.DeleteStorageProfileStatus(scLabels)
×
362
        scLabels = createLabels(storageClass, provisioner, isComplete, isDefault, isVirtDefault, isRWX, isSmartClone, isDegraded)
×
363
        metrics.SetStorageProfileStatus(scLabels, 1)
×
UNCOV
364
        r.log.Info(fmt.Sprintf("Set metric:%s complete:%t default:%t vdefault:%t rwx:%t smartclone:%t degraded:%t (deleted %d)",
×
365
                storageClass, isComplete, isDefault, isVirtDefault, isRWX, isSmartClone, isDegraded, metricsDeleted))
366

367
        return nil
1✔
368
}
2✔
369

2✔
370
func (r *StorageProfileReconciler) hasSmartClone(sp *cdiv1.StorageProfile) (bool, error) {
1✔
371
        strategy := sp.Status.CloneStrategy
372
        provisioner := sp.Status.Provisioner
373

1✔
UNCOV
374
        if strategy != nil {
×
UNCOV
375
                if *strategy == cdiv1.CloneStrategyHostAssisted {
×
376
                        return false, nil
1✔
377
                }
378
                if *strategy == cdiv1.CloneStrategyCsiClone && provisioner != nil {
379
                        driver := &storagev1.CSIDriver{}
2✔
380
                        if err := r.client.Get(context.TODO(), types.NamespacedName{Name: *provisioner}, driver); err != nil {
1✔
381
                                return false, cc.IgnoreNotFound(err)
1✔
382
                        }
1✔
383
                        return true, nil
1✔
384
                }
385
        }
386

1✔
387
        if (strategy == nil || *strategy == cdiv1.CloneStrategySnapshot) && provisioner != nil {
1✔
388
                vscs := &snapshotv1.VolumeSnapshotClassList{}
1✔
389
                if err := r.client.List(context.TODO(), vscs); err != nil {
1✔
390
                        return false, err
1✔
391
                }
1✔
392
                return hasDriver(vscs, *provisioner), nil
1✔
UNCOV
393
        }
×
UNCOV
394

×
395
        return false, nil
1✔
396
}
397

398
func createLabels(storageClass, provisioner string, isComplete, isDefault, isVirtDefault, isRWX, isSmartClone, isDegraded bool) prometheus.Labels {
1✔
399
        return prometheus.Labels{
1✔
400
                counterLabelStorageClass: storageClass,
1✔
401
                counterLabelProvisioner:  provisioner,
1✔
402
                counterLabelComplete:     strconv.FormatBool(isComplete),
1✔
403
                counterLabelDefault:      strconv.FormatBool(isDefault),
1✔
404
                counterLabelVirtDefault:  strconv.FormatBool(isVirtDefault),
1✔
405
                counterLabelRWX:          strconv.FormatBool(isRWX),
1✔
406
                counterLabelSmartClone:   strconv.FormatBool(isSmartClone),
1✔
UNCOV
407
                counterLabelDegraded:     strconv.FormatBool(isDegraded),
×
UNCOV
408
        }
×
409
}
410

1✔
411
// MakeEmptyStorageProfileSpec creates StorageProfile manifest
1✔
412
func MakeEmptyStorageProfileSpec(name string) *cdiv1.StorageProfile {
1✔
413
        return &cdiv1.StorageProfile{
1✔
414
                TypeMeta: metav1.TypeMeta{
1✔
415
                        Kind:       "StorageProfile",
416
                        APIVersion: "cdi.kubevirt.io/v1beta1",
417
                },
×
418
                ObjectMeta: metav1.ObjectMeta{
×
UNCOV
419
                        Name: name,
×
UNCOV
420
                        Labels: map[string]string{
×
UNCOV
421
                                common.CDILabelKey:       common.CDILabelValue,
×
UNCOV
422
                                common.CDIComponentLabel: "",
×
423
                        },
424
                },
425
        }
1✔
426
}
1✔
UNCOV
427

×
UNCOV
428
// NewStorageProfileController creates a new instance of the StorageProfile controller.
×
429
func NewStorageProfileController(mgr manager.Manager, log logr.Logger, installerLabels map[string]string) (controller.Controller, error) {
430
        uncachedClient, err := client.New(mgr.GetConfig(), client.Options{
1✔
431
                Scheme: mgr.GetScheme(),
1✔
432
                Mapper: mgr.GetRESTMapper(),
1✔
433
        })
1✔
434
        if err != nil {
1✔
435
                return nil, err
1✔
436
        }
1✔
437

1✔
438
        reconciler := &StorageProfileReconciler{
1✔
439
                client:          mgr.GetClient(),
1✔
440
                uncachedClient:  uncachedClient,
1✔
UNCOV
441
                recorder:        mgr.GetEventRecorderFor(storageProfileControllerName),
×
UNCOV
442
                scheme:          mgr.GetScheme(),
×
443
                log:             log.WithName(storageProfileControllerName),
444
                installerLabels: installerLabels,
1✔
445
        }
1✔
446

2✔
447
        storageProfileController, err := controller.New(
1✔
UNCOV
448
                storageProfileControllerName,
×
UNCOV
449
                mgr,
×
450
                controller.Options{Reconciler: reconciler, MaxConcurrentReconciles: 3})
1✔
451
        if err != nil {
1✔
452
                return nil, err
1✔
453
        }
1✔
454
        if err := addStorageProfileControllerWatches(mgr, storageProfileController, log); err != nil {
455
                return nil, err
1✔
456
        }
1✔
457

1✔
458
        log.Info("Initialized StorageProfile controller")
1✔
459
        return storageProfileController, nil
1✔
460
}
1✔
461

1✔
462
func addStorageProfileControllerWatches(mgr manager.Manager, c controller.Controller, log logr.Logger) error {
1✔
463
        if err := c.Watch(source.Kind(mgr.GetCache(), &storagev1.StorageClass{}, &handler.TypedEnqueueRequestForObject[*storagev1.StorageClass]{})); err != nil {
1✔
464
                return err
1✔
465
        }
1✔
466

467
        if err := c.Watch(source.Kind(mgr.GetCache(), &cdiv1.StorageProfile{}, &handler.TypedEnqueueRequestForObject[*cdiv1.StorageProfile]{})); err != nil {
468
                return err
1✔
469
        }
1✔
470

1✔
471
        if err := c.Watch(source.Kind(mgr.GetCache(), &v1.PersistentVolume{}, handler.TypedEnqueueRequestsFromMapFunc[*v1.PersistentVolume](
1✔
472
                func(_ context.Context, obj *v1.PersistentVolume) []reconcile.Request {
2✔
473
                        return []reconcile.Request{{
2✔
474
                                NamespacedName: types.NamespacedName{Name: scName(obj)},
1✔
475
                        }}
1✔
476
                },
2✔
477
        ),
1✔
478
                predicate.TypedFuncs[*v1.PersistentVolume]{
2✔
479
                        CreateFunc: func(e event.TypedCreateEvent[*v1.PersistentVolume]) bool {
1✔
480
                                return isNoProvisioner(scName(e.Object), mgr.GetClient())
1✔
481
                        },
1✔
482
                        UpdateFunc: func(e event.TypedUpdateEvent[*v1.PersistentVolume]) bool {
483
                                return isNoProvisioner(scName(e.ObjectNew), mgr.GetClient())
484
                        },
485
                        DeleteFunc: func(e event.TypedDeleteEvent[*v1.PersistentVolume]) bool {
2✔
486
                                return isNoProvisioner(scName(e.Object), mgr.GetClient())
1✔
487
                        },
1✔
UNCOV
488
                })); err != nil {
×
UNCOV
489
                return err
×
490
        }
1✔
491

492
        mapSnapshotClassToProfile := func(ctx context.Context, vsc *snapshotv1.VolumeSnapshotClass) []reconcile.Request {
UNCOV
493
                var scList storagev1.StorageClassList
×
494
                if err := mgr.GetClient().List(ctx, &scList); err != nil {
495
                        c.GetLogger().Error(err, "Unable to list StorageClasses")
496
                        return nil
1✔
497
                }
1✔
498
                var reqs []reconcile.Request
1✔
499
                for _, sc := range scList.Items {
1✔
500
                        if sc.Provisioner == vsc.Driver {
1✔
501
                                reqs = append(reqs, reconcile.Request{NamespacedName: types.NamespacedName{Name: sc.Name}})
1✔
502
                        }
1✔
503
                }
1✔
504
                return reqs
1✔
505
        }
1✔
506
        if err := mgr.GetClient().List(context.TODO(), &snapshotv1.VolumeSnapshotClassList{}, &client.ListOptions{Limit: 1}); err != nil {
1✔
507
                if meta.IsNoMatchError(err) {
1✔
508
                        // Back out if there's no point to attempt watch
509
                        return nil
510
                }
1✔
511
                if !cc.IsErrCacheNotStarted(err) {
1✔
512
                        return err
1✔
513
                }
1✔
514
        }
1✔
515
        if err := c.Watch(source.Kind(mgr.GetCache(), &snapshotv1.VolumeSnapshotClass{},
1✔
516
                handler.TypedEnqueueRequestsFromMapFunc[*snapshotv1.VolumeSnapshotClass](mapSnapshotClassToProfile),
1✔
517
        )); err != nil {
1✔
518
                return err
1✔
519
        }
1✔
520

1✔
521
        return nil
1✔
522
}
1✔
523

1✔
524
func scName(obj client.Object) string {
1✔
525
        return obj.(*v1.PersistentVolume).Spec.StorageClassName
526
}
UNCOV
527

×
UNCOV
528
func isIncomplete(sets []cdiv1.ClaimPropertySet) bool {
×
529
        if len(sets) > 0 {
×
530
                for _, cps := range sets {
×
531
                        if len(cps.AccessModes) == 0 || cps.VolumeMode == nil {
×
532
                                return true
×
UNCOV
533
                        }
×
534
                }
×
535
        } else {
536
                return true
×
UNCOV
537
        }
×
538

×
539
        return false
×
540
}
×
541

×
542
func hasRWX(cpSets []cdiv1.ClaimPropertySet) bool {
×
543
        for _, cpSet := range cpSets {
×
UNCOV
544
                for _, am := range cpSet.AccessModes {
×
UNCOV
545
                        if am == v1.ReadWriteMany {
×
546
                                return true
×
547
                        }
×
548
                }
×
549
        }
×
550
        return false
×
551
}
×
552

×
553
func hasDriver(vscs *snapshotv1.VolumeSnapshotClassList, driver string) bool {
×
554
        for i := range vscs.Items {
×
555
                vsc := vscs.Items[i]
556
                if vsc.Driver == driver {
×
557
                        return true
×
558
                }
559
        }
560
        return false
×
561
}
×
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