Skip to content

Commit 110c781

Browse files
Add webhook for BIOSSettings resources (#315)
1 parent 041ad5d commit 110c781

File tree

7 files changed

+388
-1
lines changed

7 files changed

+388
-1
lines changed

cmd/manager/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func init() {
4949
//+kubebuilder:scaffold:scheme
5050
}
5151

52-
func main() {
52+
func main() { // nolint: gocyclo
5353
var (
5454
metricsAddr string
5555
metricsCertPath string

config/webhook/manifests.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,26 @@ kind: ValidatingWebhookConfiguration
44
metadata:
55
name: validating-webhook-configuration
66
webhooks:
7+
- admissionReviewVersions:
8+
- v1
9+
clientConfig:
10+
service:
11+
name: webhook-service
12+
namespace: system
13+
path: /validate-metal-ironcore-dev-v1alpha1-biossettings
14+
failurePolicy: Fail
15+
name: vbiossettings-v1alpha1.kb.io
16+
rules:
17+
- apiGroups:
18+
- metal.ironcore.dev
19+
apiVersions:
20+
- v1alpha1
21+
operations:
22+
- CREATE
23+
- UPDATE
24+
resources:
25+
- biossettings
26+
sideEffects: None
727
- admissionReviewVersions:
828
- v1
929
clientConfig:

dist/chart/templates/webhook/webhooks.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,26 @@ metadata:
1111
labels:
1212
{{- include "chart.labels" . | nindent 4 }}
1313
webhooks:
14+
- name: vbiossettings-v1alpha1.kb.io
15+
clientConfig:
16+
service:
17+
name: metal-operator-webhook-service
18+
namespace: {{ .Release.Namespace }}
19+
path: /validate-metal-ironcore-dev-v1alpha1-biossettings
20+
failurePolicy: Fail
21+
sideEffects: None
22+
admissionReviewVersions:
23+
- v1
24+
rules:
25+
- operations:
26+
- CREATE
27+
- UPDATE
28+
apiGroups:
29+
- metal.ironcore.dev
30+
apiVersions:
31+
- v1alpha1
32+
resources:
33+
- biossettings
1434
- name: vendpoint-v1alpha1.kb.io
1535
clientConfig:
1636
service:
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package v1alpha1
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
apierrors "k8s.io/apimachinery/pkg/api/errors"
11+
"k8s.io/apimachinery/pkg/runtime"
12+
"k8s.io/apimachinery/pkg/runtime/schema"
13+
"k8s.io/apimachinery/pkg/util/validation/field"
14+
ctrl "sigs.k8s.io/controller-runtime"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
16+
logf "sigs.k8s.io/controller-runtime/pkg/log"
17+
"sigs.k8s.io/controller-runtime/pkg/webhook"
18+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
19+
20+
metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1"
21+
)
22+
23+
// nolint:unused
24+
// log is for logging in this package.
25+
var biossettingslog = logf.Log.WithName("biossettings-resource")
26+
27+
// SetupBIOSSettingsWebhookWithManager registers the webhook for BIOSSettings in the manager.
28+
func SetupBIOSSettingsWebhookWithManager(mgr ctrl.Manager) error {
29+
return ctrl.NewWebhookManagedBy(mgr).For(&metalv1alpha1.BIOSSettings{}).
30+
WithValidator(&BIOSSettingsCustomValidator{Client: mgr.GetClient()}).
31+
Complete()
32+
}
33+
34+
// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
35+
// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
36+
// +kubebuilder:webhook:path=/validate-metal-ironcore-dev-v1alpha1-biossettings,mutating=false,failurePolicy=fail,sideEffects=None,groups=metal.ironcore.dev,resources=biossettings,verbs=create;update,versions=v1alpha1,name=vbiossettings-v1alpha1.kb.io,admissionReviewVersions=v1
37+
38+
// BIOSSettingsCustomValidator struct is responsible for validating the BIOSSettings resource
39+
// when it is created, updated, or deleted.
40+
//
41+
// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,
42+
// as this struct is used only for temporary operations and does not need to be deeply copied.
43+
type BIOSSettingsCustomValidator struct {
44+
Client client.Client
45+
}
46+
47+
var _ webhook.CustomValidator = &BIOSSettingsCustomValidator{}
48+
49+
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type BIOSSettings.
50+
func (v *BIOSSettingsCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
51+
biossettings, ok := obj.(*metalv1alpha1.BIOSSettings)
52+
if !ok {
53+
return nil, fmt.Errorf("expected a BIOSSettings object but got %T", obj)
54+
}
55+
biossettingslog.Info("Validation for BIOSSettings upon creation", "name", biossettings.GetName())
56+
57+
biosSettingsList := &metalv1alpha1.BIOSSettingsList{}
58+
if err := v.Client.List(ctx, biosSettingsList); err != nil {
59+
return nil, fmt.Errorf("failed to list BIOSSettingsList: %w", err)
60+
}
61+
62+
for _, bs := range biosSettingsList.Items {
63+
if biossettings.Spec.ServerRef.Name == bs.Spec.ServerRef.Name {
64+
return nil, apierrors.NewInvalid(
65+
schema.GroupKind{Group: biossettings.GroupVersionKind().Group, Kind: biossettings.Kind},
66+
biossettings.GetName(), field.ErrorList{field.Duplicate(field.NewPath("spec").Child("ServerRef").Child("Name"), bs.Spec.ServerRef.Name)})
67+
}
68+
}
69+
70+
return nil, nil
71+
}
72+
73+
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type BIOSSettings.
74+
func (v *BIOSSettingsCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
75+
biossettings, ok := newObj.(*metalv1alpha1.BIOSSettings)
76+
if !ok {
77+
return nil, fmt.Errorf("expected a BIOSSettings object for the newObj but got %T", newObj)
78+
}
79+
biossettingslog.Info("Validation for BIOSSettings upon update", "name", biossettings.GetName())
80+
81+
biosSettingsList := &metalv1alpha1.BIOSSettingsList{}
82+
if err := v.Client.List(ctx, biosSettingsList); err != nil {
83+
return nil, fmt.Errorf("failed to list BIOSSettingsList: %w", err)
84+
}
85+
86+
for _, bs := range biosSettingsList.Items {
87+
if biossettings.Spec.ServerRef.Name == bs.Spec.ServerRef.Name && biossettings.Name != bs.Name {
88+
return nil, apierrors.NewInvalid(
89+
schema.GroupKind{Group: biossettings.GroupVersionKind().Group, Kind: biossettings.Kind},
90+
biossettings.GetName(), field.ErrorList{field.Duplicate(field.NewPath("spec").Child("ServerRef").Child("Name"), bs.Spec.ServerRef.Name)})
91+
}
92+
}
93+
return nil, nil
94+
}
95+
96+
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type BIOSSettings.
97+
func (v *BIOSSettingsCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
98+
biossettings, ok := obj.(*metalv1alpha1.BIOSSettings)
99+
if !ok {
100+
return nil, fmt.Errorf("expected a BIOSSettings object but got %T", obj)
101+
}
102+
biossettingslog.Info("Validation for BIOSSettings upon deletion", "name", biossettings.GetName())
103+
104+
// TODO(user): fill in your validation logic upon object deletion.
105+
106+
return nil, nil
107+
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package v1alpha1
5+
6+
import (
7+
. "github.com/onsi/ginkgo/v2"
8+
. "github.com/onsi/gomega"
9+
v1 "k8s.io/api/core/v1"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
12+
metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1"
13+
// TODO (user): Add any additional imports if needed
14+
)
15+
16+
var _ = Describe("BIOSSettings Webhook", func() {
17+
var (
18+
obj *metalv1alpha1.BIOSSettings
19+
oldObj *metalv1alpha1.BIOSSettings
20+
validator BIOSSettingsCustomValidator
21+
)
22+
23+
BeforeEach(func() {
24+
obj = &metalv1alpha1.BIOSSettings{}
25+
oldObj = &metalv1alpha1.BIOSSettings{}
26+
validator = BIOSSettingsCustomValidator{Client: k8sClient}
27+
Expect(validator).NotTo(BeNil(), "Expected validator to be initialized")
28+
Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized")
29+
Expect(obj).NotTo(BeNil(), "Expected obj to be initialized")
30+
})
31+
32+
AfterEach(func() {
33+
// TODO (user): Add any teardown logic common to all tests
34+
})
35+
36+
Context("When creating or updating BIOSSettings under Validating Webhook", func() {
37+
38+
It("Should deny creation if a Spec.ServerRef field is duplicate", func(ctx SpecContext) {
39+
By("Creating an BIOSSetttings")
40+
biosSettingsV1 := &metalv1alpha1.BIOSSettings{
41+
ObjectMeta: metav1.ObjectMeta{
42+
Namespace: "ns.Name",
43+
GenerateName: "test-",
44+
},
45+
Spec: metalv1alpha1.BIOSSettingsSpec{
46+
BIOSSettings: metalv1alpha1.Settings{Version: "P70 v1.45 (12/06/2017)", SettingsMap: map[string]string{}},
47+
ServerRef: &v1.LocalObjectReference{Name: "foo"},
48+
ServerMaintenancePolicy: metalv1alpha1.ServerMaintenancePolicyEnforced,
49+
},
50+
}
51+
Expect(k8sClient.Create(ctx, biosSettingsV1)).To(Succeed())
52+
DeferCleanup(k8sClient.Delete, biosSettingsV1)
53+
54+
By("Creating another BIOSSettings with existing ServerRef")
55+
biosSettingsV2 := &metalv1alpha1.BIOSSettings{
56+
ObjectMeta: metav1.ObjectMeta{
57+
Namespace: "ns.Name",
58+
GenerateName: "test-",
59+
},
60+
Spec: metalv1alpha1.BIOSSettingsSpec{
61+
BIOSSettings: metalv1alpha1.Settings{Version: "P71 v1.45 (12/06/2017)", SettingsMap: map[string]string{}},
62+
ServerRef: &v1.LocalObjectReference{Name: "foo"},
63+
ServerMaintenancePolicy: metalv1alpha1.ServerMaintenancePolicyEnforced,
64+
},
65+
}
66+
Expect(validator.ValidateCreate(ctx, biosSettingsV2)).Error().To(HaveOccurred())
67+
})
68+
69+
It("Should create if a Spec.ServerRef field is NOT duplicate", func() {
70+
By("Creating an BIOSSetttings")
71+
biosSettingsV1 := &metalv1alpha1.BIOSSettings{
72+
ObjectMeta: metav1.ObjectMeta{
73+
Namespace: "ns.Name",
74+
GenerateName: "test-",
75+
},
76+
Spec: metalv1alpha1.BIOSSettingsSpec{
77+
BIOSSettings: metalv1alpha1.Settings{Version: "P70 v1.45 (12/06/2017)", SettingsMap: map[string]string{}},
78+
ServerRef: &v1.LocalObjectReference{Name: "foo"},
79+
ServerMaintenancePolicy: metalv1alpha1.ServerMaintenancePolicyEnforced,
80+
},
81+
}
82+
Expect(k8sClient.Create(ctx, biosSettingsV1)).To(Succeed())
83+
DeferCleanup(k8sClient.Delete, biosSettingsV1)
84+
85+
By("Creating another BIOSSetting with different ServerRef")
86+
biosSettingsV2 := &metalv1alpha1.BIOSSettings{
87+
ObjectMeta: metav1.ObjectMeta{
88+
Namespace: "ns.Name",
89+
GenerateName: "test-",
90+
},
91+
Spec: metalv1alpha1.BIOSSettingsSpec{
92+
BIOSSettings: metalv1alpha1.Settings{Version: "P71 v1.45 (12/06/2017)", SettingsMap: map[string]string{}},
93+
ServerRef: &v1.LocalObjectReference{Name: "bar"},
94+
ServerMaintenancePolicy: metalv1alpha1.ServerMaintenancePolicyEnforced,
95+
},
96+
}
97+
Expect(k8sClient.Create(ctx, biosSettingsV2)).To(Succeed())
98+
DeferCleanup(k8sClient.Delete, biosSettingsV2)
99+
})
100+
101+
It("Should deny update if a Spec.ServerRef field is duplicate", func() {
102+
By("Creating an BIOSSetttings")
103+
biosSettingsV1 := &metalv1alpha1.BIOSSettings{
104+
ObjectMeta: metav1.ObjectMeta{
105+
Namespace: "ns.Name",
106+
GenerateName: "test-",
107+
},
108+
Spec: metalv1alpha1.BIOSSettingsSpec{
109+
BIOSSettings: metalv1alpha1.Settings{Version: "P70 v1.45 (12/06/2017)", SettingsMap: map[string]string{}},
110+
ServerRef: &v1.LocalObjectReference{Name: "foo"},
111+
ServerMaintenancePolicy: metalv1alpha1.ServerMaintenancePolicyEnforced,
112+
},
113+
}
114+
Expect(k8sClient.Create(ctx, biosSettingsV1)).To(Succeed())
115+
DeferCleanup(k8sClient.Delete, biosSettingsV1)
116+
117+
By("Creating an BIOSSetting with different ServerRef")
118+
biosSettingsV2 := &metalv1alpha1.BIOSSettings{
119+
ObjectMeta: metav1.ObjectMeta{
120+
Namespace: "ns.Name",
121+
GenerateName: "test-",
122+
},
123+
Spec: metalv1alpha1.BIOSSettingsSpec{
124+
BIOSSettings: metalv1alpha1.Settings{Version: "P71 v1.45 (12/06/2017)", SettingsMap: map[string]string{}},
125+
ServerRef: &v1.LocalObjectReference{Name: "bar"},
126+
ServerMaintenancePolicy: metalv1alpha1.ServerMaintenancePolicyEnforced,
127+
},
128+
}
129+
Expect(k8sClient.Create(ctx, biosSettingsV2)).To(Succeed())
130+
DeferCleanup(k8sClient.Delete, biosSettingsV2)
131+
132+
By("Updating an biosSettingsV2 to conflicting Spec.ServerRef")
133+
biosSettingsV2Updated := biosSettingsV2.DeepCopy()
134+
biosSettingsV2Updated.Spec.ServerRef = &v1.LocalObjectReference{Name: "foo"}
135+
Expect(validator.ValidateUpdate(ctx, biosSettingsV2, biosSettingsV2Updated)).Error().To(HaveOccurred())
136+
})
137+
138+
It("Should allow update if a different field is duplicate", func() {
139+
By("Creating an BIOSSetttings")
140+
biosSettingsV1 := &metalv1alpha1.BIOSSettings{
141+
ObjectMeta: metav1.ObjectMeta{
142+
Namespace: "ns.Name",
143+
GenerateName: "test-",
144+
},
145+
Spec: metalv1alpha1.BIOSSettingsSpec{
146+
BIOSSettings: metalv1alpha1.Settings{Version: "P70 v1.45 (12/06/2017)", SettingsMap: map[string]string{}},
147+
ServerRef: &v1.LocalObjectReference{Name: "foo"},
148+
ServerMaintenancePolicy: metalv1alpha1.ServerMaintenancePolicyEnforced,
149+
},
150+
}
151+
Expect(k8sClient.Create(ctx, biosSettingsV1)).To(Succeed())
152+
DeferCleanup(k8sClient.Delete, biosSettingsV1)
153+
154+
By("Creating an BIOSSetting with different ServerRef")
155+
biosSettingsV2 := &metalv1alpha1.BIOSSettings{
156+
ObjectMeta: metav1.ObjectMeta{
157+
Namespace: "ns.Name",
158+
GenerateName: "test-",
159+
},
160+
Spec: metalv1alpha1.BIOSSettingsSpec{
161+
BIOSSettings: metalv1alpha1.Settings{Version: "P71 v1.45 (12/06/2017)", SettingsMap: map[string]string{}},
162+
ServerRef: &v1.LocalObjectReference{Name: "bar"},
163+
ServerMaintenancePolicy: metalv1alpha1.ServerMaintenancePolicyEnforced,
164+
},
165+
}
166+
Expect(k8sClient.Create(ctx, biosSettingsV2)).To(Succeed())
167+
DeferCleanup(k8sClient.Delete, biosSettingsV2)
168+
169+
By("Updating an biosSettingsV2 to conflicting Spec.BIOSSettings")
170+
biosSettingsV2Updated := biosSettingsV2.DeepCopy()
171+
biosSettingsV2Updated.Spec.BIOSSettings = biosSettingsV1.Spec.BIOSSettings
172+
Expect(validator.ValidateUpdate(ctx, biosSettingsV2, biosSettingsV2Updated)).Error().ToNot(HaveOccurred())
173+
})
174+
175+
It("Should allow update if a ServerRef field is NOT duplicate", func() {
176+
By("Creating an BIOSSetttings")
177+
biosSettingsV1 := &metalv1alpha1.BIOSSettings{
178+
ObjectMeta: metav1.ObjectMeta{
179+
Namespace: "ns.Name",
180+
GenerateName: "test-",
181+
},
182+
Spec: metalv1alpha1.BIOSSettingsSpec{
183+
BIOSSettings: metalv1alpha1.Settings{Version: "P70 v1.45 (12/06/2017)", SettingsMap: map[string]string{}},
184+
ServerRef: &v1.LocalObjectReference{Name: "foo"},
185+
ServerMaintenancePolicy: metalv1alpha1.ServerMaintenancePolicyEnforced,
186+
},
187+
}
188+
Expect(k8sClient.Create(ctx, biosSettingsV1)).To(Succeed())
189+
DeferCleanup(k8sClient.Delete, biosSettingsV1)
190+
191+
By("Creating an BIOSSetting with different ServerRef")
192+
biosSettingsV2 := &metalv1alpha1.BIOSSettings{
193+
ObjectMeta: metav1.ObjectMeta{
194+
Namespace: "ns.Name",
195+
GenerateName: "test-",
196+
},
197+
Spec: metalv1alpha1.BIOSSettingsSpec{
198+
BIOSSettings: metalv1alpha1.Settings{Version: "P71 v1.45 (12/06/2017)", SettingsMap: map[string]string{}},
199+
ServerRef: &v1.LocalObjectReference{Name: "bar"},
200+
ServerMaintenancePolicy: metalv1alpha1.ServerMaintenancePolicyEnforced,
201+
},
202+
}
203+
Expect(k8sClient.Create(ctx, biosSettingsV2)).To(Succeed())
204+
DeferCleanup(k8sClient.Delete, biosSettingsV2)
205+
206+
By("Updating an biosSettingsV2 to NON conflicting Spec.ServerRef ")
207+
biosSettingsV2Updated := biosSettingsV2.DeepCopy()
208+
biosSettingsV2Updated.Spec.ServerRef = &v1.LocalObjectReference{Name: "foobar"}
209+
Expect(validator.ValidateUpdate(ctx, biosSettingsV2, biosSettingsV2Updated)).Error().ToNot(HaveOccurred())
210+
})
211+
})
212+
213+
})

internal/webhook/v1alpha1/webhook_suite_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ var _ = BeforeSuite(func() {
119119
err = SetupEndpointWebhookWithManager(mgr)
120120
Expect(err).NotTo(HaveOccurred())
121121

122+
err = SetupBIOSSettingsWebhookWithManager(mgr)
123+
Expect(err).NotTo(HaveOccurred())
124+
122125
// +kubebuilder:scaffold:webhook
123126

124127
go func() {

0 commit comments

Comments
 (0)