Skip to content

Commit e97183c

Browse files
(chore): Add JSONSchema validation for bundle configuration
Bundle configuration is now validated using JSONSchema. Configuration errors (typos, missing required fields, wrong types) are caught immediately with clear error messages instead of failing during installation. Assisted-by: Cursor
1 parent c95fc24 commit e97183c

File tree

8 files changed

+1014
-212
lines changed

8 files changed

+1014
-212
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require (
2323
github.com/operator-framework/operator-registry v1.61.0
2424
github.com/prometheus/client_golang v1.23.2
2525
github.com/prometheus/common v0.67.2
26+
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
2627
github.com/spf13/cobra v1.10.1
2728
github.com/spf13/pflag v1.0.10
2829
github.com/stretchr/testify v1.11.1
@@ -192,7 +193,6 @@ require (
192193
github.com/rivo/uniseg v0.4.7 // indirect
193194
github.com/rubenv/sql-migrate v1.8.0 // indirect
194195
github.com/russross/blackfriday/v2 v2.1.0 // indirect
195-
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
196196
github.com/secure-systems-lab/go-securesystemslib v0.9.1 // indirect
197197
github.com/shopspring/decimal v1.4.0 // indirect
198198
github.com/sigstore/fulcio v1.7.1 // indirect

internal/operator-controller/applier/provider.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,19 @@ func (r *RegistryV1ManifestProvider) Get(bundleFS fs.FS, ext *ocv1.ClusterExtens
6969
}
7070

7171
if r.IsSingleOwnNamespaceEnabled {
72-
bundleConfigBytes := extensionConfigBytes(ext)
73-
// treat no config as empty to properly validate the configuration
74-
// e.g. ensure that validation catches missing required fields
75-
if bundleConfigBytes == nil {
76-
bundleConfigBytes = []byte(`{}`)
72+
schema, err := rv1.Get()
73+
if err != nil {
74+
return nil, fmt.Errorf("error getting configuration schema: %w", err)
7775
}
78-
bundleConfig, err := bundle.UnmarshalConfig(bundleConfigBytes, rv1, ext.Spec.Namespace)
76+
77+
bundleConfigBytes := extensionConfigBytes(ext)
78+
bundleConfig, err := bundle.UnmarshalConfig(bundleConfigBytes, schema, ext.Spec.Namespace)
7979
if err != nil {
80-
return nil, fmt.Errorf("invalid bundle configuration: %w", err)
80+
return nil, fmt.Errorf("invalid ClusterExtension configuration: %w", err)
8181
}
8282

83-
if bundleConfig != nil && bundleConfig.WatchNamespace != nil {
84-
opts = append(opts, render.WithTargetNamespaces(*bundleConfig.WatchNamespace))
83+
if watchNS := bundleConfig.GetWatchNamespace(); watchNS != nil {
84+
opts = append(opts, render.WithTargetNamespaces(*watchNS))
8585
}
8686
}
8787

internal/operator-controller/applier/provider_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func Test_RegistryV1ManifestProvider_Integration(t *testing.T) {
9797

9898
_, err := provider.Get(bundleFS, ext)
9999
require.Error(t, err)
100-
require.Contains(t, err.Error(), "invalid bundle configuration")
100+
require.Contains(t, err.Error(), "invalid ClusterExtension configuration")
101101
})
102102

103103
t.Run("returns rendered manifests", func(t *testing.T) {
@@ -326,7 +326,7 @@ func Test_RegistryV1ManifestProvider_SingleOwnNamespaceSupport(t *testing.T) {
326326
},
327327
})
328328
require.Error(t, err)
329-
require.Contains(t, err.Error(), "required field \"watchNamespace\" is missing")
329+
require.Contains(t, err.Error(), `required field "watchNamespace" is missing`)
330330
})
331331

332332
t.Run("accepts bundles with {OwnNamespace} install modes when the appropriate configuration is given", func(t *testing.T) {
@@ -371,7 +371,7 @@ func Test_RegistryV1ManifestProvider_SingleOwnNamespaceSupport(t *testing.T) {
371371
},
372372
})
373373
require.Error(t, err)
374-
require.Contains(t, err.Error(), "required field \"watchNamespace\" is missing")
374+
require.Contains(t, err.Error(), `required field "watchNamespace" is missing`)
375375
})
376376

377377
t.Run("rejects bundles with {OwnNamespace} install modes when watchNamespace is not install namespace", func(t *testing.T) {
@@ -392,7 +392,9 @@ func Test_RegistryV1ManifestProvider_SingleOwnNamespaceSupport(t *testing.T) {
392392
},
393393
})
394394
require.Error(t, err)
395-
require.Contains(t, err.Error(), "invalid 'watchNamespace' \"not-install-namespace\": must be install namespace (install-namespace)")
395+
require.Contains(t, err.Error(), "invalid ClusterExtension configuration:")
396+
require.Contains(t, err.Error(), "watchNamespace must be")
397+
require.Contains(t, err.Error(), "install-namespace")
396398
})
397399

398400
t.Run("rejects bundles without AllNamespaces, SingleNamespace, or OwnNamespace install mode support when Single/OwnNamespace install mode support is enabled", func(t *testing.T) {

0 commit comments

Comments
 (0)