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

kubevirt / containerized-data-importer / #5655

05 Nov 2025 11:51PM UTC coverage: 59.064% (-0.01%) from 59.076%
#5655

push

travis-ci

web-flow
Add InsecureSkipVerify support for ImageIO data source (#3944)

What this PR does / why we need it:

This PR adds support for skipping TLS certificate verification when using
ImageIO as a data source for DataVolumes. This enables warm migrations from
oVirt/RHV providers when "Skip certificate validation" is enabled.

The implementation includes:
- Added InsecureSkipVerify field to DataVolumeSourceImageIO API spec
- Added AnnInsecureSkipVerify annotation for passing the flag to importer pods
- Updated UpdateImageIOAnnotations to set the annotation when enabled
- Modified NewImageioDataSource to accept and use the insecureSkipVerify parameter
- Updated getOvirtClient to use tls.Insecure() when flag is true
- Modified createHTTPClient to configure TLSClientConfig.InsecureSkipVerify
- Updated all test cases to pass the new parameter

After the fix:

Warm migrations from oVirt/RHV providers now work correctly when certificate validation is skipped.
The CDI ImageIO importer can connect to oVirt ImageIO
services without requiring a valid CA certificate in a ConfigMap, as long as the InsecureSkipVerify flag is set to true in the DataVolume spec.

Example DataVolume usage:
```yaml
spec:
  source:
    imageio:
      url: "https://ovirt-engine.example.com/ovirt-engine/api"
      diskId: "disk-uuid"
      secretRef: "ovirt-credentials"
      insecureSkipVerify: true  # Skip TLS certificate validation
```

Which issue(s) this PR fixes:

Jira Ticket: https://issues.redhat.com/browse/CNV-71978

Release note:
```release-note
DataVolumes can now skip TLS certificate verification when importing from oVirt/RHV ImageIO sources
by setting `spec.source.imageio.insecureSkipVerify: true`.
```

Signed-off-by: Elad Hazan <ehazan@redhat.com>

33 of 48 new or added lines in 7 files covered. (68.75%)

4 existing lines in 1 file now uncovered.

17246 of 29199 relevant lines covered (59.06%)

0.65 hits per line

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

71.27
/pkg/controller/import-controller.go
1
package controller
2

3
import (
4
        "context"
5
        "fmt"
6
        "net/url"
7
        "path"
8
        "reflect"
9
        "strconv"
10
        "strings"
11
        "time"
12

13
        "github.com/go-logr/logr"
14
        "github.com/pkg/errors"
15

16
        corev1 "k8s.io/api/core/v1"
17
        v1 "k8s.io/api/core/v1"
18
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
19
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20
        "k8s.io/apimachinery/pkg/runtime"
21
        "k8s.io/apimachinery/pkg/types"
22
        "k8s.io/apimachinery/pkg/util/sets"
23
        "k8s.io/client-go/tools/record"
24
        "k8s.io/utils/ptr"
25

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

33
        cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
34
        "kubevirt.io/containerized-data-importer/pkg/common"
35
        cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
36
        featuregates "kubevirt.io/containerized-data-importer/pkg/feature-gates"
37
        "kubevirt.io/containerized-data-importer/pkg/util"
38
        "kubevirt.io/containerized-data-importer/pkg/util/naming"
39
        sdkapi "kubevirt.io/controller-lifecycle-operator-sdk/api"
40
)
41

42
const (
43
        // ErrImportFailedPVC provides a const to indicate an import to the PVC failed
44
        ErrImportFailedPVC = "ErrImportFailed"
45
        // ImportSucceededPVC provides a const to indicate an import to the PVC failed
46
        ImportSucceededPVC = "ImportSucceeded"
47

48
        // creatingScratch provides a const to indicate scratch is being created.
49
        creatingScratch = "CreatingScratchSpace"
50

51
        // ImportTargetInUse is reason for event created when an import pvc is in use
52
        ImportTargetInUse = "ImportTargetInUse"
53

54
        // importPodImageStreamFinalizer ensures image stream import pod is deleted when pvc is deleted,
55
        // as in this case pod has no pvc OwnerReference
56
        importPodImageStreamFinalizer = "cdi.kubevirt.io/importImageStream"
57

58
        // secretExtraHeadersVolumeName is the format string that specifies where extra HTTP header secrets will be mounted
59
        secretExtraHeadersVolumeName = "cdi-secret-extra-headers-vol-%d"
60
)
61

62
// ImportReconciler members
63
type ImportReconciler struct {
64
        client             client.Client
65
        uncachedClient     client.Client
66
        recorder           record.EventRecorder
67
        scheme             *runtime.Scheme
68
        log                logr.Logger
69
        image              string
70
        verbose            string
71
        pullPolicy         string
72
        filesystemOverhead string //nolint:unused // TODO: check if need to remove this field
73
        cdiNamespace       string
74
        featureGates       featuregates.FeatureGates
75
        installerLabels    map[string]string
76
}
77

78
type importPodEnvVar struct {
79
        ep                        string
80
        secretName                string
81
        source                    string
82
        contentType               string
83
        imageSize                 string
84
        certConfigMap             string
85
        diskID                    string
86
        uuid                      string
87
        pullMethod                string
88
        readyFile                 string
89
        doneFile                  string
90
        backingFile               string
91
        thumbprint                string
92
        filesystemOverhead        string
93
        insecureTLS               bool
94
        currentCheckpoint         string
95
        previousCheckpoint        string
96
        finalCheckpoint           string
97
        preallocation             bool
98
        httpProxy                 string
99
        httpsProxy                string
100
        noProxy                   string
101
        certConfigMapProxy        string
102
        extraHeaders              []string
103
        secretExtraHeaders        []string
104
        cacheMode                 string
105
        registryImageArchitecture string
106
}
107

108
type importerPodArgs struct {
109
        image                   string
110
        importImage             string
111
        verbose                 string
112
        pullPolicy              string
113
        podEnvVar               *importPodEnvVar
114
        pvc                     *corev1.PersistentVolumeClaim
115
        scratchPvcName          *string
116
        podResourceRequirements *corev1.ResourceRequirements
117
        imagePullSecrets        []corev1.LocalObjectReference
118
        workloadNodePlacement   *sdkapi.NodePlacement
119
        vddkImageName           *string
120
        vddkExtraArgs           *string
121
        priorityClassName       string
122
}
123

124
// NewImportController creates a new instance of the import controller.
125
func NewImportController(mgr manager.Manager, log logr.Logger, importerImage, pullPolicy, verbose string, installerLabels map[string]string) (controller.Controller, error) {
×
126
        uncachedClient, err := client.New(mgr.GetConfig(), client.Options{
×
127
                Scheme: mgr.GetScheme(),
×
128
                Mapper: mgr.GetRESTMapper(),
×
129
        })
×
130
        if err != nil {
×
131
                return nil, err
×
132
        }
×
133
        client := mgr.GetClient()
×
134
        reconciler := &ImportReconciler{
×
135
                client:          client,
×
136
                uncachedClient:  uncachedClient,
×
137
                scheme:          mgr.GetScheme(),
×
138
                log:             log.WithName("import-controller"),
×
139
                image:           importerImage,
×
140
                verbose:         verbose,
×
141
                pullPolicy:      pullPolicy,
×
142
                recorder:        mgr.GetEventRecorderFor("import-controller"),
×
143
                cdiNamespace:    util.GetNamespace(),
×
144
                featureGates:    featuregates.NewFeatureGates(client),
×
145
                installerLabels: installerLabels,
×
146
        }
×
147
        importController, err := controller.New("import-controller", mgr, controller.Options{
×
148
                MaxConcurrentReconciles: 3,
×
149
                Reconciler:              reconciler,
×
150
        })
×
151
        if err != nil {
×
152
                return nil, err
×
153
        }
×
154
        if err := addImportControllerWatches(mgr, importController); err != nil {
×
155
                return nil, err
×
156
        }
×
157
        return importController, nil
×
158
}
159

160
func addImportControllerWatches(mgr manager.Manager, importController controller.Controller) error {
×
161
        // Setup watches
×
162
        if err := importController.Watch(source.Kind(mgr.GetCache(), &corev1.PersistentVolumeClaim{}, &handler.TypedEnqueueRequestForObject[*corev1.PersistentVolumeClaim]{})); err != nil {
×
163
                return err
×
164
        }
×
165
        if err := importController.Watch(source.Kind(mgr.GetCache(), &corev1.Pod{}, handler.TypedEnqueueRequestForOwner[*corev1.Pod](
×
166
                mgr.GetScheme(), mgr.GetClient().RESTMapper(), &corev1.PersistentVolumeClaim{}, handler.OnlyControllerOwner()))); err != nil {
×
167
                return err
×
168
        }
×
169

170
        return nil
×
171
}
172

173
func (r *ImportReconciler) shouldReconcilePVC(pvc *corev1.PersistentVolumeClaim,
174
        log logr.Logger) (bool, error) {
1✔
175
        _, pvcUsesExternalPopulator := pvc.Annotations[cc.AnnExternalPopulation]
1✔
176
        if pvcUsesExternalPopulator {
1✔
177
                return false, nil
×
178
        }
×
179

180
        waitForFirstConsumerEnabled, err := cc.IsWaitForFirstConsumerEnabled(pvc, r.featureGates)
1✔
181
        if err != nil {
1✔
182
                return false, err
×
183
        }
×
184

185
        return (!cc.IsPVCComplete(pvc) || cc.IsMultiStageImportInProgress(pvc)) &&
1✔
186
                        (checkPVC(pvc, cc.AnnEndpoint, log) || checkPVC(pvc, cc.AnnSource, log)) &&
1✔
187
                        shouldHandlePvc(pvc, waitForFirstConsumerEnabled, log),
1✔
188
                nil
1✔
189
}
190

191
// Reconcile the reconcile loop for the CDIConfig object.
192
func (r *ImportReconciler) Reconcile(_ context.Context, req reconcile.Request) (reconcile.Result, error) {
1✔
193
        log := r.log.WithValues("PVC", req.NamespacedName)
1✔
194
        log.V(1).Info("reconciling Import PVCs")
1✔
195

1✔
196
        // Get the PVC.
1✔
197
        pvc := &corev1.PersistentVolumeClaim{}
1✔
198
        if err := r.client.Get(context.TODO(), req.NamespacedName, pvc); err != nil {
2✔
199
                if k8serrors.IsNotFound(err) {
2✔
200
                        return reconcile.Result{}, nil
1✔
201
                }
1✔
202
                return reconcile.Result{}, err
×
203
        }
204

205
        // only want to update bound condition for relevant type
206
        if checkPVC(pvc, cc.AnnEndpoint, log) || checkPVC(pvc, cc.AnnSource, log) {
2✔
207
                if err := cc.UpdatePVCBoundContionFromEvents(pvc, r.client, log); err != nil {
1✔
208
                        return reconcile.Result{}, err
×
209
                }
×
210
        }
211

212
        shouldReconcile, err := r.shouldReconcilePVC(pvc, log)
1✔
213
        if err != nil {
1✔
214
                return reconcile.Result{}, err
×
215
        }
×
216
        if !shouldReconcile {
2✔
217
                multiStageImport := metav1.HasAnnotation(pvc.ObjectMeta, cc.AnnCurrentCheckpoint)
1✔
218
                multiStageAlreadyDone := metav1.HasAnnotation(pvc.ObjectMeta, cc.AnnMultiStageImportDone)
1✔
219

1✔
220
                log.V(3).Info("Should not reconcile this PVC",
1✔
221
                        "pvc.annotation.phase.complete", cc.IsPVCComplete(pvc),
1✔
222
                        "pvc.annotations.endpoint", checkPVC(pvc, cc.AnnEndpoint, log),
1✔
223
                        "pvc.annotations.source", checkPVC(pvc, cc.AnnSource, log),
1✔
224
                        "isBound", isBound(pvc, log), "isMultistage", multiStageImport, "multiStageDone", multiStageAlreadyDone)
1✔
225
                return reconcile.Result{}, nil
1✔
226
        }
1✔
227

228
        return r.reconcilePvc(pvc, log)
1✔
229
}
230

231
func (r *ImportReconciler) findImporterPod(pvc *corev1.PersistentVolumeClaim, log logr.Logger) (*corev1.Pod, error) {
1✔
232
        podName := getImportPodNameFromPvc(pvc)
1✔
233
        pod := &corev1.Pod{}
1✔
234
        if err := r.client.Get(context.TODO(), types.NamespacedName{Name: podName, Namespace: pvc.GetNamespace()}, pod); err != nil {
2✔
235
                if !k8serrors.IsNotFound(err) {
1✔
236
                        return nil, errors.Wrapf(err, "error getting import pod %s/%s", pvc.Namespace, podName)
×
237
                }
×
238
                return nil, nil
1✔
239
        }
240
        if !metav1.IsControlledBy(pod, pvc) && !cc.IsImageStream(pvc) {
2✔
241
                return nil, errors.Errorf("Pod is not owned by PVC")
1✔
242
        }
1✔
243
        log.V(1).Info("Pod is owned by PVC", pod.Name, pvc.Name)
1✔
244
        return pod, nil
1✔
245
}
246

247
func (r *ImportReconciler) reconcilePvc(pvc *corev1.PersistentVolumeClaim, log logr.Logger) (reconcile.Result, error) {
1✔
248
        // See if we have a pod associated with the PVC, we know the PVC has the needed annotations.
1✔
249
        pod, err := r.findImporterPod(pvc, log)
1✔
250
        if err != nil {
2✔
251
                return reconcile.Result{}, err
1✔
252
        }
1✔
253

254
        if pod == nil {
2✔
255
                if cc.IsPVCComplete(pvc) {
1✔
256
                        // Don't create the POD if the PVC is completed already
×
257
                        log.V(1).Info("PVC is already complete")
×
258
                } else if pvc.DeletionTimestamp == nil {
2✔
259
                        podsUsingPVC, err := cc.GetPodsUsingPVCs(context.TODO(), r.client, pvc.Namespace, sets.New(pvc.Name), false)
1✔
260
                        if err != nil {
1✔
261
                                return reconcile.Result{}, err
×
262
                        }
×
263

264
                        if len(podsUsingPVC) > 0 {
2✔
265
                                for _, pod := range podsUsingPVC {
2✔
266
                                        r.log.V(1).Info("can't create import pod, pvc in use by other pod",
1✔
267
                                                "namespace", pvc.Namespace, "name", pvc.Name, "pod", pod.Name)
1✔
268
                                        r.recorder.Eventf(pvc, corev1.EventTypeWarning, ImportTargetInUse,
1✔
269
                                                "pod %s/%s using PersistentVolumeClaim %s", pod.Namespace, pod.Name, pvc.Name)
1✔
270
                                }
1✔
271
                                return reconcile.Result{Requeue: true}, nil
1✔
272
                        }
273

274
                        if _, ok := pvc.Annotations[cc.AnnImportPod]; ok {
2✔
275
                                // Create importer pod, make sure the PVC owns it.
1✔
276
                                if err := r.createImporterPod(pvc); err != nil {
1✔
277
                                        return reconcile.Result{}, err
×
278
                                }
×
279
                        } else {
1✔
280
                                // Create importer pod Name and store in PVC?
1✔
281
                                if err := r.initPvcPodName(pvc, log); err != nil {
1✔
282
                                        return reconcile.Result{}, err
×
283
                                }
×
284
                        }
285
                }
286
        } else {
1✔
287
                if pvc.DeletionTimestamp != nil {
1✔
288
                        log.V(1).Info("PVC being terminated, delete pods", "pod.Name", pod.Name)
×
289
                        if err := r.cleanup(pvc, pod, log); err != nil {
×
290
                                return reconcile.Result{}, err
×
291
                        }
×
292
                } else {
1✔
293
                        // Copy import proxy ConfigMap (if exists) from cdi namespace to the import namespace
1✔
294
                        if err := r.copyImportProxyConfigMap(pvc, pod); err != nil {
1✔
295
                                return reconcile.Result{}, err
×
296
                        }
×
297
                        // Pod exists, we need to update the PVC status.
298
                        if err := r.updatePvcFromPod(pvc, pod, log); err != nil {
1✔
299
                                return reconcile.Result{}, err
×
300
                        }
×
301
                }
302
        }
303

304
        if !cc.IsPVCComplete(pvc) {
2✔
305
                // We are not done yet, force a re-reconcile in 2 seconds to get an update.
1✔
306
                log.V(1).Info("Force Reconcile pvc import not finished", "pvc.Name", pvc.Name)
1✔
307

1✔
308
                return reconcile.Result{RequeueAfter: 2 * time.Second}, nil
1✔
309
        }
1✔
310
        return reconcile.Result{}, nil
1✔
311
}
312

313
func (r *ImportReconciler) copyImportProxyConfigMap(pvc *corev1.PersistentVolumeClaim, pod *corev1.Pod) error {
1✔
314
        cdiConfig := &cdiv1.CDIConfig{}
1✔
315
        if err := r.client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiConfig); err != nil {
1✔
316
                return err
×
317
        }
×
318
        cmName, err := GetImportProxyConfig(cdiConfig, common.ImportProxyConfigMapName)
1✔
319
        if err != nil || cmName == "" {
2✔
320
                return nil
1✔
321
        }
1✔
322
        cdiConfigMap := &corev1.ConfigMap{}
×
323
        if err := r.uncachedClient.Get(context.TODO(), types.NamespacedName{Name: cmName, Namespace: r.cdiNamespace}, cdiConfigMap); err != nil {
×
324
                return err
×
325
        }
×
326
        importConfigMap := &corev1.ConfigMap{
×
327
                ObjectMeta: metav1.ObjectMeta{
×
328
                        Name:      GetImportProxyConfigMapName(pvc.Name),
×
329
                        Namespace: pvc.Namespace,
×
330
                        OwnerReferences: []metav1.OwnerReference{{
×
331
                                APIVersion:         pod.APIVersion,
×
332
                                Kind:               pod.Kind,
×
333
                                Name:               pod.Name,
×
334
                                UID:                pod.UID,
×
335
                                BlockOwnerDeletion: ptr.To[bool](true),
×
336
                                Controller:         ptr.To[bool](true),
×
337
                        }},
×
338
                },
×
339
                Data: cdiConfigMap.Data,
×
340
        }
×
341
        if err := r.client.Create(context.TODO(), importConfigMap); err != nil && !k8serrors.IsAlreadyExists(err) {
×
342
                return err
×
343
        }
×
344
        return nil
×
345
}
346

347
// GetImportProxyConfigMapName returns the import proxy ConfigMap name
348
func GetImportProxyConfigMapName(pvcName string) string {
×
349
        return naming.GetResourceName("import-proxy-cm", pvcName)
×
350
}
×
351

352
func (r *ImportReconciler) initPvcPodName(pvc *corev1.PersistentVolumeClaim, log logr.Logger) error {
1✔
353
        currentPvcCopy := pvc.DeepCopyObject()
1✔
354

1✔
355
        log.V(1).Info("Init pod name on PVC")
1✔
356
        anno := pvc.GetAnnotations()
1✔
357

1✔
358
        anno[cc.AnnImportPod] = createImportPodNameFromPvc(pvc)
1✔
359

1✔
360
        requiresScratch := r.requiresScratchSpace(pvc)
1✔
361
        if requiresScratch {
1✔
362
                anno[cc.AnnRequiresScratch] = "true"
×
363
        }
×
364

365
        if !reflect.DeepEqual(currentPvcCopy, pvc) {
2✔
366
                if err := r.updatePVC(pvc, log); err != nil {
1✔
367
                        return err
×
368
                }
×
369
                log.V(1).Info("Updated PVC", "pvc.anno.AnnImportPod", anno[cc.AnnImportPod])
1✔
370
        }
371
        return nil
1✔
372
}
373

374
func (r *ImportReconciler) updatePvcFromPod(pvc *corev1.PersistentVolumeClaim, pod *corev1.Pod, log logr.Logger) error {
1✔
375
        // Keep a copy of the original for comparison later.
1✔
376
        currentPvcCopy := pvc.DeepCopyObject()
1✔
377

1✔
378
        log.V(1).Info("Updating PVC from pod")
1✔
379
        anno := pvc.GetAnnotations()
1✔
380

1✔
381
        termMsg, err := parseTerminationMessage(pod)
1✔
382
        if err != nil {
2✔
383
                log.V(3).Info("Ignoring failure to parse termination message", "error", err.Error())
1✔
384
        }
1✔
385
        setAnnotationsFromPodWithPrefix(anno, pod, termMsg, cc.AnnRunningCondition)
1✔
386

1✔
387
        scratchSpaceRequired := termMsg != nil && termMsg.ScratchSpaceRequired != nil && *termMsg.ScratchSpaceRequired
1✔
388
        if scratchSpaceRequired {
2✔
389
                log.V(1).Info("Pod requires scratch space, terminating pod, and restarting with scratch space", "pod.Name", pod.Name)
1✔
390
        }
1✔
391
        podModificationsNeeded := scratchSpaceRequired
1✔
392

1✔
393
        if statuses := pod.Status.ContainerStatuses; len(statuses) > 0 {
2✔
394
                if isOOMKilled(statuses[0]) {
2✔
395
                        log.V(1).Info("Pod died of an OOM, deleting pod, and restarting with qemu cache mode=none if storage supports it", "pod.Name", pod.Name)
1✔
396
                        podModificationsNeeded = true
1✔
397
                        anno[cc.AnnRequiresDirectIO] = "true"
1✔
398
                }
1✔
399
                if terminated := statuses[0].State.Terminated; terminated != nil && terminated.ExitCode > 0 {
2✔
400
                        log.Info("Pod termination code", "pod.Name", pod.Name, "ExitCode", terminated.ExitCode)
1✔
401
                        r.recorder.Event(pvc, corev1.EventTypeWarning, ErrImportFailedPVC, terminated.Message)
1✔
402
                }
1✔
403
        }
404

405
        if anno[cc.AnnCurrentCheckpoint] != "" {
1✔
406
                anno[cc.AnnCurrentPodID] = string(pod.ObjectMeta.UID)
×
407
        }
×
408

409
        anno[cc.AnnImportPod] = pod.Name
1✔
410
        if !podModificationsNeeded {
2✔
411
                // No scratch space required, update the phase based on the pod. If we require scratch space we don't want to update the
1✔
412
                // phase, because the pod might terminate cleanly and mistakenly mark the import complete.
1✔
413
                anno[cc.AnnPodPhase] = string(pod.Status.Phase)
1✔
414
        }
1✔
415

416
        anno[cc.AnnPodSchedulable] = "true"
1✔
417
        if phase, ok := anno[cc.AnnPodPhase]; ok && phase == string(corev1.PodPending) {
2✔
418
                for _, cond := range pod.Status.Conditions {
1✔
419
                        if cond.Type == corev1.PodScheduled && cond.Reason == corev1.PodReasonUnschedulable {
×
420
                                anno[cc.AnnPodSchedulable] = "false"
×
421
                                break
×
422
                        }
423
                }
424
        }
425

426
        for _, ev := range pod.Spec.Containers[0].Env {
2✔
427
                if ev.Name == common.CacheMode && ev.Value == common.CacheModeTryNone {
1✔
428
                        anno[cc.AnnRequiresDirectIO] = "false"
×
429
                }
×
430
        }
431

432
        // Check if the POD is waiting for scratch space, if so create some.
433
        if pod.Status.Phase == corev1.PodPending && r.requiresScratchSpace(pvc) {
2✔
434
                if err := r.createScratchPvcForPod(pvc, pod); err != nil {
1✔
435
                        if !k8serrors.IsAlreadyExists(err) {
×
436
                                return err
×
437
                        }
×
438
                }
439
        } else {
1✔
440
                // No scratch space, or scratch space is bound, remove annotation
1✔
441
                delete(anno, cc.AnnBoundCondition)
1✔
442
                delete(anno, cc.AnnBoundConditionMessage)
1✔
443
                delete(anno, cc.AnnBoundConditionReason)
1✔
444
        }
1✔
445

446
        if pvc.GetLabels() == nil {
2✔
447
                pvc.SetLabels(make(map[string]string, 0))
1✔
448
        }
1✔
449
        if !checkIfLabelExists(pvc, common.CDILabelKey, common.CDILabelValue) {
2✔
450
                pvc.GetLabels()[common.CDILabelKey] = common.CDILabelValue
1✔
451
        }
1✔
452
        if cc.IsPVCComplete(pvc) {
2✔
453
                pvc.SetLabels(addLabelsFromTerminationMessage(pvc.GetLabels(), termMsg))
1✔
454
        }
1✔
455

456
        if !reflect.DeepEqual(currentPvcCopy, pvc) {
2✔
457
                if err := r.updatePVC(pvc, log); err != nil {
1✔
458
                        return err
×
459
                }
×
460
                log.V(1).Info("Updated PVC", "pvc.anno.Phase", anno[cc.AnnPodPhase], "pvc.anno.Restarts", anno[cc.AnnPodRestarts])
1✔
461
        }
462

463
        if cc.IsPVCComplete(pvc) || podModificationsNeeded {
2✔
464
                if !podModificationsNeeded {
2✔
465
                        r.recorder.Event(pvc, corev1.EventTypeNormal, ImportSucceededPVC, "Import Successful")
1✔
466
                        log.V(1).Info("Import completed successfully")
1✔
467
                }
1✔
468
                if cc.ShouldDeletePod(pvc) {
2✔
469
                        log.V(1).Info("Deleting pod", "pod.Name", pod.Name)
1✔
470
                        if err := r.cleanup(pvc, pod, log); err != nil {
1✔
471
                                return err
×
472
                        }
×
473
                }
474
        }
475
        return nil
1✔
476
}
477

478
func (r *ImportReconciler) cleanup(pvc *corev1.PersistentVolumeClaim, pod *corev1.Pod, log logr.Logger) error {
1✔
479
        if err := r.client.Delete(context.TODO(), pod); cc.IgnoreNotFound(err) != nil {
1✔
480
                return err
×
481
        }
×
482
        if cc.HasFinalizer(pvc, importPodImageStreamFinalizer) {
1✔
483
                cc.RemoveFinalizer(pvc, importPodImageStreamFinalizer)
×
484
                if err := r.updatePVC(pvc, log); err != nil {
×
485
                        return err
×
486
                }
×
487
        }
488
        return nil
1✔
489
}
490

491
func (r *ImportReconciler) updatePVC(pvc *corev1.PersistentVolumeClaim, log logr.Logger) error {
1✔
492
        if err := r.client.Update(context.TODO(), pvc); err != nil {
1✔
493
                return err
×
494
        }
×
495
        return nil
1✔
496
}
497

