Skip to content

Conversation

@camilamacedo86
Copy link
Contributor

@camilamacedo86 camilamacedo86 commented Nov 9, 2025

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 ConfigSchemaProvider interface 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:

What Your Operator Supports Do You Need watchNamespace?
AllNamespaces only No - you can omit it or set it to null
OwnNamespace only Yes - must be the same namespace where the operator is installed
SingleNamespace only Yes - must be a different namespace than where the operator is installed
AllNamespaces + OwnNamespace No - optional, defaults to AllNamespaces if you don't specify
OwnNamespace + SingleNamespace Yes - you must pick a namespace

Examples That Work

No configuration (AllNamespaces mode)

spec:
  packageName: my-operator

Watch specific namespace (SingleNamespace mode)

metadata:
  namespace: operator-install-ns
spec:
  packageName: my-operator
  config:
    watchNamespace: target-namespace  # Different from install namespace

Watch install namespace (OwnNamespace mode)

metadata:
  namespace: my-namespace
spec:
  packageName: my-operator
  config:
    watchNamespace: my-namespace  # Same as install namespace

Error Messages

All errors start with invalid ClusterExtension configuration: invalid configuration: followed by the specific problem.

Typo in Field Name

spec:
  config:
    watchNamespce: my-namespace  # Typo

Error:

invalid ClusterExtension configuration: invalid configuration: unknown field "watchNamespce"

Missing Required Field

spec:
  packageName: my-operator
  # No config - but operator requires watchNamespace

Error:

invalid ClusterExtension configuration: invalid configuration: required field "watchNamespace" is missing

Wrong Type

spec:
  config:
    watchNamespace: 123  # Should be a string

Error:

invalid ClusterExtension configuration: invalid configuration: invalid value type for field "watchNamespace": expected "string" but got "number"

OwnNamespace Mode - Wrong Namespace

metadata:
  namespace: correct-namespace
spec:
  config:
    watchNamespace: wrong-namespace  # Must match install namespace

Error:

invalid ClusterExtension configuration: invalid configuration: configuration validation failed: 
- at '/watchNamespace': 'wrong-namespace' is not valid ownNamespaceInstallMode: 
  invalid value "wrong-namespace": watchNamespace must be "correct-namespace" 
  (the namespace where the operator is installed) because this operator only 
  supports OwnNamespace install mode

SingleNamespace Mode - Can't Use Install Namespace

metadata:
  namespace: install-ns
spec:
  config:
    watchNamespace: install-ns  # Must be different from install namespace

Error:

invalid ClusterExtension configuration: invalid configuration: configuration validation failed: 
- at '/watchNamespace': 'install-ns' is not valid singleNamespaceInstallMode: 
  invalid value "install-ns": watchNamespace must be different from "install-ns" 
  (the install namespace) because this operator uses SingleNamespace install mode 
  to watch a different namespace

Invalid JSON/YAML

