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

kubevirt / hyperconverged-cluster-operator / 25718245997

12 May 2026 06:45AM UTC coverage: 80.463% (+0.009%) from 80.454%
25718245997

Pull #4237

github

web-flow
Merge 9a158c309 into 89d22a364
Pull Request #4237: Use v1 in the hyperconverged controller

503 of 523 new or added lines in 45 files covered. (96.18%)

9 existing lines in 5 files now uncovered.

10432 of 12965 relevant lines covered (80.46%)

2.06 hits per line

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

89.87
/pkg/webhooks/validator/validator.go
1
package validator
2

3
import (
4
        "context"
5
        "errors"
6
        "fmt"
7
        "net/http"
8
        "reflect"
9
        "strings"
10
        "time"
11

12
        "github.com/go-logr/logr"
13
        openshiftconfigv1 "github.com/openshift/api/config/v1"
14
        "github.com/samber/lo"
15
        xsync "golang.org/x/sync/errgroup"
16
        admissionv1 "k8s.io/api/admission/v1"
17
        corev1 "k8s.io/api/core/v1"
18
        apierrors "k8s.io/apimachinery/pkg/api/errors"
19
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20
        "k8s.io/component-helpers/scheduling/corev1/nodeaffinity"
21
        "k8s.io/utils/ptr"
22
        "sigs.k8s.io/controller-runtime/pkg/client"
23
        "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
24

25
        networkaddonsv1 "github.com/kubevirt/cluster-network-addons-operator/pkg/apis/networkaddonsoperator/v1"
26
        kubevirtcorev1 "kubevirt.io/api/core/v1"
27
        cdiv1beta1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
28
        sspv1beta3 "kubevirt.io/ssp-operator/api/v1beta3"
29

30
        hcov1 "github.com/kubevirt/hyperconverged-cluster-operator/api/v1"
31
        hcov1fg "github.com/kubevirt/hyperconverged-cluster-operator/api/v1/featuregates"
32
        hcov1beta1 "github.com/kubevirt/hyperconverged-cluster-operator/api/v1beta1"
33
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/handlers"
34
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/featuregatedetails"
35
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/featuregates"
36
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/nodeinfo"
37
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/tlssecprofile"
38
        hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util"
39
)
40

41
const (
42
        updateDryRunTimeOut = time.Second * 3
43

44
        validatorV1Name = "hyperConverged v1 validator"
45
)
46

47
type ValidationWarning struct {
48
        warnings []string
49
}
50

51
func newValidationWarning(warnings []string) *ValidationWarning {
1✔
52
        return &ValidationWarning{
1✔
53
                warnings: warnings,
1✔
54
        }
1✔
55
}
1✔
56

57
func (v *ValidationWarning) Error() string {
×
58
        return ""
×
59
}
×
60

61
func (v *ValidationWarning) Warnings() []string {
1✔
62
        return v.warnings
1✔
63
}
1✔
64

65
type WebhookHandler struct {
66
        logger      logr.Logger
67
        cli         client.Client
68
        namespace   string
69
        isOpenshift bool
70
        decoder     admission.Decoder
71
}
72

73
func NewWebhookHandler(logger logr.Logger, cli client.Client, decoder admission.Decoder, namespace string, isOpenshift bool) *WebhookHandler {
2✔
74
        return &WebhookHandler{
2✔
75
                logger:      logger.WithName(validatorV1Name),
2✔
76
                cli:         cli,
2✔
77
                namespace:   namespace,
2✔
78
                isOpenshift: isOpenshift,
2✔
79
                decoder:     decoder,
2✔
80
        }
2✔
81
}
2✔
82