498
func (r *ImportReconciler) createImporterPod(pvc *corev1.PersistentVolumeClaim) error {
1✔
499
        r.log.V(1).Info("Creating importer POD for PVC", "pvc.Name", pvc.Name)
1✔
500
        var scratchPvcName *string
1✔
501
        var vddkImageName *string
1✔
502
        var vddkExtraArgs *string
1✔
503
        var err error
1✔
504

1✔
505
        requiresScratch := r.requiresScratchSpace(pvc)
1✔
506
        if requiresScratch {
1✔
507
                name := createScratchNameFromPvc(pvc)
×
508
                scratchPvcName = &name
×
509
        }
×
510

511
        if cc.GetSource(pvc) == cc.SourceVDDK {
2✔
512
                r.log.V(1).Info("Pod requires VDDK sidecar for VMware transfer")
1✔
513
                anno := pvc.GetAnnotations()
1✔
514
                if imageName, ok := anno[cc.AnnVddkInitImageURL]; ok {
2✔
515
                        vddkImageName = &imageName
1✔
516
                } else {
2✔
517
                        if vddkImageName, err = r.getVddkImageName(); err != nil {
2✔
518
                                r.log.V(1).Error(err, "failed to get VDDK image name from configmap")
1✔
519
                        }
1✔
520
                }
521
                if vddkImageName == nil {
2✔
522
                        message := fmt.Sprintf("waiting for %s configmap or %s annotation for VDDK image", common.VddkConfigMap, cc.AnnVddkInitImageURL)
1✔
523
                        anno[cc.AnnBoundCondition] = "false"
1✔
524
                        anno[cc.AnnBoundConditionMessage] = message
1✔
525
                        anno[cc.AnnBoundConditionReason] = common.AwaitingVDDK
1✔
526
                        if err := r.updatePVC(pvc, r.log); err != nil {
1✔
527
                                return err
×
528
                        }
×
529
                        return errors.New(message)
1✔
530
                }
531

532
                if extraArgs, ok := anno[cc.AnnVddkExtraArgs]; ok && extraArgs != "" {
2✔
533
                        r.log.V(1).Info("Mounting extra VDDK args ConfigMap to importer pod", "ConfigMap", extraArgs)
1✔
534
                        vddkExtraArgs = &extraArgs
1✔
535
                }
1✔
536
        }
537

538
        podEnvVar, err := r.createImportEnvVar(pvc)
1✔
539
        if err != nil {
1✔
540
                return err
×
541
        }
×
542
        // all checks passed, let's create the importer pod!
543
        podArgs := &importerPodArgs{
1✔
544
                image:             r.image,
1✔
545
                verbose:           r.verbose,
1✔
546
                pullPolicy:        r.pullPolicy,
1✔
547
                podEnvVar:         podEnvVar,
1✔
548
                pvc:               pvc,
1✔
549
                scratchPvcName:    scratchPvcName,
1✔
550
                vddkImageName:     vddkImageName,
1✔
551
                vddkExtraArgs:     vddkExtraArgs,
1✔
552
                priorityClassName: cc.GetPriorityClass(pvc),
1✔
553
        }
1✔
554

1✔
555
        pod, err := createImporterPod(context.TODO(), r.log, r.client, podArgs, r.installerLabels)
1✔
556
        // Check if pod has failed and, in that case, record an event with the error
1✔
557
        if podErr := cc.HandleFailedPod(err, pvc.Annotations[cc.AnnImportPod], pvc, r.recorder, r.client); podErr != nil {
1✔
558
                return podErr
×
559
        }
×
560

561
        r.log.V(1).Info("Created POD", "pod.Name", pod.Name)
1✔
562

1✔
563
        // If importing from image stream, add finalizer. Note we don't watch the importer pod in this case,
1✔
564
        // so to prevent a deadlock we add finalizer only if the pod is not retained after completion.
1✔
565
        if cc.IsImageStream(pvc) && pvc.GetAnnotations()[cc.AnnPodRetainAfterCompletion] != "true" {
1✔
566
                cc.AddFinalizer(pvc, importPodImageStreamFinalizer)
×
567
                if err := r.updatePVC(pvc, r.log); err != nil {
×
568
                        return err
×
569
                }
×
570
        }
571

572
        if requiresScratch {
1✔
573
                r.log.V(1).Info("Pod requires scratch space")
×
574
                return r.createScratchPvcForPod(pvc, pod)
×
575
        }
×
576

577
        return nil
1✔
578
}
579

580
func createScratchNameFromPvc(pvc *v1.PersistentVolumeClaim) string {
×
581
        return naming.GetResourceName(pvc.Name, common.ScratchNameSuffix)
×
582
}
×
583

584
func (r *ImportReconciler) createImportEnvVar(pvc *corev1.PersistentVolumeClaim) (*importPodEnvVar, error) {
1✔
585
        podEnvVar := &importPodEnvVar{}
1✔
586
        podEnvVar.source = cc.GetSource(pvc)
1✔
587
        podEnvVar.contentType = string(cc.GetPVCContentType(pvc))
1✔
588

1✔
589
        var err error
1✔
590
        if podEnvVar.source != cc.SourceNone {
2✔
591
                podEnvVar.ep, err = cc.GetEndpoint(pvc)
1✔
592
                if err != nil {
1✔
593
                        return nil, err
×
594
                }
×
595
                podEnvVar.secretName = r.getSecretName(pvc)
1✔
596
                if podEnvVar.secretName == "" {
2✔
597
                        r.log.V(2).Info("no secret will be supplied to endpoint", "endPoint", podEnvVar.ep)
1✔
598
                }
1✔
599
                //get the CDIConfig to extract the proxy configuration to be used to import an image
600
                cdiConfig := &cdiv1.CDIConfig{}
1✔
601
                err = r.client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiConfig)
1✔
602
                if err != nil {
1✔
603
                        return nil, err
×
604
                }
×
605
                podEnvVar.certConfigMap, err = r.getCertConfigMap(pvc)
1✔
606
                if err != nil {
1✔
607
                        return nil, err
×
608
                }
×
609
                podEnvVar.insecureTLS, err = r.isInsecureTLS(pvc, cdiConfig)
1✔
610
                if err != nil {
1✔
611
                        return nil, err
×
612
                }
×
613
                podEnvVar.diskID = getValueFromAnnotation(pvc, cc.AnnDiskID)
1✔
614
                podEnvVar.backingFile = getValueFromAnnotation(pvc, cc.AnnBackingFile)
1✔
615
                podEnvVar.uuid = getValueFromAnnotation(pvc, cc.AnnUUID)
1✔
616
                podEnvVar.thumbprint = getValueFromAnnotation(pvc, cc.AnnThumbprint)
1✔
617
                podEnvVar.previousCheckpoint = getValueFromAnnotation(pvc, cc.AnnPreviousCheckpoint)
1✔
618
                podEnvVar.currentCheckpoint = getValueFromAnnotation(pvc, cc.AnnCurrentCheckpoint)
1✔
619
                podEnvVar.finalCheckpoint = getValueFromAnnotation(pvc, cc.AnnFinalCheckpoint)
1✔
620
                podEnvVar.registryImageArchitecture = getValueFromAnnotation(pvc, cc.AnnRegistryImageArchitecture)
1✔
621

1✔
622
                for annotation, value := range pvc.Annotations {
2✔
623
                        if strings.HasPrefix(annotation, cc.AnnExtraHeaders) {
1✔
624
                                podEnvVar.extraHeaders = append(podEnvVar.extraHeaders, value)
×
625
                        }
×
626
                        if strings.HasPrefix(annotation, cc.AnnSecretExtraHeaders) {
1✔
627
                                podEnvVar.secretExtraHeaders = append(podEnvVar.secretExtraHeaders, value)
×
628
                        }
×
629
                }
630

631
                var field string
1✔
632
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyHTTP); err != nil {
2✔
633
                        r.log.V(3).Info("no proxy http url will be supplied:", "error", err.Error())
1✔
634
                }
1✔
635
                podEnvVar.httpProxy = field
1✔
636
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyHTTPS); err != nil {
2✔
637
                        r.log.V(3).Info("no proxy https url will be supplied:", "error", err.Error())
1✔
638
                }
1✔
639
                podEnvVar.httpsProxy = field
1✔
640
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyNoProxy); err != nil {
2✔
641
                        r.log.V(3).Info("the noProxy field will not be supplied:", "error", err.Error())
1✔
642
                }
1✔
643
                podEnvVar.noProxy = field
1✔
644
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyConfigMapName); err != nil {
2✔
645
                        r.log.V(3).Info("no proxy CA certiticate will be supplied:", "error", err.Error())
1✔
646
                }
1✔
647
                podEnvVar.certConfigMapProxy = field
1✔
648
        }
649

650
        fsOverhead, err := GetFilesystemOverhead(context.TODO(), r.client, pvc)
1✔
651
        if err != nil {
1✔
652
                return nil, err
×
653
        }
×
654
        podEnvVar.filesystemOverhead = string(fsOverhead)
1✔
655

1✔
656
        if preallocation, err := strconv.ParseBool(getValueFromAnnotation(pvc, cc.AnnPreallocationRequested)); err == nil {
1✔
657
                podEnvVar.preallocation = preallocation
×
658
        } // else use the default "false"
×
659

660
        //get the requested image size.
661
        podEnvVar.imageSize, err = cc.GetRequestedImageSize(pvc)
1✔
662
        if err != nil {
1✔
663
                return nil, err
×
664
        }
×
665

666
        if v, ok := pvc.Annotations[cc.AnnRequiresDirectIO]; ok && v == "true" {
2✔
667
                podEnvVar.cacheMode = common.CacheModeTryNone
1✔
668
        }
1✔
669

670
        return podEnvVar, nil
1✔
671
}
672