spec:
  config: {"incomplete

Error:

invalid ClusterExtension configuration: invalid configuration: found unexpected end of stream

Technical Details

Design Diagram

┌─────────────────────────────────────────────────────────────┐
│                    ConfigSchemaProvider                     │
│                         (Interface)                         │
└────────────┬────────────────────────────────┬───────────────┘
             │                                │
             ▼                                ▼
    ┌────────────────┐              ┌─────────────────┐
    │  RegistryV1    │              │  Future: Helm,  │
    │                │              │  Registry+v2    │
    │ GetConfigSchema│              │                 │
    │ (generates     │              │ GetConfigSchema │
    │  from install  │              │ (reads from     │
    │  modes)        │              │  chart/bundle)  │
    └────────┬───────┘              └────────┬────────┘
             │                               │
             └───────────┬───────────────────┘
                         ▼
              ┌──────────────────────┐
              │  UnmarshalConfig()   │
              │                      │
              │  1. Get schema       │
              │  2. Validate         │──→ Custom Validators:
              │  3. Parse            │    • ownNamespaceInstallMode
              └──────────┬───────────┘    • singleNamespaceInstallMode
                         │
                         ▼
                 ┌──────────────┐
                 │ bundle.Config│
                 │              │
                 │ - Opaque data│
                 │ - Accessors  │
                 └──────────────┘

@camilamacedo86 camilamacedo86 requested a review from a team as a code owner November 9, 2025 09:32
@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Nov 9, 2025
@netlify
Copy link

netlify bot commented Nov 9, 2025

Deploy Preview for olmv1 ready!

Name Link
🔨 Latest commit 6392c11
🔍 Latest deploy log https://app.netlify.com/projects/olmv1/deploys/69162f9cadd488000854b7bd
😎 Deploy Preview https://deploy-preview-2316--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.

@codecov
Copy link

codecov bot commented Nov 9, 2025

Codecov Report

❌ Patch coverage is 81.12245% with 37 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.31%. Comparing base (c95fc24) to head (6392c11).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...ternal/operator-controller/rukpak/bundle/config.go 74.26% 23 Missing and 12 partials ⚠️
internal/operator-controller/applier/provider.go 75.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2316      +/-   ##
==========================================
+ Coverage   74.30%   74.31%   +0.01%     
==========================================
  Files          91       92       +1     
  Lines        7083     7227     +144     
==========================================
+ Hits         5263     5371     +108     
- Misses       1405     1429      +24     
- Partials      415      427      +12     
Flag Coverage Δ
e2e 44.67% <0.00%> (-0.97%) ⬇️
experimental-e2e 48.67% <60.20%> (+0.32%) ⬆️
unit 58.93% <81.12%> (+0.32%) ⬆️

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.

@camilamacedo86 camilamacedo86 force-pushed the add-checks-config branch 3 times, most recently from a753a5c to f15ea9f Compare November 9, 2025 10:35
@camilamacedo86 camilamacedo86 changed the title WIP: ✨ (chore): Add structured bundle config validation helpers and tests. ✨ (chore): Add structured bundle config validation helpers and tests. Nov 9, 2025
@openshift-ci openshift-ci bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Nov 9, 2025
@openshift-ci
Copy link

openshift-ci bot commented Nov 9, 2025

[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 joelanford for approval. For more information see the Code Review Process.

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

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

@camilamacedo86 camilamacedo86 changed the title ✨ (chore): Add structured bundle config validation helpers and tests. ✨ (chore): Bundle configuration is now validated - errors caught early with clear messages. Nov 9, 2025
@camilamacedo86 camilamacedo86 changed the title ✨ (chore): Bundle configuration is now validated - errors caught early with clear messages. ✨ Bundle configuration is now validated - errors caught early with clear messages. Nov 9, 2025
Copilot AI review requested due to automatic review settings November 10, 2025 10:13
@camilamacedo86 camilamacedo86 force-pushed the add-checks-config branch 2 times, most recently from 1f3451a to 65bd4ef Compare November 10, 2025 10:17

This comment was marked as outdated.

@camilamacedo86 camilamacedo86 force-pushed the add-checks-config branch 2 times, most recently from d78bf4a to 920f78e Compare November 10, 2025 11:08

This comment was marked as outdated.

This comment was marked as outdated.

This comment was marked as outdated.

Copilot AI review requested due to automatic review settings November 13, 2025 12:16
Copilot finished reviewing on behalf of camilamacedo86 November 13, 2025 12:18
Copy link

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 8 out of 8 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.

Copy link

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 8 out of 8 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// UnmarshalConfig, which does the validation.
type Config struct {
WatchNamespace *string `json:"watchNamespace"`
data map[string]any
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@perdasilva we are removing to make more Generic
So, I think is important we move with it , if we want change it, before we promote Config API ( consequentely single/own ns )

return nil
}
// Extract string value
if str, ok := val.(string); ok {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can drop now lines 100-102, because it is covered by line 103.

//
// If the user doesn't provide any configuration but the bundle requires some fields
// (like watchNamespace), validation will fail with a helpful error.
func UnmarshalConfig(bytes []byte, schema map[string]any, installNamespace string) (*Config, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the function unmarshal + validatates optionally the config. I would split these into two functions at least so that we can achieve something like:

c := NewConfigBuilder.FromBytes(bytes).WithSchema(schema).WithInstallNamespace(installNamespace).Build()
err := v.Validate()

Copy link
Contributor Author

@camilamacedo86 camilamacedo86 Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the Builder approach, but I think we should add it only IF/when we start to have many args.
I asked Claude AI to evaluate the current approach vs Builder Pattern. Here's the summary:


Claude AI Analysis Summary

Current Approach

config, err := bundle.UnmarshalConfig(bytes, schema, installNamespace)

Design principle: Validation happens at construction. If you have a Config object, it's already valid.

Builder Pattern Option 1: Validation in Build()

config, err := bundle.NewConfigBuilder().
    FromBytes(bytes).
    WithSchema(schema).
    WithInstallNamespace(ns).
    Build()  // validation happens here

Result: Functionally identical to current approach, just more verbose (1 function call → 5 method calls).

Builder Pattern Option 2: Separate Validate()

config := builder.Build()  // unvalidated
err := config.Validate()   // validate separately

Problems:

  • Allows invalid Config objects to exist
  • Developers might forget to call Validate()
  • Would need to run validation twice (inefficient)
  • Breaks safety guarantee

When Builder Pattern Makes Sense

✅ Good for:

  • 7-10+ parameters
  • Many optional parameters
  • Parameters can be set independently
  • Multiple valid construction paths

❌ Our situation:

  • 3 required parameters
  • All interdependent
  • One construction path

Maintainability Comparison

Current: 1 function to maintain

  • Add parameter → update 1 function + call sites
  • Bug → debug 1 function
  • Test → 1 test function

Builder: 5+ methods to maintain

  • Add parameter → update struct + new method + Build() + call sites
  • Bug → debug 5 methods to find which one
  • Test → 6+ test functions

Comparison Table

Aspect Current Builder Option 1 Builder Option 2
Lines of code 1 5 6
API methods 1 4 5
Validation timing At construction At construction After construction
Can have invalid Config? ❌ No ❌ No ✅ Yes (bad!)
Double validation risk ❌ No ❌ No ✅ Yes
Maintainability ✅ High ⚠️ Medium ❌ Low
Safety guarantees ✅ Strong ✅ Strong ❌ Weak

Problems with Each Option

Builder Option 1 Problems:

  • More code to write and maintain (5 methods vs 1 function)
  • When adding new parameter: must update struct + new method + Build() + all callers
  • Bug fixing: must search through 5 methods instead of 1
  • Testing: need 6+ test functions instead of 1
  • More opportunities for breaking changes (5 methods can break vs 1)

Builder Option 2 Problems:

  • Critical: Allows invalid Config objects to exist
  • Developers can forget to call Validate()
  • Must track validation state internally
  • Double validation problem: If Build() validates → Validate() runs same checks again (waste). If Build() doesn't validate → invalid Config objects exist until Validate() is called (unsafe). Either way, it's problematic.
  • Errors can occur in multiple places (Build() and Validate())
  • Violates "safe by construction" principle

Conclusion from Analysis (Recommendation)

Keep current approach. Builder pattern would add complexity without benefit for 3 required interdependent parameters. Refactor to builder only if we grow to 7+ parameters.

The current design already follows best practices:

  • ✅ Validation at construction time
  • ✅ Immutable validated objects
  • ✅ Simple, clear API
  • ✅ Strong safety guarantees

Why wait?

  1. YAGNI principle - Don't add complexity for hypothetical future needs
  2. Current code is clean - Simple, safe, well-tested
  3. Easy to refactor later - If we add more parameters, we can migrate then
  4. No breaking change risk - Adding builder later doesn't break existing code

Copy link
Contributor Author

@camilamacedo86 camilamacedo86 Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, @pedjak could we agree in keep it as it is until we need to change it ? Can we go KISS?

// The Test_ErrorFormatting_* tests make sure this parsing works. If we upgrade the
// JSON schema library and those tests start failing, we'll know we need to update
// this parsing logic.
func formatSchemaError(err error) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatSchemaError is unprecise because it converts ValueError into another error instance. Also, why a user could not understand the original ValueError message?

Copy link
Contributor Author

@camilamacedo86 camilamacedo86 Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The minimal option here would be like

// formatSchemaError removes technical schema file paths from validation errors
// while preserving the library's clear error messages.
func formatSchemaError(err error) error {
	msg := err.Error()
	// Strip the "jsonschema validation failed with 'file:///.../schema.json#'" prefix
	// Keep everything after the newline which has the actual error details
	if idx := strings.Index(msg, "\n"); idx != -1 {
		return fmt.Errorf("invalid configuration:%s", msg[idx:])
	}
	return fmt.Errorf("invalid configuration: %s", msg)
}

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)

Scenario Example Input Minimal Approach Current Approach Winner
Missing required field {} when watchNamespace required invalid configuration:
- at '': missing property 'watchNamespace'
required field "watchNamespace" is missing Current - Clearer terminology
Null instead of required {"watchNamespace": null} invalid configuration:
- at '/watchNamespace': got null, want string
required field "watchNamespace" is missing Current - Recognizes intent
Unknown field {"unknownField": "value"} invalid configuration:
- at '': additional properties 'unknownField' not allowed
unknown field "unknownField" Current - Concise
Type mismatch {"watchNamespace": 123} invalid configuration:
- at '/watchNamespace': got number, want string
invalid type for field "watchNamespace": got number, want string Current - Adds field context
Nested field error {"resources": {"memory": 512}} invalid configuration:
- at '/resources/memory': got number, want string
invalid type for field "resources.memory": got number, want string Current - Dot notation for paths
Root type error true (not an object) invalid configuration:
- at '': got boolean, want object
invalid type: got boolean, want object Similar
Constraint violation {"replicaCount": 0} with min=1 invalid configuration:
- at '/replicaCount': value should be >= 1
value should be >= 1 Similar - Pass through
Enum violation {"type": "Invalid"} invalid configuration:
- at '/type': value should be one of [...]
value should be one of [...] Similar - Pass through
Custom format validator {"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) ... Current - No bullet prefix
Multiple errors {} requires 2 fields invalid configuration:
- at '': missing property 'replicaCount'
(stops at first error)
multiple errors found:
- required field "replicaCount" is missing
- required field "image" is missing
Current - Shows ALL errors!

I think we should keep the custom as it is now.
WDYT?

Copilot AI review requested due to automatic review settings November 13, 2025 17:55
Copilot finished reviewing on behalf of camilamacedo86 November 13, 2025 17:56
Copy link

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 8 out of 8 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.

Copilot AI review requested due to automatic review settings November 13, 2025 18:40
Copilot finished reviewing on behalf of camilamacedo86 November 13, 2025 18:44
Copy link

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 8 out of 8 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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

3 participants