diff --git a/api/v1/ytsaurus_types.go b/api/v1/ytsaurus_types.go index c3f28605..21d54aee 100644 --- a/api/v1/ytsaurus_types.go +++ b/api/v1/ytsaurus_types.go @@ -18,9 +18,8 @@ package v1 import ( corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! diff --git a/api/v1/ytsaurus_webhook.go b/api/v1/ytsaurus_webhook.go index e778fff9..4c0318be 100644 --- a/api/v1/ytsaurus_webhook.go +++ b/api/v1/ytsaurus_webhook.go @@ -17,10 +17,12 @@ limitations under the License. package v1 import ( + "context" "fmt" "strings" corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -32,7 +34,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" "github.com/ytsaurus/ytsaurus-k8s-operator/pkg/consts" @@ -41,48 +42,54 @@ import ( // log is for logging in this package. var ytsauruslog = logf.Log.WithName("ytsaurus-resource") +type ytsaurusValidator struct { + Client client.Client +} + func (r *Ytsaurus) SetupWebhookWithManager(mgr ctrl.Manager) error { + validator := &ytsaurusValidator{ + Client: mgr.GetClient(), + } return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(validator). Complete() } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. //+kubebuilder:webhook:path=/validate-cluster-ytsaurus-tech-v1-ytsaurus,mutating=false,failurePolicy=fail,sideEffects=None,groups=cluster.ytsaurus.tech,resources=ytsaurus,verbs=create;update,versions=v1,name=vytsaurus.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &Ytsaurus{} - ////////////////////////////////////////////////// -func (r *Ytsaurus) validateDiscovery(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateDiscovery(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList - allErrors = append(allErrors, r.validateInstanceSpec(r.Spec.Discovery.InstanceSpec, field.NewPath("spec").Child("discovery"))...) + allErrors = append(allErrors, r.validateInstanceSpec(newYtsaurus.Spec.Discovery.InstanceSpec, field.NewPath("spec").Child("discovery"))...) return allErrors } -func (r *Ytsaurus) validatePrimaryMasters(old *Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validatePrimaryMasters(newYtsaurus, oldYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList path := field.NewPath("spec").Child("primaryMasters") - allErrors = append(allErrors, r.validateInstanceSpec(r.Spec.PrimaryMasters.InstanceSpec, path)...) - allErrors = append(allErrors, r.validateHostAddresses(r.Spec.PrimaryMasters, path)...) + allErrors = append(allErrors, r.validateInstanceSpec(newYtsaurus.Spec.PrimaryMasters.InstanceSpec, path)...) + allErrors = append(allErrors, r.validateHostAddresses(newYtsaurus, path)...) - if FindFirstLocation(r.Spec.PrimaryMasters.Locations, LocationTypeMasterChangelogs) == nil { + if FindFirstLocation(newYtsaurus.Spec.PrimaryMasters.Locations, LocationTypeMasterChangelogs) == nil { allErrors = append(allErrors, field.NotFound(path.Child("locations"), LocationTypeMasterChangelogs)) } - if FindFirstLocation(r.Spec.PrimaryMasters.Locations, LocationTypeMasterSnapshots) == nil { + if FindFirstLocation(newYtsaurus.Spec.PrimaryMasters.Locations, LocationTypeMasterSnapshots) == nil { allErrors = append(allErrors, field.NotFound(path.Child("locations"), LocationTypeMasterSnapshots)) } - if old != nil && old.Spec.PrimaryMasters.CellTag != r.Spec.PrimaryMasters.CellTag { - allErrors = append(allErrors, field.Invalid(path.Child("cellTag"), r.Spec.PrimaryMasters.CellTag, "Could not be changed")) + if oldYtsaurus != nil && oldYtsaurus.Spec.PrimaryMasters.CellTag != newYtsaurus.Spec.PrimaryMasters.CellTag { + allErrors = append(allErrors, field.Invalid(path.Child("cellTag"), newYtsaurus.Spec.PrimaryMasters.CellTag, "Could not be changed")) } - if r.Spec.PrimaryMasters.InstanceCount > 1 && !r.Spec.EphemeralCluster { - affinity := r.Spec.PrimaryMasters.Affinity + if newYtsaurus.Spec.PrimaryMasters.InstanceCount > 1 && !newYtsaurus.Spec.EphemeralCluster { + affinity := newYtsaurus.Spec.PrimaryMasters.Affinity if affinity == nil || affinity.PodAntiAffinity == nil { allErrors = append(allErrors, field.Required(path.Child("affinity").Child("podAntiAffinity"), "Masters should be placed on different nodes")) } @@ -91,23 +98,23 @@ func (r *Ytsaurus) validatePrimaryMasters(old *Ytsaurus) field.ErrorList { return allErrors } -func (r *Ytsaurus) validateSecondaryMasters(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateSecondaryMasters(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList - - for i, sm := range r.Spec.SecondaryMasters { + for i, sm := range newYtsaurus.Spec.SecondaryMasters { path := field.NewPath("spec").Child("secondaryMasters").Index(i) allErrors = append(allErrors, r.validateInstanceSpec(sm.InstanceSpec, path)...) - allErrors = append(allErrors, r.validateHostAddresses(r.Spec.PrimaryMasters, path)...) + // allErrors = append(allErrors, r.validateHostAddresses(newYtsaurus.Spec.PrimaryMasters, path)...) + allErrors = append(allErrors, r.validateHostAddresses(newYtsaurus, path)...) } return allErrors } -func (r *Ytsaurus) validateHostAddresses(masterSpec MastersSpec, fieldPath *field.Path) field.ErrorList { +func (r *ytsaurusValidator) validateHostAddresses(newYtsaurus *Ytsaurus, fieldPath *field.Path) field.ErrorList { var allErrors field.ErrorList hostAddressesFieldPath := fieldPath.Child("hostAddresses") - if !ptr.Deref(masterSpec.HostNetwork, r.Spec.HostNetwork) && len(masterSpec.HostAddresses) != 0 { + if !ptr.Deref(newYtsaurus.Spec.PrimaryMasters.HostNetwork, newYtsaurus.Spec.HostNetwork) && len(newYtsaurus.Spec.PrimaryMasters.HostAddresses) != 0 { allErrors = append( allErrors, field.Required( @@ -117,13 +124,13 @@ func (r *Ytsaurus) validateHostAddresses(masterSpec MastersSpec, fieldPath *fiel ) } - if len(masterSpec.HostAddresses) != 0 && len(masterSpec.HostAddresses) != int(masterSpec.InstanceCount) { + if len(newYtsaurus.Spec.PrimaryMasters.HostAddresses) != 0 && len(newYtsaurus.Spec.PrimaryMasters.HostAddresses) != int(newYtsaurus.Spec.PrimaryMasters.InstanceCount) { instanceCountFieldPath := fieldPath.Child("instanceCount") allErrors = append( allErrors, field.Invalid( hostAddressesFieldPath, - masterSpec.HostAddresses, + newYtsaurus.Spec.PrimaryMasters.HostAddresses, fmt.Sprintf("%s list length shoud be equal to %s", hostAddressesFieldPath.String(), instanceCountFieldPath.String()), ), ) @@ -132,12 +139,12 @@ func (r *Ytsaurus) validateHostAddresses(masterSpec MastersSpec, fieldPath *fiel return allErrors } -func (r *Ytsaurus) validateHTTPProxies(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateHTTPProxies(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList httpRoles := make(map[string]bool) hasDefaultHTTPProxy := false - for i, hp := range r.Spec.HTTPProxies { + for i, hp := range newYtsaurus.Spec.HTTPProxies { path := field.NewPath("spec").Child("httpProxies").Index(i) if _, exists := httpRoles[hp.Role]; exists { allErrors = append(allErrors, field.Duplicate(path.Child("role"), hp.Role)) @@ -159,11 +166,11 @@ func (r *Ytsaurus) validateHTTPProxies(*Ytsaurus) field.ErrorList { return allErrors } -func (r *Ytsaurus) validateRPCProxies(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateRPCProxies(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList rpcRoles := make(map[string]bool) - for i, rp := range r.Spec.RPCProxies { + for i, rp := range newYtsaurus.Spec.RPCProxies { path := field.NewPath("spec").Child("rpcProxies").Index(i) if _, exists := rpcRoles[rp.Role]; exists { allErrors = append(allErrors, field.Duplicate(path.Child("role"), rp.Role)) @@ -176,11 +183,11 @@ func (r *Ytsaurus) validateRPCProxies(*Ytsaurus) field.ErrorList { return allErrors } -func (r *Ytsaurus) validateTCPProxies(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateTCPProxies(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList tcpRoles := make(map[string]bool) - for i, rp := range r.Spec.TCPProxies { + for i, rp := range newYtsaurus.Spec.TCPProxies { path := field.NewPath("spec").Child("tcpProxies").Index(i) if _, exists := tcpRoles[rp.Role]; exists { allErrors = append(allErrors, field.Duplicate(path.Child("role"), rp.Role)) @@ -193,11 +200,11 @@ func (r *Ytsaurus) validateTCPProxies(*Ytsaurus) field.ErrorList { return allErrors } -func (r *Ytsaurus) validateDataNodes(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateDataNodes(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList names := make(map[string]bool) - for i, dn := range r.Spec.DataNodes { + for i, dn := range newYtsaurus.Spec.DataNodes { path := field.NewPath("spec").Child("dataNodes").Index(i) if _, exists := names[dn.Name]; exists { @@ -233,11 +240,11 @@ func validateSidecars(sidecars []string, path *field.Path) field.ErrorList { return allErrors } -func (r *Ytsaurus) validateExecNodes(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateExecNodes(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList names := make(map[string]bool) - for i, en := range r.Spec.ExecNodes { + for i, en := range newYtsaurus.Spec.ExecNodes { path := field.NewPath("spec").Child("execNodes").Index(i) if _, exists := names[en.Name]; exists { @@ -263,8 +270,8 @@ func (r *Ytsaurus) validateExecNodes(*Ytsaurus) field.ErrorList { } } - if r.Spec.ExecNodes != nil && len(r.Spec.ExecNodes) > 0 { - if r.Spec.Schedulers == nil { + if newYtsaurus.Spec.ExecNodes != nil && len(newYtsaurus.Spec.ExecNodes) > 0 { + if newYtsaurus.Spec.Schedulers == nil { allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("schedulers"), "execNodes doesn't make sense without schedulers")) } } @@ -272,14 +279,14 @@ func (r *Ytsaurus) validateExecNodes(*Ytsaurus) field.ErrorList { return allErrors } -func (r *Ytsaurus) validateSchedulers(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateSchedulers(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList - if r.Spec.Schedulers != nil { + if newYtsaurus.Spec.Schedulers != nil { path := field.NewPath("spec").Child("schedulers") - allErrors = append(allErrors, r.validateInstanceSpec(r.Spec.Schedulers.InstanceSpec, path)...) + allErrors = append(allErrors, r.validateInstanceSpec(newYtsaurus.Spec.Schedulers.InstanceSpec, path)...) - if r.Spec.ControllerAgents == nil { + if newYtsaurus.Spec.ControllerAgents == nil { allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("controllerAgents"), "schedulers doesn't make sense without controllerAgents")) } } @@ -287,14 +294,14 @@ func (r *Ytsaurus) validateSchedulers(*Ytsaurus) field.ErrorList { return allErrors } -func (r *Ytsaurus) validateControllerAgents(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateControllerAgents(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList - if r.Spec.ControllerAgents != nil { + if newYtsaurus.Spec.ControllerAgents != nil { path := field.NewPath("spec").Child("controllerAgents") - allErrors = append(allErrors, r.validateInstanceSpec(r.Spec.ControllerAgents.InstanceSpec, path)...) + allErrors = append(allErrors, r.validateInstanceSpec(newYtsaurus.Spec.ControllerAgents.InstanceSpec, path)...) - if r.Spec.Schedulers == nil { + if newYtsaurus.Spec.Schedulers == nil { allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("schedulers"), "controllerAgents doesn't make sense without schedulers")) } } @@ -302,11 +309,11 @@ func (r *Ytsaurus) validateControllerAgents(*Ytsaurus) field.ErrorList { return allErrors } -func (r *Ytsaurus) validateTabletNodes(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateTabletNodes(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList names := make(map[string]bool) - for i, tn := range r.Spec.TabletNodes { + for i, tn := range newYtsaurus.Spec.TabletNodes { path := field.NewPath("spec").Child("tabletNodes").Index(i) if _, exists := names[tn.Name]; exists { @@ -320,17 +327,17 @@ func (r *Ytsaurus) validateTabletNodes(*Ytsaurus) field.ErrorList { return allErrors } -func (r *Ytsaurus) validateChyt(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateChyt(_ *Ytsaurus) field.ErrorList { var allErrors field.ErrorList return allErrors } -func (r *Ytsaurus) validateStrawberry(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateStrawberry(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList - if r.Spec.StrawberryController != nil { - if r.Spec.Schedulers == nil { + if newYtsaurus.Spec.StrawberryController != nil { + if newYtsaurus.Spec.Schedulers == nil { allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("schedulers"), "schedulers are required for strawberry")) } } @@ -338,18 +345,18 @@ func (r *Ytsaurus) validateStrawberry(*Ytsaurus) field.ErrorList { return allErrors } -func (r *Ytsaurus) validateQueryTrackers(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateQueryTrackers(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList - if r.Spec.QueryTrackers != nil { + if newYtsaurus.Spec.QueryTrackers != nil { path := field.NewPath("spec").Child("queryTrackers") - allErrors = append(allErrors, r.validateInstanceSpec(r.Spec.QueryTrackers.InstanceSpec, path)...) + allErrors = append(allErrors, r.validateInstanceSpec(newYtsaurus.Spec.QueryTrackers.InstanceSpec, path)...) - if r.Spec.TabletNodes == nil || len(r.Spec.TabletNodes) == 0 { + if newYtsaurus.Spec.TabletNodes == nil || len(newYtsaurus.Spec.TabletNodes) == 0 { allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("tabletNodes"), "tabletNodes are required for queryTrackers")) } - if r.Spec.Schedulers == nil { + if newYtsaurus.Spec.Schedulers == nil { allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("schedulers"), "schedulers are required for queryTrackers")) } } @@ -357,14 +364,14 @@ func (r *Ytsaurus) validateQueryTrackers(*Ytsaurus) field.ErrorList { return allErrors } -func (r *Ytsaurus) validateQueueAgents(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateQueueAgents(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList - if r.Spec.QueueAgents != nil { + if newYtsaurus.Spec.QueueAgents != nil { path := field.NewPath("spec").Child("queueAgents") - allErrors = append(allErrors, r.validateInstanceSpec(r.Spec.QueueAgents.InstanceSpec, path)...) + allErrors = append(allErrors, r.validateInstanceSpec(newYtsaurus.Spec.QueueAgents.InstanceSpec, path)...) - if r.Spec.TabletNodes == nil || len(r.Spec.TabletNodes) == 0 { + if newYtsaurus.Spec.TabletNodes == nil || len(newYtsaurus.Spec.TabletNodes) == 0 { allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("tabletNodes"), "tabletNodes are required for queueAgents")) } } @@ -372,25 +379,25 @@ func (r *Ytsaurus) validateQueueAgents(*Ytsaurus) field.ErrorList { return allErrors } -func (r *Ytsaurus) validateSpyt(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateSpyt(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList path := field.NewPath("spec").Child("spyt") - if r.Spec.Spyt != nil { - allErrors = append(allErrors, field.Invalid(path, r.Spec.Spyt, "spyt is deprecated here, use Spyt resource instead")) + if newYtsaurus.Spec.Spyt != nil { + allErrors = append(allErrors, field.Invalid(path, newYtsaurus.Spec.Spyt, "spyt is deprecated here, use Spyt resource instead")) } return allErrors } -func (r *Ytsaurus) validateYQLAgents(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateYQLAgents(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList - if r.Spec.YQLAgents != nil { + if newYtsaurus.Spec.YQLAgents != nil { path := field.NewPath("spec").Child("YQLAgents") - allErrors = append(allErrors, r.validateInstanceSpec(r.Spec.YQLAgents.InstanceSpec, path)...) + allErrors = append(allErrors, r.validateInstanceSpec(newYtsaurus.Spec.YQLAgents.InstanceSpec, path)...) - if r.Spec.QueryTrackers == nil { + if newYtsaurus.Spec.QueryTrackers == nil { allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("queryTrackers"), "yqlAgents doesn't make sense without queryTrackers")) } } @@ -398,11 +405,11 @@ func (r *Ytsaurus) validateYQLAgents(*Ytsaurus) field.ErrorList { return allErrors } -func (r *Ytsaurus) validateUi(*Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateUi(newYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList - if r.Spec.UI != nil && r.Spec.UI.Secure { - for i, hp := range r.Spec.HTTPProxies { + if newYtsaurus.Spec.UI != nil && newYtsaurus.Spec.UI.Secure { + for i, hp := range newYtsaurus.Spec.HTTPProxies { if hp.Role != consts.DefaultHTTPProxyRole { continue } @@ -420,7 +427,7 @@ func (r *Ytsaurus) validateUi(*Ytsaurus) field.ErrorList { ////////////////////////////////////////////////// -func (r *Ytsaurus) validateInstanceSpec(instanceSpec InstanceSpec, path *field.Path) field.ErrorList { +func (r *ytsaurusValidator) validateInstanceSpec(instanceSpec InstanceSpec, path *field.Path) field.ErrorList { var allErrors field.ErrorList if instanceSpec.EnableAntiAffinity != nil { @@ -446,63 +453,100 @@ func (r *Ytsaurus) validateInstanceSpec(instanceSpec InstanceSpec, path *field.P return allErrors } -func (r *Ytsaurus) validateYtsaurus(old *Ytsaurus) field.ErrorList { +func (r *ytsaurusValidator) validateExistsYtsaurus(ctx context.Context, newYtsaurus *Ytsaurus) field.ErrorList { + var allErrors field.ErrorList + + var ytsaurusList YtsaurusList + err := r.Client.List(ctx, &ytsaurusList, &client.ListOptions{Namespace: newYtsaurus.Namespace}) + ytsauruslog.Info("validateExistsYTsaurus", "ytsaurusList", ytsaurusList) + + if err != nil && !apierrors.IsNotFound(err) { + allErrors = append(allErrors, field.InternalError(field.NewPath("k8sClient"), err)) + return allErrors + } + + // if ytsaurus is already exists and it's the same one, it is an update operation + if len(ytsaurusList.Items) == 1 && ytsaurusList.Items[0].Name == newYtsaurus.Name { + return allErrors + } else if len(ytsaurusList.Items) == 0 { + // it's the creation operation for the first ytsaurus object + return allErrors + } else { + allErrors = append(allErrors, field.Forbidden(field.NewPath("metadata").Child("namespace"), fmt.Sprintf("A Ytsaurus object already exists in the given namespace %s", newYtsaurus.Namespace))) + } + + return allErrors +} + +func (r *ytsaurusValidator) validateYtsaurus(ctx context.Context, newYtsaurus, oldYtsaurus *Ytsaurus) field.ErrorList { var allErrors field.ErrorList - allErrors = append(allErrors, r.validateDiscovery(old)...) - allErrors = append(allErrors, r.validatePrimaryMasters(old)...) - allErrors = append(allErrors, r.validateSecondaryMasters(old)...) - allErrors = append(allErrors, r.validateHTTPProxies(old)...) - allErrors = append(allErrors, r.validateRPCProxies(old)...) - allErrors = append(allErrors, r.validateTCPProxies(old)...) - allErrors = append(allErrors, r.validateDataNodes(old)...) - allErrors = append(allErrors, r.validateExecNodes(old)...) - allErrors = append(allErrors, r.validateSchedulers(old)...) - allErrors = append(allErrors, r.validateControllerAgents(old)...) - allErrors = append(allErrors, r.validateTabletNodes(old)...) - allErrors = append(allErrors, r.validateChyt(old)...) - allErrors = append(allErrors, r.validateStrawberry(old)...) - allErrors = append(allErrors, r.validateQueryTrackers(old)...) - allErrors = append(allErrors, r.validateQueueAgents(old)...) - allErrors = append(allErrors, r.validateSpyt(old)...) - allErrors = append(allErrors, r.validateYQLAgents(old)...) - allErrors = append(allErrors, r.validateUi(old)...) + allErrors = append(allErrors, r.validateDiscovery(newYtsaurus)...) + allErrors = append(allErrors, r.validatePrimaryMasters(newYtsaurus, oldYtsaurus)...) + allErrors = append(allErrors, r.validateSecondaryMasters(newYtsaurus)...) + allErrors = append(allErrors, r.validateHTTPProxies(newYtsaurus)...) + allErrors = append(allErrors, r.validateRPCProxies(newYtsaurus)...) + allErrors = append(allErrors, r.validateTCPProxies(newYtsaurus)...) + allErrors = append(allErrors, r.validateDataNodes(newYtsaurus)...) + allErrors = append(allErrors, r.validateExecNodes(newYtsaurus)...) + allErrors = append(allErrors, r.validateSchedulers(newYtsaurus)...) + allErrors = append(allErrors, r.validateControllerAgents(newYtsaurus)...) + allErrors = append(allErrors, r.validateTabletNodes(newYtsaurus)...) + allErrors = append(allErrors, r.validateChyt(newYtsaurus)...) + allErrors = append(allErrors, r.validateStrawberry(newYtsaurus)...) + allErrors = append(allErrors, r.validateQueryTrackers(newYtsaurus)...) + allErrors = append(allErrors, r.validateQueueAgents(newYtsaurus)...) + allErrors = append(allErrors, r.validateSpyt(newYtsaurus)...) + allErrors = append(allErrors, r.validateYQLAgents(newYtsaurus)...) + allErrors = append(allErrors, r.validateUi(newYtsaurus)...) + allErrors = append(allErrors, r.validateExistsYtsaurus(ctx, newYtsaurus)...) return allErrors } -func (r *Ytsaurus) evaluateYtsaurusValidation(old *Ytsaurus) error { - allErrors := r.validateYtsaurus(old) +func (r *ytsaurusValidator) evaluateYtsaurusValidation(ctx context.Context, newYtsaurus, oldYtsaurus *Ytsaurus) error { + allErrors := r.validateYtsaurus(ctx, newYtsaurus, oldYtsaurus) if len(allErrors) == 0 { return nil } return apierrors.NewInvalid( schema.GroupKind{Group: "cluster.ytsaurus.tech", Kind: "Ytsaurus"}, - r.Name, + newYtsaurus.Name, allErrors) } // ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *Ytsaurus) ValidateCreate() (admission.Warnings, error) { - ytsauruslog.Info("validate create", "name", r.Name) - - return nil, r.evaluateYtsaurusValidation(nil) +func (r *ytsaurusValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + newYtsaurus, ok := obj.(*Ytsaurus) + ytsauruslog.Info("validate create", "name", newYtsaurus.Name) + if !ok { + return nil, fmt.Errorf("expected a Ytsaurus but got a %T", obj) + } + return nil, r.evaluateYtsaurusValidation(ctx, newYtsaurus, nil) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *Ytsaurus) ValidateUpdate(oldObject runtime.Object) (admission.Warnings, error) { - ytsauruslog.Info("validate update", "name", r.Name) - old, ok := oldObject.(*Ytsaurus) +func (r *ytsaurusValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + oldYtsaurus, ok := oldObj.(*Ytsaurus) + if !ok { + return nil, fmt.Errorf("expected a Ytsaurus but got a %T", oldYtsaurus) + } + ytsauruslog.Info("validate update", "name", oldYtsaurus.Name) + newYtsaurus, ok := newObj.(*Ytsaurus) if !ok { - return nil, fmt.Errorf("expected a Ytsaurus but got a %T", oldObject) + return nil, fmt.Errorf("expected a Ytsaurus but got a %T", newYtsaurus) } - return nil, r.evaluateYtsaurusValidation(old) + return nil, r.evaluateYtsaurusValidation(ctx, newYtsaurus, oldYtsaurus) } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *Ytsaurus) ValidateDelete() (admission.Warnings, error) { - ytsauruslog.Info("validate delete", "name", r.Name) +func (r *ytsaurusValidator) ValidateDelete(_ context.Context, newObj runtime.Object) (admission.Warnings, error) { + newYtsaurus, ok := newObj.(*Ytsaurus) + if !ok { + return nil, fmt.Errorf("expected a Ytsaurus but got a %T", newYtsaurus) + } + ytsauruslog.Info("validate delete", "name", newYtsaurus.Name) // TODO(user): fill in your validation logic upon object deletion. return nil, nil diff --git a/docs/api.md b/docs/api.md index 1302e94c..155880c6 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1662,7 +1662,8 @@ Ytsaurus is the Schema for the ytsaurus API - +_Appears in:_ +- [YtsaurusValidator](#ytsaurusvalidator) | Field | Description | Default | Validation | | --- | --- | --- | --- | @@ -1728,3 +1729,5 @@ _Appears in:_ + + diff --git a/test/webhooks/ytsaurus_webhooks_test.go b/test/webhooks/ytsaurus_webhooks_test.go index 4b7a55e6..ae0766e4 100644 --- a/test/webhooks/ytsaurus_webhooks_test.go +++ b/test/webhooks/ytsaurus_webhooks_test.go @@ -3,13 +3,12 @@ package webhooks import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + ytv1 "github.com/ytsaurus/ytsaurus-k8s-operator/api/v1" + "github.com/ytsaurus/ytsaurus-k8s-operator/pkg/testutil" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - - ytv1 "github.com/ytsaurus/ytsaurus-k8s-operator/api/v1" - "github.com/ytsaurus/ytsaurus-k8s-operator/pkg/testutil" ) var _ = Describe("Test for Ytsaurus webhooks", func() { @@ -319,5 +318,9 @@ var _ = Describe("Test for Ytsaurus webhooks", func() { Expect(k8sClient.Create(ctx, ytsaurus)).Should(MatchError(ContainSubstring("spec.primaryMasters.hostAddresses: Invalid value"))) }) + It("should deny the creation of another YTsaurus CRD in the same namespace", func() { + ytsaurus1 := testutil.CreateBaseYtsaurusResource(namespace) + Expect(k8sClient.Create(ctx, ytsaurus1)).Should(MatchError(ContainSubstring("already exists"))) + }) }) })