673
func (r *ImportReconciler) isInsecureTLS(pvc *corev1.PersistentVolumeClaim, cdiConfig *cdiv1.CDIConfig) (bool, error) {
1✔
674
        // Check if insecureSkipVerify annotation is set (only applicable for ImageIO sources)
1✔
675
        source, sourceOk := pvc.Annotations[cc.AnnSource]
1✔
676
        if sourceOk && source == cc.SourceImageio {
1✔
NEW
677
                if insecureSkipVerify, ok := pvc.Annotations[cc.AnnInsecureSkipVerify]; ok && insecureSkipVerify == "true" {
×
NEW
678
                        return true, nil
×
NEW
679
                }
×
680
        }
681

682
        ep, ok := pvc.Annotations[cc.AnnEndpoint]
1✔
683
        if !ok || ep == "" {
2✔
684
                return false, nil
1✔
685
        }
1✔
686
        return IsInsecureTLS(ep, cdiConfig, r.log)
1✔
687
}
688

689
// IsInsecureTLS checks if TLS security is disabled for the given endpoint
690
func IsInsecureTLS(ep string, cdiConfig *cdiv1.CDIConfig, log logr.Logger) (bool, error) {
1✔
691
        url, err := url.Parse(ep)
1✔
692
        if err != nil {
1✔
693
                return false, err
×
694
        }
×
695

696
        if url.Scheme != "docker" {
2✔
697
                return false, nil
1✔
698
        }
1✔
699

700
        for _, value := range cdiConfig.Spec.InsecureRegistries {
2✔
701
                log.V(1).Info("Checking host against value", "host", url.Host, "value", value)
1✔
702
                if value == url.Host {
2✔
703
                        return true, nil
1✔
704
                }
1✔
705
        }
706
        return false, nil
1✔
707
}
708

709
func (r *ImportReconciler) getCertConfigMap(pvc *corev1.PersistentVolumeClaim) (string, error) {
1✔
710
        value, ok := pvc.Annotations[cc.AnnCertConfigMap]
1✔
711
        if !ok || value == "" {
2✔
712
                return "", nil
1✔
713
        }
1✔
714

715
        configMap := &corev1.ConfigMap{}
1✔
716
        if err := r.uncachedClient.Get(context.TODO(), types.NamespacedName{Name: value, Namespace: pvc.Namespace}, configMap); err != nil {
2✔
717
                if k8serrors.IsNotFound(err) {
2✔
718
                        r.log.V(1).Info("Configmap does not exist, pod will not start until it does", "configMapName", value)
1✔
719
                        return value, nil
1✔
720
                }
1✔
721

722
                return "", err
×
723
        }
724

725
        return value, nil
1✔
726
}
727

728
// returns the name of the secret containing endpoint credentials consumed by the importer pod.
729
// A value of "" implies there are no credentials for the endpoint being used. A returned error
730
// causes processNextItem() to stop.
731
func (r *ImportReconciler) getSecretName(pvc *corev1.PersistentVolumeClaim) string {
1✔
732
        ns := pvc.Namespace
1✔
733
        name, found := pvc.Annotations[cc.AnnSecret]
1✔
734
        if !found || name == "" {
2✔
735
                msg := "getEndpointSecret: "
1✔
736
                if !found {
2✔
737
                        msg += fmt.Sprintf("annotation %q is missing in pvc \"%s/%s\"", cc.AnnSecret, ns, pvc.Name)
1✔
738
                } else {
1✔
739
                        msg += fmt.Sprintf("secret name is missing from annotation %q in pvc \"%s/%s\"", cc.AnnSecret, ns, pvc.Name)
×
740
                }
×
741
                r.log.V(2).Info(msg)
1✔
742
                return "" // importer pod will not contain secret credentials
1✔
743
        }
744
        return name
1✔
745
}
746

747
func (r *ImportReconciler) requiresScratchSpace(pvc *corev1.PersistentVolumeClaim) bool {
1✔
748
        scratchRequired := false
1✔
749
        contentType := cc.GetPVCContentType(pvc)
1✔
750
        // All archive requires scratch space.
1✔
751
        if contentType == cdiv1.DataVolumeArchive {
1✔
752
                scratchRequired = true
×
753
        } else {
1✔
754
                switch cc.GetSource(pvc) {
1✔
755
                case cc.SourceGlance:
×
756
                        scratchRequired = true
×
757
                case cc.SourceImageio:
×
758
                        if val, ok := pvc.Annotations[cc.AnnCurrentCheckpoint]; ok {
×
759
                                scratchRequired = val != ""
×
760
                        }
×
761
                case cc.SourceRegistry:
1✔
762
                        scratchRequired = pvc.Annotations[cc.AnnRegistryImportMethod] != string(cdiv1.RegistryPullNode)
1✔
763
                }
764
        }
765
        value, ok := pvc.Annotations[cc.AnnRequiresScratch]
1✔
766
        if ok {
2✔
767
                boolVal, _ := strconv.ParseBool(value)
1✔
768
                scratchRequired = scratchRequired || boolVal
1✔
769
        }
1✔
770
        return scratchRequired
1✔
771
}
772

773
func (r *ImportReconciler) createScratchPvcForPod(pvc *corev1.PersistentVolumeClaim, pod *corev1.Pod) error {
1✔
774
        scratchPvc := &corev1.PersistentVolumeClaim{}
1✔
775
        scratchPVCName, exists := getScratchNameFromPod(pod)
1✔
776
        if !exists {
1✔
777
                return errors.New("Scratch Volume not configured for pod")
×
778
        }
×
779
        anno := pvc.GetAnnotations()
1✔
780
        err := r.client.Get(context.TODO(), types.NamespacedName{Namespace: pvc.GetNamespace(), Name: scratchPVCName}, scratchPvc)
1✔
781
        if cc.IgnoreNotFound(err) != nil {
1✔
782
                return err
×
783
        }
×
784
        if k8serrors.IsNotFound(err) {
2✔
785
                r.log.V(1).Info("Creating scratch space for POD and PVC", "pod.Name", pod.Name, "pvc.Name", pvc.Name)
1✔
786

1✔
787
                storageClassName := GetScratchPvcStorageClass(r.client, pvc)
1✔
788
                // Scratch PVC doesn't exist yet, create it. Determine which storage class to use.
1✔
789
                _, err = createScratchPersistentVolumeClaim(r.client, pvc, pod, scratchPVCName, storageClassName, r.installerLabels, r.recorder)
1✔
790
                if err != nil {
1✔
791
                        return err
×
792
                }
×
793
                anno[cc.AnnBoundCondition] = "false"
1✔
794
                anno[cc.AnnBoundConditionMessage] = "Creating scratch space"
1✔
795
                anno[cc.AnnBoundConditionReason] = creatingScratch
1✔
796
        } else {
×
797
                if scratchPvc.DeletionTimestamp != nil {
×
798
                        // Delete the pod since we are in a deadlock situation now. The scratch PVC from the previous import is not gone
×
799
                        // yet but terminating, and the new pod is still being created and the scratch PVC now has a finalizer on it.
×
800
                        // Only way to break it, is to delete the importer pod, and give the pvc a chance to disappear.
×
801
                        err = r.client.Delete(context.TODO(), pod)
×
802
                        if err != nil {
×
803
                                return err
×
804
                        }
×
805
                        return fmt.Errorf("terminating scratch space found, deleting pod %s", pod.Name)
×
806
                }
807
        }
808
        anno[cc.AnnRequiresScratch] = "false"
1✔
809
        return nil
1✔
810
}
811

812
// Get path to VDDK image from 'v2v-vmware' ConfigMap
813
func (r *ImportReconciler) getVddkImageName() (*string, error) {
1✔
814
        namespace := util.GetNamespace()
1✔
815

1✔
816
        cm := &corev1.ConfigMap{}
1✔
817
        err := r.uncachedClient.Get(context.TODO(), types.NamespacedName{Name: common.VddkConfigMap, Namespace: namespace}, cm)
1✔
818
        if k8serrors.IsNotFound(err) {
2✔
819
                return nil, errors.Errorf("No %s ConfigMap present in namespace %s", common.VddkConfigMap, namespace)
1✔
820
        }
1✔
821

822
        image, found := cm.Data[common.VddkConfigDataKey]
1✔
823
        if found {
2✔
824
                msg := fmt.Sprintf("Found %s ConfigMap in namespace %s, VDDK image path is: ", common.VddkConfigMap, namespace)
1✔
825
                r.log.V(1).Info(msg, common.VddkConfigDataKey, image)
1✔
826
                return &image, nil
1✔
827
        }
1✔
828

829
        return nil, errors.Errorf("found %s ConfigMap in namespace %s, but it does not contain a '%s' entry", common.VddkConfigMap, namespace, common.VddkConfigDataKey)
×
830
}
831

832
// returns the import image part of the endpoint string
833
func getRegistryImportImage(pvc *corev1.PersistentVolumeClaim) (string, error) {
1✔
834
        ep, err := cc.GetEndpoint(pvc)
1✔
835
        if err != nil {
1✔
836
                return "", nil
×
837
        }
×
838
        if cc.IsImageStream(pvc) {
1✔
839
                return ep, nil
×
840
        }
×
841
        url, err := url.Parse(ep)
1✔
842
        if err != nil {
1✔
843
                return "", errors.Errorf("illegal registry endpoint %s", ep)
×
844
        }
×
845
        return url.Host + url.Path, nil
1✔
846
}
847

