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

kubevirt / hyperconverged-cluster-operator / 20021419104

08 Dec 2025 08:22AM UTC coverage: 76.505% (-0.2%) from 76.718%
20021419104

Pull #3912

github

web-flow
Merge 5909fb4a9 into cae67e758
Pull Request #3912: CNV-61721: Add ValidatingAdmissionPolicy to validate the HyperConverged namespace

115 of 189 new or added lines in 4 files covered. (60.85%)

5 existing lines in 1 file now uncovered.

8183 of 10696 relevant lines covered (76.51%)

1.81 hits per line

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

90.63
/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/strings/slices"
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
        "github.com/kubevirt/hyperconverged-cluster-operator/api/v1beta1"
31
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/handlers"
32
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/nodeinfo"
33
        hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util"
34
)
35

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

40
type ValidationWarning struct {
41
        warnings []string
42
}
43

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

50
func (v *ValidationWarning) Error() string {
×
51
        return ""
×
52
}
×
53

54
func (v *ValidationWarning) Warnings() []string {
×
55
        return v.warnings
×
56
}
×
57

58
type WebhookHandler struct {
59
        logger      logr.Logger
60
        cli         client.Client
61
        namespace   string
62
        isOpenshift bool
63
        decoder     admission.Decoder
64
}
65

66
var hcoTLSConfigCache *openshiftconfigv1.TLSSecurityProfile
67

68
func NewWebhookHandler(logger logr.Logger, cli client.Client, decoder admission.Decoder, namespace string, isOpenshift bool, hcoTLSSecurityProfile *openshiftconfigv1.TLSSecurityProfile) *WebhookHandler {
2✔
69
        hcoTLSConfigCache = hcoTLSSecurityProfile
2✔
70
        return &WebhookHandler{
2✔
71
                logger:      logger,
2✔
72
                cli:         cli,
2✔
73
                namespace:   namespace,
2✔
74
                isOpenshift: isOpenshift,
2✔
75
                decoder:     decoder,
2✔
76
        }
2✔
77
}
2✔
78

79
func (wh *WebhookHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
1✔
80

1✔
81
        ctx = admission.NewContextWithRequest(ctx, req)
1✔
82

1✔
83
        // Get the object in the request
1✔
84
        obj := &v1beta1.HyperConverged{}
1✔
85

1✔
86
        dryRun := req.DryRun != nil && *req.DryRun
1✔
87

1✔
88
        var err error
1✔
89
        switch req.Operation {
1✔
90
        case admissionv1.Create:
1✔
91
                if err := wh.decoder.Decode(req, obj); err != nil {
2✔
92
                        return admission.Errored(http.StatusBadRequest, err)
1✔
93
                }
1✔
94

95
                err = wh.ValidateCreate(ctx, dryRun, obj)
1✔
96
        case admissionv1.Update:
1✔
97
                oldObj := &v1beta1.HyperConverged{}
1✔
98
                if err := wh.decoder.DecodeRaw(req.Object, obj); err != nil {
2✔
99
                        return admission.Errored(http.StatusBadRequest, err)
1✔
100
                }
1✔
101
                if err := wh.decoder.DecodeRaw(req.OldObject, oldObj); err != nil {
2✔
102
                        return admission.Errored(http.StatusBadRequest, err)
1✔
103
                }
1✔
104

105
                err = wh.ValidateUpdate(ctx, dryRun, obj, oldObj)
1✔
106
        case admissionv1.Delete:
1✔
107
                // In reference to PR: https://github.com/kubernetes/kubernetes/pull/76346
1✔
108
                // OldObject contains the object being deleted
1✔
109
                if err := wh.decoder.DecodeRaw(req.OldObject, obj); err != nil {
2✔
110
                        return admission.Errored(http.StatusBadRequest, err)
1✔
111
                }
1✔
112

113
                err = wh.ValidateDelete(ctx, dryRun, obj)
1✔
114
        default:
1✔
115
                return admission.Errored(http.StatusBadRequest, fmt.Errorf("unknown operation request %q", req.Operation))
1✔
116
        }
117

118
        // Check the error message first.
119
        if err != nil {
1✔
UNCOV
120
                var apiStatus apierrors.APIStatus
×
UNCOV
121
                if errors.As(err, &apiStatus) {
×
122
                        return validationResponseFromStatus(false, apiStatus.Status())
×
123
                }
×
124

UNCOV
125
                var vw *ValidationWarning
×
UNCOV
126
                if errors.As(err, &vw) {
×
127
                        return admission.Allowed("").WithWarnings(vw.Warnings()...)
×
128
                }
×
129

UNCOV
130
                return admission.Denied(err.Error())
×
131
        }
132

133
        // Return allowed if everything succeeded.
134
        return admission.Allowed("")
1✔
135
}
136