83
func (wh *WebhookHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
1✔
84
        ctx = admission.NewContextWithRequest(ctx, req)
1✔
85
        logger, err := logr.FromContext(ctx)
1✔
86
        if err != nil {
2✔
87
                logger = wh.logger
1✔
88
        } else {
1✔
89
                logger = logger.WithName(validatorV1Name)
×
90
        }
×
91

92
        // Get the object in the request
93
        obj := &hcov1.HyperConverged{}
1✔
94

1✔
95
        dryRun := req.DryRun != nil && *req.DryRun
1✔
96

1✔
97
        switch req.Operation {
1✔
98
        case admissionv1.Create:
1✔
99
                if err = wh.decoder.Decode(req, obj); err != nil {
2✔
100
                        return admission.Errored(http.StatusBadRequest, err)
1✔
101
                }
1✔
102

103
                return wh.validateCreate(logger, dryRun, obj)
1✔
104

105
        case admissionv1.Update:
1✔
106
                if err = wh.decoder.DecodeRaw(req.Object, obj); err != nil {
2✔
107
                        return admission.Errored(http.StatusBadRequest, err)
1✔
108
                }
1✔
109

110
                oldObj := &hcov1.HyperConverged{}
1✔
111
                if err = wh.decoder.DecodeRaw(req.OldObject, oldObj); err != nil {
2✔
112
                        return admission.Errored(http.StatusBadRequest, err)
1✔
113
                }
1✔
114

115
                return wh.validateUpdate(ctx, logger, dryRun, obj, oldObj)
1✔
116

117
        case admissionv1.Delete:
1✔
118
                if err = wh.decoder.DecodeRaw(req.OldObject, obj); err != nil {
2✔
119
                        return admission.Errored(http.StatusBadRequest, err)
1✔
120
                }
1✔
121

122
                return wh.validateDelete(ctx, logger, dryRun, obj)
1✔
123

124
        default:
1✔
125
                return admission.Errored(http.StatusBadRequest, fmt.Errorf("unknown operation request %q", req.Operation))
1✔
126
        }
127
}
128

129
func (wh *WebhookHandler) validateCreate(logger logr.Logger, dryrun bool, hc *hcov1.HyperConverged) admission.Response {
1✔
130
        logger.Info("Validating create", "name", hc.Name, "namespace:", hc.Namespace)
1✔
131

1✔
132
        if err := wh.validateCreateHyperConverged(hc); err != nil {
2✔
133
                return errToResponse(err)
1✔
134
        }
1✔
135

136
        err := wh.validateCreateComponents(hc)
1✔
137
        if err != nil {
2✔
138
                return errToResponse(err)
1✔
139
        }
1✔
140

141
        if !dryrun {
2✔
142
                tlssecprofile.SetHyperConvergedTLSSecurityProfile(hc.Spec.Security.TLSSecurityProfile)
1✔
143
        }
1✔
144

145
        return admission.Allowed("")
1✔
146
}
147

148
func (wh *WebhookHandler) validateUpdate(ctx context.Context, logger logr.Logger, dryrun bool, requested *hcov1.HyperConverged, exists *hcov1.HyperConverged) admission.Response {
1✔
149
        logger.Info("Validating update", "name", requested.Name)
1✔
150

1✔
151
        // If no change is detected in the spec nor the annotations - nothing to validate
1✔
152
        if reflect.DeepEqual(exists.Spec, requested.Spec) &&
1✔
153
                reflect.DeepEqual(exists.Annotations, requested.Annotations) {
2✔
154
                return admission.Allowed("")
1✔
155
        }
1✔
156

157
        if err := wh.validateUpdateHyperConverged(requested, exists); err != nil {
1✔
158
                return errToResponse(err)
×
159
        }
×
160

161
        if err := checkOperands(ctx, wh.cli, logger, requested, wh.isOpenshift); err != nil {
2✔
162
                return errToResponse(err)
1✔
163
        }
1✔
164

165
        if !dryrun {
2✔
166
                tlssecprofile.SetHyperConvergedTLSSecurityProfile(requested.Spec.Security.TLSSecurityProfile)
1✔
167
        }
1✔
168

169
        return admission.Allowed("")
1✔
170
}
171

