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

kubevirt / hyperconverged-cluster-operator / 25399368950

05 May 2026 08:04PM UTC coverage: 80.392% (-0.1%) from 80.512%
25399368950

Pull #4218

github

web-flow
Merge 1177bc40c into fc42f3a16
Pull Request #4218: Add discontinued feature gate phase

4 of 5 new or added lines in 2 files covered. (80.0%)

17 existing lines in 1 file now uncovered.

10676 of 13280 relevant lines covered (80.39%)

2.06 hits per line

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

84.43
/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
        hcov1 "github.com/kubevirt/hyperconverged-cluster-operator/api/v1"
26
        hcov1fg "github.com/kubevirt/hyperconverged-cluster-operator/api/v1/featuregates"
27
        hcov1beta1 "github.com/kubevirt/hyperconverged-cluster-operator/api/v1beta1"
28
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/handlers"
29
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/featuregatedetails"
30
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/featuregates"
31
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/nodeinfo"
32
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/tlssecprofile"
33
        hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util"
34
)
35

36
const (
37
        updateDryRunTimeOut = time.Second * 3
38

39
        validatorV1Name = "hyperConverged v1 validator"
40
)
41

42
type ValidationWarning struct {
43
        warnings []string
44
}
45

46
func newValidationWarning(warnings []string) *ValidationWarning {
1✔
47
        return &ValidationWarning{
1✔
48
                warnings: warnings,
1✔
49
        }
1✔
50
}
1✔
51

52
func (v *ValidationWarning) Error() string {
×
53
        return ""
×
54
}
×
55

56
func (v *ValidationWarning) Warnings() []string {
1✔
57
        return v.warnings
1✔
58
}
1✔
59

60
type WebhookHandler struct {
61
        logger         logr.Logger
62
        cli            client.Client
63
        namespace      string
64
        isOpenshift    bool
65
        decoder        admission.Decoder
66
        v1beta1Handler *WebhookV1Beta1Handler
67
}
68

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

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

89
        // Get the object in the request
90
        v1obj := &hcov1.HyperConverged{}
1✔
91

1✔
92
        dryRun := req.DryRun != nil && *req.DryRun
1✔
93

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

100
                return wh.validateCreate(logger, dryRun, v1obj)
1✔
101

102
        case admissionv1.Update:
1✔
103
                v1OldObj := &hcov1.HyperConverged{}
1✔
104
                if err = wh.decoder.DecodeRaw(req.Object, v1obj); err != nil {
2✔
105
                        return admission.Errored(http.StatusBadRequest, err)
1✔
106
                }
1✔
107
                if err = wh.decoder.DecodeRaw(req.OldObject, v1OldObj); err != nil {
2✔
108
                        return admission.Errored(http.StatusBadRequest, err)
1✔
109
                }
1✔
110

111
                obj := &hcov1beta1.HyperConverged{}
1✔
112
                err = obj.ConvertFrom(v1obj)
1✔
113
                if err != nil {
1✔
114
                        return admission.Errored(http.StatusInternalServerError, err)
×
115
                }
×
116

117
                oldObj := &hcov1beta1.HyperConverged{}
1✔
118
                err = oldObj.ConvertFrom(v1OldObj)
1✔
119
                if err != nil {
1✔
120
                        return admission.Errored(http.StatusInternalServerError, err)
×
121
                }
×
122

123
                err = wh.v1beta1Handler.ValidateUpdate(ctx, logger, dryRun, obj, oldObj)
1✔
124

125
        case admissionv1.Delete:
1✔
126
                if err = wh.decoder.DecodeRaw(req.OldObject, v1obj); err != nil {
2✔
127
                        return admission.Errored(http.StatusBadRequest, err)
1✔
128
                }
1✔
129

130
                obj := &hcov1beta1.HyperConverged{}
1✔
131
                err = obj.ConvertFrom(v1obj)
1✔
132
                if err != nil {
1✔
133
                        return admission.Errored(http.StatusInternalServerError, err)
×
134
                }
×
135

136
                err = wh.v1beta1Handler.ValidateDelete(ctx, logger, dryRun, obj)
1✔
137

138
        default:
1✔
139
                return admission.Errored(http.StatusBadRequest, fmt.Errorf("unknown operation request %q", req.Operation))
1✔
140
        }
141

142
        // Check the error message first.
143
        if err != nil {
2✔
144
                var apiStatus apierrors.APIStatus
1✔
145
                if errors.As(err, &apiStatus) {
1✔
146
                        return validationResponseFromStatus(false, apiStatus.Status())
×
147
                }
×
148

149
                var vw *ValidationWarning
1✔
150
                if errors.As(err, &vw) {
1✔
151
                        return admission.Allowed("").WithWarnings(vw.Warnings()...)
×
152
                }
×
153

154
                return admission.Denied(err.Error())
1✔
155
        }
156

157
        // Return allowed if everything succeeded.
158
        return admission.Allowed("")
1✔
159
}
160

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

1✔
164
        if err := wh.validateCreateHyperConverged(hc); err != nil {
2✔
165
                return errToResponse(err)
1✔
166
        }
1✔
167

168
        v1beta1HC := &hcov1beta1.HyperConverged{}
1✔
169
        err := v1beta1HC.ConvertFrom(hc)
1✔
170
        if err != nil {
1✔
171
                return admission.Errored(http.StatusInternalServerError, err)
×
172
        }
×
173

174
        err = wh.validateCreateComponents(v1beta1HC)
1✔
175
        if err != nil {
2✔
176
                return errToResponse(err)
1✔
177
        }
1✔
178

179
        if !dryrun {
2✔
180
                tlssecprofile.SetHyperConvergedTLSSecurityProfile(hc.Spec.Security.TLSSecurityProfile)
1✔
181
        }
1✔
182

183
        return admission.Allowed("")
1✔
184
}
185

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

1✔
189
        // If no change is detected in the spec nor the annotations - nothing to validate
1✔
190
        if reflect.DeepEqual(exists.Spec, requested.Spec) &&
1✔
191
                reflect.DeepEqual(exists.Annotations, requested.Annotations) {
2✔
192
                return admission.Allowed("")
1✔
193
        }
1✔
194

195
        if err := wh.validateUpdateHyperConverged(requested, exists); err != nil {
1✔
196
                return errToResponse(err)
×
197
        }
×
198

199
        v1beta1Req := &hcov1beta1.HyperConverged{}
1✔
200
        err := v1beta1Req.ConvertFrom(requested)
1✔
201
        if err != nil {
1✔
202
                return admission.Errored(http.StatusInternalServerError, err)
×
203
        }
×
204

205
        v1beta1Old := &hcov1beta1.HyperConverged{}
1✔
206
        err = v1beta1Old.ConvertFrom(exists)
1✔
207
        if err != nil {
1✔
208
                return admission.Errored(http.StatusInternalServerError, err)
×
209
        }
×
210

211
        err = wh.dryRunUpdateComponents(ctx, logger, v1beta1Req, requested)
1✔
212
        if err != nil {
2✔
213
                return errToResponse(err)
1✔
214
        }
1✔
215

216
        if !dryrun {
2✔
217
                tlssecprofile.SetHyperConvergedTLSSecurityProfile(requested.Spec.Security.TLSSecurityProfile)
1✔
218
        }
1✔
219

220
        return admission.Allowed("")
1✔
221
}
222

223
func (wh *WebhookHandler) validateHyperConverged(hc *hcov1.HyperConverged) error {
1✔
224
        if err := wh.validateCertConfig(hc); err != nil {
1✔
225
                return err
×
226
        }
×
227

228
        if err := wh.validateDataImportCronTemplates(hc); err != nil {
2✔
229
                return err
1✔
230
        }
1✔
231

232
        if err := wh.validateTLSSecurityProfiles(hc); err != nil {
2✔
233
                return err
1✔
234
        }
1✔
235

236
        if err := wh.validateTuningPolicy(hc); err != nil {
2✔
237
                return err
1✔
238
        }
1✔
239

240
        if err := wh.validateAffinity(hc); err != nil {
2✔
241
                return err
1✔
242
        }
1✔
243

244
        return nil
1✔
245
}
246

247
func (wh *WebhookHandler) validateCreateHyperConverged(hc *hcov1.HyperConverged) error {
1✔
248
        if err := wh.validateHyperConverged(hc); err != nil {
2✔
249
                return err
1✔
250
        }
1✔
251

252
        if err := wh.validateFeatureGatesOnCreate(hc); err != nil {
1✔
UNCOV
253
                return err
×
UNCOV
254
        }
×
255

256
        return nil
1✔
257
}
258

259
func (wh *WebhookHandler) validateUpdateHyperConverged(hc, oldHC *hcov1.HyperConverged) error {
1✔
260
        if err := wh.validateHyperConverged(hc); err != nil {
1✔
261
                return err
×
262
        }
×
263

264
        if err := wh.validateFeatureGatesOnUpdate(hc, oldHC); err != nil {
1✔
265
                return err
×
266
        }
×
267

268
        return nil
1✔
269
}
270

271
func (wh *WebhookHandler) validateCreateComponents(v1beta1HC *hcov1beta1.HyperConverged) error {
1✔
272
        if _, err := handlers.NewKubeVirt(v1beta1HC); err != nil {
2✔
273
                return err
1✔
274
        }
1✔
275

276
        if _, err := handlers.NewCDI(v1beta1HC); err != nil {
2✔
277
                return err
1✔
278
        }
1✔
279

280
        if _, err := handlers.NewNetworkAddons(v1beta1HC); err != nil {
2✔
281
                return err
1✔
282
        }
1✔
283

284
        if _, _, err := handlers.NewSSP(v1beta1HC); err != nil {
2✔
285
                return err
1✔
286
        }
1✔
287

288
        return nil
1✔
289
}
290

291
func (wh *WebhookHandler) dryRunUpdateComponents(ctx context.Context, logger logr.Logger, v1beta1Req *hcov1beta1.HyperConverged, requested *hcov1.HyperConverged) error {
1✔
292
        kv, cdi, cna, err := wh.v1beta1Handler.getOperands(ctx, v1beta1Req)
1✔
293
        if err != nil {
2✔
294
                return err
1✔
295
        }
1✔
296

297
        toCtx, cancel := context.WithTimeout(ctx, updateDryRunTimeOut)
1✔
298
        defer cancel()
1✔
299

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

1✔
303
        resources := []client.Object{
1✔
304
                kv,
1✔
305
                cdi,
1✔
306
                cna,
1✔
307
        }
1✔
308

1✔
309
        if wh.isOpenshift {
2✔
310
                origGetControlPlaneArchitectures := nodeinfo.GetControlPlaneArchitectures
1✔
311
                origGetWorkloadsArchitectures := nodeinfo.GetWorkloadsArchitectures
1✔
312
                defer func() {
2✔
313
                        nodeinfo.GetControlPlaneArchitectures = origGetControlPlaneArchitectures
1✔
314
                        nodeinfo.GetWorkloadsArchitectures = origGetWorkloadsArchitectures
1✔
315
                }()
1✔
316

317
                nodeinfo.GetControlPlaneArchitectures = func() []string {
2✔
318
                        return requested.Status.NodeInfo.ControlPlaneArchitectures
1✔
319
                }
1✔
320
                nodeinfo.GetWorkloadsArchitectures = func() []string {
2✔
321
                        return requested.Status.NodeInfo.WorkloadsArchitectures
1✔
322
                }
1✔
323

324
                ssp, _, err := handlers.NewSSP(v1beta1Req)
1✔
325
                if err != nil {
2✔
326
                        return err
1✔
327
                }
1✔
328
                resources = append(resources, ssp)
1✔
329
        }
330

331
        for _, obj := range resources {
2✔
332
                func(o client.Object) {
2✔
333
                        eg.Go(func() error {
2✔
334
                                return wh.v1beta1Handler.updateOperatorCr(egCtx, logger, v1beta1Req, o, opts)
1✔
335
                        })
1✔
336
                }(obj)
337
        }
338

339
        return eg.Wait()
1✔
340
}
341

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

1✔
345
        ccValues := make(map[string]time.Duration)
1✔
346
        ccValues["spec.certConfig.ca.duration"] = hc.Spec.Security.CertConfig.CA.Duration.Duration
1✔
347
        ccValues["spec.certConfig.ca.renewBefore"] = hc.Spec.Security.CertConfig.CA.RenewBefore.Duration
1✔
348
        ccValues["spec.certConfig.server.duration"] = hc.Spec.Security.CertConfig.Server.Duration.Duration
1✔
349
        ccValues["spec.certConfig.server.renewBefore"] = hc.Spec.Security.CertConfig.Server.RenewBefore.Duration
1✔
350

1✔
351
        for key, value := range ccValues {
2✔
352
                if value < minimalDuration.Duration {
1✔
353
                        return fmt.Errorf("%v: value is too small", key)
×
354
                }
×
355
        }
356

357
        if hc.Spec.Security.CertConfig.CA.Duration.Duration < hc.Spec.Security.CertConfig.CA.RenewBefore.Duration {
1✔
358
                return errors.New("spec.certConfig.ca: duration is smaller than renewBefore")
×
359
        }
×
360

361
        if hc.Spec.Security.CertConfig.Server.Duration.Duration < hc.Spec.Security.CertConfig.Server.RenewBefore.Duration {
1✔
362
                return errors.New("spec.certConfig.server: duration is smaller than renewBefore")
×
363
        }
×
364

365
        if hc.Spec.Security.CertConfig.CA.Duration.Duration < hc.Spec.Security.CertConfig.Server.Duration.Duration {
1✔
366
                return errors.New("spec.certConfig: ca.duration is smaller than server.duration")
×
367
        }
×
368

369
        return nil
1✔
370
}
371

372
func (wh *WebhookHandler) validateDataImportCronTemplates(hc *hcov1.HyperConverged) error {
1✔
373

1✔
374
        for _, dict := range hc.Spec.WorkloadSources.DataImportCronTemplates {
2✔
375
                val, ok := dict.Annotations[hcoutil.DataImportCronEnabledAnnotation]
1✔
376
                val = strings.ToLower(val)
1✔
377
                if ok && val != "false" && val != "true" {
2✔
378
                        return fmt.Errorf(`the %s annotation of a dataImportCronTemplate must be either "true" or "false"`, hcoutil.DataImportCronEnabledAnnotation)
1✔
379
                }
1✔
380

381
                enabled := !ok || val == "true"
1✔
382

1✔
383
                if enabled && dict.Spec == nil {
2✔
384
                        return fmt.Errorf("dataImportCronTemplate spec is empty for an enabled DataImportCronTemplate")
1✔
385
                }
1✔
386
        }
387

388
        return nil
1✔
389
}
390

391
func (wh *WebhookHandler) validateTLSSecurityProfiles(hc *hcov1.HyperConverged) error {
1✔
392
        tlsSP := hc.Spec.Security.TLSSecurityProfile
1✔
393

1✔
394
        if tlsSP == nil {
2✔
395
                return nil
1✔
396
        }
1✔
397

398
        if tlsSP.Custom == nil {
2✔
399
                if tlsSP.Type == openshiftconfigv1.TLSProfileCustomType {
2✔
400
                        return fmt.Errorf("missing required field spec.tlsSecurityProfile.custom when type is Custom")
1✔
401
                }
1✔
402
                return nil
1✔
403
        }
404

405
        if !isValidTLSProtocolVersion(tlsSP.Custom.MinTLSVersion) {
2✔
406
                return fmt.Errorf("invalid value for spec.tlsSecurityProfile.custom.minTLSVersion: %q", tlsSP.Custom.MinTLSVersion)
1✔
407
        }
1✔
408

409
        if tlsSP.Custom.MinTLSVersion < openshiftconfigv1.VersionTLS13 && !hasRequiredHTTP2Ciphers(tlsSP.Custom.Ciphers) {
2✔
410
                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✔
411
        } else if tlsSP.Custom.MinTLSVersion == openshiftconfigv1.VersionTLS13 && len(tlsSP.Custom.Ciphers) > 0 {
3✔
412
                return fmt.Errorf("custom ciphers cannot be selected when minTLSVersion is VersionTLS13")
1✔
413
        }
1✔
414

415
        return nil
1✔
416
}
417

418
func (wh *WebhookHandler) validateTuningPolicy(hc *hcov1.HyperConverged) error {
1✔
419
        if hc.Spec.Virtualization.TuningPolicy == hcov1beta1.HyperConvergedHighBurstProfile { //nolint SA1019
2✔
420
                return newValidationWarning([]string{"spec.virtualization.tuningPolicy: the highBurst profile is not supported and ignored"})
1✔
421
        }
1✔
422
        return nil
1✔
423
}
424

425
func (wh *WebhookHandler) validateFeatureGatesOnCreate(hc *hcov1.HyperConverged) error {
1✔
426
        fgMap := v1FGsToMap(hc.Spec.FeatureGates)
1✔
427

1✔
428
        warnings := wh.validateDeprecatedFeatureGates(fgMap, nil)
1✔
429

1✔
430
        if len(warnings) > 0 {
1✔
UNCOV
431
                return newValidationWarning(warnings)
×
UNCOV
432
        }
×
433

434
        return nil
1✔
435
}
436

437
func (wh *WebhookHandler) validateFeatureGatesOnUpdate(requested, exists *hcov1.HyperConverged) error {
1✔
438
        reqFGMap := v1FGsToMap(requested.Spec.FeatureGates)
1✔
439
        oldFGMap := v1FGsToMap(exists.Spec.FeatureGates)
1✔
440

1✔
441
        warnings := wh.validateDeprecatedFeatureGates(reqFGMap, oldFGMap)
1✔
442

1✔
443
        if len(warnings) > 0 {
1✔
444
                return newValidationWarning(warnings)
×
445
        }
×
446

447
        return nil
1✔
448
}
449

450
func (wh *WebhookHandler) validateAffinity(hc *hcov1.HyperConverged) error {
1✔
451
        if hc.Spec.Deployment.NodePlacements == nil {
2✔
452
                return nil
1✔
453
        }
1✔
454

455
        nodePlacements := hc.Spec.Deployment.NodePlacements
1✔
456

1✔
457
        if nodePlacements.Workload != nil {
2✔
458
                if err := validateAffinity(nodePlacements.Workload.Affinity); err != nil {
2✔
459
                        return fmt.Errorf("invalid workloads node placement affinity: %v", err.Error())
1✔
460
                }
1✔
461
        }
462

463
        if nodePlacements.Infra != nil {
2✔
464
                if err := validateAffinity(nodePlacements.Infra.Affinity); err != nil {
2✔
465
                        return fmt.Errorf("invalid infra node placement affinity: %v", err.Error())
1✔
466
                }
1✔
467
        }
468

469
        return nil
1✔
470
}
471

472
const (
473
        fgv1Unknown            = "the %s featureGate is unknown and ignored."
474
        fgv1MovedWarning       = "the %s featureGate is deprecated and ignored. use %s field instead"
475
        fgv1DeprecationWarning = "the %s featureGate deprecated and ignored."
476
)
477

478
var movedFGs = map[string]string{
479
        "enableApplicationAwareQuota": "spec.enableApplicationAwareQuota",
480
        "enableCommonBootImageImport": "spec.enableCommonBootImageImport",
481
        "deployVmConsoleProxy":        "spec.deployVmConsoleProxy",
482
}
483

484
func (wh *WebhookHandler) validateDeprecatedFeatureGates(fgMap, oldFgMap map[string]bool) []string {
1✔
485
        var warnings []string
1✔
486

1✔
487
        for fgName, enabled := range fgMap {
1✔
UNCOV
488
                phase, exists := featuregatedetails.GetFeatureGatePhase(fgName)
×
UNCOV
489
                if !exists {
×
490
                        warnings = append(warnings, fmt.Sprintf(fgv1Unknown, fgName))
×
NEW
491
                        continue
×
492
                }
493

UNCOV
494
                if phase != featuregates.PhaseDeprecated {
×
UNCOV
495
                        continue
×
496
                }
497

UNCOV
498
                if newFiled, ok := movedFGs[fgName]; ok {
×
UNCOV
499
                        if oldEnabled, oldExists := oldFgMap[fgName]; !oldExists || enabled != oldEnabled {
×
UNCOV
500
                                warnings = append(warnings, fmt.Sprintf(fgv1MovedWarning, fgName, newFiled))
×
UNCOV
501
                        }
×
UNCOV
502
                } else {
×
UNCOV
503
                        warnings = append(warnings, fmt.Sprintf(fgv1DeprecationWarning, fgName))
×
UNCOV
504
                }
×
505
        }
506

507
        return warnings
1✔
508
}
509

510
func hasRequiredHTTP2Ciphers(ciphers []string) bool {
1✔
511
        var requiredHTTP2Ciphers = []string{
1✔
512
                "ECDHE-RSA-AES128-GCM-SHA256",
1✔
513
                "ECDHE-ECDSA-AES128-GCM-SHA256",
1✔
514
        }
1✔
515

1✔
516
        // lo.Some returns true if at least 1 element of a subset is contained into a collection
1✔
517
        return lo.Some[string](requiredHTTP2Ciphers, ciphers)
1✔
518
}
1✔
519

520
// validationResponseFromStatus returns a response for admitting a request with provided Status object.
521
func validationResponseFromStatus(allowed bool, status metav1.Status) admission.Response {
1✔
522
        resp := admission.Response{
1✔
523
                AdmissionResponse: admissionv1.AdmissionResponse{
1✔
524
                        Allowed: allowed,
1✔
525
                        Result:  &status,
1✔
526
                },
1✔
527
        }
1✔
528
        return resp
1✔
529
}
1✔
530

531
func isValidTLSProtocolVersion(pv openshiftconfigv1.TLSProtocolVersion) bool {
1✔
532
        switch pv {
1✔
533
        case
534
                openshiftconfigv1.VersionTLS10,
535
                openshiftconfigv1.VersionTLS11,
536
                openshiftconfigv1.VersionTLS12,
537
                openshiftconfigv1.VersionTLS13:
1✔
538
                return true
1✔
539
        }
540
        return false
1✔
541
}
542

543
func validateAffinity(affinity *corev1.Affinity) error {
1✔
544
        if affinity == nil || affinity.NodeAffinity == nil || affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil {
2✔
545
                return nil
1✔
546
        }
1✔
547

548
        _, err := nodeaffinity.NewNodeSelector(affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution)
1✔
549

1✔
550
        return err
1✔
551
}
552

553
func errToResponse(err error) admission.Response {
1✔
554
        if err != nil {
2✔
555
                var apiStatus apierrors.APIStatus
1✔
556
                if errors.As(err, &apiStatus) {
2✔
557
                        return validationResponseFromStatus(false, apiStatus.Status())
1✔
558
                }
1✔
559

560
                var vw *ValidationWarning
1✔
561
                if errors.As(err, &vw) {
2✔
562
                        return admission.Allowed("").WithWarnings(vw.Warnings()...)
1✔
563
                }
1✔
564

565
                return admission.Denied(err.Error())
1✔
566
        }
567

568
        // Return allowed if everything succeeded.
569
        return admission.Allowed("")
×
570
}
571

572
func v1FGsToMap(fgs hcov1fg.HyperConvergedFeatureGates) map[string]bool {
1✔
573
        m := map[string]bool{}
1✔
574
        for _, fg := range fgs {
1✔
UNCOV
575
                m[fg.Name] = ptr.Deref(fg.State, hcov1fg.Enabled) == hcov1fg.Enabled
×
UNCOV
576
        }
×
577

578
        return m
1✔
579
}
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