Skip to content

Commit c97f40a

Browse files
author
Per Goncalves da Silva
committed
Add csv webhook validation
Signed-off-by: Per Goncalves da Silva <[email protected]>
1 parent 87717a5 commit c97f40a

File tree

2 files changed

+497
-0
lines changed

2 files changed

+497
-0
lines changed

internal/operator-controller/rukpak/convert/validator.go

+100
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package convert
22

33
import (
4+
"cmp"
45
"errors"
56
"fmt"
7+
"maps"
68
"slices"
79

810
"k8s.io/apimachinery/pkg/util/sets"
11+
12+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
913
)
1014

1115
type BundleValidator []func(v1 *RegistryV1) []error
@@ -25,6 +29,9 @@ var RegistryV1BundleValidator = BundleValidator{
2529
CheckDeploymentSpecUniqueness,
2630
CheckCRDResourceUniqueness,
2731
CheckOwnedCRDExistence,
32+
CheckWebhookDeploymentReferentialIntegrity,
33+
CheckWebhookNameUniqueness,
34+
CheckConversionWebhookCRDReferences,
2835
}
2936

3037
// CheckDeploymentSpecUniqueness checks that each strategy deployment spec in the csv has a unique name.
@@ -87,3 +94,96 @@ func CheckCRDResourceUniqueness(rv1 *RegistryV1) []error {
8794
}
8895
return errs
8996
}
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

Comments
 (0)