172
func (wh *WebhookHandler) validateDelete(ctx context.Context, logger logr.Logger, dryrun bool, hc *hcov1.HyperConverged) admission.Response {
1✔
173
        logger.Info("Validating delete", "name", hc.Name, "namespace", hc.Namespace)
1✔
174

1✔
175
        var err error
1✔
176
        for _, obj := range []client.Object{
1✔
177
                handlers.NewKubeVirtWithNameOnly(),
1✔
178
                handlers.NewCDIWithNameOnly(),
1✔
179
        } {
2✔
180
                _, err = hcoutil.EnsureDeleted(ctx, wh.cli, obj, hc.Name, logger, true, false, true)
1✔
181
                if err != nil {
2✔
182
                        logger.Error(err, "Delete validation failed", "GVK", obj.GetObjectKind().GroupVersionKind())
1✔
183
                        break
1✔
184
                }
185
        }
186

187
        if err != nil && !dryrun {
1✔
NEW
188
                tlssecprofile.SetHyperConvergedTLSSecurityProfile(nil)
×
NEW
189
        }
×
190

191
        return errToResponse(err)
1✔
192
}
193

194
func (wh *WebhookHandler) validateHyperConverged(hc *hcov1.HyperConverged) error {
1✔
195
        if err := wh.validateCertConfig(hc); err != nil {
1✔
196
                return err
×
197
        }
×
198

199
        if err := wh.validateDataImportCronTemplates(hc); err != nil {
2✔
200
                return err
1✔
201
        }
1✔
202

203
        if err := wh.validateTLSSecurityProfiles(hc); err != nil {
2✔
204
                return err
1✔
205
        }
1✔
206

207
        if err := wh.validateTuningPolicy(hc); err != nil {
2✔
208
                return err
1✔
209
        }
1✔
210

211
        if err := wh.validateAffinity(hc); err != nil {
2✔
212
                return err
1✔
213
        }
1✔
214

215
        return nil
1✔
216
}
217

218
func (wh *WebhookHandler) validateCreateHyperConverged(hc *hcov1.HyperConverged) error {
1✔
219
        if err := wh.validateHyperConverged(hc); err != nil {
2✔
220
                return err
1✔
221
        }
1✔
222

223
        if err := wh.validateFeatureGatesOnCreate(hc); err != nil {
1✔
224
                return err
×
225
        }
×
226

227
        return nil
1✔
228
}
229

230
func (wh *WebhookHandler) validateUpdateHyperConverged(hc, oldHC *hcov1.HyperConverged) error {
1✔
231
        if err := wh.validateHyperConverged(hc); err != nil {
1✔
232
                return err
×
233
        }
×
234

235
        if err := wh.validateFeatureGatesOnUpdate(hc, oldHC); err != nil {
1✔
236
                return err
×
237
        }
×
238

239
        return nil
1✔
240
}
241

242
func (wh *WebhookHandler) validateCreateComponents(hc *hcov1.HyperConverged) error {
1✔
243
        if _, err := handlers.NewKubeVirt(hc); err != nil {
2✔
244
                return err
1✔
245
        }
1✔
246

247
        if _, err := handlers.NewCDI(hc); err != nil {
2✔
248
                return err
1✔
249
        }
1✔
250

251
        if _, err := handlers.NewNetworkAddons(hc); err != nil {
2✔
252
                return err
1✔
253
        }
1✔
254

255
        if _, _, err := handlers.NewSSP(hc); err != nil {
2✔
256
                return err
1✔
257
        }
1✔
258

259
        return nil
1✔
260
}
261

262
func (wh *WebhookHandler) validateCertConfig(hc *hcov1.HyperConverged) error {
1✔
263
        minimalDuration := metav1.Duration{Duration: 10 * time.Minute}
1✔
264

1✔
265
        ccValues := make(map[string]time.Duration)
1✔
266
        ccValues["spec.certConfig.ca.duration"] = hc.Spec.Security.CertConfig.CA.Duration.Duration
1✔
267
        ccValues["spec.certConfig.ca.renewBefore"] = hc.Spec.Security.CertConfig.CA.RenewBefore.Duration
1✔
268
        ccValues["spec.certConfig.server.duration"] = hc.Spec.Security.CertConfig.Server.Duration.Duration
1✔
269
        ccValues["spec.certConfig.server.renewBefore"] = hc.Spec.Security.CertConfig.Server.RenewBefore.Duration
1✔
270

1✔
271
        for key, value := range ccValues {
2✔
272
                if value < minimalDuration.Duration {
1✔
273
                        return fmt.Errorf("%v: value is too small", key)
×
274
                }
×
275
        }
276

277
        if hc.Spec.Security.CertConfig.CA.Duration.Duration < hc.Spec.Security.CertConfig.CA.RenewBefore.Duration {
1✔
278
                return errors.New("spec.certConfig.ca: duration is smaller than renewBefore")
×
279
        }
×
280

281
        if hc.Spec.Security.CertConfig.Server.Duration.Duration < hc.Spec.Security.CertConfig.Server.RenewBefore.Duration {
1✔
282
                return errors.New("spec.certConfig.server: duration is smaller than renewBefore")
×
283
        }
×
284

285
        if hc.Spec.Security.CertConfig.CA.Duration.Duration < hc.Spec.Security.CertConfig.Server.Duration.Duration {
1✔
286
                return errors.New("spec.certConfig: ca.duration is smaller than server.duration")
×
287
        }
×
288

289
        return nil
1✔
290
}
291

292
func (wh *WebhookHandler) validateDataImportCronTemplates(hc *hcov1.HyperConverged) error {
1✔
293

1✔
294
        for _, dict := range hc.Spec.WorkloadSources.DataImportCronTemplates {
2✔
295
                val, ok := dict.Annotations[hcoutil.DataImportCronEnabledAnnotation]
1✔
296
                val = strings.ToLower(val)
1✔
297
                if ok && val != "false" && val != "true" {
2✔
298
                        return fmt.Errorf(`the %s annotation of a dataImportCronTemplate must be either "true" or "false"`, hcoutil.DataImportCronEnabledAnnotation)
1✔
299
                }
1✔
300

301
                enabled := !ok || val == "true"
1✔
302

1✔
303
                if enabled && dict.Spec == nil {
2✔
304
                        return fmt.Errorf("dataImportCronTemplate spec is empty for an enabled DataImportCronTemplate")
1✔
305
                }
1✔
306
        }
307

308
        return nil
1✔
309
}
310

311
func (wh *WebhookHandler) validateTLSSecurityProfiles(hc *hcov1.HyperConverged) error {
1✔
312
        tlsSP := hc.Spec.Security.TLSSecurityProfile
1✔
313

1✔
314
        if tlsSP == nil {
2✔
315
                return nil
1✔
316
        }
1✔
317

318
        if tlsSP.Custom == nil {
2✔
319
                if tlsSP.Type == openshiftconfigv1.TLSProfileCustomType {
2✔
320
                        return fmt.Errorf("missing required field spec.tlsSecurityProfile.custom when type is Custom")
1✔
321
                }
1✔
322
                return nil
1✔
323
        }
324

325
        if !isValidTLSProtocolVersion(tlsSP.Custom.MinTLSVersion) {
2✔
326
                return fmt.Errorf("invalid value for spec.tlsSecurityProfile.custom.minTLSVersion: %q", tlsSP.Custom.MinTLSVersion)
1✔
327
        }
1✔
328

329
        if tlsSP.Custom.MinTLSVersion < openshiftconfigv1.VersionTLS13 && !hasRequiredHTTP2Ciphers(tlsSP.Custom.Ciphers) {
2✔
330
                return fmt.Errorf("http2: TLSConfig.CipherSuites is missing an HTTP/2-required AES_128_GCM_SHA256 cipher (need at least one of ECDHE-RSA-AES128-GCM-SHA256 or ECDHE-ECDSA-AES128-GCM-SHA256)")
1✔
331
        } else if tlsSP.Custom.MinTLSVersion == openshiftconfigv1.VersionTLS13 && len(tlsSP.Custom.Ciphers) > 0 {
3✔
332
                return fmt.Errorf("custom ciphers cannot be selected when minTLSVersion is VersionTLS13")
1✔
333
        }
1✔
334

335
        return nil
1✔
336
}
337