137
func (wh *WebhookHandler) ValidateCreate(_ context.Context, dryrun bool, hc *v1beta1.HyperConverged) error {
1✔
138
        wh.logger.Info("Validating create", "name", hc.Name, "namespace:", hc.Namespace)
1✔
139

1✔
140
        if err := wh.validateCertConfig(hc); err != nil {
1✔
141
                return err
×
142
        }
×
143

144
        if err := wh.validateDataImportCronTemplates(hc); err != nil {
2✔
145
                return err
1✔
146
        }
1✔
147

148
        if err := wh.validateTLSSecurityProfiles(hc); err != nil {
2✔
149
                return err
1✔
150
        }
1✔
151

152
        if err := wh.validateMediatedDeviceTypes(hc); err != nil {
2✔
153
                return err
1✔
154
        }
1✔
155

156
        if err := wh.validateFeatureGatesOnCreate(hc); err != nil {
2✔
157
                return err
1✔
158
        }
1✔
159

160
        if err := wh.validateTuningPolicy(hc); err != nil {
2✔
161
                return err
1✔
162
        }
1✔
163

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

168
        if _, err := handlers.NewKubeVirt(hc); err != nil {
2✔
169
                return err
1✔
170
        }
1✔
171

172
        if _, err := handlers.NewCDI(hc); err != nil {
2✔
173
                return err
1✔
174
        }
1✔
175

176
        if _, err := handlers.NewNetworkAddons(hc); err != nil {
2✔
177
                return err
1✔
178
        }
1✔
179

180
        if _, _, err := handlers.NewSSP(hc); err != nil {
2✔
181
                return err
1✔
182
        }
1✔
183

184
        if !dryrun {
2✔
185
                hcoTLSConfigCache = hc.Spec.TLSSecurityProfile
1✔
186
        }
1✔
187

188
        return nil
1✔
189
}
190

191
func (wh *WebhookHandler) getOperands(requested *v1beta1.HyperConverged) (*kubevirtcorev1.KubeVirt, *cdiv1beta1.CDI, *networkaddonsv1.NetworkAddonsConfig, error) {
1✔
192
        if err := wh.validateCertConfig(requested); err != nil {
2✔
193
                return nil, nil, nil, err
1✔
194
        }
1✔
195

196
        kv, err := handlers.NewKubeVirt(requested)
1✔
197
        if err != nil {
2✔
198
                return nil, nil, nil, err
1✔
199
        }
1✔
200

201
        cdi, err := handlers.NewCDI(requested)
1✔
202
        if err != nil {
2✔
203
                return nil, nil, nil, err
1✔
204
        }
1✔
205

206
        cna, err := handlers.NewNetworkAddons(requested)
1✔
207
        if err != nil {
2✔
208
                return nil, nil, nil, err
1✔
209
        }
1✔
210

211
        return kv, cdi, cna, nil
1✔
212
}
213