848
// getValueFromAnnotation returns the value of an annotation
849
func getValueFromAnnotation(pvc *corev1.PersistentVolumeClaim, annotation string) string {
1✔
850
        return pvc.Annotations[annotation]
1✔
851
}
1✔
852

853
// If this pod is going to transfer one checkpoint in a multi-stage import, attach the checkpoint name to the pod name so
854
// that each checkpoint gets a unique pod. That way each pod can be inspected using the retainAfterCompletion annotation.
855
func podNameWithCheckpoint(pvc *corev1.PersistentVolumeClaim) string {
1✔
856
        if checkpoint := pvc.Annotations[cc.AnnCurrentCheckpoint]; checkpoint != "" {
2✔
857
                return pvc.Name + "-checkpoint-" + checkpoint
1✔
858
        }
1✔
859
        return pvc.Name
1✔
860
}
861

862
func getImportPodNameFromPvc(pvc *corev1.PersistentVolumeClaim) string {
1✔
863
        podName, ok := pvc.Annotations[cc.AnnImportPod]
1✔
864
        if ok {
2✔
865
                return podName
1✔
866
        }
1✔
867
        // fallback to legacy naming, in fact the following function is fully compatible with legacy
868
        // name concatenation "importer-{pvc.Name}" if the name length is under the size limits,
869
        return naming.GetResourceName(common.ImporterPodName, podNameWithCheckpoint(pvc))
1✔
870
}
871

872
func createImportPodNameFromPvc(pvc *corev1.PersistentVolumeClaim) string {
1✔
873
        return naming.GetResourceName(common.ImporterPodName, podNameWithCheckpoint(pvc))
1✔
874
}
1✔
875

876
// createImporterPod creates and returns a pointer to a pod which is created based on the passed-in endpoint, secret
877
// name, and pvc. A nil secret means the endpoint credentials are not passed to the
878
// importer pod.
879
func createImporterPod(ctx context.Context, log logr.Logger, client client.Client, args *importerPodArgs, installerLabels map[string]string) (*corev1.Pod, error) {
1✔
880
        var err error
1✔
881
        args.podResourceRequirements, err = cc.GetDefaultPodResourceRequirements(client)
1✔
882
        if err != nil {
1✔
883
                return nil, err
×
884
        }
×
885

886
        args.imagePullSecrets, err = cc.GetImagePullSecrets(client)
1✔
887
        if err != nil {
1✔
888
                return nil, err
×
889
        }
×
890

891
        args.workloadNodePlacement, err = cc.GetWorkloadNodePlacement(ctx, client)
1✔
892
        if err != nil {
1✔
893
                return nil, err
×
894
        }
×
895

896
        if isRegistryNodeImport(args) {
2✔
897
                args.importImage, err = getRegistryImportImage(args.pvc)
1✔
898
                if err != nil {
1✔
899
                        return nil, err
×
900
                }
×
901
                setRegistryNodeImportEnvVars(args)
1✔
902
                if args.podEnvVar.registryImageArchitecture != "" {
1✔
903
                        setRegistryNodeImportNodeSelector(args)
×
904
                }
×
905
        }
906

907
        pod := makeImporterPodSpec(args)
1✔
908

1✔
909
        util.SetRecommendedLabels(pod, installerLabels, "cdi-controller")
1✔
910

1✔
911
        // add any labels from pvc to the importer pod
1✔
912
        util.MergeLabels(args.pvc.Labels, pod.Labels)
1✔
913

1✔
914
        if err = client.Create(context.TODO(), pod); err != nil {
1✔
915
                return nil, err
×
916
        }
×
917

918
        log.V(3).Info("importer pod created\n", "pod.Name", pod.Name, "pod.Namespace", pod.Namespace, "image name", args.image)
1✔
919
        return pod, nil
1✔
920
}
921

922
// makeImporterPodSpec creates and return the importer pod spec based on the passed-in endpoint, secret and pvc.
923
func makeImporterPodSpec(args *importerPodArgs) *corev1.Pod {
1✔
924
        // importer pod name contains the pvc name
1✔
925
        podName := args.pvc.Annotations[cc.AnnImportPod]
1✔
926

1✔
927
        pod := &corev1.Pod{
1✔
928
                TypeMeta: metav1.TypeMeta{
1✔
929
                        Kind:       "Pod",
1✔
930
                        APIVersion: "v1",
1✔
931
                },
1✔
932
                ObjectMeta: metav1.ObjectMeta{
1✔
933
                        Name:      podName,
1✔
934
                        Namespace: args.pvc.Namespace,
1✔
935
                        Annotations: map[string]string{
1✔
936
                                cc.AnnCreatedBy: "yes",
1✔
937
                        },
1✔
938
                        Labels: map[string]string{
1✔
939
                                common.CDILabelKey:        common.CDILabelValue,
1✔
940
                                common.CDIComponentLabel:  common.ImporterPodName,
1✔
941
                                common.PrometheusLabelKey: common.PrometheusLabelValue,
1✔
942
                        },
1✔
943
                        OwnerReferences: []metav1.OwnerReference{
1✔
944
                                {
1✔
945
                                        APIVersion:         "v1",
1✔
946
                                        Kind:               "PersistentVolumeClaim",
1✔
947
                                        Name:               args.pvc.Name,
1✔
948
                                        UID:                args.pvc.GetUID(),
1✔
949
                                        BlockOwnerDeletion: ptr.To[bool](true),
1✔
950
                                        Controller:         ptr.To[bool](true),
1✔
951
                                },
1✔
952
                        },
1✔
953
                },
1✔
954
                Spec: corev1.PodSpec{
1✔
955
                        Containers:        makeImporterContainerSpec(args),
1✔
956
                        InitContainers:    makeImporterInitContainersSpec(args),
1✔
957
                        Volumes:           makeImporterVolumeSpec(args),
1✔
958
                        RestartPolicy:     corev1.RestartPolicyOnFailure,
1✔
959
                        NodeSelector:      args.workloadNodePlacement.NodeSelector,
1✔
960
                        Tolerations:       args.workloadNodePlacement.Tolerations,
1✔
961
                        Affinity:          args.workloadNodePlacement.Affinity,
1✔
962
                        PriorityClassName: args.priorityClassName,
1✔
963
                        ImagePullSecrets:  args.imagePullSecrets,
1✔
964
                },
1✔
965
        }
1✔
966

1✔
967
        /**
1✔
968
        FIXME: When registry source is ImageStream, if we set importer pod OwnerReference (to its pvc, like all other cases),
1✔
969
        for some reason (OCP issue?) we get the following error:
1✔
970
                Failed to pull image "imagestream-name": rpc error: code = Unknown
1✔
971
                desc = Error reading manifest latest in docker.io/library/imagestream-name: errors:
1✔
972
                denied: requested access to the resource is denied
1✔
973
                unauthorized: authentication required
1✔
974
        When we don't set pod OwnerReferences, all works well.
1✔
975
        */
1✔
976
        if isRegistryNodeImport(args) && cc.IsImageStream(args.pvc) {
1✔
977
                pod.OwnerReferences = nil
×
978
                pod.Annotations[cc.AnnOpenShiftImageLookup] = "*"
×
979
        }
×
980

981
        cc.CopyAllowedAnnotations(args.pvc, pod)
1✔
982
        cc.SetRestrictedSecurityContext(&pod.Spec)
1✔
983
        // We explicitly define a NodeName for dynamically provisioned PVCs
1✔
984
        // when the PVC is being handled by a populator (PVC')
1✔
985
        cc.SetNodeNameIfPopulator(args.pvc, &pod.Spec)
1✔
986

1✔
987
        return pod
1✔
988
}
989

990
func makeImporterContainerSpec(args *importerPodArgs) []corev1.Container {
1✔
991
        containers := []corev1.Container{
1✔
992
                {
1✔
993
                        Name:            common.ImporterPodName,
1✔
994
                        Image:           args.image,
1✔
995
                        ImagePullPolicy: corev1.PullPolicy(args.pullPolicy),
1✔
996
                        Args:            []string{"-v=" + args.verbose},
1✔
997
                        Env:             makeImportEnv(args.podEnvVar, getOwnerUID(args)),
1✔
998
                        Ports: []corev1.ContainerPort{
1✔
999
                                {
1✔
1000
                                        Name:          "metrics",
1✔
1001
                                        ContainerPort: 8443,
1✔
1002
                                        Protocol:      corev1.ProtocolTCP,
1✔
1003
                                },
1✔
1004
                        },
1✔
1005
                        TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
1✔
1006
                },
1✔
1007
        }
1✔
1008
        if cc.GetVolumeMode(args.pvc) == corev1.PersistentVolumeBlock {
2✔
1009
                containers[0].VolumeDevices = cc.AddVolumeDevices()
1✔
1010
        } else {
2✔
1011
                containers[0].VolumeMounts = cc.AddImportVolumeMounts()
1✔
1012
        }
1✔
1013
        if isRegistryNodeImport(args) {
2✔
1014
                containers = append(containers, corev1.Container{
1✔
1015
                        Name:            "server",
1✔
1016
                        Image:           args.importImage,
1✔
1017
                        ImagePullPolicy: corev1.PullPolicy(args.pullPolicy),
1✔
1018
                        Command:         []string{"/shared/server", "-p", "8100", "-image-dir", "/disk", "-ready-file", "/shared/ready", "-done-file", "/shared/done"},
1✔
1019
                        VolumeMounts: []corev1.VolumeMount{
1✔
1020
                                {
1✔
1021
                                        MountPath: "/shared",
1✔
1022
                                        Name:      "shared-volume",
1✔
1023
                                },
1✔
1024
                        },
1✔
1025
                        TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
1✔
1026
                })
1✔
1027
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
1✔
1028
                        MountPath: "/shared",
1✔
1029
                        Name:      "shared-volume",
1✔
1030
                })
