diff --git a/PROJECT b/PROJECT index 0fc5562e2..6a659e10e 100644 --- a/PROJECT +++ b/PROJECT @@ -19,5 +19,6 @@ resources: version: v1 webhooks: defaulting: true + validating: true # or might be validation: true webhookVersion: v1 version: "3" diff --git a/config/openshift/kustomization.yaml b/config/openshift/kustomization.yaml index 22e168162..dd9471671 100644 --- a/config/openshift/kustomization.yaml +++ b/config/openshift/kustomization.yaml @@ -19,4 +19,5 @@ bases: patches: - path: manager_webhook_patch.yaml -- path: webhookcainjection_patch.yaml +- path: webhookcainjection_mpatch.yaml +- path: webhookcainjection_vpatch.yaml diff --git a/config/openshift/webhookcainjection_patch.yaml b/config/openshift/webhookcainjection_mpatch.yaml similarity index 100% rename from config/openshift/webhookcainjection_patch.yaml rename to config/openshift/webhookcainjection_mpatch.yaml diff --git a/config/openshift/webhookcainjection_vpatch.yaml b/config/openshift/webhookcainjection_vpatch.yaml new file mode 100644 index 000000000..91f741a05 --- /dev/null +++ b/config/openshift/webhookcainjection_vpatch.yaml @@ -0,0 +1,7 @@ +# This patch add annotation to admission webhook config +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: validating-webhook-configuration + annotations: + service.beta.openshift.io/inject-cabundle: "true" diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index faf91696b..c14976cfe 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -25,3 +25,30 @@ webhooks: resources: - rayclusters sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + creationTimestamp: null + name: validating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-ray-io-v1-raycluster + failurePolicy: Fail + name: vraycluster.kb.io + rules: + - apiGroups: + - ray.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - rayclusters + sideEffects: None diff --git a/pkg/controllers/raycluster_webhook.go b/pkg/controllers/raycluster_webhook.go index 0cd635040..5536aab20 100644 --- a/pkg/controllers/raycluster_webhook.go +++ b/pkg/controllers/raycluster_webhook.go @@ -23,10 +23,12 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/utils/pointer" 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/project-codeflare/codeflare-operator/pkg/config" ) @@ -40,16 +42,24 @@ func SetupRayClusterWebhookWithManager(mgr ctrl.Manager, cfg *config.KubeRayConf WithDefaulter(&rayClusterDefaulter{ Config: cfg, }). + WithValidator(&rayClusterValidator{ + Config: cfg, + }). Complete() } // +kubebuilder:webhook:path=/mutate-ray-io-v1-raycluster,mutating=true,failurePolicy=fail,sideEffects=None,groups=ray.io,resources=rayclusters,verbs=create;update,versions=v1,name=mraycluster.kb.io,admissionReviewVersions=v1 +// +kubebuilder:webhook:path=/validate-ray-io-v1-raycluster,mutating=false,failurePolicy=fail,sideEffects=None,groups=ray.io,resources=rayclusters,verbs=create;update,versions=v1,name=vraycluster.kb.io,admissionReviewVersions=v1 type rayClusterDefaulter struct { Config *config.KubeRayConfiguration } +type rayClusterValidator struct { + Config *config.KubeRayConfiguration +} var _ webhook.CustomDefaulter = &rayClusterDefaulter{} +var _ webhook.CustomValidator = &rayClusterValidator{} // Default implements webhook.Defaulter so a webhook will be registered for the type func (r *rayClusterDefaulter) Default(ctx context.Context, obj runtime.Object) error { @@ -132,3 +142,32 @@ func (r *rayClusterDefaulter) Default(ctx context.Context, obj runtime.Object) e return nil } + +func (v *rayClusterValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + raycluster := obj.(*rayv1.RayCluster) + var warnings admission.Warnings + var allErrors field.ErrorList + specPath := field.NewPath("spec") + + if raycluster.Spec.HeadGroupSpec.EnableIngress == nil || *raycluster.Spec.HeadGroupSpec.EnableIngress { + rayclusterlog.Info("Creating RayCluster resources with EnableIngress set to true or unspecified is not allowed") + allErrors = append(allErrors, field.Invalid(specPath.Child("headGroupSpec").Child("enableIngress"), raycluster.Spec.HeadGroupSpec.EnableIngress, "creating RayCluster resources with EnableIngress set to true or unspecified is not allowed")) + } + + return warnings, allErrors.ToAggregate() +} + +func (v *rayClusterValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + newRayCluster := newObj.(*rayv1.RayCluster) + if !newRayCluster.DeletionTimestamp.IsZero() { + // Object is being deleted, skip validations + return nil, nil + } + warnings, err := v.ValidateCreate(ctx, newRayCluster) + return warnings, err +} + +func (v *rayClusterValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + // Optional: Add delete validation logic here + return nil, nil +}