Skip to content

Commit 2401775

Browse files
Merge pull request #301 from mshitrit/support-multiple-same-kind-templates
Enabling multiple templates of the same kind
2 parents ff1a2eb + 1c01b3d commit 2401775

17 files changed

+466
-175
lines changed

api/v1alpha1/nodehealthcheck_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,11 @@ type Remediation struct {
289289
//+optional
290290
//+operator-sdk:csv:customresourcedefinitions:type=status
291291
TimedOut *metav1.Time `json:"timedOut,omitempty"`
292+
293+
// TemplateName is required when using several templates of the same kind
294+
// +optional
295+
//+operator-sdk:csv:customresourcedefinitions:type=status
296+
TemplateName string `json:"templateName,omitempty"`
292297
}
293298

294299
//+kubebuilder:object:root=true

api/v1alpha1/nodehealthcheck_webhook.go

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,23 @@ limitations under the License.
1717
package v1alpha1
1818

1919
import (
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

3439
const (
@@ -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")
4954
func (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+
182212
func (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

Comments
 (0)