1✔
1031
        }
1✔
1032
        if args.scratchPvcName != nil {
2✔
1033
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
1✔
1034
                        Name:      cc.ScratchVolName,
1✔
1035
                        MountPath: common.ScratchDataDir,
1✔
1036
                })
1✔
1037
        }
1✔
1038
        if args.vddkImageName != nil {
2✔
1039
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
1✔
1040
                        Name:      "vddk-vol-mount",
1✔
1041
                        MountPath: "/opt",
1✔
1042
                })
1✔
1043
        }
1✔
1044
        if args.vddkExtraArgs != nil {
2✔
1045
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
1✔
1046
                        Name:      common.VddkArgsVolName,
1✔
1047
                        MountPath: common.VddkArgsDir,
1✔
1048
                })
1✔
1049
        }
1✔
1050
        if args.podEnvVar.certConfigMap != "" {
1✔
1051
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
×
1052
                        Name:      CertVolName,
×
1053
                        MountPath: common.ImporterCertDir,
×
1054
                })
×
1055
        }
×
1056
        if args.podEnvVar.certConfigMapProxy != "" {
1✔
1057
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
×
1058
                        Name:      ProxyCertVolName,
×
1059
                        MountPath: common.ImporterProxyCertDir,
×
1060
                })
×
1061
        }
×
1062
        if args.podEnvVar.source == cc.SourceGCS && args.podEnvVar.secretName != "" {
1✔
1063
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
×
1064
                        Name:      SecretVolName,
×
1065
                        MountPath: common.ImporterGoogleCredentialDir,
×
1066
                })
×
1067
        }
×
1068
        for index := range args.podEnvVar.secretExtraHeaders {
1✔
1069
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
×
1070
                        Name:      fmt.Sprintf(secretExtraHeadersVolumeName, index),
×
1071
                        MountPath: path.Join(common.ImporterSecretExtraHeadersDir, fmt.Sprint(index)),
×
1072
                })
×
1073
        }
×
1074
        if args.podResourceRequirements != nil {
1✔
1075
                for i := range containers {
×
1076
                        containers[i].Resources = *args.podResourceRequirements
×
1077
                }
×
1078
        }
1079
        return containers
1✔
1080
}
1081

1082
func makeImporterVolumeSpec(args *importerPodArgs) []corev1.Volume {
1✔
1083
        volumes := []corev1.Volume{
1✔
1084
                {
1✔
1085
                        Name: cc.DataVolName,
1✔
1086
                        VolumeSource: corev1.VolumeSource{
1✔
1087
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
1✔
1088
                                        ClaimName: args.pvc.Name,
1✔
1089
                                        ReadOnly:  false,
1✔
1090
                                },
1✔
1091
                        },
1✔
1092
                },
1✔
1093
        }
1✔
1094
        if isRegistryNodeImport(args) {
2✔
1095
                volumes = append(volumes, corev1.Volume{
1✔
1096
                        Name: "shared-volume",
1✔
1097
                        VolumeSource: corev1.VolumeSource{
1✔
1098
                                EmptyDir: &corev1.EmptyDirVolumeSource{},
1✔
1099
                        },
1✔
1100
                })
1✔
1101
        }
1✔
1102
        if args.scratchPvcName != nil {
2✔
1103
                volumes = append(volumes, corev1.Volume{
1✔
1104
                        Name: cc.ScratchVolName,
1✔
1105
                        VolumeSource: corev1.VolumeSource{
1✔
1106
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
1✔
1107
                                        ClaimName: *args.scratchPvcName,
1✔
1108
                                        ReadOnly:  false,
1✔
1109
                                },
1✔
1110
                        },
1✔
1111
                })
1✔
1112
        }
1✔
1113
        if args.vddkImageName != nil {
2✔
1114
                volumes = append(volumes, corev1.Volume{
1✔
1115
                        Name: "vddk-vol-mount",
1✔
1116
                        VolumeSource: corev1.VolumeSource{
1✔
1117
                                EmptyDir: &corev1.EmptyDirVolumeSource{},
1✔
1118
                        },
1✔
1119
                })
1✔
1120
        }
1✔
1121
        if args.vddkExtraArgs != nil {
2✔
1122
                volumes = append(volumes, corev1.Volume{
1✔
1123
                        Name: common.VddkArgsVolName,
1✔
1124
                        VolumeSource: corev1.VolumeSource{
1✔
1125
                                ConfigMap: &v1.ConfigMapVolumeSource{
1✔
1126
                                        LocalObjectReference: v1.LocalObjectReference{
1✔
1127
                                                Name: *args.vddkExtraArgs,
1✔
1128
                                        },
1✔
1129
                                },
1✔
1130
                        },
1✔
1131
                })
1✔
1132
        }
1✔
1133
        if args.podEnvVar.certConfigMap != "" {
1✔
1134
                volumes = append(volumes, createConfigMapVolume(CertVolName, args.podEnvVar.certConfigMap))
×
1135
        }
×
1136
        if args.podEnvVar.certConfigMapProxy != "" {
1✔
1137
                volumes = append(volumes, createConfigMapVolume(ProxyCertVolName, GetImportProxyConfigMapName(args.pvc.Name)))
×
1138
        }
×
1139
        if args.podEnvVar.source == cc.SourceGCS && args.podEnvVar.secretName != "" {
1✔
1140
                volumes = append(volumes, createSecretVolume(SecretVolName, args.podEnvVar.secretName))
×
1141
        }
×
1142
        for index, header := range args.podEnvVar.secretExtraHeaders {
1✔
1143
                volumes = append(volumes, corev1.Volume{
×
1144
                        Name: fmt.Sprintf(secretExtraHeadersVolumeName, index),
×
1145
                        VolumeSource: corev1.VolumeSource{
×
1146
                                Secret: &corev1.SecretVolumeSource{
×
1147
                                        SecretName: header,
×
1148
                                },
×
1149
                        },
×
1150
                })
×
1151
        }
×
1152
        return volumes
1✔
1153
}
1154

1155
func makeImporterInitContainersSpec(args *importerPodArgs) []corev1.Container {
1✔
1156
        var initContainers []corev1.Container
1✔
1157
        if isRegistryNodeImport(args) {
2✔
1158
                initContainers = append(initContainers, corev1.Container{
1✔
1159
                        Name:            "init",
1✔
1160
                        Image:           args.image,
1✔
1161
                        ImagePullPolicy: corev1.PullPolicy(args.pullPolicy),
1✔
1162
                        Command:         []string{"sh", "-c", "cp /usr/bin/cdi-containerimage-server /shared/server"},
1✔
1163
                        VolumeMounts: []corev1.VolumeMount{
1✔
1164
                                {
1✔
1165
                                        MountPath: "/shared",
1✔
1166
                                        Name:      "shared-volume",
1✔
1167
                                },
1✔
1168
                        },
1✔
1169
                        TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
1✔
1170
                })
1✔
1171
        }
1✔
1172
        if args.vddkImageName != nil {
2✔
1173
                initContainers = append(initContainers, corev1.Container{
1✔
1174
                        Name:  "vddk-side-car",
1✔
1175
                        Image: *args.vddkImageName,
1✔
1176
                        VolumeMounts: []corev1.VolumeMount{
1✔
1177
                                {
1✔
1178
                                        Name:      "vddk-vol-mount",
1✔
1179
                                        MountPath: "/opt",
1✔
1180
                                },
1✔
1181
                        },
1✔
1182
                        TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
1✔
1183
                })
1✔
1184
        }
1✔
1185
        if args.podResourceRequirements != nil {
1✔
1186
                for i := range initContainers {
×
1187
                        initContainers[i].Resources = *args.podResourceRequirements
×
1188
                }
×
1189
        }
1190
        return initContainers
1✔
1191
}
1192

1193
func isRegistryNodeImport(args *importerPodArgs) bool {
1✔
1194
        return cc.GetSource(args.pvc) == cc.SourceRegistry &&
1✔
1195
                args.pvc.Annotations[cc.AnnRegistryImportMethod] == string(cdiv1.RegistryPullNode)
1✔
1196
}
1✔
1197

1198
func getOwnerUID(args *importerPodArgs) types.UID {
1✔
1199
        if len(args.pvc.OwnerReferences) == 1 {
1✔
1200
                return args.pvc.OwnerReferences[0].UID
×
1201
        }
×
1202
        return args.pvc.UID
1✔
1203
}
1204

