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

kubevirt / containerized-data-importer / #6089

30 Jun 2026 05:33PM UTC coverage: 49.752% (-0.03%) from 49.781%
#6089

Pull #4190

travis-ci

HoustonBoston
Suppressed ClaimMisbound warning message with relevant test case residing in pkg/controller/populators/import-populator_test.go

Signed-off-by: HoustonBoston <rorajesh@redhat.com>
Pull Request #4190: Suppress ClaimMisbound Warning

0 of 2 new or added lines in 1 file covered. (0.0%)

9 existing lines in 3 files now uncovered.

15127 of 30405 relevant lines covered (49.75%)

0.56 hits per line

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

13.77
/pkg/controller/common/util.go
1
/*
2
Copyright 2022 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 common
18

19
import (
20
        "context"
21
        "crypto/rand"
22
        "crypto/rsa"
23
        "crypto/tls"
24
        "fmt"
25
        "io"
26
        "net"
27
        "net/http"
28
        "reflect"
29
        "regexp"
30
        "sort"
31
        "strconv"
32
        "strings"
33
        "sync"
34
        "time"
35

36
        "github.com/go-logr/logr"
37
        snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
38
        ocpconfigv1 "github.com/openshift/api/config/v1"
39
        "github.com/pkg/errors"
40

41
        corev1 "k8s.io/api/core/v1"
42
        storagev1 "k8s.io/api/storage/v1"
43
        extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
44
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
45
        "k8s.io/apimachinery/pkg/api/meta"
46
        "k8s.io/apimachinery/pkg/api/resource"
47
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
48
        "k8s.io/apimachinery/pkg/labels"
49
        "k8s.io/apimachinery/pkg/runtime"
50
        "k8s.io/apimachinery/pkg/types"
51
        "k8s.io/apimachinery/pkg/util/sets"
52
        "k8s.io/client-go/tools/cache"
53
        "k8s.io/client-go/tools/record"
54
        "k8s.io/klog/v2"
55
        "k8s.io/utils/ptr"
56

57
        runtimecache "sigs.k8s.io/controller-runtime/pkg/cache"
58
        "sigs.k8s.io/controller-runtime/pkg/client"
59
        "sigs.k8s.io/controller-runtime/pkg/client/fake"
60

61
        cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
62
        cdiv1utils "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1/utils"
63
        "kubevirt.io/containerized-data-importer/pkg/client/clientset/versioned/scheme"
64
        "kubevirt.io/containerized-data-importer/pkg/common"
65
        featuregates "kubevirt.io/containerized-data-importer/pkg/feature-gates"
66
        "kubevirt.io/containerized-data-importer/pkg/token"
67
        "kubevirt.io/containerized-data-importer/pkg/util"
68
        sdkapi "kubevirt.io/controller-lifecycle-operator-sdk/api"
69
)
70

71
const (
72
        // DataVolName provides a const to use for creating volumes in pod specs
73
        DataVolName = "cdi-data-vol"
74

75
        // ScratchVolName provides a const to use for creating scratch pvc volumes in pod specs
76
        ScratchVolName = "cdi-scratch-vol"
77

78
        // AnnAPIGroup is the APIGroup for CDI
79
        AnnAPIGroup = "cdi.kubevirt.io"
80
        // AnnCreatedBy is a pod annotation indicating if the pod was created by the PVC
81
        AnnCreatedBy = AnnAPIGroup + "/storage.createdByController"
82
        // AnnPodPhase is a PVC annotation indicating the related pod progress (phase)
83
        AnnPodPhase = AnnAPIGroup + "/storage.pod.phase"
84
        // AnnPodReady tells whether the pod is ready
85
        AnnPodReady = AnnAPIGroup + "/storage.pod.ready"
86
        // AnnPodRestarts is a PVC annotation that tells how many times a related pod was restarted
87
        AnnPodRestarts = AnnAPIGroup + "/storage.pod.restarts"
88
        // AnnPodSchedulable is a PVC annotation that tells if the Pod is schedulable or not
89
        AnnPodSchedulable = AnnAPIGroup + "/storage.pod.schedulable"
90
        // AnnImportFatalError is a PVC annotation that indicates a fatal import error that should not be retried
91
        AnnImportFatalError = AnnAPIGroup + "/storage.import.fatalError"
92
        // AnnPopulatedFor is a PVC annotation telling the datavolume controller that the PVC is already populated
93
        AnnPopulatedFor = AnnAPIGroup + "/storage.populatedFor"
94
        // AnnPrePopulated is a PVC annotation telling the datavolume controller that the PVC is already populated
95
        AnnPrePopulated = AnnAPIGroup + "/storage.prePopulated"
96
        // AnnPriorityClassName is PVC annotation to indicate the priority class name for importer, cloner and uploader pod
97
        AnnPriorityClassName = AnnAPIGroup + "/storage.pod.priorityclassname"
98
        // AnnPodServiceAccount is a PVC annotation to indicate the service account name for importer and uploader pod
99
        AnnPodServiceAccount = AnnAPIGroup + "/storage.pod.serviceAccountName"
100
        // AnnExternalPopulation annotation marks a PVC as "externally populated", allowing the import-controller to skip it
101
        AnnExternalPopulation = AnnAPIGroup + "/externalPopulation"
102

103
        // AnnPodRetainAfterCompletion is PVC annotation for retaining transfer pods after completion
104
        AnnPodRetainAfterCompletion = AnnAPIGroup + "/storage.pod.retainAfterCompletion"
105

106
        // AnnPreviousCheckpoint provides a const to indicate the previous snapshot for a multistage import
107
        AnnPreviousCheckpoint = AnnAPIGroup + "/storage.checkpoint.previous"
108
        // AnnCurrentCheckpoint provides a const to indicate the current snapshot for a multistage import
109
        AnnCurrentCheckpoint = AnnAPIGroup + "/storage.checkpoint.current"
110
        // AnnFinalCheckpoint provides a const to indicate whether the current checkpoint is the last one
111
        AnnFinalCheckpoint = AnnAPIGroup + "/storage.checkpoint.final"
112
        // AnnCheckpointsCopied is a prefix for recording which checkpoints have already been copied
113
        AnnCheckpointsCopied = AnnAPIGroup + "/storage.checkpoint.copied"
114

115
        // AnnCurrentPodID keeps track of the latest pod servicing this PVC
116
        AnnCurrentPodID = AnnAPIGroup + "/storage.checkpoint.pod.id"
117
        // AnnMultiStageImportDone marks a multi-stage import as totally finished
118
        AnnMultiStageImportDone = AnnAPIGroup + "/storage.checkpoint.done"
119

120
        // AnnPopulatorProgress is a standard annotation that can be used progress reporting
121
        AnnPopulatorProgress = AnnAPIGroup + "/storage.populator.progress"
122

123
        // AnnPreallocationRequested provides a const to indicate whether preallocation should be performed on the PV
124
        AnnPreallocationRequested = AnnAPIGroup + "/storage.preallocation.requested"
125
        // AnnPreallocationApplied provides a const for PVC preallocation annotation
126
        AnnPreallocationApplied = AnnAPIGroup + "/storage.preallocation"
127

128
        // AnnRunningCondition provides a const for the running condition
129
        AnnRunningCondition = AnnAPIGroup + "/storage.condition.running"
130
        // AnnRunningConditionMessage provides a const for the running condition
131
        AnnRunningConditionMessage = AnnAPIGroup + "/storage.condition.running.message"
132
        // AnnRunningConditionReason provides a const for the running condition
133
        AnnRunningConditionReason = AnnAPIGroup + "/storage.condition.running.reason"
134

135
        // AnnBoundCondition provides a const for the running condition
136
        AnnBoundCondition = AnnAPIGroup + "/storage.condition.bound"
137
        // AnnBoundConditionMessage provides a const for the running condition
138
        AnnBoundConditionMessage = AnnAPIGroup + "/storage.condition.bound.message"
139
        // AnnBoundConditionReason provides a const for the running condition
140
        AnnBoundConditionReason = AnnAPIGroup + "/storage.condition.bound.reason"
141

142
        // AnnSourceRunningCondition provides a const for the running condition
143
        AnnSourceRunningCondition = AnnAPIGroup + "/storage.condition.source.running"
144
        // AnnSourceRunningConditionMessage provides a const for the running condition
145
        AnnSourceRunningConditionMessage = AnnAPIGroup + "/storage.condition.source.running.message"
146
        // AnnSourceRunningConditionReason provides a const for the running condition
147
        AnnSourceRunningConditionReason = AnnAPIGroup + "/storage.condition.source.running.reason"
148

149
        // AnnVddkVersion shows the last VDDK library version used by a DV's importer pod
150
        AnnVddkVersion = AnnAPIGroup + "/storage.pod.vddk.version"
151
        // AnnVddkHostConnection shows the last ESX host that serviced a DV's importer pod
152
        AnnVddkHostConnection = AnnAPIGroup + "/storage.pod.vddk.host"
153
        // AnnVddkInitImageURL saves a per-DV VDDK image URL on the PVC
154
        AnnVddkInitImageURL = AnnAPIGroup + "/storage.pod.vddk.initimageurl"
155
        // AnnVddkExtraArgs references a ConfigMap that holds arguments to pass directly to the VDDK library
156
        AnnVddkExtraArgs = AnnAPIGroup + "/storage.pod.vddk.extraargs"
157

158
        // AnnRequiresScratch provides a const for our PVC requiring scratch annotation
159
        AnnRequiresScratch = AnnAPIGroup + "/storage.import.requiresScratch"
160

161
        // AnnRequiresDirectIO provides a const for our PVC requiring direct io annotation (due to OOMs we need to try qemu cache=none)
162
        AnnRequiresDirectIO = AnnAPIGroup + "/storage.import.requiresDirectIo"
163
        // OOMKilledReason provides a value that container runtimes must return in the reason field for an OOMKilled container
164
        OOMKilledReason = "OOMKilled"
165

166
        // AnnContentType provides a const for the PVC content-type
167
        AnnContentType = AnnAPIGroup + "/storage.contentType"
168

169
        // AnnSource provide a const for our PVC import source annotation
170
        AnnSource = AnnAPIGroup + "/storage.import.source"
171
        // AnnEndpoint provides a const for our PVC endpoint annotation
172
        AnnEndpoint = AnnAPIGroup + "/storage.import.endpoint"
173

174
        // AnnSecret provides a const for our PVC secretName annotation
175
        AnnSecret = AnnAPIGroup + "/storage.import.secretName"
176
        // AnnCertConfigMap is the name of a configmap containing tls certs
177
        AnnCertConfigMap = AnnAPIGroup + "/storage.import.certConfigMap"
178
        // AnnRegistryImportMethod provides a const for registry import method annotation
179
        AnnRegistryImportMethod = AnnAPIGroup + "/storage.import.registryImportMethod"
180
        // AnnRegistryImageStream provides a const for registry image stream annotation
181
        AnnRegistryImageStream = AnnAPIGroup + "/storage.import.registryImageStream"
182
        // AnnImportPod provides a const for our PVC importPodName annotation
183
        AnnImportPod = AnnAPIGroup + "/storage.import.importPodName"
184
        // AnnDiskID provides a const for our PVC diskId annotation
185
        AnnDiskID = AnnAPIGroup + "/storage.import.diskId"
186
        // AnnUUID provides a const for our PVC uuid annotation
187
        AnnUUID = AnnAPIGroup + "/storage.import.uuid"
188
        // AnnInsecureSkipVerify provides a const for skipping certificate verification
189
        AnnInsecureSkipVerify = AnnAPIGroup + "/storage.import.insecureSkipVerify"
190
        // AnnBackingFile provides a const for our PVC backing file annotation
191
        AnnBackingFile = AnnAPIGroup + "/storage.import.backingFile"
192
        // AnnThumbprint provides a const for our PVC backing thumbprint annotation
193
        AnnThumbprint = AnnAPIGroup + "/storage.import.vddk.thumbprint"
194
        // AnnExtraHeaders provides a const for our PVC extraHeaders annotation
195
        AnnExtraHeaders = AnnAPIGroup + "/storage.import.extraHeaders"
196
        // AnnSecretExtraHeaders provides a const for our PVC secretExtraHeaders annotation
197
        AnnSecretExtraHeaders = AnnAPIGroup + "/storage.import.secretExtraHeaders"
198
        // AnnChecksum provides a const for our PVC checksum annotation
199
        AnnChecksum = AnnAPIGroup + "/storage.import.checksum"
200
        // AnnRegistryImageArchitecture provides a const for our PVC registryImageArchitecture annotation
201
        AnnRegistryImageArchitecture = AnnAPIGroup + "/storage.import.registryImageArchitecture"
202

203
        // AnnCloneToken is the annotation containing the clone token
204
        AnnCloneToken = AnnAPIGroup + "/storage.clone.token"
205
        // AnnExtendedCloneToken is the annotation containing the long term clone token
206
        AnnExtendedCloneToken = AnnAPIGroup + "/storage.extended.clone.token"
207
        // AnnPermissiveClone annotation allows the clone-controller to skip the clone size validation
208
        AnnPermissiveClone = AnnAPIGroup + "/permissiveClone"
209
        // AnnOwnerUID annotation has the owner UID
210
        AnnOwnerUID = AnnAPIGroup + "/ownerUID"
211
        // AnnCloneType is the comuuted/requested clone type
212
        AnnCloneType = AnnAPIGroup + "/cloneType"
213
        // AnnCloneSourcePod name of the source clone pod
214
        AnnCloneSourcePod = AnnAPIGroup + "/storage.sourceClonePodName"
215

216
        // AnnUploadRequest marks that a PVC should be made available for upload
217
        AnnUploadRequest = AnnAPIGroup + "/storage.upload.target"
218

219
        // AnnCheckStaticVolume checks if a statically allocated PV exists before creating the target PVC.
220
        // If so, PVC is still created but population is skipped
221
        AnnCheckStaticVolume = AnnAPIGroup + "/storage.checkStaticVolume"
222

223
        // AnnPersistentVolumeList is an annotation storing a list of PV names
224
        AnnPersistentVolumeList = AnnAPIGroup + "/storage.persistentVolumeList"
225

226
        // AnnPopulatorKind annotation is added to a PVC' to specify the population kind, so it's later
227
        // checked by the common populator watches.
228
        AnnPopulatorKind = AnnAPIGroup + "/storage.populator.kind"
229
        // AnnUsePopulator annotation indicates if the datavolume population will use populators
230
        AnnUsePopulator = AnnAPIGroup + "/storage.usePopulator"
231

232
        // AnnMinimumSupportedPVCSize annotation on a StorageProfile specifies its minimum supported PVC size
233
        AnnMinimumSupportedPVCSize = AnnAPIGroup + "/minimumSupportedPvcSize"
234
        // AnnUseReadWriteOnceForDataImportCron annotation on a StorageProfile signals that DataImportCron should use RWO for DataImportCron PVCs
235
        AnnUseReadWriteOnceForDataImportCron = AnnAPIGroup + "/useReadWriteOnceForDataImportCron"
236
        // AnnSnapshotClassForDataImportCron annotation on a StorageProfile specifies the VolumeSnapshotClass to use for DataImportCron snapshots
237
        AnnSnapshotClassForDataImportCron = AnnAPIGroup + "/snapshotClassForDataImportCron"
238

239
        // AnnDefaultStorageClass is the annotation indicating that a storage class is the default one
240
        AnnDefaultStorageClass = "storageclass.kubernetes.io/is-default-class"
241
        // AnnDefaultVirtStorageClass is the annotation indicating that a storage class is the default one for virtualization purposes
242
        AnnDefaultVirtStorageClass = "storageclass.kubevirt.io/is-default-virt-class"
243
        // AnnDefaultSnapshotClass is the annotation indicating that a snapshot class is the default one
244
        AnnDefaultSnapshotClass = "snapshot.storage.kubernetes.io/is-default-class"
245

246
        // AnnSourceVolumeMode is the volume mode of the source PVC specified as an annotation on snapshots
247
        AnnSourceVolumeMode = AnnAPIGroup + "/storage.import.sourceVolumeMode"
248
        // AnnAdvisedRestoreSize is the advised restore size for disks restored from the snapshot
249
        AnnAdvisedRestoreSize = AnnAPIGroup + "/storage.import.advisedRestoreSize"
250

251
        // AnnOpenShiftImageLookup is the annotation for OpenShift image stream lookup
252
        AnnOpenShiftImageLookup = "alpha.image.policy.openshift.io/resolve-names"
253

254
        // AnnCloneRequest sets our expected annotation for a CloneRequest
255
        AnnCloneRequest = "k8s.io/CloneRequest"
256
        // AnnCloneOf is used to indicate that cloning was complete
257
        AnnCloneOf = "k8s.io/CloneOf"
258

259
        // AnnPodNetwork is used for specifying Pod Network
260
        AnnPodNetwork = "k8s.v1.cni.cncf.io/networks"
261
        // AnnPodMultusDefaultNetwork is used for specifying default Pod Network
262
        AnnPodMultusDefaultNetwork = "v1.multus-cni.io/default-network"
263
        // AnnOpenDefaultPorts allows incoming traffic on specific ports through the
264
        // pod's default network interface in OVN-Kubernetes managed clusters.
265
        AnnOpenDefaultPorts = "k8s.ovn.org/open-default-ports"
266
        // AnnPodSidecarInjectionIstio is used for enabling/disabling Pod istio/AspenMesh sidecar injection
267
        AnnPodSidecarInjectionIstio = "sidecar.istio.io/inject"
268
        // AnnPodSidecarInjectionIstioDefault is the default value passed for AnnPodSidecarInjection
269
        AnnPodSidecarInjectionIstioDefault = "false"
270
        // AnnPodSidecarInjectionLinkerd is used to enable/disable linkerd sidecar injection
271
        AnnPodSidecarInjectionLinkerd = "linkerd.io/inject"
272
        // AnnPodSidecarInjectionLinkerdDefault is the default value passed for AnnPodSidecarInjectionLinkerd
273
        AnnPodSidecarInjectionLinkerdDefault = "disabled"
274

275
        // AnnImmediateBinding provides a const to indicate whether immediate binding should be performed on the PV (overrides global config)
276
        AnnImmediateBinding = AnnAPIGroup + "/storage.bind.immediate.requested"
277

278
        // AnnSelectedNode annotation is added to a PVC that has been triggered by scheduler to
279
        // be dynamically provisioned. Its value is the name of the selected node.
280
        AnnSelectedNode = "volume.kubernetes.io/selected-node"
281

282
        // CloneUniqueID is used as a special label to be used when we search for the pod
283
        CloneUniqueID = AnnAPIGroup + "/storage.clone.cloneUniqeId"
284

285
        // CloneSourceInUse is reason for event created when clone source pvc is in use
286
        CloneSourceInUse = "CloneSourceInUse"
287

288
        // CloneComplete message
289
        CloneComplete = "Clone Complete"
290

291
        cloneTokenLeeway = 10 * time.Second
292

293
        // Default value for preallocation option if not defined in DV or CDIConfig
294
        defaultPreallocation = false
295

296
        // ErrStartingPod provides a const to indicate that a pod wasn't able to start without providing sensitive information (reason)
297
        ErrStartingPod = "ErrStartingPod"
298
        // MessageErrStartingPod provides a const to indicate that a pod wasn't able to start without providing sensitive information (message)
299
        MessageErrStartingPod = "Error starting pod '%s': For more information, request access to cdi-deploy logs from your sysadmin"
300
        // ErrClaimNotValid provides a const to indicate a claim is not valid
301
        ErrClaimNotValid = "ErrClaimNotValid"
302
        // ErrExceededQuota provides a const to indicate the claim has exceeded the quota
303
        ErrExceededQuota = "ErrExceededQuota"
304
        // ErrIncompatiblePVC provides a const to indicate a clone is not possible due to an incompatible PVC
305
        ErrIncompatiblePVC = "ErrIncompatiblePVC"
306

307
        // SourceHTTP is the source type HTTP, if unspecified or invalid, it defaults to SourceHTTP
308
        SourceHTTP = "http"
309
        // SourceS3 is the source type S3
310
        SourceS3 = "s3"
311
        // SourceGCS is the source type GCS
312
        SourceGCS = "gcs"
313
        // SourceGlance is the source type of glance
314
        SourceGlance = "glance"
315
        // SourceNone means there is no source.
316
        SourceNone = "none"
317
        // SourceRegistry is the source type of Registry
318
        SourceRegistry = "registry"
319
        // SourceImageio is the source type ovirt-imageio
320
        SourceImageio = "imageio"
321
        // SourceVDDK is the source type of VDDK
322
        SourceVDDK = "vddk"
323

324
        // VolumeSnapshotClassSelected reports that a VolumeSnapshotClass was selected
325
        VolumeSnapshotClassSelected = "VolumeSnapshotClassSelected"
326
        // MessageStorageProfileVolumeSnapshotClassSelected reports that a VolumeSnapshotClass was selected according to StorageProfile
327
        MessageStorageProfileVolumeSnapshotClassSelected = "VolumeSnapshotClass selected according to StorageProfile"
328
        // MessageDefaultVolumeSnapshotClassSelected reports that the default VolumeSnapshotClass was selected
329
        MessageDefaultVolumeSnapshotClassSelected = "Default VolumeSnapshotClass selected"
330
        // MessageFirstVolumeSnapshotClassSelected reports that the first VolumeSnapshotClass was selected
331
        MessageFirstVolumeSnapshotClassSelected = "First VolumeSnapshotClass selected"
332

333
        // ClaimLost reason const
334
        ClaimLost = "ClaimLost"
335
        // NotFound reason const
336
        NotFound = "NotFound"
337

338
        // LabelDefaultInstancetype provides a default VirtualMachine{ClusterInstancetype,Instancetype} that can be used by a VirtualMachine booting from a given PVC
339
        LabelDefaultInstancetype = "instancetype.kubevirt.io/default-instancetype"
340
        // LabelDefaultInstancetypeKind provides a default kind of either VirtualMachineClusterInstancetype or VirtualMachineInstancetype
341
        LabelDefaultInstancetypeKind = "instancetype.kubevirt.io/default-instancetype-kind"
342
        // LabelDefaultPreference provides a default VirtualMachine{ClusterPreference,Preference} that can be used by a VirtualMachine booting from a given PVC
343
        LabelDefaultPreference = "instancetype.kubevirt.io/default-preference"
344
        // LabelDefaultPreferenceKind provides a default kind of either VirtualMachineClusterPreference or VirtualMachinePreference
345
        LabelDefaultPreferenceKind = "instancetype.kubevirt.io/default-preference-kind"
346

347
        // LabelDynamicCredentialSupport specifies if the OS supports updating credentials at runtime.
348
        //nolint:gosec // These are not credentials
349
        LabelDynamicCredentialSupport = "kubevirt.io/dynamic-credentials-support"
350

351
        // LabelExcludeFromVeleroBackup provides a const to indicate whether an object should be excluded from velero backup
352
        LabelExcludeFromVeleroBackup = "velero.io/exclude-from-backup"
353

354
        // ProgressDone this means we are DONE
355
        ProgressDone = "100.0%"
356

357
        // AnnEventSourceKind is the source kind that should be related to events
358
        AnnEventSourceKind = AnnAPIGroup + "/events.source.kind"
359
        // AnnEventSource is the source that should be related to events (namespace/name)
360
        AnnEventSource = AnnAPIGroup + "/events.source"
361

362
        // AnnAllowClaimAdoption is the annotation that allows a claim to be adopted by a DataVolume
363
        AnnAllowClaimAdoption = AnnAPIGroup + "/allowClaimAdoption"
364

365
        // AnnCdiCustomizeComponentHash annotation is a hash of all customizations that live under spec.CustomizeComponents
366
        AnnCdiCustomizeComponentHash = AnnAPIGroup + "/customizer-identifier"
367

368
        // AnnCreatedForDataVolume stores the UID of the datavolume that the PVC was created for
369
        AnnCreatedForDataVolume = AnnAPIGroup + "/createdForDataVolume"
370

371
        // AnnPVCPrimeName annotation is the name of the PVC' that is used to populate the PV which is then rebound to the target PVC
372
        AnnPVCPrimeName = AnnAPIGroup + "/storage.populator.pvcPrime"
373

374
        // ClaimMisbound event reason
375
        EventReasonClaimMisbound = "ClaimMisbound"
376
)
377

378
// Size-detection pod error codes
379
const (
380
        NoErr int = iota
381
        ErrBadArguments
382
        ErrInvalidFile
383
        ErrInvalidPath
384
        ErrBadTermFile
385
        ErrUnknown
386
)
387

388
var (
389
        // BlockMode is raw block device mode
390
        BlockMode = corev1.PersistentVolumeBlock
391
        // FilesystemMode is filesystem device mode
392
        FilesystemMode = corev1.PersistentVolumeFilesystem
393

394
        // DefaultInstanceTypeLabels is a list of currently supported default instance type labels
395
        DefaultInstanceTypeLabels = []string{
396
                LabelDefaultInstancetype,
397
                LabelDefaultInstancetypeKind,
398
                LabelDefaultPreference,
399
                LabelDefaultPreferenceKind,
400
        }
401

402
        apiServerKeyOnce sync.Once
403
        apiServerKey     *rsa.PrivateKey
404

405
        // allowedAnnotations is a list of annotations
406
        // that can be propagated from the pvc/dv to a pod
407
        allowedAnnotations = map[string]string{
408
                AnnPodNetwork:                 "",
409
                AnnPodSidecarInjectionIstio:   AnnPodSidecarInjectionIstioDefault,
410
                AnnPodSidecarInjectionLinkerd: AnnPodSidecarInjectionLinkerdDefault,
411
                AnnPriorityClassName:          "",
412
                AnnPodServiceAccount:          "",
413
                AnnPodMultusDefaultNetwork:    "",
414
        }
415

416
        validLabelsMatch = regexp.MustCompile(`^([\w.]+\.kubevirt.io|kubevirt.io)/[\w.-]+$`)
417

418
        ErrDataSourceMaxDepthReached = errors.New("DataSource reference chain exceeds maximum depth of 1")
419
        ErrDataSourceSelfReference   = errors.New("DataSource cannot self-reference")
420
        ErrDataSourceCrossNamespace  = errors.New("DataSource cannot reference a DataSource in another namespace")
421
)
422

423
// FakeValidator is a fake token validator
424
type FakeValidator struct {
425
        Match     string
426
        Operation token.Operation
427
        Name      string
428
        Namespace string
429
        Resource  metav1.GroupVersionResource
430
        Params    map[string]string
431
}
432

433
// Validate is a fake token validation
434
func (v *FakeValidator) Validate(value string) (*token.Payload, error) {
×
435
        if value != v.Match {
×
436
                return nil, fmt.Errorf("token does not match expected")
×
437
        }
×
438
        resource := metav1.GroupVersionResource{
×
439
                Resource: "persistentvolumeclaims",
×
440
        }
×
441
        return &token.Payload{
×
442
                Name:      v.Name,
×
443
                Namespace: v.Namespace,
×
444
                Operation: token.OperationClone,
×
445
                Resource:  resource,
×
446
                Params:    v.Params,
×
447
        }, nil
×
448
}
449

450
// MultiTokenValidator is a token validator that can validate both short and long tokens
451
type MultiTokenValidator struct {
452
        ShortTokenValidator token.Validator
453
        LongTokenValidator  token.Validator
454
}
455

456
// ValidatePVC validates a PVC
457
func (mtv *MultiTokenValidator) ValidatePVC(source, target *corev1.PersistentVolumeClaim) error {
×
458
        tok, v := mtv.getTokenAndValidator(target)
×
459
        return ValidateCloneTokenPVC(tok, v, source, target)
×
460
}
×
461

462
// ValidatePopulator valades a token for a populator
463
func (mtv *MultiTokenValidator) ValidatePopulator(vcs *cdiv1.VolumeCloneSource, pvc *corev1.PersistentVolumeClaim) error {
×
464
        if vcs.Namespace == pvc.Namespace {
×
465
                return nil
×
466
        }
×
467

468
        tok, v := mtv.getTokenAndValidator(pvc)
×
469

×
470
        tokenData, err := v.Validate(tok)
×
471
        if err != nil {
×
472
                return errors.Wrap(err, "error verifying token")
×
473
        }
×
474

475
        var tokenResourceName string
×
476
        switch vcs.Spec.Source.Kind {
×
477
        case "PersistentVolumeClaim":
×
478
                tokenResourceName = "persistentvolumeclaims"
×
479
        case "VolumeSnapshot":
×
480
                tokenResourceName = "volumesnapshots"
×
481
        }
482
        srcName := vcs.Spec.Source.Name
×
483

×
484
        return validateTokenData(tokenData, vcs.Namespace, srcName, pvc.Namespace, pvc.Name, string(pvc.UID), tokenResourceName)
×
485
}
486

487
func (mtv *MultiTokenValidator) getTokenAndValidator(pvc *corev1.PersistentVolumeClaim) (string, token.Validator) {
×
488
        v := mtv.LongTokenValidator
×
489
        tok, ok := pvc.Annotations[AnnExtendedCloneToken]
×
490
        if !ok {
×
491
                // if token doesn't exist, no prob for same namespace
×
492
                tok = pvc.Annotations[AnnCloneToken]
×
493
                v = mtv.ShortTokenValidator
×
494
        }
×
495
        return tok, v
×
496
}
497

498
// NewMultiTokenValidator returns a new multi token validator
499
func NewMultiTokenValidator(key *rsa.PublicKey) *MultiTokenValidator {
×
500
        return &MultiTokenValidator{
×
501
                ShortTokenValidator: NewCloneTokenValidator(common.CloneTokenIssuer, key),
×
502
                LongTokenValidator:  NewCloneTokenValidator(common.ExtendedCloneTokenIssuer, key),
×
503
        }
×
504
}
×
505

506
// NewCloneTokenValidator returns a new token validator
507
func NewCloneTokenValidator(issuer string, key *rsa.PublicKey) token.Validator {
×
508
        return token.NewValidator(issuer, key, cloneTokenLeeway)
×
509
}
×
510

511
// GetRequestedImageSize returns the PVC requested size
512
func GetRequestedImageSize(pvc *corev1.PersistentVolumeClaim) (string, error) {
1✔
513
        pvcSize, found := pvc.Spec.Resources.Requests[corev1.ResourceStorage]
1✔
514
        if !found {
2✔
515
                return "", errors.Errorf("storage request is missing in pvc \"%s/%s\"", pvc.Namespace, pvc.Name)
1✔
516
        }
1✔
517
        return pvcSize.String(), nil
1✔
518
}
519

520
// GetVolumeMode returns the volumeMode from PVC handling default empty value
521
func GetVolumeMode(pvc *corev1.PersistentVolumeClaim) corev1.PersistentVolumeMode {
×
522
        return util.ResolveVolumeMode(pvc.Spec.VolumeMode)
×
523
}
×
524

525
// IsDataVolumeUsingDefaultStorageClass checks if the DataVolume is using the default StorageClass
526
func IsDataVolumeUsingDefaultStorageClass(dv *cdiv1.DataVolume) bool {
×
527
        return GetStorageClassFromDVSpec(dv) == nil
×
528
}
×
529

530
// GetStorageClassFromDVSpec returns the StorageClassName from DataVolume PVC or Storage spec
531
func GetStorageClassFromDVSpec(dv *cdiv1.DataVolume) *string {
×
532
        if dv.Spec.PVC != nil {
×
533
                return dv.Spec.PVC.StorageClassName
×
534
        }
×
535

536
        if dv.Spec.Storage != nil {
×
537
                return dv.Spec.Storage.StorageClassName
×
538
        }
×
539

540
        return nil
×
541
}
542

543
// getStorageClassByName looks up the storage class based on the name.
544
// If name is nil, it performs fallback to default according to the provided content type
545
// If no storage class is found, returns nil
546
func getStorageClassByName(ctx context.Context, client client.Client, name *string, contentType cdiv1.DataVolumeContentType) (*storagev1.StorageClass, error) {
1✔
547
        if name == nil {
2✔
548
                return getFallbackStorageClass(ctx, client, contentType)
1✔
549
        }
1✔
550

551
        // look up storage class by name
552
        storageClass := &storagev1.StorageClass{}
×
553
        if err := client.Get(ctx, types.NamespacedName{Name: *name}, storageClass); err != nil {
×
554
                if k8serrors.IsNotFound(err) {
×
555
                        return nil, nil
×
556
                }
×
557
                klog.V(3).Info("Unable to retrieve storage class", "storage class name", *name)
×
558
                return nil, errors.Errorf("unable to retrieve storage class %s", *name)
×
559
        }
560

561
        return storageClass, nil
×
562
}
563

564
// GetStorageClassByNameWithK8sFallback looks up the storage class based on the name
565
// If name is nil, it looks for the default k8s storage class storageclass.kubernetes.io/is-default-class
566
// If no storage class is found, returns nil
567
func GetStorageClassByNameWithK8sFallback(ctx context.Context, client client.Client, name *string) (*storagev1.StorageClass, error) {
1✔
568
        return getStorageClassByName(ctx, client, name, cdiv1.DataVolumeArchive)
1✔
569
}
1✔
570

571
// GetStorageClassByNameWithVirtFallback looks up the storage class based on the name
572
// If name is nil, it looks for the following, in this order:
573
// default kubevirt storage class (if the caller is interested) storageclass.kubevirt.io/is-default-class
574
// default k8s storage class storageclass.kubernetes.io/is-default-class
575
// If no storage class is found, returns nil
576
func GetStorageClassByNameWithVirtFallback(ctx context.Context, client client.Client, name *string, contentType cdiv1.DataVolumeContentType) (*storagev1.StorageClass, error) {
1✔
577
        return getStorageClassByName(ctx, client, name, contentType)
1✔
578
}
1✔
579

580
// getFallbackStorageClass looks for a default virt/k8s storage class according to the content type
581
// If no storage class is found, returns nil
582
func getFallbackStorageClass(ctx context.Context, client client.Client, contentType cdiv1.DataVolumeContentType) (*storagev1.StorageClass, error) {
1✔
583
        storageClasses := &storagev1.StorageClassList{}
1✔
584
        if err := client.List(ctx, storageClasses); err != nil {
1✔
585
                klog.V(3).Info("Unable to retrieve available storage classes")
×
586
                return nil, errors.New("unable to retrieve storage classes")
×
587
        }
×
588

589
        if GetContentType(contentType) == cdiv1.DataVolumeKubeVirt {
2✔
590
                if virtSc := GetPlatformDefaultStorageClass(storageClasses, AnnDefaultVirtStorageClass); virtSc != nil {
2✔
591
                        return virtSc, nil
1✔
592
                }
1✔
593
        }
594
        return GetPlatformDefaultStorageClass(storageClasses, AnnDefaultStorageClass), nil
1✔
595
}
596

597
// GetPlatformDefaultStorageClass returns the default storage class according to the provided annotation or nil if none found
598
func GetPlatformDefaultStorageClass(storageClasses *storagev1.StorageClassList, defaultAnnotationKey string) *storagev1.StorageClass {
1✔
599
        defaultClasses := []storagev1.StorageClass{}
1✔
600

1✔
601
        for _, storageClass := range storageClasses.Items {
2✔
602
                if storageClass.Annotations[defaultAnnotationKey] == "true" {
2✔
603
                        defaultClasses = append(defaultClasses, storageClass)
1✔
604
                }
1✔
605
        }
606

607
        if len(defaultClasses) == 0 {
2✔
608
                return nil
1✔
609
        }
1✔
610

611
        // Primary sort by creation timestamp, newest first
612
        // Secondary sort by class name, ascending order
613
        // Follows k8s behavior
614
        // https://github.com/kubernetes/kubernetes/blob/731068288e112c8b5af70f676296cc44661e84f4/pkg/volume/util/storageclass.go#L58-L59
615
        sort.Slice(defaultClasses, func(i, j int) bool {
2✔
616
                if defaultClasses[i].CreationTimestamp.UnixNano() == defaultClasses[j].CreationTimestamp.UnixNano() {
2✔
617
                        return defaultClasses[i].Name < defaultClasses[j].Name
1✔
618
                }
1✔
619
                return defaultClasses[i].CreationTimestamp.UnixNano() > defaultClasses[j].CreationTimestamp.UnixNano()
1✔
620
        })
621
        if len(defaultClasses) > 1 {
2✔
622
                klog.V(3).Infof("%d default StorageClasses were found, choosing: %s", len(defaultClasses), defaultClasses[0].Name)
1✔
623
        }
1✔
624

625
        return &defaultClasses[0]
1✔
626
}
627

628
// GetFilesystemOverheadForStorageClass determines the filesystem overhead defined in CDIConfig for the storageClass.
629
func GetFilesystemOverheadForStorageClass(ctx context.Context, client client.Client, storageClassName *string) (cdiv1.Percent, error) {
×
630
        if storageClassName != nil && *storageClassName == "" {
×
631
                klog.V(3).Info("No storage class name passed")
×
632
                return "0", nil
×
633
        }
×
634

635
        cdiConfig := &cdiv1.CDIConfig{}
×
636
        if err := client.Get(ctx, types.NamespacedName{Name: common.ConfigName}, cdiConfig); err != nil {
×
637
                if k8serrors.IsNotFound(err) {
×
638
                        klog.V(1).Info("CDIConfig does not exist, pod will not start until it does")
×
639
                        return "0", nil
×
640
                }
×
641
                return "0", err
×
642
        }
643

644
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(ctx, client, storageClassName)
×
645
        if err != nil || targetStorageClass == nil {
×
646
                klog.V(3).Info("Storage class", storageClassName, "not found, trying default storage class")
×
647
                targetStorageClass, err = GetStorageClassByNameWithK8sFallback(ctx, client, nil)
×
648
                if err != nil {
×
649
                        klog.V(3).Info("No default storage class found, continuing with global overhead")
×
650
                        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
651
                }
×
652
        }
653

654
        if cdiConfig.Status.FilesystemOverhead == nil {
×
655
                klog.Errorf("CDIConfig filesystemOverhead used before config controller ran reconcile. Hopefully this only happens during unit testing.")
×
656
                return "0", nil
×
657
        }
×
658

659
        if targetStorageClass == nil {
×
660
                klog.V(3).Info("Storage class", storageClassName, "not found, continuing with global overhead")
×
661
                return cdiConfig.Status.FilesystemOverhead.Global, nil
×
662
        }
×
663

664
        klog.V(3).Info("target storage class for overhead", targetStorageClass.GetName())
×
665

×
666
        perStorageConfig := cdiConfig.Status.FilesystemOverhead.StorageClass
×
667

×
668
        storageClassOverhead, found := perStorageConfig[targetStorageClass.GetName()]
×
669
        if found {
×
670
                return storageClassOverhead, nil
×
671
        }
×
672

673
        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
674
}
675

676
// GetDefaultPodResourceRequirements gets default pod resource requirements from cdi config status
677
func GetDefaultPodResourceRequirements(client client.Client) (*corev1.ResourceRequirements, error) {
×
678
        cdiconfig := &cdiv1.CDIConfig{}
×
679
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiconfig); err != nil {
×
680
                klog.Errorf("Unable to find CDI configuration, %v\n", err)
×
681
                return nil, err
×
682
        }
×
683

684
        return cdiconfig.Status.DefaultPodResourceRequirements, nil
×
685
}
686

687
// GetImagePullSecrets gets the imagePullSecrets needed to pull images from the cdi config
688
func GetImagePullSecrets(client client.Client) ([]corev1.LocalObjectReference, error) {
×
689
        cdiconfig := &cdiv1.CDIConfig{}
×
690
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiconfig); err != nil {
×
691
                klog.Errorf("Unable to find CDI configuration, %v\n", err)
×
692
                return nil, err
×
693
        }
×
694

695
        return cdiconfig.Status.ImagePullSecrets, nil
×
696
}
697

698
// GetPodFromPvc determines the pod associated with the pvc passed in.
699
func GetPodFromPvc(c client.Client, namespace string, pvc *corev1.PersistentVolumeClaim) (*corev1.Pod, error) {
×
700
        l, _ := labels.Parse(common.PrometheusLabelKey)
×
701
        pods := &corev1.PodList{}
×
702
        listOptions := client.ListOptions{
×
703
                LabelSelector: l,
×
704
        }
×
705
        if err := c.List(context.TODO(), pods, &listOptions); err != nil {
×
706
                return nil, err
×
707
        }
×
708

709
        pvcUID := pvc.GetUID()
×
710
        for _, pod := range pods.Items {
×
711
                if ShouldIgnorePod(&pod, pvc) {
×
712
                        continue
×
713
                }
714
                for _, or := range pod.OwnerReferences {
×
715
                        if or.UID == pvcUID {
×
716
                                return &pod, nil
×
717
                        }
×
718
                }
719

720
                // TODO: check this
721
                val, exists := pod.Labels[CloneUniqueID]
×
722
                if exists && val == string(pvcUID)+common.ClonerSourcePodNameSuffix {
×
723
                        return &pod, nil
×
724
                }
×
725
        }
726
        return nil, errors.Errorf("Unable to find pod owned by UID: %s, in namespace: %s", string(pvcUID), namespace)
×
727
}
728

729
// AddVolumeDevices returns VolumeDevice slice with one block device for pods using PV with block volume mode
730
func AddVolumeDevices() []corev1.VolumeDevice {
×
731
        volumeDevices := []corev1.VolumeDevice{
×
732
                {
×
733
                        Name:       DataVolName,
×
734
                        DevicePath: common.WriteBlockPath,
×
735
                },
×
736
        }
×
737
        return volumeDevices
×
738
}
×
739

740
// GetPodsUsingPVCs returns Pods currently using PVCs
741
func GetPodsUsingPVCs(ctx context.Context, c client.Client, namespace string, names sets.Set[string], allowReadOnly bool) ([]corev1.Pod, error) {
×
742
        pl := &corev1.PodList{}
×
743
        // hopefully using cached client here
×
744
        err := c.List(ctx, pl, &client.ListOptions{Namespace: namespace})
×
745
        if err != nil {
×
746
                return nil, err
×
747
        }
×
748

749
        var pods []corev1.Pod
×
750
        for _, pod := range pl.Items {
×
751
                if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
×
752
                        continue
×
753
                }
754
                for _, volume := range pod.Spec.Volumes {
×
755
                        if volume.VolumeSource.PersistentVolumeClaim != nil &&
×
756
                                names.Has(volume.PersistentVolumeClaim.ClaimName) {
×
757
                                addPod := true
×
758
                                if allowReadOnly {
×
759
                                        if !volume.VolumeSource.PersistentVolumeClaim.ReadOnly {
×
760
                                                onlyReadOnly := true
×
761
                                                for _, c := range pod.Spec.Containers {
×
762
                                                        for _, vm := range c.VolumeMounts {
×
763
                                                                if vm.Name == volume.Name && !vm.ReadOnly {
×
764
                                                                        onlyReadOnly = false
×
765
                                                                }
×
766
                                                        }
767
                                                        for _, vm := range c.VolumeDevices {
×
768
                                                                if vm.Name == volume.Name {
×
769
                                                                        // Node level rw mount and container can't mount block device ro
×
770
                                                                        onlyReadOnly = false
×
771
                                                                }
×
772
                                                        }
773
                                                }
774
                                                if onlyReadOnly {
×
775
                                                        // no rw mounts
×
776
                                                        addPod = false
×
777
                                                }
×
778
                                        } else {
×
779
                                                // all mounts must be ro
×
780
                                                addPod = false
×
781
                                        }
×
782
                                        if strings.HasSuffix(pod.Name, common.ClonerSourcePodNameSuffix) && pod.Labels != nil &&
×
783
                                                pod.Labels[common.CDIComponentLabel] == common.ClonerSourcePodName {
×
784
                                                // Host assisted clone source pod only reads from source
×
785
                                                // But some drivers disallow mounting a block PVC ReadOnly
×
786
                                                addPod = false
×
787
                                        }
×
788
                                }
789
                                if addPod {
×
790
                                        pods = append(pods, pod)
×
791
                                        break
×
792
                                }
793
                        }
794
                }
795
        }
796

797
        return pods, nil
×
798
}
799

800
// GetWorkloadNodePlacement extracts the workload-specific nodeplacement values from the CDI CR
801
func GetWorkloadNodePlacement(ctx context.Context, c client.Client) (*sdkapi.NodePlacement, error) {
×
802
        cr, err := GetActiveCDI(ctx, c)
×
803
        if err != nil {
×
804
                return nil, err
×
805
        }
×
806

807
        if cr == nil {
×
808
                return nil, fmt.Errorf("no active CDI")
×
809
        }
×
810

811
        return &cr.Spec.Workloads, nil
×
812
}
813

814
// GetActiveCDI returns the active CDI CR
815
func GetActiveCDI(ctx context.Context, c client.Client) (*cdiv1.CDI, error) {
1✔
816
        crList := &cdiv1.CDIList{}
1✔
817
        if err := c.List(ctx, crList, &client.ListOptions{}); err != nil {
1✔
818
                return nil, err
×
819
        }
×
820

821
        if len(crList.Items) == 0 {
2✔
822
                return nil, nil
1✔
823
        }
1✔
824

825
        if len(crList.Items) == 1 {
2✔
826
                return &crList.Items[0], nil
1✔
827
        }
1✔
828

829
        var activeResources []cdiv1.CDI
1✔
830
        for _, cr := range crList.Items {
2✔
831
                if cr.Status.Phase != sdkapi.PhaseError {
2✔
832
                        activeResources = append(activeResources, cr)
1✔
833
                }
1✔
834
        }
835

836
        if len(activeResources) != 1 {
2✔
837
                return nil, fmt.Errorf("invalid number of active CDI resources: %d", len(activeResources))
1✔
838
        }
1✔
839

840
        return &activeResources[0], nil
1✔
841
}
842

843
// IsPopulated returns if the passed in PVC has been populated according to the rules outlined in pkg/apis/core/<version>/utils.go
844
func IsPopulated(pvc *corev1.PersistentVolumeClaim, c client.Client) (bool, error) {
×
845
        return cdiv1utils.IsPopulated(pvc, func(name, namespace string) (*cdiv1.DataVolume, error) {
×
846
                dv := &cdiv1.DataVolume{}
×
847
                err := c.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, dv)
×
848
                return dv, err
×
849
        })
×
850
}
851

852
// GetPreallocation returns the preallocation setting for the specified object (DV or VolumeImportSource), falling back to StorageClass and global setting (in this order)
853
func GetPreallocation(ctx context.Context, client client.Client, preallocation *bool) bool {
×
854
        // First, the DV's preallocation
×
855
        if preallocation != nil {
×
856
                return *preallocation
×
857
        }
×
858

859
        cdiconfig := &cdiv1.CDIConfig{}
×
860
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiconfig); err != nil {
×
861
                klog.Errorf("Unable to find CDI configuration, %v\n", err)
×
862
                return defaultPreallocation
×
863
        }
×
864

865
        return cdiconfig.Status.Preallocation
×
866
}
867

868
// ImmediateBindingRequested returns if an object has the ImmediateBinding annotation
869
func ImmediateBindingRequested(obj metav1.Object) bool {
×
870
        _, isImmediateBindingRequested := obj.GetAnnotations()[AnnImmediateBinding]
×
871
        return isImmediateBindingRequested
×
872
}
×
873

874
// GetPriorityClass gets PVC priority class
875
func GetPriorityClass(pvc *corev1.PersistentVolumeClaim) string {
×
876
        anno := pvc.GetAnnotations()
×
877
        return anno[AnnPriorityClassName]
×
878
}
×
879

880
// GetPodServiceAccount gets PVC service account name
881
func GetPodServiceAccount(pvc *corev1.PersistentVolumeClaim) string {
×
882
        anno := pvc.GetAnnotations()
×
883
        return anno[AnnPodServiceAccount]
×
884
}
×
885

886
// ShouldDeletePod returns whether the PVC workload pod should be deleted
887
func ShouldDeletePod(pvc *corev1.PersistentVolumeClaim) bool {
×
888
        return pvc.GetAnnotations()[AnnPodRetainAfterCompletion] != "true" || pvc.GetAnnotations()[AnnRequiresScratch] == "true" || pvc.GetAnnotations()[AnnRequiresDirectIO] == "true" || pvc.DeletionTimestamp != nil
×
889
}
×
890

891
// AddFinalizer adds a finalizer to a resource
892
func AddFinalizer(obj metav1.Object, name string) {
×
893
        if HasFinalizer(obj, name) {
×
894
                return
×
895
        }
×
896

897
        obj.SetFinalizers(append(obj.GetFinalizers(), name))
×
898
}
899

900
// RemoveFinalizer removes a finalizer from a resource
901
func RemoveFinalizer(obj metav1.Object, name string) {
×
902
        if !HasFinalizer(obj, name) {
×
903
                return
×
904
        }
×
905

906
        var finalizers []string
×
907
        for _, f := range obj.GetFinalizers() {
×
908
                if f != name {
×
909
                        finalizers = append(finalizers, f)
×
910
                }
×
911
        }
912

913
        obj.SetFinalizers(finalizers)
×
914
}
915

916
// HasFinalizer returns true if a resource has a specific finalizer
917
func HasFinalizer(object metav1.Object, value string) bool {
×
918
        for _, f := range object.GetFinalizers() {
×
919
                if f == value {
×
920
                        return true
×
921
                }
×
922
        }
923
        return false
×
924
}
925

926
// ValidateCloneTokenPVC validates clone token for source and target PVCs
927
func ValidateCloneTokenPVC(t string, v token.Validator, source, target *corev1.PersistentVolumeClaim) error {
×
928
        if source.Namespace == target.Namespace {
×
929
                return nil
×
930
        }
×
931

932
        tokenData, err := v.Validate(t)
×
933
        if err != nil {
×
934
                return errors.Wrap(err, "error verifying token")
×
935
        }
×
936

937
        tokenResourceName := getTokenResourceNamePvc(source)
×
938
        srcName := getSourceNamePvc(source)
×
939

×
940
        return validateTokenData(tokenData, source.Namespace, srcName, target.Namespace, target.Name, string(target.UID), tokenResourceName)
×
941
}
942

943
// ValidateCloneTokenDV validates clone token for DV
944
func ValidateCloneTokenDV(validator token.Validator, dv *cdiv1.DataVolume) error {
×
945
        _, sourceName, sourceNamespace := GetCloneSourceInfo(dv)
×
946
        if sourceNamespace == "" || sourceNamespace == dv.Namespace {
×
947
                return nil
×
948
        }
×
949

950
        tok, ok := dv.Annotations[AnnCloneToken]
×
951
        if !ok {
×
952
                return errors.New("clone token missing")
×
953
        }
×
954

955
        tokenData, err := validator.Validate(tok)
×
956
        if err != nil {
×
957
                return errors.Wrap(err, "error verifying token")
×
958
        }
×
959

960
        tokenResourceName := getTokenResourceNameDataVolume(dv.Spec.Source)
×
961
        if tokenResourceName == "" {
×
962
                return errors.New("token resource name empty, can't verify properly")
×
963
        }
×
964

965
        return validateTokenData(tokenData, sourceNamespace, sourceName, dv.Namespace, dv.Name, "", tokenResourceName)
×
966
}
967

968
func getTokenResourceNameDataVolume(source *cdiv1.DataVolumeSource) string {
×
969
        if source.PVC != nil {
×
970
                return "persistentvolumeclaims"
×
971
        } else if source.Snapshot != nil {
×
972
                return "volumesnapshots"
×
973
        }
×
974

975
        return ""
×
976
}
977

978
func getTokenResourceNamePvc(sourcePvc *corev1.PersistentVolumeClaim) string {
×
979
        if v, ok := sourcePvc.Labels[common.CDIComponentLabel]; ok && v == common.CloneFromSnapshotFallbackPVCCDILabel {
×
980
                return "volumesnapshots"
×
981
        }
×
982

983
        return "persistentvolumeclaims"
×
984
}
985

986
func getSourceNamePvc(sourcePvc *corev1.PersistentVolumeClaim) string {
×
987
        if v, ok := sourcePvc.Labels[common.CDIComponentLabel]; ok && v == common.CloneFromSnapshotFallbackPVCCDILabel {
×
988
                if sourcePvc.Spec.DataSourceRef != nil {
×
989
                        return sourcePvc.Spec.DataSourceRef.Name
×
990
                }
×
991
        }
992

993
        return sourcePvc.Name
×
994
}
995

996
func validateTokenData(tokenData *token.Payload, srcNamespace, srcName, targetNamespace, targetName, targetUID, tokenResourceName string) error {
×
997
        uid := tokenData.Params["uid"]
×
998
        if tokenData.Operation != token.OperationClone ||
×
999
                tokenData.Name != srcName ||
×
1000
                tokenData.Namespace != srcNamespace ||
×
1001
                tokenData.Resource.Resource != tokenResourceName ||
×
1002
                tokenData.Params["targetNamespace"] != targetNamespace ||
×
1003
                tokenData.Params["targetName"] != targetName ||
×
1004
                (uid != "" && uid != targetUID) {
×
1005
                return errors.New("invalid token")
×
1006
        }
×
1007

1008
        return nil
×
1009
}
1010

1011
// IsSnapshotValidForClone returns an error if the passed snapshot is not valid for cloning
1012
func IsSnapshotValidForClone(sourceSnapshot *snapshotv1.VolumeSnapshot) error {
×
1013
        if sourceSnapshot.Status == nil {
×
1014
                return fmt.Errorf("no status on source snapshot yet")
×
1015
        }
×
1016
        if !IsSnapshotReady(sourceSnapshot) {
×
1017
                klog.V(3).Info("snapshot not ReadyToUse, while we allow this, probably going to be an issue going forward", "namespace", sourceSnapshot.Namespace, "name", sourceSnapshot.Name)
×
1018
        }
×
1019
        if sourceSnapshot.Status.Error != nil {
×
1020
                errMessage := "no details"
×
1021
                if msg := sourceSnapshot.Status.Error.Message; msg != nil {
×
1022
                        errMessage = *msg
×
1023
                }
×
1024
                return fmt.Errorf("snapshot in error state with msg: %s", errMessage)
×
1025
        }
1026
        if sourceSnapshot.Spec.VolumeSnapshotClassName == nil ||
×
1027
                *sourceSnapshot.Spec.VolumeSnapshotClassName == "" {
×
1028
                return fmt.Errorf("snapshot %s/%s does not have volume snap class populated, can't clone", sourceSnapshot.Name, sourceSnapshot.Namespace)
×
1029
        }
×
1030
        return nil
×
1031
}
1032

1033
// AddAnnotation adds an annotation to an object
1034
func AddAnnotation(obj metav1.Object, key, value string) {
1✔
1035
        if obj.GetAnnotations() == nil {
2✔
1036
                obj.SetAnnotations(make(map[string]string))
1✔
1037
        }
1✔
1038
        obj.GetAnnotations()[key] = value
1✔
1039
}
1040

1041
// AddLabel adds a label to an object
1042
func AddLabel(obj metav1.Object, key, value string) {
1✔
1043
        if obj.GetLabels() == nil {
2✔
1044
                obj.SetLabels(make(map[string]string))
1✔
1045
        }
1✔
1046
        obj.GetLabels()[key] = value
1✔
1047
}
1048

1049
// HandleFailedPod handles pod-creation errors and updates the pod's PVC without providing sensitive information
1050
func HandleFailedPod(err error, podName string, pvc *corev1.PersistentVolumeClaim, recorder record.EventRecorder, c client.Client) error {
×
1051
        if err == nil {
×
1052
                return nil
×
1053
        }
×
1054
        // Generic reason and msg to avoid providing sensitive information
1055
        reason := ErrStartingPod
×
1056
        msg := fmt.Sprintf(MessageErrStartingPod, podName)
×
1057

×
1058
        // Error handling to fine-tune the event with pertinent info
×
1059
        if ErrQuotaExceeded(err) {
×
1060
                reason = ErrExceededQuota
×
1061
        }
×
1062

1063
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1064

×
1065
        if isCloneSourcePod := CreateCloneSourcePodName(pvc) == podName; isCloneSourcePod {
×
1066
                AddAnnotation(pvc, AnnSourceRunningCondition, "false")
×
1067
                AddAnnotation(pvc, AnnSourceRunningConditionReason, reason)
×
1068
                AddAnnotation(pvc, AnnSourceRunningConditionMessage, msg)
×
1069
        } else {
×
1070
                AddAnnotation(pvc, AnnRunningCondition, "false")
×
1071
                AddAnnotation(pvc, AnnRunningConditionReason, reason)
×
1072
                AddAnnotation(pvc, AnnRunningConditionMessage, msg)
×
1073
        }
×
1074

1075
        AddAnnotation(pvc, AnnPodPhase, string(corev1.PodFailed))
×
1076
        if err := c.Update(context.TODO(), pvc); err != nil {
×
1077
                return err
×
1078
        }
×
1079

1080
        return err
×
1081
}
1082

1083
// GetSource returns the source string which determines the type of source. If no source or invalid source found, default to http
1084
func GetSource(pvc *corev1.PersistentVolumeClaim) string {
×
1085
        source, found := pvc.Annotations[AnnSource]
×
1086
        if !found {
×
1087
                source = ""
×
1088
        }
×
1089
        switch source {
×
1090
        case
1091
                SourceHTTP,
1092
                SourceS3,
1093
                SourceGCS,
1094
                SourceGlance,
1095
                SourceNone,
1096
                SourceRegistry,
1097
                SourceImageio,
1098
                SourceVDDK:
×
1099
        default:
×
1100
                source = SourceHTTP
×
1101
        }
1102
        return source
×
1103
}
1104

1105
// GetEndpoint returns the endpoint string which contains the full path URI of the target object to be copied.
1106
func GetEndpoint(pvc *corev1.PersistentVolumeClaim) (string, error) {
×
1107
        ep, found := pvc.Annotations[AnnEndpoint]
×
1108
        if !found || ep == "" {
×
1109
                verb := "empty"
×
1110
                if !found {
×
1111
                        verb = "missing"
×
1112
                }
×
1113
                return ep, errors.Errorf("annotation %q in pvc \"%s/%s\" is %s", AnnEndpoint, pvc.Namespace, pvc.Name, verb)
×
1114
        }
1115
        return ep, nil
×
1116
}
1117

1118
// AddImportVolumeMounts is being called for pods using PV with filesystem volume mode
1119
func AddImportVolumeMounts() []corev1.VolumeMount {
×
1120
        volumeMounts := []corev1.VolumeMount{
×
1121
                {
×
1122
                        Name:      DataVolName,
×
1123
                        MountPath: common.ImporterDataDir,
×
1124
                },
×
1125
        }
×
1126
        return volumeMounts
×
1127
}
×
1128

1129
// GetEffectiveStorageResources returns the maximum of the passed storageResources and the storageProfile minimumSupportedPVCSize.
1130
// If the passed storageResources has no size, it is returned as-is.
1131
func GetEffectiveStorageResources(ctx context.Context, client client.Client, storageResources corev1.VolumeResourceRequirements,
1132
        storageClassName *string, contentType cdiv1.DataVolumeContentType, log logr.Logger) (corev1.VolumeResourceRequirements, error) {
×
1133
        sc, err := GetStorageClassByNameWithVirtFallback(ctx, client, storageClassName, contentType)
×
1134
        if err != nil || sc == nil {
×
1135
                return storageResources, err
×
1136
        }
×
1137

1138
        requestedSize, hasSize := storageResources.Requests[corev1.ResourceStorage]
×
1139
        if !hasSize {
×
1140
                return storageResources, nil
×
1141
        }
×
1142

1143
        if requestedSize, err = GetEffectiveVolumeSize(ctx, client, requestedSize, sc.Name, &log); err != nil {
×
1144
                return storageResources, err
×
1145
        }
×
1146

1147
        return corev1.VolumeResourceRequirements{
×
1148
                Requests: corev1.ResourceList{
×
1149
                        corev1.ResourceStorage: requestedSize,
×
1150
                },
×
1151
        }, nil
×
1152
}
1153

1154
// GetEffectiveVolumeSize returns the maximum of the passed requestedSize and the storageProfile minimumSupportedPVCSize.
1155
func GetEffectiveVolumeSize(ctx context.Context, client client.Client, requestedSize resource.Quantity, storageClassName string, log *logr.Logger) (resource.Quantity, error) {
×
1156
        storageProfile := &cdiv1.StorageProfile{}
×
1157
        if err := client.Get(ctx, types.NamespacedName{Name: storageClassName}, storageProfile); err != nil {
×
1158
                return requestedSize, IgnoreNotFound(err)
×
1159
        }
×
1160

1161
        if val, exists := storageProfile.Annotations[AnnMinimumSupportedPVCSize]; exists {
×
1162
                if minSize, err := resource.ParseQuantity(val); err == nil {
×
1163
                        if requestedSize.Cmp(minSize) == -1 {
×
1164
                                return minSize, nil
×
1165
                        }
×
1166
                } else if log != nil {
×
1167
                        log.V(1).Info("Invalid minimum PVC size in annotation", "value", val, "error", err)
×
1168
                }
×
1169
        }
1170

1171
        return requestedSize, nil
×
1172
}
1173

1174
// ValidateRequestedCloneSize validates the clone size requirements on block
1175
func ValidateRequestedCloneSize(sourceResources, targetResources corev1.VolumeResourceRequirements) error {
×
1176
        sourceRequest, hasSource := sourceResources.Requests[corev1.ResourceStorage]
×
1177
        targetRequest, hasTarget := targetResources.Requests[corev1.ResourceStorage]
×
1178
        if !hasSource || !hasTarget {
×
1179
                return errors.New("source/target missing storage resource requests")
×
1180
        }
×
1181

1182
        // Verify that the target PVC size is equal or larger than the source.
1183
        if sourceRequest.Value() > targetRequest.Value() {
×
1184
                return errors.Errorf("target resources requests storage size is smaller than the source %d < %d", targetRequest.Value(), sourceRequest.Value())
×
1185
        }
×
1186
        return nil
×
1187
}
1188

1189
// CreateCloneSourcePodName creates clone source pod name
1190
func CreateCloneSourcePodName(targetPvc *corev1.PersistentVolumeClaim) string {
×
1191
        return string(targetPvc.GetUID()) + common.ClonerSourcePodNameSuffix
×
1192
}
×
1193

1194
// IsPVCComplete returns true if a PVC is in 'Succeeded' phase, false if not
1195
func IsPVCComplete(pvc *corev1.PersistentVolumeClaim) bool {
×
1196
        if pvc != nil {
×
1197
                phase, exists := pvc.ObjectMeta.Annotations[AnnPodPhase]
×
1198
                return exists && (phase == string(corev1.PodSucceeded))
×
1199
        }
×
1200
        return false
×
1201
}
1202

1203
// IsMultiStageImportInProgress returns true when a PVC is being part of an ongoing multi-stage import
1204
func IsMultiStageImportInProgress(pvc *corev1.PersistentVolumeClaim) bool {
×
1205
        if pvc != nil {
×
1206
                multiStageImport := metav1.HasAnnotation(pvc.ObjectMeta, AnnCurrentCheckpoint)
×
1207
                multiStageAlreadyDone := metav1.HasAnnotation(pvc.ObjectMeta, AnnMultiStageImportDone)
×
1208
                return multiStageImport && !multiStageAlreadyDone
×
1209
        }
×
1210
        return false
×
1211
}
1212

1213
// SetRestrictedSecurityContext sets the pod security params to be compatible with restricted PSA
1214
func SetRestrictedSecurityContext(podSpec *corev1.PodSpec) {
×
1215
        hasVolumeMounts := false
×
1216
        for _, containers := range [][]corev1.Container{podSpec.InitContainers, podSpec.Containers} {
×
1217
                for i := range containers {
×
1218
                        container := &containers[i]
×
1219
                        if container.SecurityContext == nil {
×
1220
                                container.SecurityContext = &corev1.SecurityContext{}
×
1221
                        }
×
1222
                        container.SecurityContext.Capabilities = &corev1.Capabilities{
×
1223
                                Drop: []corev1.Capability{
×
1224
                                        "ALL",
×
1225
                                },
×
1226
                        }
×
1227
                        container.SecurityContext.SeccompProfile = &corev1.SeccompProfile{
×
1228
                                Type: corev1.SeccompProfileTypeRuntimeDefault,
×
1229
                        }
×
1230
                        container.SecurityContext.AllowPrivilegeEscalation = ptr.To[bool](false)
×
1231
                        container.SecurityContext.RunAsNonRoot = ptr.To[bool](true)
×
1232
                        container.SecurityContext.RunAsUser = ptr.To[int64](common.QemuSubGid)
×
1233
                        if len(container.VolumeMounts) > 0 {
×
1234
                                hasVolumeMounts = true
×
1235
                        }
×
1236
                }
1237
        }
1238

1239
        if podSpec.SecurityContext == nil {
×
1240
                podSpec.SecurityContext = &corev1.PodSecurityContext{}
×
1241
        }
×
1242
        // Some tools like istio inject containers and thus rely on a pod level seccomp profile being specified
1243
        podSpec.SecurityContext.SeccompProfile = &corev1.SeccompProfile{
×
1244
                Type: corev1.SeccompProfileTypeRuntimeDefault,
×
1245
        }
×
1246
        if hasVolumeMounts {
×
1247
                podSpec.SecurityContext.FSGroup = ptr.To[int64](common.QemuSubGid)
×
1248
        }
×
1249
}
1250

1251
// SetNodeNameIfPopulator sets NodeName in a pod spec when the PVC is being handled by a CDI volume populator
1252
func SetNodeNameIfPopulator(pvc *corev1.PersistentVolumeClaim, podSpec *corev1.PodSpec) {
×
1253
        _, isPopulator := pvc.Annotations[AnnPopulatorKind]
×
1254
        nodeName := pvc.Annotations[AnnSelectedNode]
×
1255
        if isPopulator && nodeName != "" {
×
1256
                podSpec.NodeName = nodeName
×
1257
        }
×
1258
}
1259

1260
// CreatePvc creates PVC
1261
func CreatePvc(name, ns string, annotations, labels map[string]string) *corev1.PersistentVolumeClaim {
1✔
1262
        return CreatePvcInStorageClass(name, ns, nil, annotations, labels, corev1.ClaimBound)
1✔
1263
}
1✔
1264

1265
// CreatePvcInStorageClass creates PVC with storgae class
1266
func CreatePvcInStorageClass(name, ns string, storageClassName *string, annotations, labels map[string]string, phase corev1.PersistentVolumeClaimPhase) *corev1.PersistentVolumeClaim {
1✔
1267
        pvc := &corev1.PersistentVolumeClaim{
1✔
1268
                ObjectMeta: metav1.ObjectMeta{
1✔
1269
                        Name:        name,
1✔
1270
                        Namespace:   ns,
1✔
1271
                        Annotations: annotations,
1✔
1272
                        Labels:      labels,
1✔
1273
                        UID:         types.UID(ns + "-" + name),
1✔
1274
                },
1✔
1275
                Spec: corev1.PersistentVolumeClaimSpec{
1✔
1276
                        AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadOnlyMany, corev1.ReadWriteOnce},
1✔
1277
                        Resources: corev1.VolumeResourceRequirements{
1✔
1278
                                Requests: corev1.ResourceList{
1✔
1279
                                        corev1.ResourceStorage: resource.MustParse("1G"),
1✔
1280
                                },
1✔
1281
                        },
1✔
1282
                        StorageClassName: storageClassName,
1✔
1283
                },
1✔
1284
                Status: corev1.PersistentVolumeClaimStatus{
1✔
1285
                        Phase: phase,
1✔
1286
                },
1✔
1287
        }
1✔
1288
        pvc.Status.Capacity = pvc.Spec.Resources.Requests.DeepCopy()
1✔
1289
        if pvc.Status.Phase == corev1.ClaimBound {
2✔
1290
                pvc.Spec.VolumeName = "pv-" + string(pvc.UID)
1✔
1291
        }
1✔
1292
        return pvc
1✔
1293
}
1294

1295
// GetAPIServerKey returns API server RSA key
1296
func GetAPIServerKey() *rsa.PrivateKey {
×
1297
        apiServerKeyOnce.Do(func() {
×
1298
                apiServerKey, _ = rsa.GenerateKey(rand.Reader, 2048)
×
1299
        })
×
1300
        return apiServerKey
×
1301
}
1302

1303
// CreateStorageClass creates storage class CR
1304
func CreateStorageClass(name string, annotations map[string]string) *storagev1.StorageClass {
1✔
1305
        return &storagev1.StorageClass{
1✔
1306
                ObjectMeta: metav1.ObjectMeta{
1✔
1307
                        Name:        name,
1✔
1308
                        Annotations: annotations,
1✔
1309
                },
1✔
1310
        }
1✔
1311
}
1✔
1312

1313
// CreateImporterTestPod creates importer test pod CR
1314
func CreateImporterTestPod(pvc *corev1.PersistentVolumeClaim, dvname string, scratchPvc *corev1.PersistentVolumeClaim) *corev1.Pod {
×
1315
        // importer pod name contains the pvc name
×
1316
        podName := fmt.Sprintf("%s-%s", common.ImporterPodName, pvc.Name)
×
1317

×
1318
        blockOwnerDeletion := true
×
1319
        isController := true
×
1320

×
1321
        volumes := []corev1.Volume{
×
1322
                {
×
1323
                        Name: dvname,
×
1324
                        VolumeSource: corev1.VolumeSource{
×
1325
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1326
                                        ClaimName: pvc.Name,
×
1327
                                        ReadOnly:  false,
×
1328
                                },
×
1329
                        },
×
1330
                },
×
1331
        }
×
1332

×
1333
        if scratchPvc != nil {
×
1334
                volumes = append(volumes, corev1.Volume{
×
1335
                        Name: ScratchVolName,
×
1336
                        VolumeSource: corev1.VolumeSource{
×
1337
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1338
                                        ClaimName: scratchPvc.Name,
×
1339
                                        ReadOnly:  false,
×
1340
                                },
×
1341
                        },
×
1342
                })
×
1343
        }
×
1344

1345
        pod := &corev1.Pod{
×
1346
                TypeMeta: metav1.TypeMeta{
×
1347
                        Kind:       "Pod",
×
1348
                        APIVersion: "v1",
×
1349
                },
×
1350
                ObjectMeta: metav1.ObjectMeta{
×
1351
                        Name:      podName,
×
1352
                        Namespace: pvc.Namespace,
×
1353
                        Annotations: map[string]string{
×
1354
                                AnnCreatedBy: "yes",
×
1355
                        },
×
1356
                        Labels: map[string]string{
×
1357
                                common.CDILabelKey:        common.CDILabelValue,
×
1358
                                common.CDIComponentLabel:  common.ImporterPodName,
×
1359
                                common.PrometheusLabelKey: common.PrometheusLabelValue,
×
1360
                        },
×
1361
                        OwnerReferences: []metav1.OwnerReference{
×
1362
                                {
×
1363
                                        APIVersion:         "v1",
×
1364
                                        Kind:               "PersistentVolumeClaim",
×
1365
                                        Name:               pvc.Name,
×
1366
                                        UID:                pvc.GetUID(),
×
1367
                                        BlockOwnerDeletion: &blockOwnerDeletion,
×
1368
                                        Controller:         &isController,
×
1369
                                },
×
1370
                        },
×
1371
                },
×
1372
                Spec: corev1.PodSpec{
×
1373
                        Containers: []corev1.Container{
×
1374
                                {
×
1375
                                        Name:            common.ImporterPodName,
×
1376
                                        Image:           "test/myimage",
×
1377
                                        ImagePullPolicy: corev1.PullPolicy("Always"),
×
1378
                                        Args:            []string{"-v=5"},
×
1379
                                        Ports: []corev1.ContainerPort{
×
1380
                                                {
×
1381
                                                        Name:          "metrics",
×
1382
                                                        ContainerPort: 8443,
×
1383
                                                        Protocol:      corev1.ProtocolTCP,
×
1384
                                                },
×
1385
                                        },
×
1386
                                },
×
1387
                        },
×
1388
                        RestartPolicy:      corev1.RestartPolicyOnFailure,
×
1389
                        Volumes:            volumes,
×
1390
                        EnableServiceLinks: ptr.To(false),
×
1391
                },
×
1392
        }
×
1393

×
1394
        ep, _ := GetEndpoint(pvc)
×
1395
        source := GetSource(pvc)
×
1396
        contentType := GetPVCContentType(pvc)
×
1397
        imageSize, _ := GetRequestedImageSize(pvc)
×
1398
        volumeMode := GetVolumeMode(pvc)
×
1399

×
1400
        env := []corev1.EnvVar{
×
1401
                {
×
1402
                        Name:  common.ImporterSource,
×
1403
                        Value: source,
×
1404
                },
×
1405
                {
×
1406
                        Name:  common.ImporterEndpoint,
×
1407
                        Value: ep,
×
1408
                },
×
1409
                {
×
1410
                        Name:  common.ImporterContentType,
×
1411
                        Value: string(contentType),
×
1412
                },
×
1413
                {
×
1414
                        Name:  common.ImporterImageSize,
×
1415
                        Value: imageSize,
×
1416
                },
×
1417
                {
×
1418
                        Name:  common.OwnerUID,
×
1419
                        Value: string(pvc.UID),
×
1420
                },
×
1421
                {
×
1422
                        Name:  common.InsecureTLSVar,
×
1423
                        Value: "false",
×
1424
                },
×
1425
        }
×
1426
        pod.Spec.Containers[0].Env = env
×
1427
        if volumeMode == corev1.PersistentVolumeBlock {
×
1428
                pod.Spec.Containers[0].VolumeDevices = AddVolumeDevices()
×
1429
        } else {
×
1430
                pod.Spec.Containers[0].VolumeMounts = AddImportVolumeMounts()
×
1431
        }
×
1432

1433
        if scratchPvc != nil {
×
1434
                pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
×
1435
                        Name:      ScratchVolName,
×
1436
                        MountPath: common.ScratchDataDir,
×
1437
                })
×
1438
        }
×
1439

1440
        return pod
×
1441
}
1442

1443
// CreateStorageClassWithProvisioner creates CR of storage class with provisioner
1444
func CreateStorageClassWithProvisioner(name string, annotations, labels map[string]string, provisioner string) *storagev1.StorageClass {
×
1445
        return &storagev1.StorageClass{
×
1446
                Provisioner: provisioner,
×
1447
                ObjectMeta: metav1.ObjectMeta{
×
1448
                        Name:        name,
×
1449
                        Annotations: annotations,
×
1450
                        Labels:      labels,
×
1451
                },
×
1452
        }
×
1453
}
×
1454

1455
// CreateClient creates a fake client
1456
func CreateClient(objs ...runtime.Object) client.Client {
1✔
1457
        s := scheme.Scheme
1✔
1458
        _ = cdiv1.AddToScheme(s)
1✔
1459
        _ = corev1.AddToScheme(s)
1✔
1460
        _ = storagev1.AddToScheme(s)
1✔
1461
        _ = ocpconfigv1.Install(s)
1✔
1462

1✔
1463
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1464
}
1✔
1465

1466
// ErrQuotaExceeded checked is the error is of exceeded quota
1467
func ErrQuotaExceeded(err error) bool {
×
1468
        return strings.Contains(err.Error(), "exceeded quota:")
×
1469
}
×
1470

1471
// GetContentType returns the content type. If invalid or not set, default to kubevirt
1472
func GetContentType(contentType cdiv1.DataVolumeContentType) cdiv1.DataVolumeContentType {
1✔
1473
        switch contentType {
1✔
1474
        case
1475
                cdiv1.DataVolumeKubeVirt,
1476
                cdiv1.DataVolumeArchive:
1✔
1477
        default:
×
1478
                // TODO - shouldn't archive be the default?
×
1479
                contentType = cdiv1.DataVolumeKubeVirt
×
1480
        }
1481
        return contentType
1✔
1482
}
1483

1484
// GetPVCContentType returns the content type of the source image. If invalid or not set, default to kubevirt
1485
func GetPVCContentType(pvc *corev1.PersistentVolumeClaim) cdiv1.DataVolumeContentType {
×
1486
        contentType, found := pvc.Annotations[AnnContentType]
×
1487
        if !found {
×
1488
                // TODO - shouldn't archive be the default?
×
1489
                return cdiv1.DataVolumeKubeVirt
×
1490
        }
×
1491

1492
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
1493
}
1494

1495
// GetNamespace returns the given namespace if not empty, otherwise the default namespace
1496
func GetNamespace(namespace, defaultNamespace string) string {
×
1497
        if namespace == "" {
×
1498
                return defaultNamespace
×
1499
        }
×
1500
        return namespace
×
1501
}
1502

1503
// IsErrCacheNotStarted checked is the error is of cache not started
1504
func IsErrCacheNotStarted(err error) bool {
×
1505
        target := &runtimecache.ErrCacheNotStarted{}
×
1506
        return errors.As(err, &target)
×
1507
}
×
1508

1509
// NewImportDataVolume returns new import DataVolume CR
1510
func NewImportDataVolume(name string) *cdiv1.DataVolume {
×
1511
        return &cdiv1.DataVolume{
×
1512
                TypeMeta: metav1.TypeMeta{APIVersion: cdiv1.SchemeGroupVersion.String()},
×
1513
                ObjectMeta: metav1.ObjectMeta{
×
1514
                        Name:      name,
×
1515
                        Namespace: metav1.NamespaceDefault,
×
1516
                        UID:       types.UID(metav1.NamespaceDefault + "-" + name),
×
1517
                },
×
1518
                Spec: cdiv1.DataVolumeSpec{
×
1519
                        Source: &cdiv1.DataVolumeSource{
×
1520
                                HTTP: &cdiv1.DataVolumeSourceHTTP{
×
1521
                                        URL: "http://example.com/data",
×
1522
                                },
×
1523
                        },
×
1524
                        PVC: &corev1.PersistentVolumeClaimSpec{
×
1525
                                AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
×
1526
                        },
×
1527
                        PriorityClassName: "p0",
×
1528
                },
×
1529
        }
×
1530
}
×
1531

1532
// GetCloneSourceInfo returns the type, name and namespace of the cloning source
1533
func GetCloneSourceInfo(dv *cdiv1.DataVolume) (sourceType, sourceName, sourceNamespace string) {
×
1534
        // Cloning sources are mutually exclusive
×
1535
        if dv.Spec.Source.PVC != nil {
×
1536
                return "pvc", dv.Spec.Source.PVC.Name, dv.Spec.Source.PVC.Namespace
×
1537
        }
×
1538

1539
        if dv.Spec.Source.Snapshot != nil {
×
1540
                return "snapshot", dv.Spec.Source.Snapshot.Name, dv.Spec.Source.Snapshot.Namespace
×
1541
        }
×
1542

1543
        return "", "", ""
×
1544
}
1545

1546
// IsWaitForFirstConsumerEnabled tells us if we should respect "real" WFFC behavior or just let our worker pods randomly spawn
1547
func IsWaitForFirstConsumerEnabled(obj metav1.Object, gates featuregates.FeatureGates) (bool, error) {
×
1548
        // when PVC requests immediateBinding it cannot honor wffc logic
×
1549
        isImmediateBindingRequested := ImmediateBindingRequested(obj)
×
1550
        pvcHonorWaitForFirstConsumer := !isImmediateBindingRequested
×
1551
        globalHonorWaitForFirstConsumer, err := gates.HonorWaitForFirstConsumerEnabled()
×
1552
        if err != nil {
×
1553
                return false, err
×
1554
        }
×
1555

1556
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1557
}
1558

1559
// AddImmediateBindingAnnotationIfWFFCDisabled adds the immediateBinding annotation if wffc feature gate is disabled
1560
func AddImmediateBindingAnnotationIfWFFCDisabled(obj metav1.Object, gates featuregates.FeatureGates) error {
×
1561
        globalHonorWaitForFirstConsumer, err := gates.HonorWaitForFirstConsumerEnabled()
×
1562
        if err != nil {
×
1563
                return err
×
1564
        }
×
1565
        if !globalHonorWaitForFirstConsumer {
×
1566
                AddAnnotation(obj, AnnImmediateBinding, "")
×
1567
        }
×
1568
        return nil
×
1569
}
1570

1571
// InflateSizeWithOverhead inflates a storage size with proper overhead calculations
1572
func InflateSizeWithOverhead(ctx context.Context, c client.Client, imgSize int64, pvcSpec *corev1.PersistentVolumeClaimSpec) (resource.Quantity, error) {
×
1573
        var returnSize resource.Quantity
×
1574

×
1575
        if util.ResolveVolumeMode(pvcSpec.VolumeMode) == corev1.PersistentVolumeFilesystem {
×
1576
                fsOverhead, err := GetFilesystemOverheadForStorageClass(ctx, c, pvcSpec.StorageClassName)
×
1577
                if err != nil {
×
1578
                        return resource.Quantity{}, err
×
1579
                }
×
1580
                // Parse filesystem overhead (percentage) into a 64-bit float
1581
                fsOverheadFloat, _ := strconv.ParseFloat(string(fsOverhead), 64)
×
1582

×
1583
                // Merge the previous values into a 'resource.Quantity' struct
×
1584
                requiredSpace := util.GetRequiredSpace(fsOverheadFloat, imgSize)
×
1585
                returnSize = *resource.NewScaledQuantity(requiredSpace, 0)
×
1586
        } else {
×
1587
                // Inflation is not needed with 'Block' mode
×
1588
                returnSize = *resource.NewScaledQuantity(imgSize, 0)
×
1589
        }
×
1590

1591
        return returnSize, nil
×
1592
}
1593

1594
// GetSnapshotContentFromSnapshot returns the VolumeSnapshotContent of a given VolumeSnapshot
1595
func GetSnapshotContentFromSnapshot(ctx context.Context, c client.Client, snapshot *snapshotv1.VolumeSnapshot) (*snapshotv1.VolumeSnapshotContent, error) {
×
1596
        if snapshot.Status == nil || snapshot.Status.BoundVolumeSnapshotContentName == nil {
×
1597
                return nil, fmt.Errorf("volumeSnapshotContent name not found")
×
1598
        }
×
1599
        vsc := &snapshotv1.VolumeSnapshotContent{}
×
1600
        if err := c.Get(ctx, types.NamespacedName{Name: *snapshot.Status.BoundVolumeSnapshotContentName}, vsc); err != nil {
×
1601
                return nil, err
×
1602
        }
×
1603
        return vsc, nil
×
1604
}
1605

1606
// GetSnapshotSourceVolumeMode determines the volume mode of the PVC that was
1607
// used to create the given snapshot. It checks (in order):
1608
// 1. VolumeSnapshotContent.Spec.SourceVolumeMode (available since k8s 1.29)
1609
// 2. AnnSourceVolumeMode annotation on the snapshot
1610
// 3. Falls back to the provided fallback volume mode
1611
func GetSnapshotSourceVolumeMode(log logr.Logger, snapshot *snapshotv1.VolumeSnapshot, vsc *snapshotv1.VolumeSnapshotContent, fallback *corev1.PersistentVolumeMode) *corev1.PersistentVolumeMode {
×
1612
        if vsc != nil && vsc.Spec.SourceVolumeMode != nil {
×
1613
                return vsc.Spec.SourceVolumeMode
×
1614
        }
×
1615

1616
        if v, ok := snapshot.Annotations[AnnSourceVolumeMode]; ok {
×
1617
                mode := corev1.PersistentVolumeMode(v)
×
1618
                return &mode
×
1619
        }
×
1620

1621
        log.V(1).Info("Could not infer source volume mode of snapshot, assuming same as target")
×
1622
        return fallback
×
1623
}
1624

1625
// IsBound returns if the pvc is bound
1626
func IsBound(pvc *corev1.PersistentVolumeClaim) bool {
×
1627
        return pvc != nil && pvc.Status.Phase == corev1.ClaimBound
×
1628
}
×
1629

1630
// IsUnbound returns if the pvc is not bound yet
1631
func IsUnbound(pvc *corev1.PersistentVolumeClaim) bool {
×
1632
        return !IsBound(pvc)
×
1633
}
×
1634

1635
// IsLost returns if the pvc is lost
1636
func IsLost(pvc *corev1.PersistentVolumeClaim) bool {
×
1637
        return pvc != nil && pvc.Status.Phase == corev1.ClaimLost
×
1638
}
×
1639

1640
// IsImageStream returns true if registry source is ImageStream
1641
func IsImageStream(pvc *corev1.PersistentVolumeClaim) bool {
×
1642
        return pvc.Annotations[AnnRegistryImageStream] == "true"
×
1643
}
×
1644

1645
// ShouldIgnorePod checks if a pod should be ignored.
1646
// If this is a completed pod that was used for one checkpoint of a multi-stage import, it
1647
// should be ignored by pod lookups as long as the retainAfterCompletion annotation is set.
1648
func ShouldIgnorePod(pod *corev1.Pod, pvc *corev1.PersistentVolumeClaim) bool {
×
1649
        retain := pvc.ObjectMeta.Annotations[AnnPodRetainAfterCompletion]
×
1650
        checkpoint := pvc.ObjectMeta.Annotations[AnnCurrentCheckpoint]
×
1651
        if checkpoint != "" && pod.Status.Phase == corev1.PodSucceeded {
×
1652
                return retain == "true"
×
1653
        }
×
1654
        return false
×
1655
}
1656

1657
// BuildHTTPClient generates an http client that accepts any certificate, since we are using
1658
// it to get prometheus data it doesn't matter if someone can intercept the data. Once we have
1659
// a mechanism to properly sign the server, we can update this method to get a proper client.
1660
func BuildHTTPClient(httpClient *http.Client) *http.Client {
×
1661
        if httpClient == nil {
×
1662
                defaultTransport := http.DefaultTransport.(*http.Transport)
×
1663
                // Create new Transport that ignores self-signed SSL
×
1664
                //nolint:gosec
×
1665
                tr := &http.Transport{
×
1666
                        Proxy:                 defaultTransport.Proxy,
×
1667
                        DialContext:           defaultTransport.DialContext,
×
1668
                        MaxIdleConns:          defaultTransport.MaxIdleConns,
×
1669
                        IdleConnTimeout:       defaultTransport.IdleConnTimeout,
×
1670
                        ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
×
1671
                        TLSHandshakeTimeout:   defaultTransport.TLSHandshakeTimeout,
×
1672
                        TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
×
1673
                }
×
1674
                httpClient = &http.Client{
×
1675
                        Transport: tr,
×
1676
                }
×
1677
        }
×
1678
        return httpClient
×
1679
}
1680

1681
// ErrConnectionRefused checks for connection refused errors
1682
func ErrConnectionRefused(err error) bool {
×
1683
        return strings.Contains(err.Error(), "connection refused")
×
1684
}
×
1685

1686
// GetPodMetricsPort returns, if exists, the metrics port from the passed pod
1687
func GetPodMetricsPort(pod *corev1.Pod) (int, error) {
1✔
1688
        for _, container := range pod.Spec.Containers {
2✔
1689
                for _, port := range container.Ports {
2✔
1690
                        if port.Name == "metrics" {
2✔
1691
                                return int(port.ContainerPort), nil
1✔
1692
                        }
1✔
1693
                }
1694
        }
1695
        return 0, errors.New("Metrics port not found in pod")
1✔
1696
}
1697

1698
// GetMetricsURL builds the metrics URL according to the specified pod
1699
func GetMetricsURL(pod *corev1.Pod) (string, error) {
1✔
1700
        if pod == nil {
1✔
1701
                return "", nil
×
1702
        }
×
1703
        port, err := GetPodMetricsPort(pod)
1✔
1704
        if err != nil || pod.Status.PodIP == "" {
2✔
1705
                return "", err
1✔
1706
        }
1✔
1707
        domain := net.JoinHostPort(pod.Status.PodIP, fmt.Sprint(port))
1✔
1708
        url := fmt.Sprintf("https://%s/metrics", domain)
1✔
1709
        return url, nil
1✔
1710
}
1711

1712
// GetProgressReportFromURL fetches the progress report from the passed URL according to an specific metric expression and ownerUID
1713
func GetProgressReportFromURL(ctx context.Context, url string, httpClient *http.Client, metricExp, ownerUID string) (string, error) {
×
1714
        regExp := regexp.MustCompile(fmt.Sprintf("(%s)\\{ownerUID\\=%q\\} (\\d{1,3}\\.?\\d*)", metricExp, ownerUID))
×
1715
        // pod could be gone, don't block an entire thread for 30 seconds
×
1716
        // just to get back an i/o timeout
×
1717
        ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
×
1718
        defer cancel()
×
1719
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
×
1720
        if err != nil {
×
1721
                return "", err
×
1722
        }
×
1723
        resp, err := httpClient.Do(req)
×
1724
        if err != nil {
×
1725
                if ErrConnectionRefused(err) {
×
1726
                        return "", nil
×
1727
                }
×
1728
                return "", err
×
1729
        }
1730
        defer resp.Body.Close()
×
1731
        body, err := io.ReadAll(resp.Body)
×
1732
        if err != nil {
×
1733
                return "", err
×
1734
        }
×
1735

1736
        // Parse the progress from the body
1737
        progressReport := ""
×
1738
        match := regExp.FindStringSubmatch(string(body))
×
1739
        if match != nil {
×
1740
                progressReport = match[len(match)-1]
×
1741
        }
×
1742
        return progressReport, nil
×
1743
}
1744

1745
// UpdateHTTPAnnotations updates the passed annotations for proper http import
1746
func UpdateHTTPAnnotations(annotations map[string]string, http *cdiv1.DataVolumeSourceHTTP) {
×
1747
        annotations[AnnEndpoint] = http.URL
×
1748
        annotations[AnnSource] = SourceHTTP
×
1749

×
1750
        if http.SecretRef != "" {
×
1751
                annotations[AnnSecret] = http.SecretRef
×
1752
        }
×
1753
        if http.CertConfigMap != "" {
×
1754
                annotations[AnnCertConfigMap] = http.CertConfigMap
×
1755
        }
×
1756
        if http.Checksum != "" {
×
1757
                annotations[AnnChecksum] = http.Checksum
×
1758
        }
×
1759
        for index, header := range http.ExtraHeaders {
×
1760
                annotations[fmt.Sprintf("%s.%d", AnnExtraHeaders, index)] = header
×
1761
        }
×
1762
        for index, header := range http.SecretExtraHeaders {
×
1763
                annotations[fmt.Sprintf("%s.%d", AnnSecretExtraHeaders, index)] = header
×
1764
        }
×
1765
}
1766

1767
// UpdateS3Annotations updates the passed annotations for proper S3 import
1768
func UpdateS3Annotations(annotations map[string]string, s3 *cdiv1.DataVolumeSourceS3) {
×
1769
        annotations[AnnEndpoint] = s3.URL
×
1770
        annotations[AnnSource] = SourceS3
×
1771
        if s3.SecretRef != "" {
×
1772
                annotations[AnnSecret] = s3.SecretRef
×
1773
        }
×
1774
        if s3.CertConfigMap != "" {
×
1775
                annotations[AnnCertConfigMap] = s3.CertConfigMap
×
1776
        }
×
1777
}
1778

1779
// UpdateGCSAnnotations updates the passed annotations for proper GCS import
1780
func UpdateGCSAnnotations(annotations map[string]string, gcs *cdiv1.DataVolumeSourceGCS) {
×
1781
        annotations[AnnEndpoint] = gcs.URL
×
1782
        annotations[AnnSource] = SourceGCS
×
1783
        if gcs.SecretRef != "" {
×
1784
                annotations[AnnSecret] = gcs.SecretRef
×
1785
        }
×
1786
}
1787

1788
// UpdateRegistryAnnotations updates the passed annotations for proper registry import
1789
func UpdateRegistryAnnotations(annotations map[string]string, registry *cdiv1.DataVolumeSourceRegistry) {
×
1790
        annotations[AnnSource] = SourceRegistry
×
1791
        pullMethod := registry.PullMethod
×
1792
        if pullMethod != nil && *pullMethod != "" {
×
1793
                annotations[AnnRegistryImportMethod] = string(*pullMethod)
×
1794
        }
×
1795
        url := registry.URL
×
1796
        if url != nil && *url != "" {
×
1797
                annotations[AnnEndpoint] = *url
×
1798
        } else {
×
1799
                imageStream := registry.ImageStream
×
1800
                if imageStream != nil && *imageStream != "" {
×
1801
                        annotations[AnnEndpoint] = *imageStream
×
1802
                        annotations[AnnRegistryImageStream] = "true"
×
1803
                }
×
1804
        }
1805
        secretRef := registry.SecretRef
×
1806
        if secretRef != nil && *secretRef != "" {
×
1807
                annotations[AnnSecret] = *secretRef
×
1808
        }
×
1809
        certConfigMap := registry.CertConfigMap
×
1810
        if certConfigMap != nil && *certConfigMap != "" {
×
1811
                annotations[AnnCertConfigMap] = *certConfigMap
×
1812
        }
×
1813

1814
        if registry.Platform != nil && registry.Platform.Architecture != "" {
×
1815
                annotations[AnnRegistryImageArchitecture] = registry.Platform.Architecture
×
1816
        }
×
1817
}
1818

1819
// UpdateVDDKAnnotations updates the passed annotations for proper VDDK import
1820
func UpdateVDDKAnnotations(annotations map[string]string, vddk *cdiv1.DataVolumeSourceVDDK) {
×
1821
        annotations[AnnEndpoint] = vddk.URL
×
1822
        annotations[AnnSource] = SourceVDDK
×
1823
        annotations[AnnSecret] = vddk.SecretRef
×
1824
        annotations[AnnBackingFile] = vddk.BackingFile
×
1825
        annotations[AnnUUID] = vddk.UUID
×
1826
        annotations[AnnThumbprint] = vddk.Thumbprint
×
1827
        if vddk.InitImageURL != "" {
×
1828
                annotations[AnnVddkInitImageURL] = vddk.InitImageURL
×
1829
        }
×
1830
        if vddk.ExtraArgs != "" {
×
1831
                annotations[AnnVddkExtraArgs] = vddk.ExtraArgs
×
1832
        }
×
1833
}
1834

1835
// UpdateImageIOAnnotations updates the passed annotations for proper imageIO import
1836
func UpdateImageIOAnnotations(annotations map[string]string, imageio *cdiv1.DataVolumeSourceImageIO) {
×
1837
        annotations[AnnEndpoint] = imageio.URL
×
1838
        annotations[AnnSource] = SourceImageio
×
1839
        annotations[AnnSecret] = imageio.SecretRef
×
1840
        annotations[AnnCertConfigMap] = imageio.CertConfigMap
×
1841
        annotations[AnnDiskID] = imageio.DiskID
×
1842
        if imageio.InsecureSkipVerify != nil && *imageio.InsecureSkipVerify {
×
1843
                annotations[AnnInsecureSkipVerify] = "true"
×
1844
        }
×
1845
}
1846

1847
// IsPVBoundToPVC checks if a PV is bound to a specific PVC
1848
func IsPVBoundToPVC(pv *corev1.PersistentVolume, pvc *corev1.PersistentVolumeClaim) bool {
1✔
1849
        claimRef := pv.Spec.ClaimRef
1✔
1850
        return claimRef != nil && claimRef.Name == pvc.Name && claimRef.Namespace == pvc.Namespace && claimRef.UID == pvc.UID
1✔
1851
}
1✔
1852

1853
// Rebind binds the PV of source to target
1854
func Rebind(ctx context.Context, c client.Client, source, target *corev1.PersistentVolumeClaim) error {
1✔
1855
        pv := &corev1.PersistentVolume{
1✔
1856
                ObjectMeta: metav1.ObjectMeta{
1✔
1857
                        Name: source.Spec.VolumeName,
1✔
1858
                },
1✔
1859
        }
1✔
1860

1✔
1861
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
2✔
1862
                return err
1✔
1863
        }
1✔
1864

1865
        // Examine the claimref for the PV and see if it's still bound to PVC'
1866
        if pv.Spec.ClaimRef == nil {
1✔
1867
                return fmt.Errorf("PV %s claimRef is nil", pv.Name)
×
1868
        }
×
1869

1870
        if !IsPVBoundToPVC(pv, source) {
2✔
1871
                // Something is not right if the PV is neither bound to PVC' nor target PVC
1✔
1872
                if !IsPVBoundToPVC(pv, target) {
2✔
1873
                        klog.Errorf("PV bound to unexpected PVC: Could not rebind to target PVC '%s'", target.Name)
1✔
1874
                        return fmt.Errorf("PV %s bound to unexpected claim %s", pv.Name, pv.Spec.ClaimRef.Name)
1✔
1875
                }
1✔
1876
                // our work is done
1877
                return nil
1✔
1878
        }
1879

1880
        // Rebind PVC to target PVC
1881
        pv.Spec.ClaimRef = &corev1.ObjectReference{
1✔
1882
                Namespace:       target.Namespace,
1✔
1883
                Name:            target.Name,
1✔
1884
                UID:             target.UID,
1✔
1885
                ResourceVersion: target.ResourceVersion,
1✔
1886
        }
1✔
1887
        klog.V(3).Info("Rebinding PV to target PVC", "PVC", target.Name)
1✔
1888
        if err := c.Update(context.TODO(), pv); err != nil {
1✔
1889
                return err
×
1890
        }
×
1891

1892
        return nil
1✔
1893
}
1894

1895
// BulkDeleteResources deletes a bunch of resources
1896
func BulkDeleteResources(ctx context.Context, c client.Client, obj client.ObjectList, lo client.ListOption) error {
×
1897
        if err := c.List(ctx, obj, lo); err != nil {
×
1898
                if meta.IsNoMatchError(err) {
×
1899
                        return nil
×
1900
                }
×
1901
                return err
×
1902
        }
1903

1904
        sv := reflect.ValueOf(obj).Elem()
×
1905
        iv := sv.FieldByName("Items")
×
1906

×
1907
        for i := 0; i < iv.Len(); i++ {
×
1908
                obj := iv.Index(i).Addr().Interface().(client.Object)
×
1909
                if obj.GetDeletionTimestamp().IsZero() {
×
1910
                        klog.V(3).Infof("Deleting type %+v %+v", reflect.TypeOf(obj), obj)
×
1911
                        if err := c.Delete(ctx, obj); err != nil {
×
1912
                                return err
×
1913
                        }
×
1914
                }
1915
        }
1916

1917
        return nil
×
1918
}
1919

1920
// ValidateSnapshotCloneSize does proper size validation when doing a clone from snapshot operation
1921
func ValidateSnapshotCloneSize(snapshot *snapshotv1.VolumeSnapshot, pvcSpec *corev1.PersistentVolumeClaimSpec, targetSC *storagev1.StorageClass, log logr.Logger) (bool, error) {
×
1922
        restoreSize := snapshot.Status.RestoreSize
×
1923
        if restoreSize == nil {
×
1924
                return false, fmt.Errorf("snapshot has no RestoreSize")
×
1925
        }
×
1926
        targetRequest, hasTargetRequest := pvcSpec.Resources.Requests[corev1.ResourceStorage]
×
1927
        allowExpansion := targetSC.AllowVolumeExpansion != nil && *targetSC.AllowVolumeExpansion
×
1928
        if hasTargetRequest {
×
1929
                // otherwise will just use restoreSize
×
1930
                if restoreSize.Cmp(targetRequest) < 0 && !allowExpansion {
×
1931
                        log.V(3).Info("Can't expand restored PVC because SC does not allow expansion, need to fall back to host assisted")
×
1932
                        return false, nil
×
1933
                }
×
1934
        }
1935
        return true, nil
×
1936
}
1937

1938
// ValidateSnapshotCloneProvisioners validates the target PVC storage class against the snapshot class provisioner
1939
func ValidateSnapshotCloneProvisioners(vsc *snapshotv1.VolumeSnapshotContent, storageClass *storagev1.StorageClass) (bool, error) {
×
1940
        // Do snapshot and storage class validation
×
1941
        if storageClass == nil {
×
1942
                return false, fmt.Errorf("target storage class not found")
×
1943
        }
×
1944
        if storageClass.Provisioner != vsc.Spec.Driver {
×
1945
                return false, nil
×
1946
        }
×
1947
        // TODO: get sourceVolumeMode from volumesnapshotcontent and validate against target spec
1948
        // currently don't have CRDs in CI with sourceVolumeMode which is pretty new
1949
        // converting volume mode is possible but has security implications
1950
        return true, nil
×
1951
}
1952

1953
// GetSnapshotClassForSmartClone looks up the snapshot class based on the storage class
1954
func GetSnapshotClassForSmartClone(pvc *corev1.PersistentVolumeClaim, targetPvcStorageClassName, snapshotClassName *string, log logr.Logger, client client.Client, recorder record.EventRecorder) (string, error) {
×
1955
        logger := log.WithName("GetSnapshotClassForSmartClone").V(3)
×
1956
        // Check if relevant CRDs are available
×
1957
        if !isCsiCrdsDeployed(client, log) {
×
1958
                logger.Info("Missing CSI snapshotter CRDs, falling back to host assisted clone")
×
1959
                return "", nil
×
1960
        }
×
1961

1962
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(context.TODO(), client, targetPvcStorageClassName)
×
1963
        if err != nil {
×
1964
                return "", err
×
1965
        }
×
1966
        if targetStorageClass == nil {
×
1967
                logger.Info("Target PVC's Storage Class not found")
×
1968
                return "", nil
×
1969
        }
×
1970

1971
        vscName, err := GetVolumeSnapshotClass(context.TODO(), client, pvc, targetStorageClass.Provisioner, snapshotClassName, logger, recorder)
×
1972
        if err != nil {
×
1973
                return "", err
×
1974
        }
×
1975
        if vscName != nil {
×
1976
                if pvc != nil {
×
1977
                        logger.Info("smart-clone is applicable for datavolume", "datavolume",
×
1978
                                pvc.Name, "snapshot class", *vscName)
×
1979
                }
×
1980
                return *vscName, nil
×
1981
        }
1982

1983
        logger.Info("Could not match snapshotter with storage class, falling back to host assisted clone")
×
1984
        return "", nil
×
1985
}
1986

1987
// GetVolumeSnapshotClass looks up the snapshot class based on the driver and an optional specific name
1988
// In case of multiple candidates, it returns the default-annotated one, or the sorted list first one if no such default
1989
func GetVolumeSnapshotClass(ctx context.Context, c client.Client, pvc *corev1.PersistentVolumeClaim, driver string, snapshotClassName *string, log logr.Logger, recorder record.EventRecorder) (*string, error) {
×
1990
        logger := log.WithName("GetVolumeSnapshotClass").V(3)
×
1991

×
1992
        logEvent := func(message, vscName string) {
×
1993
                logger.Info(message, "name", vscName)
×
1994
                if pvc != nil {
×
1995
                        msg := fmt.Sprintf("%s %s", message, vscName)
×
1996
                        recorder.Event(pvc, corev1.EventTypeNormal, VolumeSnapshotClassSelected, msg)
×
1997
                }
×
1998
        }
1999

2000
        if snapshotClassName != nil {
×
2001
                vsc := &snapshotv1.VolumeSnapshotClass{}
×
2002
                if err := c.Get(context.TODO(), types.NamespacedName{Name: *snapshotClassName}, vsc); err != nil {
×
2003
                        return nil, err
×
2004
                }
×
2005
                if vsc.Driver == driver {
×
2006
                        logEvent(MessageStorageProfileVolumeSnapshotClassSelected, vsc.Name)
×
2007
                        return snapshotClassName, nil
×
2008
                }
×
2009
                return nil, nil
×
2010
        }
2011

2012
        vscList := &snapshotv1.VolumeSnapshotClassList{}
×
2013
        if err := c.List(ctx, vscList); err != nil {
×
2014
                if meta.IsNoMatchError(err) {
×
2015
                        return nil, nil
×
2016
                }
×
2017
                return nil, err
×
2018
        }
2019

2020
        var candidates []string
×
2021
        for _, vsc := range vscList.Items {
×
2022
                if vsc.Driver == driver {
×
2023
                        if vsc.Annotations[AnnDefaultSnapshotClass] == "true" {
×
2024
                                logEvent(MessageDefaultVolumeSnapshotClassSelected, vsc.Name)
×
2025
                                vscName := vsc.Name
×
2026
                                return &vscName, nil
×
2027
                        }
×
2028
                        candidates = append(candidates, vsc.Name)
×
2029
                }
2030
        }
2031

2032
        if len(candidates) > 0 {
×
2033
                sort.Strings(candidates)
×
2034
                logEvent(MessageFirstVolumeSnapshotClassSelected, candidates[0])
×
2035
                return &candidates[0], nil
×
2036
        }
×
2037

2038
        return nil, nil
×
2039
}
2040

2041
// isCsiCrdsDeployed checks whether the CSI snapshotter CRD are deployed
2042
func isCsiCrdsDeployed(c client.Client, log logr.Logger) bool {
×
2043
        version := "v1"
×
2044
        vsClass := "volumesnapshotclasses." + snapshotv1.GroupName
×
2045
        vsContent := "volumesnapshotcontents." + snapshotv1.GroupName
×
2046
        vs := "volumesnapshots." + snapshotv1.GroupName
×
2047

×
2048
        return isCrdDeployed(c, vsClass, version, log) &&
×
2049
                isCrdDeployed(c, vsContent, version, log) &&
×
2050
                isCrdDeployed(c, vs, version, log)
×
2051
}
×
2052

2053
// isCrdDeployed checks whether a CRD is deployed
2054
func isCrdDeployed(c client.Client, name, version string, log logr.Logger) bool {
×
2055
        crd := &extv1.CustomResourceDefinition{}
×
2056
        err := c.Get(context.TODO(), types.NamespacedName{Name: name}, crd)
×
2057
        if err != nil {
×
2058
                if !k8serrors.IsNotFound(err) {
×
2059
                        log.Info("Error looking up CRD", "crd name", name, "version", version, "error", err)
×
2060
                }
×
2061
                return false
×
2062
        }
2063

2064
        for _, v := range crd.Spec.Versions {
×
2065
                if v.Name == version && v.Served {
×
2066
                        return true
×
2067
                }
×
2068
        }
2069

2070
        return false
×
2071
}
2072

2073
// IsSnapshotReady indicates if a volume snapshot is ready to be used
2074
func IsSnapshotReady(snapshot *snapshotv1.VolumeSnapshot) bool {
×
2075
        return snapshot.Status != nil && snapshot.Status.ReadyToUse != nil && *snapshot.Status.ReadyToUse
×
2076
}
×
2077

2078
// GetResource updates given obj with the data of the object with the same name and namespace
2079
func GetResource(ctx context.Context, c client.Client, namespace, name string, obj client.Object) (bool, error) {
×
2080
        obj.SetNamespace(namespace)
×
2081
        obj.SetName(name)
×
2082

×
2083
        err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
×
2084
        if err != nil {
×
2085
                if k8serrors.IsNotFound(err) {
×
2086
                        return false, nil
×
2087
                }
×
2088

2089
                return false, err
×
2090
        }
2091

2092
        return true, nil
×
2093
}
2094

2095
// PatchArgs are the args for Patch
2096
type PatchArgs struct {
2097
        Client client.Client
2098
        Log    logr.Logger
2099
        Obj    client.Object
2100
        OldObj client.Object
2101
}
2102

2103
// GetAnnotatedEventSource returns resource referenced by AnnEventSource annotations
2104
func GetAnnotatedEventSource(ctx context.Context, c client.Client, obj client.Object) (client.Object, error) {
×
2105
        esk, ok := obj.GetAnnotations()[AnnEventSourceKind]
×
2106
        if !ok {
×
2107
                return obj, nil
×
2108
        }
×
2109
        if esk != "PersistentVolumeClaim" {
×
2110
                return obj, nil
×
2111
        }
×
2112
        es, ok := obj.GetAnnotations()[AnnEventSource]
×
2113
        if !ok {
×
2114
                return obj, nil
×
2115
        }
×
2116
        namespace, name, err := cache.SplitMetaNamespaceKey(es)
×
2117
        if err != nil {
×
2118
                return nil, err
×
2119
        }
×
2120
        pvc := &corev1.PersistentVolumeClaim{
×
2121
                ObjectMeta: metav1.ObjectMeta{
×
2122
                        Namespace: namespace,
×
2123
                        Name:      name,
×
2124
                },
×
2125
        }
×
2126
        if err := c.Get(ctx, client.ObjectKeyFromObject(pvc), pvc); err != nil {
×
2127
                return nil, err
×
2128
        }
×
2129
        return pvc, nil
×
2130
}
2131

2132
// OwnedByDataVolume returns true if the object is owned by a DataVolume
2133
func OwnedByDataVolume(obj metav1.Object) bool {
×
2134
        owner := metav1.GetControllerOf(obj)
×
2135
        return owner != nil && owner.Kind == "DataVolume"
×
2136
}
×
2137

2138
// CopyAllowedAnnotations copies the allowed annotations from the source object
2139
// to the destination object
2140
func CopyAllowedAnnotations(srcObj, dstObj metav1.Object) {
×
2141
        for ann, def := range allowedAnnotations {
×
2142
                val, ok := srcObj.GetAnnotations()[ann]
×
2143
                if !ok && def != "" {
×
2144
                        val = def
×
2145
                }
×
2146
                if val != "" {
×
2147
                        klog.V(1).Info("Applying annotation", "Name", dstObj.GetName(), ann, val)
×
2148
                        AddAnnotation(dstObj, ann, val)
×
2149
                }
×
2150
        }
2151
}
2152

2153
// CopyAllowedLabels copies allowed labels matching the validLabelsMatch regexp from the
2154
// source map to the destination object allowing overwrites
2155
func CopyAllowedLabels(srcLabels map[string]string, dstObj metav1.Object, overwrite bool) {
1✔
2156
        for label, value := range srcLabels {
2✔
2157
                if _, found := dstObj.GetLabels()[label]; (!found || overwrite) && validLabelsMatch.MatchString(label) {
2✔
2158
                        AddLabel(dstObj, label, value)
1✔
2159
                }
1✔
2160
        }
2161
}
2162

2163
// ClaimMayExistBeforeDataVolume returns true if the PVC may exist before the DataVolume
2164
func ClaimMayExistBeforeDataVolume(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2165
        if ClaimIsPopulatedForDataVolume(pvc, dv) {
×
2166
                return true, nil
×
2167
        }
×
2168
        return AllowClaimAdoption(c, pvc, dv)
×
2169
}
2170

2171
// ClaimIsPopulatedForDataVolume returns true if the PVC is populated for the given DataVolume
2172
func ClaimIsPopulatedForDataVolume(pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) bool {
×
2173
        return pvc != nil && dv != nil && pvc.Annotations[AnnPopulatedFor] == dv.Name
×
2174
}
×
2175

2176
// AllowClaimAdoption returns true if the PVC may be adopted
2177
func AllowClaimAdoption(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2178
        if pvc == nil || dv == nil {
×
2179
                return false, nil
×
2180
        }
×
2181
        anno, ok := pvc.Annotations[AnnCreatedForDataVolume]
×
2182
        if ok && anno == string(dv.UID) {
×
2183
                return false, nil
×
2184
        }
×
2185
        anno, ok = dv.Annotations[AnnAllowClaimAdoption]
×
2186
        // if annotation exists, go with that regardless of featuregate
×
2187
        if ok {
×
2188
                val, _ := strconv.ParseBool(anno)
×
2189
                return val, nil
×
2190
        }
×
2191
        return featuregates.NewFeatureGates(c).ClaimAdoptionEnabled()
×
2192
}
2193

2194
// ResolveDataSourceChain resolves a DataSource reference.
2195
// Returns an error if DataSource reference is not found or
2196
// DataSource reference points to another DataSource
2197
func ResolveDataSourceChain(ctx context.Context, client client.Client, dataSource *cdiv1.DataSource) (*cdiv1.DataSource, error) {
×
2198
        if dataSource.Spec.Source.DataSource == nil {
×
2199
                return dataSource, nil
×
2200
        }
×
2201

2202
        ref := dataSource.Spec.Source.DataSource
×
2203
        refNs := GetNamespace(ref.Namespace, dataSource.Namespace)
×
2204
        if dataSource.Namespace != refNs {
×
2205
                return dataSource, ErrDataSourceCrossNamespace
×
2206
        }
×
2207
        if ref.Name == dataSource.Name && refNs == dataSource.Namespace {
×
2208
                return nil, ErrDataSourceSelfReference
×
2209
        }
×
2210

2211
        resolved := &cdiv1.DataSource{}
×
2212
        if err := client.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: refNs}, resolved); err != nil {
×
2213
                return nil, err
×
2214
        }
×
2215

2216
        if resolved.Spec.Source.DataSource != nil {
×
2217
                return nil, ErrDataSourceMaxDepthReached
×
2218
        }
×
2219

2220
        return resolved, nil
×
2221
}
2222

2223
func sortEvents(events *corev1.EventList, usingPopulator bool, pvcPrimeName string) {
1✔
2224
        // Sort event lists by containing primeName substring and most recent timestamp
1✔
2225
        sort.Slice(events.Items, func(i, j int) bool {
2✔
2226
                if usingPopulator {
2✔
2227
                        firstContainsPrime := strings.Contains(events.Items[i].Message, pvcPrimeName)
1✔
2228
                        secondContainsPrime := strings.Contains(events.Items[j].Message, pvcPrimeName)
1✔
2229

1✔
2230
                        if firstContainsPrime && !secondContainsPrime {
2✔
2231
                                return true
1✔
2232
                        }
1✔
2233
                        if !firstContainsPrime && secondContainsPrime {
2✔
2234
                                return false
1✔
2235
                        }
1✔
2236
                }
2237

2238
                // if the timestamps are the same, prioritze longer messages to make sure our sorting is deterministic
2239
                if events.Items[i].LastTimestamp.Time.Equal(events.Items[j].LastTimestamp.Time) {
1✔
2240
                        return len(events.Items[i].Message) > len(events.Items[j].Message)
×
2241
                }
×
2242

2243
                // if both contains primeName substring or neither, just sort on timestamp
2244
                return events.Items[i].LastTimestamp.Time.After(events.Items[j].LastTimestamp.Time)
1✔
2245
        })
2246
}
2247

2248
// UpdatePVCBoundConditionFromEvents updates the bound condition annotations on the PVC based on recent events
2249
// This function can be used by both controller and populator packages to update PVC bound condition information
2250
func UpdatePVCBoundConditionFromEvents(pvc *corev1.PersistentVolumeClaim, c client.Client, log logr.Logger) error {
×
2251
        currentPvcCopy := pvc.DeepCopy()
×
2252

×
2253
        anno := pvc.GetAnnotations()
×
2254
        if anno == nil {
×
2255
                return nil
×
2256
        }
×
2257

2258
        if IsBound(pvc) {
×
2259
                anno := pvc.GetAnnotations()
×
2260
                delete(anno, AnnBoundCondition)
×
2261
                delete(anno, AnnBoundConditionReason)
×
2262
                delete(anno, AnnBoundConditionMessage)
×
2263

×
2264
                if !reflect.DeepEqual(currentPvcCopy, pvc) {
×
2265
                        patch := client.MergeFrom(currentPvcCopy)
×
2266
                        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2267
                                return err
×
2268
                        }
×
2269
                }
2270

2271
                return nil
×
2272
        }
2273

2274
        if pvc.Status.Phase != corev1.ClaimPending {
×
2275
                return nil
×
2276
        }
×
2277

2278
        // set bound condition by getting the latest event
2279
        events := &corev1.EventList{}
×
2280

×
2281
        err := c.List(context.TODO(), events,
×
2282
                client.InNamespace(pvc.GetNamespace()),
×
2283
                client.MatchingFields{"involvedObject.name": pvc.GetName(),
×
2284
                        "involvedObject.uid": string(pvc.GetUID())},
×
2285
        )
×
2286

×
2287
        if err != nil {
×
2288
                // Log the error but don't fail the reconciliation
×
2289
                log.Error(err, "Unable to list events for PVC bound condition update", "pvc", pvc.Name)
×
2290
                return nil
×
2291
        }
×
2292

2293
        if len(events.Items) == 0 {
×
2294
                return nil
×
2295
        }
×
2296

2297
        pvcPrime, usingPopulator := anno[AnnPVCPrimeName]
×
2298

×
2299
        // Sort event lists by containing primeName substring and most recent timestamp
×
2300
        sortEvents(events, usingPopulator, pvcPrime)
×
2301

×
2302
        boundMessage := ""
×
2303
        // check if prime name annotation exists
×
2304
        if usingPopulator {
×
2305
                // if we are using populators get the latest event from prime pvc
×
2306
                pvcPrime = fmt.Sprintf("[%s] : ", pvcPrime)
×
2307

×
2308
                // if the first event does not contain a prime message, none will so return
×
2309
                primeIdx := strings.Index(events.Items[0].Message, pvcPrime)
×
2310
                if primeIdx == -1 {
×
2311
                        log.V(1).Info("No bound message found, skipping bound condition update", "pvc", pvc.Name)
×
2312
                        return nil
×
2313
                }
×
2314
                boundMessage = events.Items[0].Message[primeIdx+len(pvcPrime):]
×
2315
        } else {
×
2316
                // if not using populators just get the latest event
×
2317
                boundMessage = events.Items[0].Message
×
2318
        }
×
2319

2320
        // since we checked status of phase above, we know this is pending
2321
        anno[AnnBoundCondition] = "false"
×
2322
        anno[AnnBoundConditionReason] = "Pending"
×
2323
        anno[AnnBoundConditionMessage] = boundMessage
×
2324

×
2325
        patch := client.MergeFrom(currentPvcCopy)
×
2326
        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2327
                return err
×
2328
        }
×
2329

2330
        return nil
×
2331
}
2332

2333
// CopyEvents gets srcPvc events and re-emits them on the target PVC with the src name prefix
2334
func CopyEvents(srcPVC, targetPVC client.Object, c client.Client, recorder record.EventRecorder) {
×
2335
        srcPrefixMsg := fmt.Sprintf("[%s] : ", srcPVC.GetName())
×
2336

×
2337
        newEvents := &corev1.EventList{}
×
2338
        err := c.List(context.TODO(), newEvents,
×
2339
                client.InNamespace(srcPVC.GetNamespace()),
×
2340
                client.MatchingFields{"involvedObject.name": srcPVC.GetName(),
×
2341
                        "involvedObject.uid": string(srcPVC.GetUID())},
×
2342
        )
×
2343

×
2344
        if err != nil {
×
2345
                klog.Error(err, "Could not retrieve srcPVC list of Events")
×
2346
        }
×
2347

2348
        currEvents := &corev1.EventList{}
×
2349
        err = c.List(context.TODO(), currEvents,
×
2350
                client.InNamespace(targetPVC.GetNamespace()),
×
2351
                client.MatchingFields{"involvedObject.name": targetPVC.GetName(),
×
2352
                        "involvedObject.uid": string(targetPVC.GetUID())},
×
2353
        )
×
2354

×
2355
        if err != nil {
×
2356
                klog.Error(err, "Could not retrieve targetPVC list of Events")
×
2357
        }
×
2358

2359
        // use this to hash each message for quick lookup, value is unused
2360
        eventMap := map[string]struct{}{}
×
2361

×
2362
        for _, event := range currEvents.Items {
×
2363
                eventMap[event.Message] = struct{}{}
×
2364
        }
×
2365

2366
        for _, newEvent := range newEvents.Items {
×
2367
                msg := newEvent.Message
×
2368

×
2369
                // check if target PVC already has this equivalent event
×
2370
                if _, exists := eventMap[msg]; exists {
×
2371
                        continue
×
2372
                }
2373

2374
                formattedMsg := srcPrefixMsg + msg
×
2375
                // check if we already emitted this event with the src prefix
×
2376
                if _, exists := eventMap[formattedMsg]; exists {
×
2377
                        continue
×
2378
                }
2379

2380
                // simply check if event is a ClaimMisbound event, if so, skip it
NEW
2381
                if newEvent.Reason == EventReasonClaimMisbound {
×
NEW
2382
                        continue
×
2383
                }
2384

UNCOV
2385
                recorder.Event(targetPVC, newEvent.Type, newEvent.Reason, formattedMsg)
×
2386
        }
2387
}
2388

2389
// IsWebhookPvcRenderingEnabled returns true unless
2390
// CDIConfigSpec.WebhookPvcRendering is explicitly set to "Disabled"
2391
func IsWebhookPvcRenderingEnabled(c client.Client) (bool, error) {
1✔
2392
        config := &cdiv1.CDIConfig{}
1✔
2393
        if err := c.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, config); err != nil {
2✔
2394
                return false, errors.Wrap(err, "error getting CDIConfig")
1✔
2395
        }
1✔
2396
        if config.Spec.WebhookPvcRendering == cdiv1.WebhookPvcRenderingDisabled {
2✔
2397
                return false, nil
1✔
2398
        }
1✔
2399
        return true, nil
1✔
2400
}
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