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

kubevirt / containerized-data-importer / #4937

03 Sep 2024 12:06PM UTC coverage: 59.167% (+0.002%) from 59.165%
#4937

push

travis-ci

web-flow
Run bazelisk run //robots/cmd/uploader:uploader -- -workspace /home/prow/go/src/github.com/kubevirt/project-infra/../containerized-data-importer/WORKSPACE -dry-run=false (#3420)

Signed-off-by: kubevirt-bot <kubevirtbot@redhat.com>

16626 of 28100 relevant lines covered (59.17%)

0.65 hits per line

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

86.44
/pkg/controller/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
limitations under the License.
14
See the License for the specific language governing permissions and
15
*/
16

17
package controller
18

19
import (
20
        "context"
21
        "crypto/rsa"
22
        "encoding/json"
23
        "fmt"
24
        "strconv"
25
        "strings"
26

27
        "github.com/go-logr/logr"
28
        "github.com/pkg/errors"
29

30
        corev1 "k8s.io/api/core/v1"
31
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
32
        "k8s.io/apimachinery/pkg/api/resource"
33
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34
        "k8s.io/apimachinery/pkg/types"
35
        "k8s.io/client-go/tools/record"
36
        "k8s.io/klog/v2"
37

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

40
        cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
41
        "kubevirt.io/containerized-data-importer/pkg/common"
42
        cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
43
        "kubevirt.io/containerized-data-importer/pkg/util"
44
        "kubevirt.io/containerized-data-importer/pkg/util/cert"
45
)
46

47
const (
48
        // CertVolName is the name of the volume containing certs
49
        CertVolName = "cdi-cert-vol"
50

51
        // SecretVolName is the name of the volume containing gcs key
52
        //nolint:gosec // This is not a real secret
53
        SecretVolName = "cdi-secret-vol"
54

55
        // AnnOwnerRef is used when owner is in a different namespace
56
        AnnOwnerRef = cc.AnnAPIGroup + "/storage.ownerRef"
57

58
        // PodRunningReason is const that defines the pod was started as a reason
59
        PodRunningReason = "Pod is running"
60

61
        // ScratchSpaceRequiredReason is a const that defines the pod exited due to a lack of scratch space
62
        ScratchSpaceRequiredReason = "Scratch space required"
63

64
        // ImagePullFailedReason is a const that defines the pod exited due to failure when pulling image
65
        ImagePullFailedReason = "ImagePullFailed"
66

67
        // ImportCompleteMessage is a const that defines the pod completeded the import successfully
68
        ImportCompleteMessage = "Import Complete"
69

70
        // ProxyCertVolName is the name of the volumecontaining certs
71
        ProxyCertVolName = "cdi-proxy-cert-vol"
72
        // ClusterWideProxyAPIGroup is the APIGroup for OpenShift Cluster Wide Proxy
73
        ClusterWideProxyAPIGroup = "config.openshift.io"
74
        // ClusterWideProxyAPIKind is the APIKind for OpenShift Cluster Wide Proxy
75
        ClusterWideProxyAPIKind = "Proxy"
76
        // ClusterWideProxyAPIVersion is the APIVersion for OpenShift Cluster Wide Proxy
77
        ClusterWideProxyAPIVersion = "v1"
78
        // ClusterWideProxyName is the OpenShift Cluster Wide Proxy object name. There is only one obj in the cluster.
79
        ClusterWideProxyName = "cluster"
80
        // ClusterWideProxyConfigMapName is the OpenShift Cluster Wide Proxy ConfigMap name for CA certificates.
81
        ClusterWideProxyConfigMapName = "user-ca-bundle"
82
        // ClusterWideProxyConfigMapNameSpace is the OpenShift Cluster Wide Proxy ConfigMap namespace for CA certificates.
83
        ClusterWideProxyConfigMapNameSpace = "openshift-config"
84
        // ClusterWideProxyConfigMapKey is the OpenShift Cluster Wide Proxy ConfigMap key name for CA certificates.
85
        ClusterWideProxyConfigMapKey = "ca-bundle.crt"
86
)
87

88
func checkPVC(pvc *corev1.PersistentVolumeClaim, annotation string, log logr.Logger) bool {
1✔
89
        // check if we have proper annotation
1✔
90
        if !metav1.HasAnnotation(pvc.ObjectMeta, annotation) {
2✔
91
                log.V(1).Info("PVC annotation not found, skipping pvc", "annotation", annotation)
1✔
92
                return false
1✔
93
        }
1✔
94

95
        return true
1✔
96
}
97

98
// - when the SkipWFFCVolumesEnabled is true, the CDI controller will only handle BOUND the PVC
99
// - when the SkipWFFCVolumesEnabled is false, the CDI controller will can handle it - it will create worker pods for the PVC (this will bind it)
100
func shouldHandlePvc(pvc *corev1.PersistentVolumeClaim, honorWaitForFirstConsumerEnabled bool, log logr.Logger) bool {
1✔
101
        if honorWaitForFirstConsumerEnabled {
2✔
102
                return isBound(pvc, log)
1✔
103
        }
1✔
104
        return true
1✔
105
}
106

107
func isBound(pvc *corev1.PersistentVolumeClaim, log logr.Logger) bool {
1✔
108
        if pvc.Status.Phase != corev1.ClaimBound {
2✔
109
                log.V(1).Info("PVC not bound, skipping pvc", "Phase", pvc.Status.Phase)
1✔
110
                return false
1✔
111
        }
1✔
112

113
        return true
1✔
114
}
115

116
// checks if particular label exists in pvc
117
func checkIfLabelExists(pvc *corev1.PersistentVolumeClaim, lbl string, val string) bool {
1✔
118
        value, exists := pvc.ObjectMeta.Labels[lbl]
1✔
119
        if exists && value == val {
2✔
120
                return true
1✔
121
        }
1✔
122
        return false
1✔
123
}
124

125
// newScratchPersistentVolumeClaimSpec creates a new PVC based on the size of the passed in PVC.
126
// It also sets the appropriate OwnerReferences on the resource
127
// which allows handleObject to discover the pod resource that 'owns' it, and clean up when needed.
128
func newScratchPersistentVolumeClaimSpec(pvc *corev1.PersistentVolumeClaim, pod *corev1.Pod, name, storageClassName string) *corev1.PersistentVolumeClaim {
1✔
129
        labels := map[string]string{
1✔
130
                "app": "containerized-data-importer",
1✔
131
        }
1✔
132

1✔
133
        annotations := make(map[string]string)
1✔
134
        // Copy kubevirt.io annotations, but NOT the CDI annotations as those will trigger another import/upload/clone on the scratchspace
1✔
135
        // pvc.
1✔
136
        if len(pvc.GetAnnotations()) > 0 {
2✔
137
                for k, v := range pvc.GetAnnotations() {
2✔
138
                        if strings.Contains(k, common.KubeVirtAnnKey) && !strings.Contains(k, common.CDIAnnKey) {
1✔
139
                                annotations[k] = v
×
140
                        }
×
141
                }
142
        }
143
        // When the original PVC is being handled by a populator, copy AnnSelectedNode to avoid issues with k8s scheduler
144
        _, isPopulator := pvc.Annotations[cc.AnnPopulatorKind]
1✔
145
        selectedNode := pvc.Annotations[cc.AnnSelectedNode]
1✔
146
        if isPopulator && selectedNode != "" {
1✔
147
                annotations[cc.AnnSelectedNode] = selectedNode
×
148
        }
×
149

150
        pvcDef := &corev1.PersistentVolumeClaim{
1✔
151
                ObjectMeta: metav1.ObjectMeta{
1✔
152
                        Name:        name,
1✔
153
                        Namespace:   pvc.Namespace,
1✔
154
                        Labels:      labels,
1✔
155
                        Annotations: annotations,
1✔
156
                        OwnerReferences: []metav1.OwnerReference{
1✔
157
                                MakePodOwnerReference(pod),
1✔
158
                        },
1✔
159
                },
1✔
160
                Spec: corev1.PersistentVolumeClaimSpec{
1✔
161
                        AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"},
1✔
162
                        Resources:   *pvc.Spec.Resources.DeepCopy(),
1✔
163
                },
1✔
164
        }
1✔
165
        if storageClassName != "" {
2✔
166
                pvcDef.Spec.StorageClassName = &storageClassName
1✔
167
        }
1✔
168
        return pvcDef
1✔
169
}
170

171
// createScratchPersistentVolumeClaim creates and returns a pointer to a scratch PVC which is created based on the passed-in pvc and storage class name.
172
func createScratchPersistentVolumeClaim(client client.Client, pvc *corev1.PersistentVolumeClaim, pod *corev1.Pod, name, storageClassName string, installerLabels map[string]string, recorder record.EventRecorder) (*corev1.PersistentVolumeClaim, error) {
1✔
173
        scratchPvcSpec := newScratchPersistentVolumeClaimSpec(pvc, pod, name, storageClassName)
1✔
174

1✔
175
        sizeRequest := pvc.Spec.Resources.Requests[corev1.ResourceStorage]
1✔
176
        scratchFsOverhead, err := GetFilesystemOverhead(context.TODO(), client, scratchPvcSpec)
1✔
177
        if err != nil {
1✔
178
                return nil, errors.Wrap(err, "failed to get filesystem overhead for scratch PVC")
×
179
        }
×
180
        scratchFsOverheadFloat, _ := strconv.ParseFloat(string(scratchFsOverhead), 64)
1✔
181
        pvcFsOverhead, err := GetFilesystemOverhead(context.TODO(), client, pvc)
1✔
182
        if err != nil {
1✔
183
                return nil, errors.Wrap(err, "failed to get filesystem overhead for original PVC")
×
184
        }
×
185
        pvcFsOverheadFloat, _ := strconv.ParseFloat(string(pvcFsOverhead), 64)
1✔
186
        expectedVirtualSize := util.GetUsableSpace(pvcFsOverheadFloat, sizeRequest.Value())
1✔
187

1✔
188
        usableSpaceRaw := util.CalculateOverheadSpace(scratchFsOverheadFloat, expectedVirtualSize)
1✔
189

1✔
190
        scratchPvcSpec.Spec.Resources.Requests[corev1.ResourceStorage] = *resource.NewScaledQuantity(usableSpaceRaw, 0)
1✔
191

1✔
192
        util.SetRecommendedLabels(scratchPvcSpec, installerLabels, "cdi-controller")
1✔
193
        if err := client.Create(context.TODO(), scratchPvcSpec); err != nil {
1✔
194
                if cc.ErrQuotaExceeded(err) {
×
195
                        recorder.Event(pvc, corev1.EventTypeWarning, cc.ErrExceededQuota, err.Error())
×
196
                }
×
197
                if !k8serrors.IsAlreadyExists(err) {
×
198
                        return nil, errors.Wrap(err, "scratch PVC API create errored")
×
199
                }
×
200
        }
201
        scratchPvc := &corev1.PersistentVolumeClaim{}
1✔
202
        if err := client.Get(context.TODO(), types.NamespacedName{Name: scratchPvcSpec.Name, Namespace: pvc.Namespace}, scratchPvc); err != nil {
1✔
203
                klog.Errorf("Unable to get scratch space pvc, %v\n", err)
×
204
                return nil, err
×
205
        }
×
206
        klog.V(3).Infof("scratch PVC \"%s/%s\" created\n", scratchPvc.Namespace, scratchPvc.Name)
1✔
207
        return scratchPvc, nil
1✔
208
}
209

210
// GetFilesystemOverhead determines the filesystem overhead defined in CDIConfig for this PVC's volumeMode and storageClass.
211
func GetFilesystemOverhead(ctx context.Context, client client.Client, pvc *corev1.PersistentVolumeClaim) (cdiv1.Percent, error) {
1✔
212
        if cc.GetVolumeMode(pvc) != corev1.PersistentVolumeFilesystem {
2✔
213
                return "0", nil
1✔
214
        }
1✔
215

216
        return cc.GetFilesystemOverheadForStorageClass(ctx, client, pvc.Spec.StorageClassName)
1✔
217
}
218

219
// GetScratchPvcStorageClass tries to determine which storage class to use for use with a scratch persistent
220
// volume claim. The order of preference is the following:
221
// 1. Defined value in CDI Config field scratchSpaceStorageClass.
222
// 2. If 1 is not available, use the storage class name of the original pvc that will own the scratch pvc.
223
// 3. If none of those are available, return blank.
224
func GetScratchPvcStorageClass(client client.Client, pvc *corev1.PersistentVolumeClaim) string {
1✔
225
        config := &cdiv1.CDIConfig{}
1✔
226
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, config); err != nil {
2✔
227
                return ""
1✔
228
        }
1✔
229
        storageClassName := config.Status.ScratchSpaceStorageClass
1✔
230
        if storageClassName == "" {
2✔
231
                // Unable to determine scratch storage class, attempt to read the storage class from the pvc.
1✔
232
                if pvc.Spec.StorageClassName != nil {
2✔
233
                        storageClassName = *pvc.Spec.StorageClassName
1✔
234
                        if storageClassName != "" {
2✔
235
                                return storageClassName
1✔
236
                        }
1✔
237
                }
238
        } else {
1✔
239
                return storageClassName
1✔
240
        }
1✔
241
        return ""
1✔
242
}
243

244
// DecodePublicKey turns a bunch of bytes into a public key
245
func DecodePublicKey(keyBytes []byte) (*rsa.PublicKey, error) {
1✔
246
        keys, err := cert.ParsePublicKeysPEM(keyBytes)
1✔
247
        if err != nil {
2✔
248
                return nil, err
1✔
249
        }
1✔
250

251
        if len(keys) != 1 {
1✔
252
                return nil, errors.New("unexected number of pulic keys")
×
253
        }
×
254

255
        key, ok := keys[0].(*rsa.PublicKey)
1✔
256
        if !ok {
1✔
257
                return nil, errors.New("PEM does not contain RSA key")
×
258
        }
×
259

260
        return key, nil
1✔
261
}
262

263
// MakePVCOwnerReference makes owner reference from a PVC
264
func MakePVCOwnerReference(pvc *corev1.PersistentVolumeClaim) metav1.OwnerReference {
1✔
265
        blockOwnerDeletion := true
1✔
266
        isController := true
1✔
267
        return metav1.OwnerReference{
1✔
268
                APIVersion:         "v1",
1✔
269
                Kind:               "PersistentVolumeClaim",
1✔
270
                Name:               pvc.Name,
1✔
271
                UID:                pvc.GetUID(),
1✔
272
                BlockOwnerDeletion: &blockOwnerDeletion,
1✔
273
                Controller:         &isController,
1✔
274
        }
1✔
275
}
1✔
276

277
// MakePodOwnerReference makes owner reference from a Pod
278
func MakePodOwnerReference(pod *corev1.Pod) metav1.OwnerReference {
1✔
279
        blockOwnerDeletion := true
1✔
280
        isController := true
1✔
281
        return metav1.OwnerReference{
1✔
282
                APIVersion:         "v1",
1✔
283
                Kind:               "Pod",
1✔
284
                Name:               pod.Name,
1✔
285
                UID:                pod.GetUID(),
1✔
286
                BlockOwnerDeletion: &blockOwnerDeletion,
1✔
287
                Controller:         &isController,
1✔
288
        }
1✔
289
}
1✔
290

291
func podPhaseFromPVC(pvc *corev1.PersistentVolumeClaim) corev1.PodPhase {
1✔
292
        phase := pvc.ObjectMeta.Annotations[cc.AnnPodPhase]
1✔
293
        return corev1.PodPhase(phase)
1✔
294
}
1✔
295

296
func podSucceededFromPVC(pvc *corev1.PersistentVolumeClaim) bool {
1✔
297
        return podPhaseFromPVC(pvc) == corev1.PodSucceeded
1✔
298
}
1✔
299

300
func setAnnotationsFromPodWithPrefix(anno map[string]string, pod *corev1.Pod, termMsg *common.TerminationMessage, prefix string) {
1✔
301
        if pod == nil || pod.Status.ContainerStatuses == nil {
2✔
302
                return
1✔
303
        }
1✔
304
        annPodRestarts, _ := strconv.Atoi(anno[cc.AnnPodRestarts])
1✔
305
        podRestarts := int(pod.Status.ContainerStatuses[0].RestartCount)
1✔
306
        if podRestarts >= annPodRestarts {
2✔
307
                anno[cc.AnnPodRestarts] = strconv.Itoa(podRestarts)
1✔
308
        }
1✔
309

310
        containerState := pod.Status.ContainerStatuses[0].State
1✔
311
        if containerState.Running != nil {
2✔
312
                anno[prefix] = "true"
1✔
313
                anno[prefix+".message"] = ""
1✔
314
                anno[prefix+".reason"] = PodRunningReason
1✔
315
                return
1✔
316
        }
1✔
317

318
        anno[cc.AnnRunningCondition] = "false"
1✔
319

1✔
320
        for _, status := range pod.Status.ContainerStatuses {
2✔
321
                if status.Started != nil && !(*status.Started) {
1✔
322
                        if status.State.Waiting != nil &&
×
323
                                (status.State.Waiting.Reason == "ImagePullBackOff" || status.State.Waiting.Reason == "ErrImagePull") {
×
324
                                anno[prefix+".message"] = fmt.Sprintf("%s: %s", common.ImagePullFailureText, status.Image)
×
325
                                anno[prefix+".reason"] = ImagePullFailedReason
×
326
                                return
×
327
                        }
×
328
                }
329
        }
330

331
        if containerState.Waiting != nil && containerState.Waiting.Reason != "CrashLoopBackOff" {
2✔
332
                anno[prefix+".message"] = simplifyKnownMessage(containerState.Waiting.Message)
1✔
333
                anno[prefix+".reason"] = containerState.Waiting.Reason
1✔
334
                return
1✔
335
        }
1✔
336

337
        if containerState.Terminated != nil {
2✔
338
                if termMsg != nil {
2✔
339
                        if termMsg.ScratchSpaceRequired != nil && *termMsg.ScratchSpaceRequired {
2✔
340
                                anno[cc.AnnRequiresScratch] = "true"
1✔
341
                                anno[prefix+".message"] = common.ScratchSpaceRequired
1✔
342
                                anno[prefix+".reason"] = ScratchSpaceRequiredReason
1✔
343
                                return
1✔
344
                        }
1✔
345
                        // Handle extended termination message
346
                        if termMsg.Message != nil {
2✔
347
                                anno[prefix+".message"] = *termMsg.Message
1✔
348
                        }
1✔
349
                        if termMsg.VddkInfo != nil {
2✔
350
                                if termMsg.VddkInfo.Host != "" {
2✔
351
                                        anno[cc.AnnVddkHostConnection] = termMsg.VddkInfo.Host
1✔
352
                                }
1✔
353
                                if termMsg.VddkInfo.Version != "" {
2✔
354
                                        anno[cc.AnnVddkVersion] = termMsg.VddkInfo.Version
1✔
355
                                }
1✔
356
                        }
357
                        if termMsg.PreallocationApplied != nil && *termMsg.PreallocationApplied {
2✔
358
                                anno[cc.AnnPreallocationApplied] = "true"
1✔
359
                        }
1✔
360
                } else {
1✔
361
                        // Handle plain termination message (legacy)
1✔
362
                        anno[prefix+".message"] = simplifyKnownMessage(containerState.Terminated.Message)
1✔
363
                        if strings.Contains(containerState.Terminated.Message, common.PreallocationApplied) {
1✔
364
                                anno[cc.AnnPreallocationApplied] = "true"
×
365
                        }
×
366

367
                        if strings.Contains(containerState.Terminated.Message, common.ImagePullFailureText) {
2✔
368
                                anno[prefix+".reason"] = ImagePullFailedReason
1✔
369
                                return
1✔
370
                        }
1✔
371
                }
372
                anno[prefix+".reason"] = containerState.Terminated.Reason
1✔
373
        }
374
}
375

376
func addLabelsFromTerminationMessage(labels map[string]string, termMsg *common.TerminationMessage) map[string]string {
1✔
377
        newLabels := make(map[string]string, 0)
1✔
378
        for k, v := range labels {
2✔
379
                newLabels[k] = v
1✔
380
        }
1✔
381
        if termMsg != nil {
2✔
382
                for k, v := range termMsg.Labels {
2✔
383
                        if _, found := newLabels[k]; !found {
2✔
384
                                newLabels[k] = v
1✔
385
                        }
1✔
386
                }
387
        }
388
        return newLabels
1✔
389
}
390

391
func simplifyKnownMessage(msg string) string {
1✔
392
        if strings.Contains(msg, "is larger than the reported available") ||
1✔
393
                strings.Contains(msg, "no space left on device") ||
1✔
394
                strings.Contains(msg, "file largest block is bigger than maxblock") {
1✔
395
                return "DataVolume too small to contain image"
×
396
        }
×
397

398
        return msg
1✔
399
}
400

