@@ -23,14 +23,18 @@ import (
2323 "slices"
2424
2525 "github.com/go-logr/logr"
26+ admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
2627 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2728 apierrors "k8s.io/apimachinery/pkg/api/errors"
29+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2830 "k8s.io/apimachinery/pkg/types"
31+ "k8s.io/utils/ptr"
2932 ctrl "sigs.k8s.io/controller-runtime"
3033 logf "sigs.k8s.io/controller-runtime/pkg/log"
3134 "sigs.k8s.io/yaml"
3235
3336 apiextensionsv1alpha1 "github.com/openshift/api/apiextensions/v1alpha1"
37+ "github.com/openshift/cluster-capi-operator/pkg/controllers/crdcompatibility/objectvalidation"
3438 "github.com/openshift/cluster-capi-operator/pkg/crdchecker"
3539 "github.com/openshift/cluster-capi-operator/pkg/util"
3640)
@@ -155,6 +159,7 @@ func (r *reconcileState) reconcileCreateOrUpdate(ctx context.Context, obj *apiex
155159 r .parseCompatibilityCRD (obj ),
156160 r .fetchCurrentCRD (ctx , logger ),
157161 r .checkCompatibilityRequirement (),
162+ r .ensureObjectValidationWebhook (ctx , obj ),
158163 )
159164
160165 if err != nil {
@@ -169,9 +174,107 @@ func (r *reconcileState) reconcileDelete(ctx context.Context, obj *apiextensions
169174
170175 logger .Info ("Reconciling CompatibilityRequirement deletion" )
171176
172- if err := clearFinalizer (ctx , r .client , obj ); err != nil {
177+ err := errors .Join (
178+ clearFinalizer (ctx , r .client , obj ),
179+ r .removeObjectValidationWebhook (ctx , obj ),
180+ )
181+
182+ if err != nil {
173183 return ctrl.Result {}, err
174184 }
175185
176186 return ctrl.Result {}, nil
177187}
188+
189+ func (r * reconcileState ) ensureObjectValidationWebhook (ctx context.Context , obj * apiextensionsv1alpha1.CompatibilityRequirement ) error {
190+ if isObjectValidationWebhookEnabled (obj ) {
191+ return nil
192+ }
193+
194+ webhookConfig := validatingWebhookConfigurationFor (obj , r .compatibilityCRD )
195+ if err := r .client .Get (ctx , types.NamespacedName {Name : webhookConfig .Name }, webhookConfig ); err != nil {
196+ if apierrors .IsNotFound (err ) {
197+ return r .client .Create (ctx , webhookConfig )
198+ }
199+
200+ return err
201+ }
202+
203+ return r .client .Update (ctx , webhookConfig )
204+ }
205+
206+ func (r * reconcileState ) removeObjectValidationWebhook (ctx context.Context , obj * apiextensionsv1alpha1.CompatibilityRequirement ) error {
207+ webhookConfig := & admissionregistrationv1.ValidatingWebhookConfiguration {
208+ ObjectMeta : metav1.ObjectMeta {
209+ Name : obj .Name ,
210+ },
211+ }
212+
213+ if err := r .client .Get (ctx , types.NamespacedName {Name : webhookConfig .Name }, webhookConfig ); err != nil {
214+ if apierrors .IsNotFound (err ) {
215+ return nil
216+ }
217+ return err
218+ }
219+
220+ return r .client .Delete (ctx , webhookConfig )
221+ }
222+
223+ func isObjectValidationWebhookEnabled (obj * apiextensionsv1alpha1.CompatibilityRequirement ) bool {
224+ osv := obj .Spec .ObjectSchemaValidation
225+ return osv .Action == "" && osv .MatchConditions == nil && labelSelectorIsEmpty (osv .NamespaceSelector ) && labelSelectorIsEmpty (osv .ObjectSelector )
226+ }
227+
228+ func labelSelectorIsEmpty (ls metav1.LabelSelector ) bool {
229+ return len (ls .MatchLabels ) == 0 && len (ls .MatchExpressions ) == 0
230+ }
231+
232+ func validatingWebhookConfigurationFor (obj * apiextensionsv1alpha1.CompatibilityRequirement , crd * apiextensionsv1.CustomResourceDefinition ) * admissionregistrationv1.ValidatingWebhookConfiguration {
233+ return & admissionregistrationv1.ValidatingWebhookConfiguration {
234+ TypeMeta : metav1.TypeMeta {
235+ Kind : "ValidatingWebhookConfiguration" ,
236+ APIVersion : "admissionregistration.k8s.io/v1" ,
237+ },
238+ ObjectMeta : metav1.ObjectMeta {
239+ Name : obj .Name ,
240+ Annotations : map [string ]string {
241+ "service.beta.openshift.io/inject-cabundle" : "true" ,
242+ },
243+ },
244+ Webhooks : []admissionregistrationv1.ValidatingWebhook {
245+ {
246+ AdmissionReviewVersions : []string {"v1" },
247+ ClientConfig : admissionregistrationv1.WebhookClientConfig {
248+ Service : & admissionregistrationv1.ServiceReference {
249+ Name : "compatibility-requirements-controllers-validation-webhook-service" ,
250+ Namespace : "openshift-compatibility-requirements-operator" ,
251+ Path : ptr .To (fmt .Sprintf ("%s%s" , objectvalidation .WebhookPrefix , obj .Name )),
252+ },
253+ },
254+ SideEffects : ptr .To (admissionregistrationv1 .SideEffectClassNone ),
255+ FailurePolicy : ptr .To (admissionregistrationv1 .Fail ),
256+ MatchPolicy : ptr .To (admissionregistrationv1 .Exact ),
257+ Name : "compatibilityrequirement.operator.openshift.io" ,
258+ Rules : []admissionregistrationv1.RuleWithOperations {
259+ {
260+ Rule : admissionregistrationv1.Rule {
261+ APIGroups : []string {crd .Spec .Group },
262+ APIVersions : mapFunc (crd .Spec .Versions , func (version apiextensionsv1.CustomResourceDefinitionVersion ) string { return version .Name }),
263+ Resources : []string {crd .Spec .Names .Plural },
264+ Scope : ptr .To (admissionregistrationv1 .ScopeType (crd .Spec .Scope )),
265+ },
266+ Operations : []admissionregistrationv1.OperationType {"CREATE" , "UPDATE" },
267+ },
268+ },
269+ },
270+ },
271+ }
272+ }
273+
274+ func mapFunc [T any , R any ](items []T , transform func (T ) R ) []R {
275+ result := make ([]R , len (items ))
276+ for i , item := range items {
277+ result [i ] = transform (item )
278+ }
279+ return result
280+ }
0 commit comments