1205
func setRegistryNodeImportEnvVars(args *importerPodArgs) {
1✔
1206
        args.podEnvVar.source = cc.SourceHTTP
1✔
1207
        args.podEnvVar.ep = "http://localhost:8100/disk.img"
1✔
1208
        args.podEnvVar.pullMethod = string(cdiv1.RegistryPullNode)
1✔
1209
        args.podEnvVar.readyFile = "/shared/ready"
1✔
1210
        args.podEnvVar.doneFile = "/shared/done"
1✔
1211
}
1✔
1212

1213
func setRegistryNodeImportNodeSelector(args *importerPodArgs) {
×
1214
        if args.workloadNodePlacement.NodeSelector == nil {
×
1215
                args.workloadNodePlacement.NodeSelector = make(map[string]string, 0)
×
1216
        }
×
1217
        args.workloadNodePlacement.NodeSelector[v1.LabelArchStable] = args.podEnvVar.registryImageArchitecture
×
1218
}
1219

1220
func createConfigMapVolume(certVolName, objRef string) corev1.Volume {
1✔
1221
        return corev1.Volume{
1✔
1222
                Name: certVolName,
1✔
1223
                VolumeSource: corev1.VolumeSource{
1✔
1224
                        ConfigMap: &corev1.ConfigMapVolumeSource{
1✔
1225
                                LocalObjectReference: corev1.LocalObjectReference{
1✔
1226
                                        Name: objRef,
1✔
1227
                                },
1✔
1228
                        },
1✔
1229
                },
1✔
1230
        }
1✔
1231
}
1✔
1232

1233
func createSecretVolume(thisVolName, objRef string) corev1.Volume {
×
1234
        return corev1.Volume{
×
1235
                Name: thisVolName,
×
1236
                VolumeSource: corev1.VolumeSource{
×
1237
                        Secret: &corev1.SecretVolumeSource{
×
1238
                                SecretName: objRef,
×
1239
                        },
×
1240
                },
×
1241
        }
×
1242
}
×
1243

1244
// return the Env portion for the importer container.
1245
func makeImportEnv(podEnvVar *importPodEnvVar, uid types.UID) []corev1.EnvVar {
1✔
1246
        env := []corev1.EnvVar{
1✔
1247
                {
1✔
1248
                        Name:  common.ImporterSource,
1✔
1249
                        Value: podEnvVar.source,
1✔
1250
                },
1✔
1251
                {
1✔
1252
                        Name:  common.ImporterEndpoint,
1✔
1253
                        Value: podEnvVar.ep,
1✔
1254
                },
1✔
1255
                {
1✔
1256
                        Name:  common.ImporterContentType,
1✔
1257
                        Value: podEnvVar.contentType,
1✔
1258
                },
1✔
1259
                {
1✔
1260
                        Name:  common.ImporterImageSize,
1✔
1261
                        Value: podEnvVar.imageSize,
1✔
1262
                },
1✔
1263
                {
1✔
1264
                        Name:  common.OwnerUID,
1✔
1265
                        Value: string(uid),
1✔
1266
                },
1✔
1267
                {
1✔
1268
                        Name:  common.FilesystemOverheadVar,
1✔
1269
                        Value: podEnvVar.filesystemOverhead,
1✔
1270
                },
1✔
1271
                {
1✔
1272
                        Name:  common.InsecureTLSVar,
1✔
1273
                        Value: strconv.FormatBool(podEnvVar.insecureTLS),
1✔
1274
                },
1✔
1275
                {
1✔
1276
                        Name:  common.ImporterDiskID,
1✔
1277
                        Value: podEnvVar.diskID,
1✔
1278
                },
1✔
1279
                {
1✔
1280
                        Name:  common.ImporterUUID,
1✔
1281
                        Value: podEnvVar.uuid,
1✔
1282
                },
1✔
1283
                {
1✔
1284
                        Name:  common.ImporterPullMethod,
1✔
1285
                        Value: podEnvVar.pullMethod,
1✔
1286
                },
1✔
1287
                {
1✔
1288
                        Name:  common.ImporterReadyFile,
1✔
1289
                        Value: podEnvVar.readyFile,
1✔
1290
                },
1✔
1291
                {
1✔
1292
                        Name:  common.ImporterDoneFile,
1✔
1293
                        Value: podEnvVar.doneFile,
1✔
1294
                },
1✔
1295
                {
1✔
1296
                        Name:  common.ImporterBackingFile,
1✔
1297
                        Value: podEnvVar.backingFile,
1✔
1298
                },
1✔
1299
                {
1✔
1300
                        Name:  common.ImporterThumbprint,
1✔
1301
                        Value: podEnvVar.thumbprint,
1✔
1302
                },
1✔
1303
                {
1✔
1304
                        Name:  common.ImportProxyHTTP,
1✔
1305
                        Value: podEnvVar.httpProxy,
1✔
1306
                },
1✔
1307
                {
1✔
1308
                        Name:  common.ImportProxyHTTPS,
1✔
1309
                        Value: podEnvVar.httpsProxy,
1✔
1310
                },
1✔
1311
                {
1✔
1312
                        Name:  common.ImportProxyNoProxy,
1✔
1313
                        Value: podEnvVar.noProxy,
1✔
1314
                },
1✔
1315
                {
1✔
1316
                        Name:  common.ImporterCurrentCheckpoint,
1✔
1317
                        Value: podEnvVar.currentCheckpoint,
1✔
1318
                },
1✔
1319
                {
1✔
1320
                        Name:  common.ImporterPreviousCheckpoint,
1✔
1321
                        Value: podEnvVar.previousCheckpoint,
1✔
1322
                },
1✔
1323
                {
1✔
1324
                        Name:  common.ImporterFinalCheckpoint,
1✔
1325
                        Value: podEnvVar.finalCheckpoint,
1✔
1326
                },
1✔
1327
                {
1✔
1328
                        Name:  common.Preallocation,
1✔
1329
                        Value: strconv.FormatBool(podEnvVar.preallocation),
1✔
1330
                },
1✔
1331
                {
1✔
1332
                        Name:  common.CacheMode,
1✔
1333
                        Value: podEnvVar.cacheMode,
1✔
1334
                },
1✔
1335
                {
1✔
1336
                        Name:  common.ImporterRegistryImageArchitecture,
1✔
1337
                        Value: podEnvVar.registryImageArchitecture,
1✔
1338
                },
1✔
1339
        }
1✔
1340
        if podEnvVar.secretName != "" && podEnvVar.source != cc.SourceGCS {
1✔
1341
                env = append(env, corev1.EnvVar{
×
1342
                        Name: common.ImporterAccessKeyID,
×
1343
                        ValueFrom: &corev1.EnvVarSource{
×
1344
                                SecretKeyRef: &corev1.SecretKeySelector{
×
1345
                                        LocalObjectReference: corev1.LocalObjectReference{
×
1346
                                                Name: podEnvVar.secretName,
×
1347
                                        },
×
1348
                                        Key: common.KeyAccess,
×
1349
                                },
×
1350
                        },
×
1351
                }, corev1.EnvVar{
×
1352
                        Name: common.ImporterSecretKey,
×
1353
                        ValueFrom: &corev1.EnvVarSource{
×
1354
                                SecretKeyRef: &corev1.SecretKeySelector{
×
1355
                                        LocalObjectReference: corev1.LocalObjectReference{
×
1356
                                                Name: podEnvVar.secretName,
×
1357
                                        },
×
1358
                                        Key: common.KeySecret,
×
1359
                                },
×
1360
                        },
×
1361
                })
×
1362
        }
×
1363
        if podEnvVar.secretName != "" && podEnvVar.source == cc.SourceGCS {
1✔
1364
                env = append(env, corev1.EnvVar{
×
1365
                        Name:  common.ImporterGoogleCredentialFileVar,
×
1366
                        Value: common.ImporterGoogleCredentialFile,
×
1367
                })
×
1368
        }
×
1369
        if podEnvVar.certConfigMap != "" {
1✔
1370
                env = append(env, corev1.EnvVar{
×
1371
                        Name:  common.ImporterCertDirVar,
×
1372
                        Value: common.ImporterCertDir,
×
1373
                })
×
1374
        }
×
1375
        if podEnvVar.certConfigMapProxy != "" {
1✔
1376
                env = append(env, corev1.EnvVar{
×
1377
                        Name:  common.ImporterProxyCertDirVar,
×
1378
                        Value: common.ImporterProxyCertDir,
×
1379
                })
×
1380
        }
×
1381
        for index, header := range podEnvVar.extraHeaders {
1✔
1382
                env = append(env, corev1.EnvVar{
×
1383
                        Name:  fmt.Sprintf("%s%d", common.ImporterExtraHeader, index),
×
1384
                        Value: header,
×
1385
                })
×
1386
        }
×
1387
        return env
1✔
1388
}
1389

1390
func isOOMKilled(status v1.ContainerStatus) bool {
1✔
1391
        if terminated := status.State.Terminated; terminated != nil {
2✔
1392
                if terminated.Reason == cc.OOMKilledReason {
2✔
1393
                        return true
1✔
1394
                }
1✔
1395
        }
1396
        if terminated := status.LastTerminationState.Terminated; terminated != nil {
2✔
1397
                if terminated.Reason == cc.OOMKilledReason {
1✔
1398
                        return true
×
1399
                }
×
1400
        }
1401

1402
        return false
1✔
1403
}
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

© 2025 Coveralls, Inc