-
Notifications
You must be signed in to change notification settings - Fork 71
✨ Add automated schema generation and validation for registry+v1 bundle configuration #2454
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
base: main
Are you sure you want to change the base?
✨ Add automated schema generation and validation for registry+v1 bundle configuration #2454
Conversation
✅ Deploy Preview for olmv1 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
1367534 to
2cd3619
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
This PR implements Phase 1 of the DeploymentConfig feature to achieve feature parity with OLMv0's SubscriptionConfig for registry+v1 bundles. It adds automated JSON schema generation and validation infrastructure for the deploymentConfig field in ClusterExtension.
Changes:
- Added reflection-based schema generator tool that introspects
v1alpha1.SubscriptionConfigfrom the operator-framework/api package - Implemented embedded JSON schema validation infrastructure with comprehensive test coverage
- Integrated deploymentConfig schema into bundle config validation with make target for regeneration
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| hack/tools/schema-generator/main.go | Implements reflection-based generator that introspects Go types and parses AST to extract documentation, handling nested k8s types and inline fields |
| hack/tools/schema-generator/main_test.go | Comprehensive test suite validating schema structure, selector field exclusion, and comparing generated vs committed schemas |
| internal/operator-controller/rukpak/bundle/schema/validator.go | Validation infrastructure with embedded schema compilation and multi-format input support (JSON bytes, strings, structs) |
| internal/operator-controller/rukpak/bundle/schema/validator_test.go | Test coverage for valid and invalid deployment configurations including edge cases |
| internal/operator-controller/rukpak/bundle/schema/deploymentconfig.json | Generated JSON Schema Draft 7 document (1862 lines) defining all SubscriptionConfig fields except selector |
| internal/operator-controller/rukpak/bundle/schema/README.md | Documentation explaining schema purpose, included/excluded fields, and regeneration workflow |
| internal/operator-controller/rukpak/bundle/registryv1.go | Integration of deploymentConfig schema into bundle config validation |
| internal/operator-controller/config/config.go | Added GetDeploymentConfig accessor method with nil handling |
| hack/tools/update-deploymentconfig-schema.sh | Shell script for regenerating schema from vendor dependencies |
| Makefile | Added update-deploymentconfig-schema make target |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
internal/operator-controller/rukpak/bundle/schema/validator_test.go
Outdated
Show resolved
Hide resolved
2cd3619 to
56d10e2
Compare
56d10e2 to
fce993a
Compare
cd32996 to
5bfc310
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 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| // Write to file | ||
| if err := os.WriteFile(outputFile, data, 0600); err != nil { |
Copilot
AI
Jan 20, 2026
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.
The file permission 0600 makes the generated schema file read/write for owner only. Since this is a JSON schema file that needs to be committed to the repository and read by the build process, it should use more permissive permissions like 0644 (readable by all, writable by owner).
| if err := os.WriteFile(outputFile, data, 0600); err != nil { | |
| if err := os.WriteFile(outputFile, data, 0644); err != nil { |
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.
0644 is what I started out with, but the linter fails with
hack/tools/schema-generator/main.go:106:12 gosec G306: Expect WriteFile permissions to be 0600 or less
But if I change this to 0600, Copilot says it should be 0644.
The machines are fighting with each other, and I'm caught in between.
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.
Use 644 and add a //nolint:gosec 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.
There's only one user in the pod. There's no need to give group and other users access. 0600 has been used in many other places.
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.
let's start off with 0600 and see if more is required in that case then
internal/operator-controller/rukpak/bundle/schema/validator_test.go
Outdated
Show resolved
Hide resolved
5bfc310 to
64e7f6b
Compare
64e7f6b to
6b8cb00
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 11 out of 11 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2454 +/- ##
==========================================
- Coverage 69.49% 69.35% -0.15%
==========================================
Files 101 103 +2
Lines 7754 8115 +361
==========================================
+ Hits 5389 5628 +239
- Misses 1930 2043 +113
- Partials 435 444 +9
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:
|
|
@anik120 have you considered approaching this by having the schema-generator generate the base registry+v1 bundle configuration schema including watchNamespace? Then the process of generating the final schema for a bundle could be: Also, you could delete the deploymentConfig from the schema if the featuregate isn't enabled... |
Makefile
Outdated
| env JQ=$(GOJQ) hack/tools/update-tls-profiles.sh | ||
|
|
||
| .PHONY: update-deploymentconfig-schema | ||
| update-deploymentconfig-schema: #EXHELP Update DeploymentConfig JSON schema by introspecting v1alpha1.SubscriptionConfig |
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.
should we call it something like update-bundle-config-schema or somehow relate the naming of things back to bundle configuration? It might make it easier to understand if you're coming in fresh?
same with hack/tools/schema-generator
6b8cb00 to
9f7247e
Compare
9f7247e to
a289b06
Compare
a289b06 to
819a1de
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 11 out of 11 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
internal/operator-controller/rukpak/bundle/registryv1.go:169
- The
buildBundleConfigSchemafunction and its helper functions (buildWatchNamespaceProperty,selectNamespaceFormat,isWatchNamespaceConfigurable,isWatchNamespaceConfigRequired) lack test coverage. Given that this code modifies the schema at runtime based on install modes, it would benefit from comprehensive unit tests to ensure all combinations of install modes produce the correct schema structure. Consider adding aregistryv1_test.gofile with tests covering different install mode scenarios.
func buildBundleConfigSchema(installModes sets.Set[v1alpha1.InstallMode]) (map[string]any, error) {
// Load the base schema
baseSchema, err := bundleSchema.GetBundleConfigSchemaMap()
if err != nil {
return nil, fmt.Errorf("failed to get base bundle config schema: %w", err)
}
// Get properties map from the schema
properties, ok := baseSchema["properties"].(map[string]any)
if !ok {
return nil, fmt.Errorf("base schema missing properties")
}
// Modify watchNamespace field based on install modes
if isWatchNamespaceConfigurable(installModes) {
// Replace the generic watchNamespace with install-mode-specific version
watchNSProperty, isRequired := buildWatchNamespaceProperty(installModes)
properties["watchNamespace"] = watchNSProperty
if isRequired {
baseSchema["required"] = []any{"watchNamespace"}
} else {
// Ensure no required field if it's optional
delete(baseSchema, "required")
}
} else {
// AllNamespaces only - remove watchNamespace property entirely
// (operator always watches all namespaces, no configuration needed)
delete(properties, "watchNamespace")
delete(baseSchema, "required")
}
return baseSchema, nil
}
// buildWatchNamespaceProperty creates the validation rules for the watchNamespace field.
//
// The rules depend on what install modes are supported:
// - AllNamespaces supported: watchNamespace is optional (can be null)
// - Only Single/Own supported: watchNamespace is required
// - Only OwnNamespace: must equal install namespace
// - Only SingleNamespace: must be different from install namespace
//
// Returns the validation rules and whether the field is required.
func buildWatchNamespaceProperty(installModes sets.Set[v1alpha1.InstallMode]) (map[string]any, bool) {
watchNSProperty := map[string]any{
"description": "The namespace that the operator should watch for custom resources",
}
hasOwnNamespace := installModes.Has(v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeOwnNamespace, Supported: true})
hasSingleNamespace := installModes.Has(v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeSingleNamespace, Supported: true})
format := selectNamespaceFormat(hasOwnNamespace, hasSingleNamespace)
if isWatchNamespaceConfigRequired(installModes) {
watchNSProperty["type"] = "string"
if format != "" {
watchNSProperty["format"] = format
}
return watchNSProperty, true
}
// allow null or valid namespace string
stringSchema := map[string]any{
"type": "string",
}
if format != "" {
stringSchema["format"] = format
}
// Convert to []any for JSON schema compatibility
watchNSProperty["anyOf"] = []any{
map[string]any{"type": "null"},
stringSchema,
}
return watchNSProperty, false
}
// selectNamespaceFormat picks which namespace constraint to apply.
//
// - OwnNamespace only: watchNamespace must equal install namespace
// - SingleNamespace only: watchNamespace must be different from install namespace
// - Both or neither: no constraint, any namespace name is valid
func selectNamespaceFormat(hasOwnNamespace, hasSingleNamespace bool) string {
if hasOwnNamespace && !hasSingleNamespace {
return config.FormatOwnNamespaceInstallMode
}
if hasSingleNamespace && !hasOwnNamespace {
return config.FormatSingleNamespaceInstallMode
}
return "" // No format constraint needed for generic case
}
// isWatchNamespaceConfigurable checks if the user can set a watchNamespace.
//
// Returns true if:
// - SingleNamespace is supported (user picks a namespace to watch)
// - OwnNamespace is supported (user sets watchNamespace to the install namespace)
//
// Returns false if:
// - Only AllNamespaces is supported (operator always watches everything)
func isWatchNamespaceConfigurable(installModes sets.Set[v1alpha1.InstallMode]) bool {
return installModes.HasAny(
v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeSingleNamespace, Supported: true},
v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeOwnNamespace, Supported: true},
)
}
// isWatchNamespaceConfigRequired checks if the user must provide a watchNamespace.
//
// Returns true (required) when:
// - Only OwnNamespace is supported
// - Only SingleNamespace is supported
// - Both OwnNamespace and SingleNamespace are supported
//
// Returns false (optional) when:
// - AllNamespaces is supported (user can leave it unset to watch all namespaces)
func isWatchNamespaceConfigRequired(installModes sets.Set[v1alpha1.InstallMode]) bool {
return isWatchNamespaceConfigurable(installModes) &&
!installModes.Has(v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeAllNamespaces, Supported: true})
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@perdasilva I(we) have, that's what we discussed in the RFC too. I just zoomed in too much on deploymentConfig when I started this work 😅 thanks for the call out, I've updated the PR to be broader for bundle config, PTAL |
…le configuration Summary: Implements Phase 1 of the variable configuration feature to achieve feature parity with OLMv0's SubscriptionConfig for registry+v1 bundles. RFC: https://docs.google.com/document/d/18O4qBvu5I4WIJgo5KU1opyUKcrfgk64xsI3tyXxmVEU/edit?tab=t.0#heading=h.x3tfh25grvnv Details: This PR adds infrastructure for JSON schema-based validation of registry+v1 bundle configuration in ClusterExtension, including both `watchNamespace` and `deploymentConfig` fields: - **Schema Generation Tool**: Created reflection-based generator (`hack/tools/schema-generator/`) that automatically introspects `v1alpha1.SubscriptionConfig` from `github.com/operator-framework/api` to produce a complete registry+v1 bundle configuration JSON Schema Draft 7 document - Generates schema with `watchNamespace` (for operator scope) and `deploymentConfig` (for deployment customization) - Handles nested k8s corev1 types recursively - Supports inline/embedded struct fields - Extracts field documentation from Go source via AST parsing - Excludes `selector` field (unused in OLMv0) - **Runtime Schema Customization**: The base schema is loaded and modified at runtime based on operator install modes: - `watchNamespace` validation rules are customized per operator's supported install modes - AllNamespaces only: watchNamespace removed (operator always watches all namespaces) - OwnNamespace only: watchNamespace required, must equal install namespace - SingleNamespace only: watchNamespace required, must differ from install namespace - AllNamespaces + OwnNamespace: watchNamespace optional - **Validation Infrastructure**: Added `internal/operator-controller/rukpak/bundle/schema/` package with: - `GetBundleConfigSchemaMap()` function that provides the base bundle config schema - Runtime schema modification in `buildBundleConfigSchema()` based on install modes - Validation integrated into bundle config unmarshaling via `config.UnmarshalConfig()` - Comprehensive test coverage - **Regeneration Workflow**: Added `make update-registryv1-bundle-schema` target to regenerate schema when upstream SubscriptionConfig changes. When the upstream `v1alpha1.SubscriptionConfig` adds new fields (e.g., new k8s corev1 types), running the regeneration target will automatically include them in the schema without manual updates. Addresses OPRUN-4112 (Phase 1) Future PRs will implement: - Phase 2: Renderer integration to apply deploymentConfig to Deployments - Phase 3: Provider integration to extract bundle configuration from bundles - Phase 4: Documentation
819a1de to
52133b9
Compare
Summary:
Implements Phase 1 of the variable configuration feature to achieve feature parity with
OLMv0's SubscriptionConfig for registry+v1 bundles.
RFC: https://docs.google.com/document/d/18O4qBvu5I4WIJgo5KU1opyUKcrfgk64xsI3tyXxmVEU/edit?tab=t.0#heading=h.x3tfh25grvnv
Details:
This PR adds infrastructure for JSON schema-based validation of registry+v1 bundle configuration
in ClusterExtension, including both
watchNamespaceanddeploymentConfigfields:Schema Generation Tool: Created reflection-based generator (
hack/tools/schema-generator/)that automatically introspects
v1alpha1.SubscriptionConfigfromgithub.com/operator-framework/apito produce a complete registry+v1 bundle configuration JSON Schema Draft 7 document
watchNamespace(for operator scope) anddeploymentConfig(for deployment customization)selectorfield (unused in OLMv0)Runtime Schema Customization: The base schema is loaded and modified at runtime based on
operator install modes:
watchNamespacevalidation rules are customized per operator's supported install modesValidation Infrastructure: Added
internal/operator-controller/rukpak/bundle/schema/package with:GetBundleConfigSchemaMap()function that provides the base bundle config schemabuildBundleConfigSchema()based on install modesconfig.UnmarshalConfig()Regeneration Workflow: Added
make update-registryv1-bundle-schematarget to regenerate schema whenupstream SubscriptionConfig changes.
When the upstream
v1alpha1.SubscriptionConfigadds new fields (e.g., new k8s corev1 types), running theregeneration target will automatically include them in the schema without manual updates.
Addresses OPRUN-4112 (Phase 1)
Future PRs will implement: