pkg/controller/logstash/validation/webhook.go (95 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.
package validation
import (
"context"
"net/http"
admissionv1 "k8s.io/api/admission/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
lsv1alpha1 "github.com/elastic/cloud-on-k8s/v3/pkg/apis/logstash/v1alpha1"
"github.com/elastic/cloud-on-k8s/v3/pkg/utils/k8s"
ulog "github.com/elastic/cloud-on-k8s/v3/pkg/utils/log"
"github.com/elastic/cloud-on-k8s/v3/pkg/utils/set"
)
// +kubebuilder:webhook:path=/validate-logstash-k8s-elastic-co-v1alpha1-logstash,mutating=false,failurePolicy=ignore,groups=logstash.k8s.elastic.co,resources=logstashes,verbs=create;update,versions=v1alpha1,name=elastic-logstash-validation-v1alpha1.k8s.elastic.co,sideEffects=None,admissionReviewVersions=v1;v1beta1,matchPolicy=Exact
const (
webhookPath = "/validate-logstash-k8s-elastic-co-v1alpha1-logstash"
)
var lslog = ulog.Log.WithName("ls-validation")
// RegisterWebhook will register the Logstash validating webhook.
func RegisterWebhook(mgr ctrl.Manager, validateStorageClass bool, managedNamespaces []string) {
wh := &validatingWebhook{
client: mgr.GetClient(),
decoder: admission.NewDecoder(mgr.GetScheme()),
validateStorageClass: validateStorageClass,
managedNamespaces: set.Make(managedNamespaces...),
}
lslog.Info("Registering Logstash validating webhook", "path", webhookPath)
mgr.GetWebhookServer().Register(webhookPath, &webhook.Admission{Handler: wh})
}
type validatingWebhook struct {
client k8s.Client
decoder admission.Decoder
validateStorageClass bool
managedNamespaces set.StringSet
}
func (wh *validatingWebhook) ValidateCreate(ls *lsv1alpha1.Logstash) error {
lslog.V(1).Info("validate create", "name", ls.Name)
return ValidateLogstash(ls)
}
func (wh *validatingWebhook) ValidateUpdate(ctx context.Context, prev *lsv1alpha1.Logstash, curr *lsv1alpha1.Logstash) error {
lslog.V(1).Info("validate update", "name", curr.Name)
var errs field.ErrorList
for _, val := range updateValidations(ctx, wh.client, wh.validateStorageClass) {
if err := val(prev, curr); err != nil {
errs = append(errs, err...)
}
}
if len(errs) > 0 {
return apierrors.NewInvalid(
schema.GroupKind{Group: "logstash.k8s.elastic.co", Kind: lsv1alpha1.Kind},
curr.Name, errs)
}
return ValidateLogstash(curr)
}
// Handle is called when any request is sent to the webhook, satisfying the admission.Handler interface.
func (wh *validatingWebhook) Handle(ctx context.Context, req admission.Request) admission.Response {
ls := &lsv1alpha1.Logstash{}
err := wh.decoder.DecodeRaw(req.Object, ls)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
// If this Logstash instance is not within the set of managed namespaces
// for this operator ignore this request.
if wh.managedNamespaces.Count() > 0 && !wh.managedNamespaces.Has(ls.Namespace) {
lslog.V(1).Info("Skip Logstash resource validation", "name", ls.Name, "namespace", ls.Namespace)
return admission.Allowed("")
}
if req.Operation == admissionv1.Create {
err = wh.ValidateCreate(ls)
if err != nil {
return admission.Denied(err.Error())
}
}
if req.Operation == admissionv1.Update {
oldObj := &lsv1alpha1.Logstash{}
err = wh.decoder.DecodeRaw(req.OldObject, oldObj)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
err = wh.ValidateUpdate(ctx, oldObj, ls)
if err != nil {
return admission.Denied(err.Error())
}
}
return admission.Allowed("")
}
// ValidateLogstash validates an Logstash instance against a set of validation funcs.
func ValidateLogstash(ls *lsv1alpha1.Logstash) error {
errs := check(ls, validations())
if len(errs) > 0 {
return apierrors.NewInvalid(
schema.GroupKind{Group: "logstash.k8s.elastic.co", Kind: lsv1alpha1.Kind},
ls.Name,
errs,
)
}
return nil
}