214
// ValidateUpdate is the ValidateUpdate webhook implementation. It calls all the resources in parallel, to dry-run the
215
// upgrade.
216
func (wh *WebhookHandler) ValidateUpdate(ctx context.Context, dryrun bool, requested *v1beta1.HyperConverged, exists *v1beta1.HyperConverged) error {
1✔
217
        wh.logger.Info("Validating update", "name", requested.Name)
1✔
218

1✔
219
        if err := wh.validateDataImportCronTemplates(requested); err != nil {
1✔
220
                return err
×
221
        }
×
222

223
        if err := wh.validateTLSSecurityProfiles(requested); err != nil {
2✔
224
                return err
1✔
225
        }
1✔
226

227
        if err := wh.validateMediatedDeviceTypes(requested); err != nil {
2✔
228
                return err
1✔
229
        }
1✔
230

231
        if err := wh.validateFeatureGatesOnUpdate(requested, exists); err != nil {
2✔
232
                return err
1✔
233
        }
1✔
234

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

239
        if err := wh.validateAffinity(requested); err != nil {
1✔
240
                return err
×
241
        }
×
242

243
        // If no change is detected in the spec nor the annotations - nothing to validate
244
        if reflect.DeepEqual(exists.Spec, requested.Spec) &&
1✔
245
                reflect.DeepEqual(exists.Annotations, requested.Annotations) {
2✔
246
                return nil
1✔
247
        }
1✔
248

249
        kv, cdi, cna, err := wh.getOperands(requested)
1✔
250
        if err != nil {
2✔
251
                return err
1✔
252
        }
1✔
253

254
        toCtx, cancel := context.WithTimeout(ctx, updateDryRunTimeOut)
1✔
255
        defer cancel()
1✔
256

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

1✔
260
        resources := []client.Object{
1✔
261
                kv,
1✔
262
                cdi,
1✔
263
                cna,
1✔
264
        }
1✔
265

1✔
266
        if wh.isOpenshift {
2✔
267
                origGetControlPlaneArchitectures := nodeinfo.GetControlPlaneArchitectures
1✔
268
                origGetWorkloadsArchitectures := nodeinfo.GetWorkloadsArchitectures
1✔
269
                defer func() {
2✔
270
                        nodeinfo.GetControlPlaneArchitectures = origGetControlPlaneArchitectures
1✔
271
                        nodeinfo.GetWorkloadsArchitectures = origGetWorkloadsArchitectures
1✔
272
                }()
1✔
273

274
                nodeinfo.GetControlPlaneArchitectures = func() []string {
2✔
275
                        return requested.Status.NodeInfo.ControlPlaneArchitectures
1✔
276
                }
1✔
277
                nodeinfo.GetWorkloadsArchitectures = func() []string {
2✔
278
                        return requested.Status.NodeInfo.WorkloadsArchitectures
1✔
279
                }
1✔
280

281
                ssp, _, err := handlers.NewSSP(requested)
1✔
282
                if err != nil {
2✔
283
                        return err
1✔
284
                }
1✔
285
                resources = append(resources, ssp)
1✔
286
        }
287

288
        for _, obj := range resources {
2✔
289
                func(o client.Object) {
2✔
290
                        eg.Go(func() error {
2✔
291
                                return wh.updateOperatorCr(egCtx, requested, o, opts)
1✔
292
                        })
1✔
293
                }(obj)
294
        }
295

296
        err = eg.Wait()
1✔
297
        if err != nil {
2✔
298
                return err
1✔
299
        }
1✔
300

301
        if !dryrun {
2✔
302
                hcoTLSConfigCache = requested.Spec.TLSSecurityProfile
1✔
303
        }
1✔
304

305
        return nil
1✔
306
}
307

308
func (wh *WebhookHandler) updateOperatorCr(ctx context.Context, hc *v1beta1.HyperConverged, exists client.Object, opts *client.UpdateOptions) error {
1✔
309
        err := hcoutil.GetRuntimeObject(ctx, wh.cli, exists)
1✔
310
        if err != nil {
2✔
311
                wh.logger.Error(err, "failed to get object from kubernetes", "kind", exists.GetObjectKind())
1✔
312
                return err
1✔
313
        }
1✔
314

315
        switch existing := exists.(type) {
1✔
316
        case *kubevirtcorev1.KubeVirt:
1✔
317
                required, err := handlers.NewKubeVirt(hc)
1✔
318
                if err != nil {
1✔
319
                        return err
×
320
                }
×
321
                required.Spec.DeepCopyInto(&existing.Spec)
1✔
322

323
        case *cdiv1beta1.CDI:
1✔
324
                required, err := handlers.NewCDI(hc)
1✔
325
                if err != nil {
1✔
326
                        return err
×
327
                }
×
328
                required.Spec.DeepCopyInto(&existing.Spec)
1✔
329

330
        case *networkaddonsv1.NetworkAddonsConfig:
1✔
331
                required, err := handlers.NewNetworkAddons(hc)
1✔
332
                if err != nil {
1✔
333
                        return err
×
334
                }
×
335
                required.Spec.DeepCopyInto(&existing.Spec)
1✔
336

337
        case *sspv1beta3.SSP:
1✔
338
                required, _, err := handlers.NewSSP(hc)
1✔
339
                if err != nil {
1✔
340
                        return err
×
341
                }
×
342
                required.Spec.DeepCopyInto(&existing.Spec)
1✔
343
        }
344

345
        if err = wh.cli.Update(ctx, exists, opts); err != nil {
2✔
346
                wh.logger.Error(err, "failed to dry-run update the object", "kind", exists.GetObjectKind())
1✔
347
                return err
1✔
348
        }
1✔
349

350
        wh.logger.Info("dry-run update the object passed", "kind", exists.GetObjectKind())
1✔
351
        return nil
1✔
352
}
353

