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

kubevirt / hyperconverged-cluster-operator / 20108931282

10 Dec 2025 06:19PM UTC coverage: 76.573% (-0.2%) from 76.787%
20108931282

push

github

web-flow
CNV-61721: Add ValidatingAdmissionPolicy to validate the HyperConverged namespace (#3912)

* Add the new admission policy controller

The current implementation of preventing the creation of the
HyperConverged CR in non-allowed namespace, is not working in Openshift,
where becasue of a race condition, the webhook's namespace selector is
removed by OLM.

This commit adds a new controller, to create and reconcile a
ValidatingAdmissionPolicy and the related
ValidatingAdmissionPolicyBinding, to perform the same validation.

The reason we're doing it in a new controller, is because we need the
ValidatingAdmissionPolicy to be set, even if the HyperConverged CR is
not deployed, while our main controller only reconciles resources if
the HyperConverged CR is deployed.

Signed-off-by: Nahshon Unna Tsameret <nahsh.ut@gmail.com>

* Register the admission policy controller on boot

Signed-off-by: Nahshon Unna Tsameret <nahsh.ut@gmail.com>

* Remove the current validation

Remove the existing validation of the HyperConverged CR namespace from
the validation webhook, as it is now done by the policy, created by the
admission policy controller.

Signed-off-by: Nahshon Unna Tsameret <nahsh.ut@gmail.com>

* Don't remove the namespace selector from the validation wh

OLM adds a namespace selection on the validation webhook CR, causing the
namespace validation to be not relevant.

The webhook setup logic removes this selector, but actually this is
reconciled by OLM, and eventually, user can still create the
HyperConverged CR in any namespace.

The issue is now handled by a ValidationgAdmissionPolicy, and so we
don't need this logic anymore, and so this commit removes it.

Signed-off-by: Nahshon Unna Tsameret <nahsh.ut@gmail.com>

---------

Signed-off-by: Nahshon Unna Tsameret <nahsh.ut@gmail.com>

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

5 existing lines in 1 file now uncovered.

8201 of 10710 relevant lines covered (76.57%)

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

© 2026 Coveralls, Inc