338
func (wh *WebhookHandler) validateTuningPolicy(hc *hcov1.HyperConverged) error {
1✔
339
        if hc.Spec.Virtualization.TuningPolicy == hcov1beta1.HyperConvergedHighBurstProfile { //nolint SA1019
2✔
340
                return newValidationWarning([]string{"spec.virtualization.tuningPolicy: the highBurst profile is not supported and ignored"})
1✔
341
        }
1✔
342
        return nil
1✔
343
}
344

345
func (wh *WebhookHandler) validateFeatureGatesOnCreate(hc *hcov1.HyperConverged) error {
1✔
346
        fgMap := v1FGsToMap(hc.Spec.FeatureGates)
1✔
347

1✔
348
        warnings := wh.validateDeprecatedFeatureGates(fgMap, nil)
1✔
349

1✔
350
        if len(warnings) > 0 {
1✔
351
                return newValidationWarning(warnings)
×
352
        }
×
353

354
        return nil
1✔
355
}
356

357
func (wh *WebhookHandler) validateFeatureGatesOnUpdate(requested, exists *hcov1.HyperConverged) error {
1✔
358
        reqFGMap := v1FGsToMap(requested.Spec.FeatureGates)
1✔
359
        oldFGMap := v1FGsToMap(exists.Spec.FeatureGates)
1✔
360

1✔
361
        warnings := wh.validateDeprecatedFeatureGates(reqFGMap, oldFGMap)
1✔
362

1✔
363
        if len(warnings) > 0 {
1✔
364
                return newValidationWarning(warnings)
×
365
        }
×
366

367
        return nil
1✔
368
}
369

370
func (wh *WebhookHandler) validateAffinity(hc *hcov1.HyperConverged) error {
1✔
371
        if hc.Spec.Deployment.NodePlacements == nil {
2✔
372
                return nil
1✔
373
        }
1✔
374

375
        nodePlacements := hc.Spec.Deployment.NodePlacements
1✔
376

1✔
377
        if nodePlacements.Workload != nil {
2✔
378
                if err := validateAffinity(nodePlacements.Workload.Affinity); err != nil {
2✔
379
                        return fmt.Errorf("invalid workloads node placement affinity: %v", err.Error())
1✔
380
                }
1✔
381
        }
382

383
        if nodePlacements.Infra != nil {
2✔
384
                if err := validateAffinity(nodePlacements.Infra.Affinity); err != nil {
2✔
385
                        return fmt.Errorf("invalid infra node placement affinity: %v", err.Error())
1✔
386
                }
1✔
387
        }
388

389
        return nil
1✔
390
}
391

392
const (
393
        fgv1Unknown            = "the %s featureGate is unknown and ignored."
394
        fgv1DeprecationWarning = "the %s featureGate deprecated and will be removed in a future release."
395
)
396

397
func (wh *WebhookHandler) validateDeprecatedFeatureGates(fgMap, oldFgMap map[string]bool) []string {
1✔
398
        var warnings []string
1✔
399

1✔
400
        for fgName, enabled := range fgMap {
1✔
401
                phase, exists := featuregatedetails.GetFeatureGatePhase(fgName)
×
402
                if !exists {
×
403
                        warnings = append(warnings, fmt.Sprintf(fgv1Unknown, fgName))
×
404
                        continue
×
405
                }
406

407
                if phase != featuregates.PhaseDeprecated {
×
408
                        continue
×
409
                }
410

411
                if oldEnabled, oldExists := oldFgMap[fgName]; !oldExists || enabled != oldEnabled {
×
412
                        warnings = append(warnings, fmt.Sprintf(fgv1DeprecationWarning, fgName))
×
413
                }
×
414
        }
415

416
        return warnings
1✔
417
}
418