354
func (wh *WebhookHandler) ValidateDelete(ctx context.Context, dryrun bool, hc *v1beta1.HyperConverged) error {
1✔
355
        wh.logger.Info("Validating delete", "name", hc.Name, "namespace", hc.Namespace)
1✔
356

1✔
357
        kv := handlers.NewKubeVirtWithNameOnly(hc)
1✔
358
        cdi := handlers.NewCDIWithNameOnly(hc)
1✔
359

1✔
360
        for _, obj := range []client.Object{
1✔
361
                kv,
1✔
362
                cdi,
1✔
363
        } {
2✔
364
                _, err := hcoutil.EnsureDeleted(ctx, wh.cli, obj, hc.Name, wh.logger, true, false, true)
1✔
365
                if err != nil {
2✔
366
                        wh.logger.Error(err, "Delete validation failed", "GVK", obj.GetObjectKind().GroupVersionKind())
1✔
367
                        return err
1✔
368
                }
1✔
369
        }
370
        if !dryrun {
2✔
371
                hcoTLSConfigCache = nil
1✔
372
        }
1✔
373
        return nil
1✔
374
}
375

376
func (wh *WebhookHandler) validateCertConfig(hc *v1beta1.HyperConverged) error {
1✔
377
        minimalDuration := metav1.Duration{Duration: 10 * time.Minute}
1✔
378

1✔
379
        ccValues := make(map[string]time.Duration)
1✔
380
        ccValues["spec.certConfig.ca.duration"] = hc.Spec.CertConfig.CA.Duration.Duration
1✔
381
        ccValues["spec.certConfig.ca.renewBefore"] = hc.Spec.CertConfig.CA.RenewBefore.Duration
1✔
382
        ccValues["spec.certConfig.server.duration"] = hc.Spec.CertConfig.Server.Duration.Duration
1✔
383
        ccValues["spec.certConfig.server.renewBefore"] = hc.Spec.CertConfig.Server.RenewBefore.Duration
1✔
384

1✔
385
        for key, value := range ccValues {
2✔
386
                if value < minimalDuration.Duration {
2✔
387
                        return fmt.Errorf("%v: value is too small", key)
1✔
388
                }
1✔
389
        }
390

391
        if hc.Spec.CertConfig.CA.Duration.Duration < hc.Spec.CertConfig.CA.RenewBefore.Duration {
2✔
392
                return errors.New("spec.certConfig.ca: duration is smaller than renewBefore")
1✔
393
        }
1✔
394

395
        if hc.Spec.CertConfig.Server.Duration.Duration < hc.Spec.CertConfig.Server.RenewBefore.Duration {
2✔
396
                return errors.New("spec.certConfig.server: duration is smaller than renewBefore")
1✔
397
        }
1✔
398

399
        if hc.Spec.CertConfig.CA.Duration.Duration < hc.Spec.CertConfig.Server.Duration.Duration {
2✔
400
                return errors.New("spec.certConfig: ca.duration is smaller than server.duration")
1✔
401
        }
1✔
402

403
        return nil
1✔
404
}
405

406
func (wh *WebhookHandler) validateDataImportCronTemplates(hc *v1beta1.HyperConverged) error {
1✔
407

1✔
408
        for _, dict := range hc.Spec.DataImportCronTemplates {
2✔
409
                val, ok := dict.Annotations[hcoutil.DataImportCronEnabledAnnotation]
1✔
410
                val = strings.ToLower(val)
1✔
411
                if ok && val != "false" && val != "true" {
2✔
412
                        return fmt.Errorf(`the %s annotation of a dataImportCronTemplate must be either "true" or "false"`, hcoutil.DataImportCronEnabledAnnotation)
1✔
413
                }
1✔
414

415
                enabled := !ok || val == "true"
1✔
416

1✔
417
                if enabled && dict.Spec == nil {
2✔
418
                        return fmt.Errorf("dataImportCronTemplate spec is empty for an enabled DataImportCronTemplate")
1✔
419
                }
1✔
420
        }
421

422
        return nil
1✔
423
}
424

425
func (wh *WebhookHandler) validateTLSSecurityProfiles(hc *v1beta1.HyperConverged) error {
1✔
426
        tlsSP := hc.Spec.TLSSecurityProfile
1✔
427

1✔
428
        if tlsSP == nil || tlsSP.Custom == nil {
2✔
429
                return nil
1✔
430
        }
1✔
431

432
        if !isValidTLSProtocolVersion(tlsSP.Custom.MinTLSVersion) {
2✔
433
                return fmt.Errorf("invalid value for spec.tlsSecurityProfile.custom.minTLSVersion")
1✔
434
        }
1✔
435

436
        if tlsSP.Custom.MinTLSVersion < openshiftconfigv1.VersionTLS13 && !hasRequiredHTTP2Ciphers(tlsSP.Custom.Ciphers) {
2✔
437
                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✔
438
        } else if tlsSP.Custom.MinTLSVersion == openshiftconfigv1.VersionTLS13 && len(tlsSP.Custom.Ciphers) > 0 {
3✔
439
                return fmt.Errorf("custom ciphers cannot be selected when minTLSVersion is VersionTLS13")
1✔
440
        }
1✔
441

442
        return nil
1✔
443
}
444

445
func (wh *WebhookHandler) validateMediatedDeviceTypes(hc *v1beta1.HyperConverged) error {
1✔
446
        mdc := hc.Spec.MediatedDevicesConfiguration
1✔
447
        if mdc != nil {
2✔
448
                if len(mdc.MediatedDevicesTypes) > 0 && len(mdc.MediatedDeviceTypes) > 0 && !slices.Equal(mdc.MediatedDevicesTypes, mdc.MediatedDeviceTypes) { //nolint SA1019
2✔
449
                        return fmt.Errorf("mediatedDevicesTypes is deprecated, please use mediatedDeviceTypes instead")
1✔
450
                }
1✔
451
                for _, nmdc := range mdc.NodeMediatedDeviceTypes {
2✔
452
                        if len(nmdc.MediatedDevicesTypes) > 0 && len(nmdc.MediatedDeviceTypes) > 0 && !slices.Equal(nmdc.MediatedDevicesTypes, nmdc.MediatedDeviceTypes) { //nolint SA1019
2✔
453
                                return fmt.Errorf("mediatedDevicesTypes is deprecated, please use mediatedDeviceTypes instead")
1✔
454
                        }
1✔
455
                }
456
        }
457
        return nil
1✔
458
}
459

460
func (wh *WebhookHandler) validateTuningPolicy(hc *v1beta1.HyperConverged) error {
1✔
461
        if hc.Spec.TuningPolicy == v1beta1.HyperConvergedHighBurstProfile { //nolint SA1019
2✔
462
                return newValidationWarning([]string{"spec.tuningPolicy: the highBurst profile is deprecated as of v1.16.0 and will be removed in a future release"})
1✔
463
        }
1✔
464
        return nil
1✔
465
}
466

467
const (
468
        fgMovedWarning       = "spec.featureGates.%[1]s is deprecated and ignored. It will removed in a future version; use spec.%[1]s instead"
469
        fgDeprecationWarning = "spec.featureGates.%s is deprecated and ignored. It will be removed in a future version;"
470
)
471

