Skip to content

Conversation

@anik120
Copy link
Member

@anik120 anik120 commented Jan 20, 2026

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

Copilot AI review requested due to automatic review settings January 20, 2026 19:51
@netlify
Copy link

netlify bot commented Jan 20, 2026

Deploy Preview for olmv1 ready!

Name Link
🔨 Latest commit 52133b9
🔍 Latest deploy log https://app.netlify.com/projects/olmv1/deploys/697126cacb51060008e170c2
😎 Deploy Preview https://deploy-preview-2454--olmv1.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@anik120 anik120 requested review from tmshort and removed request for Copilot January 20, 2026 19:52
@openshift-ci
Copy link

openshift-ci bot commented Jan 20, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign kevinrizza for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@anik120 anik120 requested review from joelanford and perdasilva and removed request for camilamacedo86 and thetechnick January 20, 2026 19:52
@anik120 anik120 force-pushed the deploymentconfig-json-schema branch from 1367534 to 2cd3619 Compare January 20, 2026 20:05
Copilot AI review requested due to automatic review settings January 20, 2026 20:05
Copy link
Contributor

Copilot AI left a 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.SubscriptionConfig from 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.

@anik120 anik120 force-pushed the deploymentconfig-json-schema branch from 2cd3619 to 56d10e2 Compare January 20, 2026 20:26
Copilot AI review requested due to automatic review settings January 20, 2026 20:30
@anik120 anik120 force-pushed the deploymentconfig-json-schema branch from 56d10e2 to fce993a Compare January 20, 2026 20:30
@anik120 anik120 force-pushed the deploymentconfig-json-schema branch 2 times, most recently from cd32996 to 5bfc310 Compare January 20, 2026 20:37
Copy link
Contributor

Copilot AI left a 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 {
Copy link

Copilot AI Jan 20, 2026

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).

Suggested change
if err := os.WriteFile(outputFile, data, 0600); err != nil {
if err := os.WriteFile(outputFile, data, 0644); err != nil {

Copilot uses AI. Check for mistakes.
Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Contributor

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.

Copy link
Member Author

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

@anik120 anik120 force-pushed the deploymentconfig-json-schema branch from 5bfc310 to 64e7f6b Compare January 20, 2026 20:47
Copilot AI review requested due to automatic review settings January 20, 2026 21:01
@anik120 anik120 force-pushed the deploymentconfig-json-schema branch from 64e7f6b to 6b8cb00 Compare January 20, 2026 21:01
Copy link
Contributor

Copilot AI left a 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
Copy link

codecov bot commented Jan 20, 2026

Codecov Report

❌ Patch coverage is 65.53191% with 81 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.35%. Comparing base (8167ff8) to head (52133b9).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
hack/tools/schema-generator/main.go 64.07% 71 Missing and 3 partials ⚠️
...al/operator-controller/rukpak/bundle/registryv1.go 69.23% 2 Missing and 2 partials ⚠️
...rator-controller/rukpak/bundle/schema/validator.go 60.00% 1 Missing and 1 partial ⚠️
internal/operator-controller/config/config.go 90.90% 1 Missing ⚠️
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     
Flag Coverage Δ
e2e 46.58% <41.37%> (+0.46%) ⬆️
experimental-e2e 13.61% <0.00%> (+0.16%) ⬆️
unit 57.27% <61.70%> (+0.09%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@perdasilva
Copy link
Contributor

perdasilva commented Jan 21, 2026

@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:
deserialize the base schema, update it based on the bundle install mode configuration, then return that (or validate the configuration against that). This way we don't need to tie things down so closely to the deployment config.

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
Copy link
Contributor

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

@anik120 anik120 force-pushed the deploymentconfig-json-schema branch from 6b8cb00 to 9f7247e Compare January 21, 2026 19:00
Copilot AI review requested due to automatic review settings January 21, 2026 19:02
@anik120 anik120 force-pushed the deploymentconfig-json-schema branch from 9f7247e to a289b06 Compare January 21, 2026 19:02
@anik120 anik120 force-pushed the deploymentconfig-json-schema branch from a289b06 to 819a1de Compare January 21, 2026 19:06
@anik120 anik120 changed the title ✨ Add automated schema generation and validation for deploymentConfig ✨ Add automated schema generation and validation for registry+v1 bundle configuration Jan 21, 2026
Copy link
Contributor

Copilot AI left a 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 buildBundleConfigSchema function 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 a registryv1_test.go file 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.

@anik120
Copy link
Member Author

anik120 commented Jan 21, 2026

@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
@anik120 anik120 force-pushed the deploymentconfig-json-schema branch from 819a1de to 52133b9 Compare January 21, 2026 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants