1
1
package convert
2
2
3
3
import (
4
+ "cmp"
4
5
"errors"
5
6
"fmt"
7
+ "maps"
6
8
"slices"
7
9
8
10
"k8s.io/apimachinery/pkg/util/sets"
11
+
12
+ "github.com/operator-framework/api/pkg/operators/v1alpha1"
9
13
)
10
14
11
15
type BundleValidator []func (v1 * RegistryV1 ) []error
@@ -25,6 +29,9 @@ var RegistryV1BundleValidator = BundleValidator{
25
29
CheckDeploymentSpecUniqueness ,
26
30
CheckCRDResourceUniqueness ,
27
31
CheckOwnedCRDExistence ,
32
+ CheckWebhookDeploymentReferentialIntegrity ,
33
+ CheckWebhookNameUniqueness ,
34
+ CheckConversionWebhookCRDReferences ,
28
35
}
29
36
30
37
// CheckDeploymentSpecUniqueness checks that each strategy deployment spec in the csv has a unique name.
@@ -87,3 +94,96 @@ func CheckCRDResourceUniqueness(rv1 *RegistryV1) []error {
87
94
}
88
95
return errs
89
96
}
97
+
98
+ // CheckWebhookDeploymentReferentialIntegrity checks that each webhook definition in the csv
99
+ // references an existing strategy deployment spec. Errors are sorted by strategy deployment spec name,
100
+ // webhook type, and webhook name.
101
+ func CheckWebhookDeploymentReferentialIntegrity (rv1 * RegistryV1 ) []error {
102
+ webhooksByDeployment := map [string ][]v1alpha1.WebhookDescription {}
103
+ for _ , wh := range rv1 .CSV .Spec .WebhookDefinitions {
104
+ webhooksByDeployment [wh .DeploymentName ] = append (webhooksByDeployment [wh .DeploymentName ], wh )
105
+ }
106
+
107
+ for _ , depl := range rv1 .CSV .Spec .InstallStrategy .StrategySpec .DeploymentSpecs {
108
+ delete (webhooksByDeployment , depl .Name )
109
+ }
110
+
111
+ var errs []error
112
+ // Loop through sorted keys to keep error messages ordered by deployment name
113
+ for _ , deploymentName := range slices .Sorted (maps .Keys (webhooksByDeployment )) {
114
+ webhookDefns := webhooksByDeployment [deploymentName ]
115
+ slices .SortFunc (webhookDefns , func (a , b v1alpha1.WebhookDescription ) int {
116
+ return cmp .Or (cmp .Compare (a .Type , b .Type ), cmp .Compare (a .GenerateName , b .GenerateName ))
117
+ })
118
+ for _ , webhookDef := range webhookDefns {
119
+ errs = append (errs , fmt .Errorf ("webhook '%s' of type '%s' references non-existent deployment '%s'" , webhookDef .GenerateName , webhookDef .Type , webhookDef .DeploymentName ))
120
+ }
121
+ }
122
+ return errs
123
+ }
124
+
125
+ // CheckWebhookNameUniqueness checks that each webhook definition of each type (validating, mutating, or conversion)
126
+ // has a unique name. Webhooks of different types can have the same name. Errors are sorted by webhook type
127
+ // and name.
128
+ func CheckWebhookNameUniqueness (rv1 * RegistryV1 ) []error {
129
+ webhookNameSetByType := map [v1alpha1.WebhookAdmissionType ]sets.Set [string ]{}
130
+ duplicateWebhooksByType := map [v1alpha1.WebhookAdmissionType ]sets.Set [string ]{}
131
+ for _ , wh := range rv1 .CSV .Spec .WebhookDefinitions {
132
+ if _ , ok := webhookNameSetByType [wh .Type ]; ! ok {
133
+ webhookNameSetByType [wh .Type ] = sets.Set [string ]{}
134
+ }
135
+ if webhookNameSetByType [wh .Type ].Has (wh .GenerateName ) {
136
+ if _ , ok := duplicateWebhooksByType [wh .Type ]; ! ok {
137
+ duplicateWebhooksByType [wh .Type ] = sets.Set [string ]{}
138
+ }
139
+ duplicateWebhooksByType [wh .Type ].Insert (wh .GenerateName )
140
+ }
141
+ webhookNameSetByType [wh .Type ].Insert (wh .GenerateName )
142
+ }
143
+
144
+ var errs []error
145
+ for _ , whType := range slices .Sorted (maps .Keys (duplicateWebhooksByType )) {
146
+ for _ , webhookName := range slices .Sorted (slices .Values (duplicateWebhooksByType [whType ].UnsortedList ())) {
147
+ errs = append (errs , fmt .Errorf ("duplicate webhook '%s' of type '%s'" , webhookName , whType ))
148
+ }
149
+ }
150
+ return errs
151
+ }
152
+
153
+ // CheckConversionWebhookCRDReferences checks defined conversion webhooks reference bundle owned CRDs.
154
+ // Errors are sorted by webhook name and CRD name.
155
+ func CheckConversionWebhookCRDReferences (rv1 * RegistryV1 ) []error {
156
+ //nolint:prealloc
157
+ var conversionWebhooks []v1alpha1.WebhookDescription
158
+ for _ , wh := range rv1 .CSV .Spec .WebhookDefinitions {
159
+ if wh .Type != v1alpha1 .ConversionWebhook {
160
+ continue
161
+ }
162
+ conversionWebhooks = append (conversionWebhooks , wh )
163
+ }
164
+
165
+ if len (conversionWebhooks ) == 0 {
166
+ return nil
167
+ }
168
+
169
+ ownedCRDNames := sets.Set [string ]{}
170
+ for _ , crd := range rv1 .CSV .Spec .CustomResourceDefinitions .Owned {
171
+ ownedCRDNames .Insert (crd .Name )
172
+ }
173
+
174
+ slices .SortFunc (conversionWebhooks , func (a , b v1alpha1.WebhookDescription ) int {
175
+ return cmp .Compare (a .GenerateName , b .GenerateName )
176
+ })
177
+
178
+ var errs []error
179
+ for _ , webhook := range conversionWebhooks {
180
+ webhookCRDs := webhook .ConversionCRDs
181
+ slices .Sort (webhookCRDs )
182
+ for _ , crd := range webhookCRDs {
183
+ if ! ownedCRDNames .Has (crd ) {
184
+ errs = append (errs , fmt .Errorf ("conversion webhook '%s' references custom resource definition '%s' not owned bundle" , webhook .GenerateName , crd ))
185
+ }
186
+ }
187
+ }
188
+ return errs
189
+ }
0 commit comments