472
func (wh *WebhookHandler) validateFeatureGatesOnCreate(hc *v1beta1.HyperConverged) error {
1✔
473
        warnings := wh.validateDeprecatedFeatureGates(hc)
1✔
474
        warnings = validateOldFGOnCreate(warnings, hc)
1✔
475

1✔
476
        if len(warnings) > 0 {
2✔
477
                return newValidationWarning(warnings)
1✔
478
        }
1✔
479

480
        return nil
1✔
481
}
482

483
func (wh *WebhookHandler) validateFeatureGatesOnUpdate(requested, exists *v1beta1.HyperConverged) error {
1✔
484
        warnings := wh.validateDeprecatedFeatureGates(requested)
1✔
485
        warnings = validateOldFGOnUpdate(warnings, requested, exists)
1✔
486

1✔
487
        if len(warnings) > 0 {
2✔
488
                return newValidationWarning(warnings)
1✔
489
        }
1✔
490

491
        return nil
1✔
492
}
493

494
func (wh *WebhookHandler) validateDeprecatedFeatureGates(hc *v1beta1.HyperConverged) []string {
1✔
495
        var warnings []string
1✔
496

1✔
497
        //nolint:staticcheck
1✔
498
        if hc.Spec.FeatureGates.WithHostPassthroughCPU != nil {
2✔
499
                warnings = append(warnings, fmt.Sprintf(fgDeprecationWarning, "withHostPassthroughCPU"))
1✔
500
        }
1✔
501

502
        //nolint:staticcheck
503
        if hc.Spec.FeatureGates.DeployTektonTaskResources != nil {
2✔
504
                warnings = append(warnings, fmt.Sprintf(fgDeprecationWarning, "deployTektonTaskResources"))
1✔
505
        }
1✔
506

507
        //nolint:staticcheck
508
        if hc.Spec.FeatureGates.NonRoot != nil {
2✔
509
                warnings = append(warnings, fmt.Sprintf(fgDeprecationWarning, "nonRoot"))
1✔
510
        }
1✔
511

512
        //nolint:staticcheck
513
        if hc.Spec.FeatureGates.EnableManagedTenantQuota != nil {
2✔
514
                warnings = append(warnings, fmt.Sprintf(fgDeprecationWarning, "enableManagedTenantQuota"))
1✔
515
        }
1✔
516

517
        return warnings
1✔
518
}
519

520
func (wh *WebhookHandler) validateAffinity(hc *v1beta1.HyperConverged) error {
1✔
521
        if hc.Spec.Workloads.NodePlacement != nil {
2✔
522
                if err := validateAffinity(hc.Spec.Workloads.NodePlacement.Affinity); err != nil {
2✔
523
                        return fmt.Errorf("invalid workloads node placement affinity: %v", err.Error())
1✔
524
                }
1✔
525
        }
526

527
        if hc.Spec.Infra.NodePlacement != nil {
2✔
528
                if err := validateAffinity(hc.Spec.Infra.NodePlacement.Affinity); err != nil {
2✔
529
                        return fmt.Errorf("invalid infra node placement affinity: %v", err.Error())
1✔
530
                }
1✔
531
        }
532

533
        return nil
1✔
534
}
535

536
func validateAffinity(affinity *corev1.Affinity) error {
1✔
537
        if affinity == nil || affinity.NodeAffinity == nil || affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil {
2✔
538
                return nil
1✔
539
        }
1✔
540

541
        _, err := nodeaffinity.NewNodeSelector(affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution)
1✔
542

1✔
543
        return err
1✔
544
}
545

546
func validateOldFGOnCreate(warnings []string, hc *v1beta1.HyperConverged) []string {
1✔
547
        //nolint:staticcheck
1✔
548
        if hc.Spec.FeatureGates.EnableApplicationAwareQuota != nil {
2✔
549
                warnings = append(warnings, fmt.Sprintf(fgMovedWarning, "enableApplicationAwareQuota"))
1✔
550
        }
1✔
551

552
        //nolint:staticcheck
553
        if hc.Spec.FeatureGates.EnableCommonBootImageImport != nil {
2✔
554
                warnings = append(warnings, fmt.Sprintf(fgMovedWarning, "enableCommonBootImageImport"))
1✔
555
        }
1✔
556

557
        //nolint:staticcheck
558
        if hc.Spec.FeatureGates.DeployVMConsoleProxy != nil {
2✔
559
                warnings = append(warnings, fmt.Sprintf(fgMovedWarning, "deployVmConsoleProxy"))
1✔
560
        }
1✔
561

562
        return warnings
1✔
563
}
564