401
func parseTerminationMessage(pod *corev1.Pod) (*common.TerminationMessage, error) {
1✔
402
        if pod == nil || pod.Status.ContainerStatuses == nil {
2✔
403
                return nil, nil
1✔
404
        }
1✔
405

406
        state := pod.Status.ContainerStatuses[0].State
1✔
407
        if state.Terminated == nil || state.Terminated.ExitCode != 0 {
2✔
408
                return nil, nil
1✔
409
        }
1✔
410

411
        termMsg := &common.TerminationMessage{}
1✔
412
        if err := json.Unmarshal([]byte(state.Terminated.Message), termMsg); err != nil {
2✔
413
                return nil, err
1✔
414
        }
1✔
415

416
        return termMsg, nil
1✔
417
}
418

419
func setBoundConditionFromPVC(anno map[string]string, prefix string, pvc *corev1.PersistentVolumeClaim) {
1✔
420
        switch pvc.Status.Phase {
1✔
421
        case corev1.ClaimBound:
1✔
422
                anno[prefix] = "true"
1✔
423
                anno[prefix+".message"] = ""
1✔
424
                anno[prefix+".reason"] = ""
1✔
425
        case corev1.ClaimPending:
×
426
                anno[prefix] = "false"
×
427
                anno[prefix+".message"] = "Claim Pending"
×
428
                anno[prefix+".reason"] = "Claim Pending"
×
429
        case corev1.ClaimLost:
×
430
                anno[prefix] = "false"
×
431
                anno[prefix+".message"] = cc.ClaimLost
×
432
                anno[prefix+".reason"] = cc.ClaimLost
×
433
        default:
×
434
                anno[prefix] = "false"
×
435
                anno[prefix+".message"] = "Unknown"
×
436
                anno[prefix+".reason"] = "Unknown"
×
437
        }
438
}
439

440
func getScratchNameFromPod(pod *corev1.Pod) (string, bool) {
1✔
441
        for _, vol := range pod.Spec.Volumes {
2✔
442
                if vol.Name == cc.ScratchVolName {
2✔
443
                        return vol.PersistentVolumeClaim.ClaimName, true
1✔
444
                }
1✔
445
        }
446

447
        return "", false
1✔
448
}
449

450
func podUsingPVC(pvc *corev1.PersistentVolumeClaim, readOnly bool) *corev1.Pod {
1✔
451
        return &corev1.Pod{
1✔
452
                ObjectMeta: metav1.ObjectMeta{
1✔
453
                        Namespace: pvc.Namespace,
1✔
454
                        Name:      pvc.Name + "-pod",
1✔
455
                },
1✔
456
                Spec: corev1.PodSpec{
1✔
457
                        Containers: []corev1.Container{
1✔
458
                                {
1✔
459
                                        VolumeMounts: []corev1.VolumeMount{
1✔
460
                                                {
1✔
461
                                                        Name:     "v1",
1✔
462
                                                        ReadOnly: readOnly,
1✔
463
                                                },
1✔
464
                                        },
1✔
465
                                },
1✔
466
                        },
1✔
467
                        Volumes: []corev1.Volume{
1✔
468
                                {
1✔
469
                                        Name: "v1",
1✔
470
                                        VolumeSource: corev1.VolumeSource{
1✔
471
                                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
1✔
472
                                                        ClaimName: pvc.Name,
1✔
473
                                                        ReadOnly:  readOnly,
1✔
474
                                                },
1✔
475
                                        },
1✔
476
                                },
1✔
477
                        },
1✔
478
                },
1✔
479
        }
1✔
480
}
1✔
481

482
func createBlockPvc(name, ns string, annotations, labels map[string]string) *corev1.PersistentVolumeClaim {
1✔
483
        pvcDef := cc.CreatePvcInStorageClass(name, ns, nil, annotations, labels, corev1.ClaimBound)
1✔
484
        volumeMode := corev1.PersistentVolumeBlock
1✔
485
        pvcDef.Spec.VolumeMode = &volumeMode
1✔
486
        return pvcDef
1✔
487
}
1✔
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