419
func hasRequiredHTTP2Ciphers(ciphers []string) bool {
1✔
420
        var requiredHTTP2Ciphers = []string{
1✔
421
                "ECDHE-RSA-AES128-GCM-SHA256",
1✔
422
                "ECDHE-ECDSA-AES128-GCM-SHA256",
1✔
423
        }
1✔
424

1✔
425
        // lo.Some returns true if at least 1 element of a subset is contained into a collection
1✔
426
        return lo.Some[string](requiredHTTP2Ciphers, ciphers)
1✔
427
}
1✔
428

429
// validationResponseFromStatus returns a response for admitting a request with provided Status object.
430
func validationResponseFromStatus(allowed bool, status metav1.Status) admission.Response {
1✔
431
        resp := admission.Response{
1✔
432
                AdmissionResponse: admissionv1.AdmissionResponse{
1✔
433
                        Allowed: allowed,
1✔
434
                        Result:  &status,
1✔
435
                },
1✔
436
        }
1✔
437
        return resp
1✔
438
}
1✔
439

440
func isValidTLSProtocolVersion(pv openshiftconfigv1.TLSProtocolVersion) bool {
1✔
441
        switch pv {
1✔
442
        case
443
                openshiftconfigv1.VersionTLS10,
444
                openshiftconfigv1.VersionTLS11,
445
                openshiftconfigv1.VersionTLS12,
446
                openshiftconfigv1.VersionTLS13:
1✔
447
                return true
1✔
448
        }
449
        return false
1✔
450
}
451

452
func validateAffinity(affinity *corev1.Affinity) error {
1✔
453
        if affinity == nil || affinity.NodeAffinity == nil || affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil {
2✔
454
                return nil
1✔
455
        }
1✔
456

457
        _, err := nodeaffinity.NewNodeSelector(affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution)
1✔
458

1✔
459
        return err
1✔
460
}
461

462
func errToResponse(err error) admission.Response {
1✔
463
        if err != nil {
2✔
464
                var apiStatus apierrors.APIStatus
1✔
465
                if errors.As(err, &apiStatus) {
2✔
466
                        return validationResponseFromStatus(false, apiStatus.Status())
1✔
467
                }
1✔
468

469
                var vw *ValidationWarning
1✔
470
                if errors.As(err, &vw) {
2✔
471
                        return admission.Allowed("").WithWarnings(vw.Warnings()...)
1✔
472
                }
1✔
473

474
                return admission.Denied(err.Error())
1✔
475
        }
476

477
        // Return allowed if everything succeeded.
478
        return admission.Allowed("")
1✔
479
}
480

481
func v1FGsToMap(fgs hcov1fg.HyperConvergedFeatureGates) map[string]bool {
1✔
482
        m := map[string]bool{}
1✔
483
        for _, fg := range fgs {
1✔
484
                m[fg.Name] = ptr.Deref(fg.State, hcov1fg.Enabled) == hcov1fg.Enabled
×
485
        }
×
486

487
        return m
1✔
488
}
489

490
func checkOperands(ctx context.Context, cli client.Client, logger logr.Logger, requested *hcov1.HyperConverged, isOpenshift bool) error {
1✔
491
        resources, err := getOperands(ctx, cli, isOpenshift)
1✔
492
        if err != nil {
2✔
493
                return err
1✔
494
        }
1✔
495

496
        toCtx, cancel := context.WithTimeout(ctx, updateDryRunTimeOut)
1✔
497
        defer cancel()
1✔
498

1✔
499
        eg, egCtx := xsync.WithContext(toCtx)
1✔
500
        opts := &client.UpdateOptions{DryRun: []string{metav1.DryRunAll}}
1✔
501

1✔
502
        for _, obj := range resources {
2✔
503
                func(o client.Object) {
2✔
504
                        eg.Go(func() error {
2✔
505
                                return updateOperatorCr(egCtx, cli, logger, requested, o, opts)
1✔
506
                        })
1✔
507
                }(obj)
508
        }
509

510
        return eg.Wait()
1✔
511
}
512

