11package validators
22
33import (
4+ "cmp"
45 "errors"
56 "fmt"
7+ "maps"
68 "slices"
9+ "strings"
710
811 "k8s.io/apimachinery/pkg/util/sets"
912
@@ -21,6 +24,10 @@ var RegistryV1BundleValidator = render.BundleValidator{
2124 CheckCRDResourceUniqueness ,
2225 CheckOwnedCRDExistence ,
2326 CheckPackageNameNotEmpty ,
27+ CheckWebhookDeploymentReferentialIntegrity ,
28+ CheckWebhookNameUniqueness ,
29+ CheckConversionWebhookCRDReferenceUniqueness ,
30+ CheckConversionWebhooksReferenceOwnedCRDs ,
2431}
2532
2633// CheckDeploymentSpecUniqueness checks that each strategy deployment spec in the csv has a unique name.
@@ -109,3 +116,123 @@ func CheckWebhookSupport(rv1 *render.RegistryV1) []error {
109116
110117 return nil
111118}
119+
120+ // CheckWebhookDeploymentReferentialIntegrity checks that each webhook definition in the csv
121+ // references an existing strategy deployment spec. Errors are sorted by strategy deployment spec name,
122+ // webhook type, and webhook name.
123+ func CheckWebhookDeploymentReferentialIntegrity (rv1 * render.RegistryV1 ) []error {
124+ webhooksByDeployment := map [string ][]v1alpha1.WebhookDescription {}
125+ for _ , wh := range rv1 .CSV .Spec .WebhookDefinitions {
126+ webhooksByDeployment [wh .DeploymentName ] = append (webhooksByDeployment [wh .DeploymentName ], wh )
127+ }
128+
129+ for _ , depl := range rv1 .CSV .Spec .InstallStrategy .StrategySpec .DeploymentSpecs {
130+ delete (webhooksByDeployment , depl .Name )
131+ }
132+
133+ var errs []error
134+ // Loop through sorted keys to keep error messages ordered by deployment name
135+ for _ , deploymentName := range slices .Sorted (maps .Keys (webhooksByDeployment )) {
136+ webhookDefns := webhooksByDeployment [deploymentName ]
137+ slices .SortFunc (webhookDefns , func (a , b v1alpha1.WebhookDescription ) int {
138+ return cmp .Or (cmp .Compare (a .Type , b .Type ), cmp .Compare (a .GenerateName , b .GenerateName ))
139+ })
140+ for _ , webhookDef := range webhookDefns {
141+ errs = append (errs , fmt .Errorf ("webhook '%s' of type '%s' references non-existent deployment '%s'" , webhookDef .GenerateName , webhookDef .Type , webhookDef .DeploymentName ))
142+ }
143+ }
144+ return errs
145+ }
146+
147+ // CheckWebhookNameUniqueness checks that each webhook definition of each type (validating, mutating, or conversion)
148+ // has a unique name. Webhooks of different types can have the same name. Errors are sorted by webhook type
149+ // and name.
150+ func CheckWebhookNameUniqueness (rv1 * render.RegistryV1 ) []error {
151+ webhookNameSetByType := map [v1alpha1.WebhookAdmissionType ]sets.Set [string ]{}
152+ duplicateWebhooksByType := map [v1alpha1.WebhookAdmissionType ]sets.Set [string ]{}
153+ for _ , wh := range rv1 .CSV .Spec .WebhookDefinitions {
154+ if _ , ok := webhookNameSetByType [wh .Type ]; ! ok {
155+ webhookNameSetByType [wh .Type ] = sets.Set [string ]{}
156+ }
157+ if webhookNameSetByType [wh .Type ].Has (wh .GenerateName ) {
158+ if _ , ok := duplicateWebhooksByType [wh .Type ]; ! ok {
159+ duplicateWebhooksByType [wh .Type ] = sets.Set [string ]{}
160+ }
161+ duplicateWebhooksByType [wh .Type ].Insert (wh .GenerateName )
162+ }
163+ webhookNameSetByType [wh .Type ].Insert (wh .GenerateName )
164+ }
165+
166+ var errs []error
167+ for _ , whType := range slices .Sorted (maps .Keys (duplicateWebhooksByType )) {
168+ for _ , webhookName := range slices .Sorted (slices .Values (duplicateWebhooksByType [whType ].UnsortedList ())) {
169+ errs = append (errs , fmt .Errorf ("duplicate webhook '%s' of type '%s'" , webhookName , whType ))
170+ }
171+ }
172+ return errs
173+ }
174+
175+ // CheckConversionWebhooksReferenceOwnedCRDs checks defined conversion webhooks reference bundle owned CRDs.
176+ // Errors are sorted by webhook name and CRD name.
177+ func CheckConversionWebhooksReferenceOwnedCRDs (rv1 * render.RegistryV1 ) []error {
178+ //nolint:prealloc
179+ var conversionWebhooks []v1alpha1.WebhookDescription
180+ for _ , wh := range rv1 .CSV .Spec .WebhookDefinitions {
181+ if wh .Type != v1alpha1 .ConversionWebhook {
182+ continue
183+ }
184+ conversionWebhooks = append (conversionWebhooks , wh )
185+ }
186+
187+ if len (conversionWebhooks ) == 0 {
188+ return nil
189+ }
190+
191+ ownedCRDNames := sets.Set [string ]{}
192+ for _ , crd := range rv1 .CSV .Spec .CustomResourceDefinitions .Owned {
193+ ownedCRDNames .Insert (crd .Name )
194+ }
195+
196+ slices .SortFunc (conversionWebhooks , func (a , b v1alpha1.WebhookDescription ) int {
197+ return cmp .Compare (a .GenerateName , b .GenerateName )
198+ })
199+
200+ var errs []error
201+ for _ , webhook := range conversionWebhooks {
202+ webhookCRDs := webhook .ConversionCRDs
203+ slices .Sort (webhookCRDs )
204+ for _ , crd := range webhookCRDs {
205+ if ! ownedCRDNames .Has (crd ) {
206+ errs = append (errs , fmt .Errorf ("conversion webhook '%s' references custom resource definition '%s' not owned bundle" , webhook .GenerateName , crd ))
207+ }
208+ }
209+ }
210+ return errs
211+ }
212+
213+ // CheckConversionWebhookCRDReferenceUniqueness checks no two (or more) conversion webhooks reference the same CRD.
214+ func CheckConversionWebhookCRDReferenceUniqueness (rv1 * render.RegistryV1 ) []error {
215+ // collect webhooks by crd
216+ crdToWh := map [string ][]string {}
217+ for _ , wh := range rv1 .CSV .Spec .WebhookDefinitions {
218+ if wh .Type != v1alpha1 .ConversionWebhook {
219+ continue
220+ }
221+ for _ , crd := range wh .ConversionCRDs {
222+ crdToWh [crd ] = append (crdToWh [crd ], wh .GenerateName )
223+ }
224+ }
225+
226+ // remove crds with single webhook
227+ maps .DeleteFunc (crdToWh , func (crd string , whs []string ) bool {
228+ return len (whs ) == 1
229+ })
230+
231+ errs := make ([]error , 0 , len (crdToWh ))
232+ orderedCRDs := slices .Sorted (maps .Keys (crdToWh ))
233+ for _ , crd := range orderedCRDs {
234+ orderedWhs := strings .Join (slices .Sorted (slices .Values (crdToWh [crd ])), "," )
235+ errs = append (errs , fmt .Errorf ("conversion webhooks [%s] reference same custom resource definition '%s'" , orderedWhs , crd ))
236+ }
237+ return errs
238+ }
0 commit comments