feat: config overrides for structured config files#1177
Open
feat: config overrides for structured config files#1177
Conversation
20 tasks
sbernauer
reviewed
Mar 19, 2026
Member
sbernauer
left a comment
There was a problem hiding this comment.
Only small stuff noticed during daily, real review will follow
b0e1dc8 to
8d9c6cd
Compare
sbernauer
reviewed
Mar 20, 2026
Comment on lines
311
to
312
| T, | ||
| U = GenericRoleConfig, |
Member
There was a problem hiding this comment.
nit: It would be great if we could rename all T and U to Config and RoleConfg accordingly.
| @@ -302,32 +311,36 @@ pub struct Role< | |||
| T, | |||
| U = GenericRoleConfig, | |||
| ProductSpecificCommonConfig = GenericProductSpecificCommonConfig, | |||
Member
There was a problem hiding this comment.
nit: It would be great to rename ProductSpecificCommonConfig to CommonConfig for consitency
crates/xtask/src/crd/dummy.rs
Outdated
| #[serde(default)] | ||
| pub object_overrides: ObjectOverrides, | ||
|
|
||
| json_config_overrides: Option<stackable_operator::config_overrides::JsonConfigOverrides>, |
Member
There was a problem hiding this comment.
Please use this struct instead (this fields can than be removed)
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[schemars(crate = "stackable_operator::schemars")]
pub struct ProductConfigOverrides {
#[serde(rename = "my.json")]
my_json: Option<JsonConfigOverrides>,
}
Member
Author
There was a problem hiding this comment.
(this is also the commit that switches from backwards-compatible config overrides to making it break for existing operators, so they are forced to opt in to the new config overrides)
88710df to
abf16cd
Compare
abf16cd to
20e94e3
Compare
20e94e3 to
4b84601
Compare
20 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Implementation of https://github.com/stackabletech/decisions/issues/73, needed for stackabletech/opa-operator#756
Problem
The existing
configOverridesmechanism usesHashMap<String, HashMap<String, String>>(filename → flat key-value pairs). This works well for flat formats like.propertiesfiles and Hadoop XML, but it cannot express modifications to nested or structured formats like JSON. For example, there is no clean way to overridemin_delay_secondsinside a deeply nested OPAconfig.json.Solution
This PR adds strategy-based
configOverridesbuilding blocks tooperator-rs. Instead of a single flat map, operators can now compose typed override structs that choose a patch strategy per config file. The CRD schema explicitly encodes which strategies are supported for which files, which means invalid input is rejected by the Kubernetes API server before the operator ever sees it.The general architecture for config overrides is: operator-rs handles merging (combining base config with user overrides), while the operator handles rendering (turning the merged result into the actual file content). For key-value files, the merging happens inside the existing product config pipeline. For structured files like JSON, the merging happens via e.g.
JsonConfigOverrides::apply(). Both are operator-rs code. For rendering, the operator picks the right format: sometimes using shared helpers from operator-rs (e.g. properties file writers), sometimes doing it directly (e.g.serde_json::to_string_pretty).Breaking changes to
CommonConfiguration,Role, andRoleGroupThese structs gained a new required
ConfigOverridestype parameter. There is no default, which means every operator must explicitly specify itsConfigOverridestype. This is a breaking change: all existing operators need to define their own config overrides struct and pass it as a type parameter.The type parameters were also renamed for clarity:
TtoConfig,UtoRoleConfig, andProductSpecificCommonConfigtoCommonConfig.New
config_overridesmoduleIt contains:
New patch strategies for JSON files:
JsonConfigOverrides: enum supporting three strategies for JSON config files:jsonMergePatch: RFC 7396, simple nested overrides expressed as YAML/JSONjsonPatches: RFC 6902, fine-grained operations (add, remove, replace, move, test)userProvided: full file replacement escape hatchTyped key-value overrides:
KeyValueConfigOverrides: typed wrapper for flat key-value files (.properties, Hadoop XML).Uses
#[serde(flatten)]so it serializes identically to the oldHashMap<String, String>.KeyValueOverridesProvidertrait:ConfigOverridestype. The default implementation returns an empty map, so operators that only use structured overrides (like OPA) don't need any custom logic.The shared product config pipeline (
transform_all_roles_to_config) processesPropertyNameKind::Fileentries by callingget_key_value_overrideson theConfigOverridestype. Rust requires this trait bound at compile time even if a particular operator never passesPropertyNameKind::Fileentries. Operators that only use structured overrides (like OPA withJsonConfigOverrides) can rely on the default no-op implementation. Every operator's config overrides type must implement this trait, butimpl KeyValueOverridesProvider for OpaConfigOverrides {}(using the default no-op) is sufficient for operators that don't use key-value overrides.Example for a hypothetical NiFi with the new typed overrides, which uses
KeyValueConfigOverrides:Why a generic type parameter instead of an enum?
An enum containing all strategies would mean every operator advertises support for every strategy on every file. The current approach (operators compose a struct from building blocks) lets the CRD schema precisely reflect what is actually supported. Invalid combinations are rejected at admission time, not at runtime.
What is NOT included
XmlConfigOverrides/ XML patch strategy (RFC 5261): will be added when needed (e.g. for NiFi).Definition of Done Checklist
Author
Reviewer
Acceptance