@@ -17,6 +17,7 @@ limitations under the License.
1717package v1alpha1
1818
1919import (
20+ "context"
2021 "fmt"
2122
2223 apps "k8s.io/api/apps/v1"
@@ -25,61 +26,92 @@ import (
2526 "k8s.io/apimachinery/pkg/runtime/schema"
2627 "k8s.io/apimachinery/pkg/util/validation/field"
2728 ctrl "sigs.k8s.io/controller-runtime"
29+ "sigs.k8s.io/controller-runtime/pkg/client"
2830 logf "sigs.k8s.io/controller-runtime/pkg/log"
2931 "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
3032)
3133
3234// log is for logging in this package.
3335var clowdapplog = logf .Log .WithName ("clowdapp-resource" )
3436
37+ // clowdAppValidator is a webhook that validates ClowdApp resources
38+ type clowdAppValidator struct {
39+ client.Client
40+ }
41+
3542func (r * ClowdApp ) SetupWebhookWithManager (mgr ctrl.Manager ) error {
43+ // Add index for spec.envName field for webhook queries
44+ if err := mgr .GetFieldIndexer ().IndexField (
45+ context .TODO (), & ClowdApp {}, "spec.envName" , func (o client.Object ) []string {
46+ return []string {o .(* ClowdApp ).Spec .EnvName }
47+ }); err != nil {
48+ return err
49+ }
50+
3651 return ctrl .NewWebhookManagedBy (mgr ).
3752 For (r ).
53+ WithValidator (& clowdAppValidator {Client : mgr .GetClient ()}).
3854 Complete ()
3955}
4056
4157//+kubebuilder:webhook:path=/validate-cloud-redhat-com-v1alpha1-clowdapp,mutating=false,failurePolicy=fail,sideEffects=None,groups=cloud.redhat.com,resources=clowdapps,verbs=create;update,versions=v1alpha1,name=vclowdapp.kb.io,admissionReviewVersions={v1}
4258//+kubebuilder:webhook:path=/mutate-pod,mutating=true,failurePolicy=ignore,sideEffects=None,groups="",resources=pods,verbs=create;update,versions=v1,name=vclowdmutatepod.kb.io,admissionReviewVersions={v1}
4359
60+ // Define default validations that should always run
61+ var defaultValidations = []appValidationFunc {
62+ validateDatabase ,
63+ validateSidecars ,
64+ validateInit ,
65+ validateDeploymentStrategy ,
66+ }
67+
4468// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
45- func (r * ClowdApp ) ValidateCreate () (admission.Warnings , error ) {
46- clowdapplog .Info ("validate create" , "name" , r .Name )
47-
48- return []string {}, r .processValidations (r ,
49- validateDatabase ,
50- validateSidecars ,
51- validateInit ,
52- validateDeploymentStrategy ,
53- )
69+ func (v * clowdAppValidator ) ValidateCreate (ctx context.Context , obj runtime.Object ) (admission.Warnings , error ) {
70+ clowdapp := obj .(* ClowdApp )
71+ clowdapplog .Info ("validate create" , "name" , clowdapp .Name )
72+
73+ // Create validations list with default validations plus duplicate name check
74+ validations := append ([]appValidationFunc {v .validateDuplicateName }, defaultValidations ... )
75+
76+ return []string {}, v .processValidations (ctx , clowdapp , validations ... )
5477}
5578
5679// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
57- func (r * ClowdApp ) ValidateUpdate (_ runtime.Object ) (admission.Warnings , error ) {
58- clowdapplog .Info ("validate update" , "name" , r .Name )
59-
60- return []string {}, r .processValidations (r ,
61- validateDatabase ,
62- validateSidecars ,
63- validateInit ,
64- validateDeploymentStrategy ,
65- )
80+ func (v * clowdAppValidator ) ValidateUpdate (ctx context.Context , oldObj , newObj runtime.Object ) (admission.Warnings , error ) {
81+ clowdapp := newObj .(* ClowdApp )
82+ oldClowdApp := oldObj .(* ClowdApp )
83+ clowdapplog .Info ("validate update" , "name" , clowdapp .Name )
84+
85+ // Start with default validations
86+ validations := make ([]appValidationFunc , len (defaultValidations ))
87+ copy (validations , defaultValidations )
88+
89+ // Append duplicate name validation if names differ
90+ if oldClowdApp .Name != clowdapp .Name {
91+ validations = append (validations , v .validateDuplicateName )
92+ }
93+
94+ return []string {}, v .processValidations (ctx , clowdapp , validations ... )
6695}
6796
6897// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
69- func (r * ClowdApp ) ValidateDelete () (admission.Warnings , error ) {
70- clowdapplog .Info ("validate delete" , "name" , r .Name )
98+ func (v * clowdAppValidator ) ValidateDelete (ctx context.Context , obj runtime.Object ) (admission.Warnings , error ) {
99+ clowdapp := obj .(* ClowdApp )
100+ clowdapplog .Info ("validate delete" , "name" , clowdapp .Name )
71101 return []string {}, nil
72102}
73103
74- type appValidationFunc func (* ClowdApp ) field.ErrorList
104+ type appValidationFunc func (context. Context , client. Client , * ClowdApp ) field.ErrorList
75105
76- func (r * ClowdApp ) processValidations (o * ClowdApp , vfns ... appValidationFunc ) error {
106+ func (v * clowdAppValidator ) processValidations (ctx context. Context , o * ClowdApp , vfns ... appValidationFunc ) error {
77107 var allErrs field.ErrorList
78108
79109 for _ , validation := range vfns {
80- fieldList := validation (o )
81- if fieldList != nil {
82- allErrs = append (allErrs , fieldList ... )
110+ if validation != nil {
111+ fieldList := validation (ctx , v .Client , o )
112+ if fieldList != nil {
113+ allErrs = append (allErrs , fieldList ... )
114+ }
83115 }
84116 }
85117
@@ -89,11 +121,11 @@ func (r *ClowdApp) processValidations(o *ClowdApp, vfns ...appValidationFunc) er
89121
90122 return apierrors .NewInvalid (
91123 schema.GroupKind {Group : "cloud.redhat.com" , Kind : "ClowdApp" },
92- r .Name , allErrs ,
124+ o .Name , allErrs ,
93125 )
94126}
95127
96- func validateDatabase (r * ClowdApp ) field.ErrorList {
128+ func validateDatabase (_ context. Context , _ client. Client , r * ClowdApp ) field.ErrorList {
97129 allErrs := field.ErrorList {}
98130
99131 if r .Spec .Database .Name != "" && r .Spec .Database .SharedDBAppName != "" {
@@ -111,7 +143,7 @@ func validateDatabase(r *ClowdApp) field.ErrorList {
111143 return allErrs
112144}
113145
114- func validateInit (r * ClowdApp ) field.ErrorList {
146+ func validateInit (_ context. Context , _ client. Client , r * ClowdApp ) field.ErrorList {
115147 allErrs := field.ErrorList {}
116148
117149 for depIdx , deployment := range r .Spec .Deployments {
@@ -131,7 +163,7 @@ func validateInit(r *ClowdApp) field.ErrorList {
131163 return allErrs
132164}
133165
134- func validateSidecars (r * ClowdApp ) field.ErrorList {
166+ func validateSidecars (_ context. Context , _ client. Client , r * ClowdApp ) field.ErrorList {
135167 allErrs := field.ErrorList {}
136168 for depIndx , deployment := range r .Spec .Deployments {
137169 for carIndx , sidecar := range deployment .PodSpec .Sidecars {
@@ -165,7 +197,7 @@ func validateSidecars(r *ClowdApp) field.ErrorList {
165197 return allErrs
166198}
167199
168- func validateDeploymentStrategy (r * ClowdApp ) field.ErrorList {
200+ func validateDeploymentStrategy (_ context. Context , _ client. Client , r * ClowdApp ) field.ErrorList {
169201 allErrs := field.ErrorList {}
170202 for depIndex , deployment := range r .Spec .Deployments {
171203 if deployment .DeploymentStrategy != nil && deployment .WebServices .Public .Enabled && deployment .DeploymentStrategy .PrivateStrategy == apps .RecreateDeploymentStrategyType {
@@ -180,3 +212,33 @@ func validateDeploymentStrategy(r *ClowdApp) field.ErrorList {
180212 }
181213 return allErrs
182214}
215+
216+ func (v * clowdAppValidator ) validateDuplicateName (ctx context.Context , c client.Client , r * ClowdApp ) field.ErrorList {
217+ allErrs := field.ErrorList {}
218+
219+ // Check if another ClowdApp with the same name already exists in the same ClowdEnvironment
220+ existingClowdApps := & ClowdAppList {}
221+ err := c .List (ctx , existingClowdApps , client.MatchingFields {
222+ "spec.envName" : r .Spec .EnvName ,
223+ })
224+
225+ if err != nil {
226+ // If we got an error, log it but don't fail validation
227+ // This allows the webhook to continue functioning even if there are temporary
228+ // API server issues
229+ clowdapplog .Error (err , "Error checking for duplicate ClowdApp name" , "name" , r .Name )
230+ return allErrs
231+ }
232+
233+ // Iterate through existing ClowdApps to check for duplicates with same name in different namespaces
234+ for _ , existingApp := range existingClowdApps .Items {
235+ if existingApp .Name == r .Name && existingApp .Namespace != r .Namespace {
236+ allErrs = append (allErrs , field .Duplicate (
237+ field .NewPath ("metadata" ).Child ("name" ),
238+ fmt .Sprintf ("ClowdApp with name '%s' already exists in ClowdEnvironment '%s' in namespace '%s'" , r .Name , r .Spec .EnvName , existingApp .Namespace )),
239+ )
240+ }
241+ }
242+
243+ return allErrs
244+ }
0 commit comments