@@ -17,18 +17,23 @@ limitations under the License.
1717package v1alpha1
1818
1919import (
20+ "context"
2021 "fmt"
2122 "reflect"
2223 "time"
2324
25+ corev1 "k8s.io/api/core/v1"
2426 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2528 "k8s.io/apimachinery/pkg/runtime"
2629 "k8s.io/apimachinery/pkg/util/errors"
2730 "k8s.io/apimachinery/pkg/util/intstr"
2831 ctrl "sigs.k8s.io/controller-runtime"
32+ "sigs.k8s.io/controller-runtime/pkg/client"
2933 logf "sigs.k8s.io/controller-runtime/pkg/log"
30- "sigs.k8s.io/controller-runtime/pkg/webhook"
3134 "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
35+
36+ "github.com/medik8s/node-healthcheck-operator/controllers/utils/annotations"
3237)
3338
3439const (
@@ -39,7 +44,7 @@ const (
3944 mandatoryRemediationError = "Either RemediationTemplate or at least one EscalatingRemediations must be set"
4045 mutualRemediationError = "RemediationTemplate and EscalatingRemediations usage is mutual exclusive"
4146 uniqueOrderError = "EscalatingRemediation Order must be unique"
42- uniqueRemediatorError = "Using multiple templates of same kind is not supported currently "
47+ uniqueRemediatorError = "Using multiple templates of same kind is not supported for this template "
4348 minimumTimeoutError = "EscalatingRemediation Timeout must be at least one minute"
4449)
4550
@@ -49,25 +54,30 @@ var nodehealthchecklog = logf.Log.WithName("nodehealthcheck-resource")
4954func (nhc * NodeHealthCheck ) SetupWebhookWithManager (mgr ctrl.Manager ) error {
5055 return ctrl .NewWebhookManagedBy (mgr ).
5156 For (nhc ).
57+ WithValidator (& customValidator {mgr .GetClient ()}).
5258 Complete ()
5359}
5460
5561//+kubebuilder:webhook:path=/validate-remediation-medik8s-io-v1alpha1-nodehealthcheck,mutating=false,failurePolicy=fail,sideEffects=None,groups=remediation.medik8s.io,resources=nodehealthchecks,verbs=create;update;delete,versions=v1alpha1,name=vnodehealthcheck.kb.io,admissionReviewVersions=v1
5662
57- var _ webhook.Validator = & NodeHealthCheck {}
63+ type customValidator struct {
64+ client.Client
65+ }
5866
5967// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
60- func (nhc * NodeHealthCheck ) ValidateCreate () (warnings admission.Warnings , err error ) {
68+ func (v * customValidator ) ValidateCreate (ctx context.Context , obj runtime.Object ) (warnings admission.Warnings , err error ) {
69+ nhc := obj .(* NodeHealthCheck )
6170 nodehealthchecklog .Info ("validate create" , "name" , nhc .Name )
62- return admission.Warnings {}, nhc .validate ()
71+ return admission.Warnings {}, v .validate (ctx , nhc )
6372}
6473
6574// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
66- func (nhc * NodeHealthCheck ) ValidateUpdate (old runtime.Object ) (warnings admission.Warnings , err error ) {
75+ func (v * customValidator ) ValidateUpdate (ctx context.Context , old runtime.Object , new runtime.Object ) (warnings admission.Warnings , err error ) {
76+ nhc := new .(* NodeHealthCheck )
6777 nodehealthchecklog .Info ("validate update" , "name" , nhc .Name )
6878
6979 // do the normal validation
70- if err := nhc .validate (); err != nil {
80+ if err := v .validate (ctx , nhc ); err != nil {
7181 return admission.Warnings {}, err
7282 }
7383
@@ -81,20 +91,21 @@ func (nhc *NodeHealthCheck) ValidateUpdate(old runtime.Object) (warnings admissi
8191}
8292
8393// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
84- func (nhc * NodeHealthCheck ) ValidateDelete () (warnings admission.Warnings , err error ) {
94+ func (v * customValidator ) ValidateDelete (_ context.Context , obj runtime.Object ) (warnings admission.Warnings , err error ) {
95+ nhc := obj .(* NodeHealthCheck )
8596 nodehealthchecklog .Info ("validate delete" , "name" , nhc .Name )
8697 if nhc .isRemediating () {
8798 return admission.Warnings {}, fmt .Errorf ("deletion %s" , OngoingRemediationError )
8899 }
89100 return admission.Warnings {}, nil
90101}
91102
92- func (nhc * NodeHealthCheck ) validate () error {
103+ func (v * customValidator ) validate (ctx context. Context , nhc * NodeHealthCheck ) error {
93104 aggregated := errors .NewAggregate ([]error {
94- nhc .validateMinHealthy (),
95- nhc .validateSelector (),
96- nhc .validateMutualRemediations (),
97- nhc .validateEscalatingRemediations (),
105+ v .validateMinHealthy (nhc ),
106+ v .validateSelector (nhc ),
107+ v .validateMutualRemediations (nhc ),
108+ v .validateEscalatingRemediations (ctx , nhc ),
98109 })
99110
100111 // everything else should have been covered by API server validation
@@ -103,7 +114,7 @@ func (nhc *NodeHealthCheck) validate() error {
103114 return aggregated
104115}
105116
106- func (nhc * NodeHealthCheck ) validateMinHealthy () error {
117+ func (v * customValidator ) validateMinHealthy (nhc * NodeHealthCheck ) error {
107118 // Using Minimum kubebuilder marker for IntOrStr does not work (yet)
108119 if nhc .Spec .MinHealthy == nil {
109120 return fmt .Errorf ("MinHealthy must not be empty" )
@@ -114,7 +125,7 @@ func (nhc *NodeHealthCheck) validateMinHealthy() error {
114125 return nil
115126}
116127
117- func (nhc * NodeHealthCheck ) validateSelector () error {
128+ func (v * customValidator ) validateSelector (nhc * NodeHealthCheck ) error {
118129 if len (nhc .Spec .Selector .MatchExpressions ) == 0 && len (nhc .Spec .Selector .MatchLabels ) == 0 {
119130 return fmt .Errorf (missingSelectorError )
120131 }
@@ -124,7 +135,7 @@ func (nhc *NodeHealthCheck) validateSelector() error {
124135 return nil
125136}
126137
127- func (nhc * NodeHealthCheck ) validateMutualRemediations () error {
138+ func (v * customValidator ) validateMutualRemediations (nhc * NodeHealthCheck ) error {
128139 if nhc .Spec .RemediationTemplate == nil && len (nhc .Spec .EscalatingRemediations ) == 0 {
129140 return fmt .Errorf (mandatoryRemediationError )
130141 }
@@ -134,20 +145,20 @@ func (nhc *NodeHealthCheck) validateMutualRemediations() error {
134145 return nil
135146}
136147
137- func (nhc * NodeHealthCheck ) validateEscalatingRemediations () error {
148+ func (v * customValidator ) validateEscalatingRemediations (ctx context. Context , nhc * NodeHealthCheck ) error {
138149 if nhc .Spec .EscalatingRemediations == nil {
139150 return nil
140151 }
141152
142153 aggregated := errors .NewAggregate ([]error {
143- nhc .validateEscalatingRemediationsUniqueOrder (),
144- nhc .validateEscalatingRemediationsTimeout (),
145- nhc .validateEscalatingRemediationsUniqueRemediator (),
154+ v .validateEscalatingRemediationsUniqueOrder (nhc ),
155+ v .validateEscalatingRemediationsTimeout (nhc ),
156+ v .validateEscalatingRemediationsUniqueRemediator (ctx , nhc ),
146157 })
147158 return aggregated
148159}
149160
150- func (nhc * NodeHealthCheck ) validateEscalatingRemediationsUniqueOrder () error {
161+ func (v * customValidator ) validateEscalatingRemediationsUniqueOrder (nhc * NodeHealthCheck ) error {
151162 orders := make (map [int ]struct {}, len (nhc .Spec .EscalatingRemediations ))
152163 for _ , rem := range nhc .Spec .EscalatingRemediations {
153164 if _ , exists := orders [rem .Order ]; exists {
@@ -158,7 +169,7 @@ func (nhc *NodeHealthCheck) validateEscalatingRemediationsUniqueOrder() error {
158169 return nil
159170}
160171
161- func (nhc * NodeHealthCheck ) validateEscalatingRemediationsTimeout () error {
172+ func (v * customValidator ) validateEscalatingRemediationsTimeout (nhc * NodeHealthCheck ) error {
162173 for _ , rem := range nhc .Spec .EscalatingRemediations {
163174 if rem .Timeout .Duration < 1 * time .Minute {
164175 return fmt .Errorf ("%s: found timeout %v" , minimumTimeoutError , rem .Timeout )
@@ -167,18 +178,37 @@ func (nhc *NodeHealthCheck) validateEscalatingRemediationsTimeout() error {
167178 return nil
168179}
169180
170- func (nhc * NodeHealthCheck ) validateEscalatingRemediationsUniqueRemediator () error {
171- // this is a workaround until we designed a way to support multiple remediators of same kind
181+ func (v * customValidator ) validateEscalatingRemediationsUniqueRemediator (ctx context.Context , nhc * NodeHealthCheck ) error {
172182 remediators := make (map [string ]struct {}, len (nhc .Spec .EscalatingRemediations ))
173183 for _ , rem := range nhc .Spec .EscalatingRemediations {
174- if _ , exists := remediators [rem .RemediationTemplate .Kind ]; exists {
175- return fmt .Errorf ("%s: duplicate template kind: %v" , uniqueRemediatorError , rem .RemediationTemplate .Kind )
184+ kind := rem .RemediationTemplate .Kind
185+ if _ , exists := remediators [kind ]; exists && ! v .isMultipleTemplatesSupported (ctx , rem .RemediationTemplate ) {
186+ return fmt .Errorf ("%s: duplicate template kind: %v" , uniqueRemediatorError , kind )
176187 }
177- remediators [rem . RemediationTemplate . Kind ] = struct {}{}
188+ remediators [kind ] = struct {}{}
178189 }
179190 return nil
180191}
181192
193+ func (v * customValidator ) isMultipleTemplatesSupported (ctx context.Context , nhcExpectedTemplate corev1.ObjectReference ) bool {
194+ templateCRBase := & unstructured.Unstructured {}
195+ templateCRBase .SetGroupVersionKind (nhcExpectedTemplate .GroupVersionKind ())
196+ templateList := & unstructured.UnstructuredList {Object : templateCRBase .Object }
197+
198+ if err := v .Client .List (ctx , templateList ); err != nil || len (templateList .Items ) == 0 {
199+ nodehealthchecklog .Error (err , "failed to fetch CR Templates" , "template kind" , nhcExpectedTemplate .GroupVersionKind ().Kind )
200+ return false
201+ }
202+
203+ for _ , actualTemplate := range templateList .Items {
204+ if ! annotations .HasMultipleTemplatesAnnotation (& actualTemplate ) {
205+ return false
206+ }
207+ }
208+
209+ return true
210+ }
211+
182212func (nhc * NodeHealthCheck ) isRestrictedFieldUpdated (old * NodeHealthCheck ) (bool , string ) {
183213 // modifying these fields can cause dangling remediations
184214 if ! reflect .DeepEqual (nhc .Spec .Selector , old .Spec .Selector ) {
0 commit comments