-
Notifications
You must be signed in to change notification settings - Fork 68
✨ Add Bundle-Agnostic Configuration Validation Using JSON Schema #2316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨ Add Bundle-Agnostic Configuration Validation Using JSON Schema #2316
Conversation
✅ Deploy Preview for olmv1 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
c547c6d to
05d349b
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2316 +/- ##
==========================================
+ Coverage 74.30% 74.31% +0.01%
==========================================
Files 91 92 +1
Lines 7083 7226 +143
==========================================
+ Hits 5263 5370 +107
- Misses 1405 1427 +22
- Partials 415 429 +14
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
a753a5c to
f15ea9f
Compare
f15ea9f to
e9352a6
Compare
e9352a6 to
c2e887e
Compare
1f3451a to
65bd4ef
Compare
65bd4ef to
195d51d
Compare
d78bf4a to
920f78e
Compare
920f78e to
aaa71d0
Compare
aaa71d0 to
6e051d2
Compare
bf5cfd4 to
79734b2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Hi @pedjak, Regarding the function name:
I am not the best to pick names but IMHO, When someone sees Would keeping this name be a blocker for moving forward? |
| type SchemaProvider interface { | ||
| // Get returns a JSON Schema describing what configuration is valid. | ||
| // Returns nil if this package format type doesn't need configuration validation. | ||
| Get() (map[string]any, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get that at this level it becomes redundant, but maybe we should say what we're getting? something like?
| Get() (map[string]any, error) | |
| GetConfigSchema() (map[string]any, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. Done.
79734b2 to
c35dbff
Compare
c35dbff to
fddee4e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Note: Using %s not %w since ve.Error() is already a formatted string | ||
| return fmt.Errorf("%s", ve.Error()) |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Using fmt.Errorf("%s", ve.Error()) creates an error wrapping an error string, which is unnecessarily verbose. Consider using errors.New(ve.Error()) instead for cleaner error messages, or if you need to wrap the original error for debugging, use fmt.Errorf("invalid configuration: %w", ve) to preserve the error chain.
| // Note: Using %s not %w since ve.Error() is already a formatted string | |
| return fmt.Errorf("%s", ve.Error()) | |
| // Note: Using errors.New since ve.Error() is already a formatted string | |
| return errors.New(ve.Error()) |
| } | ||
|
|
||
| if len(errorMessages) == 0 { | ||
| return fmt.Errorf("invalid configuration: %s", ve.Error()) |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Using fmt.Errorf("invalid configuration: %s", ve.Error()) creates an error wrapping an error string. Consider using fmt.Errorf("invalid configuration: %w", ve) instead to preserve the error chain, or errors.New("invalid configuration: " + ve.Error()) if you don't need error wrapping.
| return fmt.Errorf("invalid configuration: %s", ve.Error()) | |
| return fmt.Errorf("invalid configuration: %w", ve) |
|
/approve 🚀 |
pedjak
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/lgtm
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: pedjak, perdasilva The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
ClusterExtension 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
fddee4e to
b10fc49
Compare
|
/lgtm |
15b4904
into
operator-framework:main
When we install an operator using ClusterExtension, we can now configure things like which namespace it should watch. Previously, if you made a typo or configured it incorrectly, the error would only show up later during deployment. Now, configuration is validated immediately using JSON Schema, and we get clear error messages right away.
Why We Did This
Issue: https://issues.redhat.com/browse/OPRUN-4112
Users were getting confusing errors when they misconfigured their operators. By validating configuration upfront, we can give much better error messages that tell you exactly what's wrong and how to fix it.
How It Works
We introduced a
ConfigSchemaProviderinterface that lets different bundle types describe their own configuration rules and all packages format types (registry/v1 or helm) use the same validation process - only the source of the rules changes.Do I Need to Configure
watchNamespace?It depends on what install modes your operator supports:
Design Diagram
TL"DR:
Examples - Use cases
No configuration (AllNamespaces mode)
Watch specific namespace (SingleNamespace mode)
Watch install namespace (OwnNamespace mode)
Error Messages
All errors start with
invalid ClusterExtension configuration: invalid configuration:followed by the specific problem.Typo in Field Name
Error:
Missing Required Field
Error:
Wrong Type
Error:
OwnNamespace Mode - Wrong Namespace
Error:
SingleNamespace Mode - Can't Use Install Namespace
Error:
Invalid JSON/YAML
Error:
Why we are formatting the messages:
The minimal option here would be like
However, I followed your suggestion to use DetailedOutput() / BasicOutput(), and I think we've achieved a better result with improved error messages that are neither overly fragile nor difficult to maintain.
Output Comparison (Minimal vs Current Approach)
{}whenwatchNamespacerequiredinvalid configuration:- at '': missing property 'watchNamespace'required field "watchNamespace" is missing{"watchNamespace": null}invalid configuration:- at '/watchNamespace': got null, want stringrequired field "watchNamespace" is missing{"unknownField": "value"}invalid configuration:- at '': additional properties 'unknownField' not allowedunknown field "unknownField"{"watchNamespace": 123}invalid configuration:- at '/watchNamespace': got number, want stringinvalid type for field "watchNamespace": got number, want string{"resources": {"memory": 512}}invalid configuration:- at '/resources/memory': got number, want stringinvalid type for field "resources.memory": got number, want stringtrue(not an object)invalid configuration:- at '': got boolean, want objectinvalid type: got boolean, want object{"replicaCount": 0}with min=1invalid configuration:- at '/replicaCount': value should be >= 1value should be >= 1{"type": "Invalid"}invalid configuration:- at '/type': value should be one of [...]value should be one of [...]{"watchNamespace": "wrong-ns"}invalid configuration:- at '/watchNamespace': 'wrong-ns' is not valid ownNamespaceInstallMode: ...invalid value "wrong-ns": watchNamespace must be "install-ns" (the namespace where the operator is installed) ...{}requires 2 fieldsinvalid configuration:- at '': missing property 'replicaCount'(stops at first error)
multiple errors found:- required field "replicaCount" is missing- required field "image" is missing