565
func validateOldFGOnUpdate(warnings []string, hc, prevHC *v1beta1.HyperConverged) []string {
1✔
566
        //nolint:staticcheck
1✔
567
        if oldFGChanged(hc.Spec.FeatureGates.EnableApplicationAwareQuota, prevHC.Spec.FeatureGates.EnableApplicationAwareQuota) {
2✔
568
                warnings = append(warnings, fmt.Sprintf(fgMovedWarning, "enableApplicationAwareQuota"))
1✔
569
        }
1✔
570

571
        //nolint:staticcheck
572
        if oldFGChanged(hc.Spec.FeatureGates.EnableCommonBootImageImport, prevHC.Spec.FeatureGates.EnableCommonBootImageImport) {
2✔
573
                warnings = append(warnings, fmt.Sprintf(fgMovedWarning, "enableCommonBootImageImport"))
1✔
574
        }
1✔
575

576
        //nolint:staticcheck
577
        if oldFGChanged(hc.Spec.FeatureGates.DeployVMConsoleProxy, prevHC.Spec.FeatureGates.DeployVMConsoleProxy) {
2✔
578
                warnings = append(warnings, fmt.Sprintf(fgMovedWarning, "deployVmConsoleProxy"))
1✔
579
        }
1✔
580

581
        return warnings
1✔
582
}
583

584
func oldFGChanged(newFG, prevFG *bool) bool {
1✔
585
        return newFG != nil && (prevFG == nil || *newFG != *prevFG)
1✔
586
}
1✔
587

588
func hasRequiredHTTP2Ciphers(ciphers []string) bool {
1✔
589
        var requiredHTTP2Ciphers = []string{
1✔
590
                "ECDHE-RSA-AES128-GCM-SHA256",
1✔
591
                "ECDHE-ECDSA-AES128-GCM-SHA256",
1✔
592
        }
1✔
593

1✔
594
        // lo.Some returns true if at least 1 element of a subset is contained into a collection
1✔
595
        return lo.Some[string](requiredHTTP2Ciphers, ciphers)
1✔
596
}
1✔
597

598
// validationResponseFromStatus returns a response for admitting a request with provided Status object.
599
func validationResponseFromStatus(allowed bool, status metav1.Status) admission.Response {
×
600
        resp := admission.Response{
×
601
                AdmissionResponse: admissionv1.AdmissionResponse{
×
602
                        Allowed: allowed,
×
603
                        Result:  &status,
×
604
                },
×
605
        }
×
606
        return resp
×
607
}
×
608

609
func SelectCipherSuitesAndMinTLSVersion() ([]string, openshiftconfigv1.TLSProtocolVersion) {
1✔
610
        ci := hcoutil.GetClusterInfo()
1✔
611
        profile := ci.GetTLSSecurityProfile(hcoTLSConfigCache)
1✔
612

1✔
613
        if profile.Custom != nil {
1✔
614
                return profile.Custom.Ciphers, profile.Custom.MinTLSVersion
×
615
        }
×
616

617
        return openshiftconfigv1.TLSProfiles[profile.Type].Ciphers, openshiftconfigv1.TLSProfiles[profile.Type].MinTLSVersion
1✔
618
}
619

620
func isValidTLSProtocolVersion(pv openshiftconfigv1.TLSProtocolVersion) bool {
1✔
621
        switch pv {
1✔
622
        case
623
                openshiftconfigv1.VersionTLS10,
624
                openshiftconfigv1.VersionTLS11,
625
                openshiftconfigv1.VersionTLS12,
626
                openshiftconfigv1.VersionTLS13:
1✔
627
                return true
1✔
628
        }
629
        return false
1✔
630
}
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