513
func getOperands(ctx context.Context, cli client.Client, isOpenshift bool) ([]client.Object, error) {
1✔
514
        kv := handlers.NewKubeVirtWithNameOnly()
1✔
515
        err := cli.Get(ctx, client.ObjectKeyFromObject(kv), kv)
1✔
516
        if err != nil {
2✔
517
                return nil, err
1✔
518
        }
1✔
519

520
        cdi := handlers.NewCDIWithNameOnly()
1✔
521
        err = cli.Get(ctx, client.ObjectKeyFromObject(cdi), cdi)
1✔
522
        if err != nil {
2✔
523
                return nil, err
1✔
524
        }
1✔
525

526
        cna := handlers.NewNetworkAddonsWithNameOnly()
1✔
527
        err = cli.Get(ctx, client.ObjectKeyFromObject(cna), cna)
1✔
528
        if err != nil {
2✔
529
                return nil, err
1✔
530
        }
1✔
531

532
        resources := make([]client.Object, 0, 4)
1✔
533
        resources = append(resources, kv, cdi, cna)
1✔
534

1✔
535
        if isOpenshift {
2✔
536
                ssp := handlers.NewSSPWithNameOnly()
1✔
537
                err = cli.Get(ctx, client.ObjectKeyFromObject(ssp), ssp)
1✔
538
                if err != nil {
2✔
539
                        return nil, err
1✔
540
                }
1✔
541

542
                resources = append(resources, ssp)
1✔
543
        }
544

545
        return resources, nil
1✔
546
}
547

548
func updateOperatorCr(ctx context.Context, cli client.Client, logger logr.Logger, hc *hcov1.HyperConverged, exists client.Object, opts *client.UpdateOptions) error {
1✔
549
        switch existing := exists.(type) {
1✔
550
        case *kubevirtcorev1.KubeVirt:
1✔
551
                required, err := handlers.NewKubeVirt(hc)
1✔
552
                if err != nil {
2✔
553
                        return err
1✔
554
                }
1✔
555
                required.Spec.DeepCopyInto(&existing.Spec)
1✔
556

557
        case *cdiv1beta1.CDI:
1✔
558
                required, err := handlers.NewCDI(hc)
1✔
559
                if err != nil {
2✔
560
                        return err
1✔
561
                }
1✔
562
                required.Spec.DeepCopyInto(&existing.Spec)
1✔
563

564
        case *networkaddonsv1.NetworkAddonsConfig:
1✔
565
                required, err := handlers.NewNetworkAddons(hc)
1✔
566
                if err != nil {
2✔
567
                        return err
1✔
568
                }
1✔
569
                required.Spec.DeepCopyInto(&existing.Spec)
1✔
570

571
        case *sspv1beta3.SSP:
1✔
572
                origGetControlPlaneArchitectures := nodeinfo.GetControlPlaneArchitectures
1✔
573
                origGetWorkloadsArchitectures := nodeinfo.GetWorkloadsArchitectures
1✔
574
                defer func() {
2✔
575
                        nodeinfo.GetControlPlaneArchitectures = origGetControlPlaneArchitectures
1✔
576
                        nodeinfo.GetWorkloadsArchitectures = origGetWorkloadsArchitectures
1✔
577
                }()
1✔
578

579
                nodeinfo.GetControlPlaneArchitectures = func() []string {
2✔
580
                        return hc.Status.NodeInfo.ControlPlaneArchitectures
1✔
581
                }
1✔
582
                nodeinfo.GetWorkloadsArchitectures = func() []string {
2✔
583
                        return hc.Status.NodeInfo.WorkloadsArchitectures
1✔
584
                }
1✔
585

586
                required, _, err := handlers.NewSSP(hc)
1✔
587
                if err != nil {
2✔
588
                        return err
1✔
589
                }
1✔
590
                required.Spec.DeepCopyInto(&existing.Spec)
1✔
591
        }
592

593
        if err := cli.Update(ctx, exists, opts); err != nil {
2✔
594
                logger.Error(err, "failed to dry-run update the object", "kind", exists.GetObjectKind())
1✔
595
                return err
1✔
596
        }
1✔
597

598
        logger.Info("dry-run update the object passed", "kind", exists.GetObjectKind())
1✔
599
        return nil
1✔
600
}
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