Skip to content

Commit 57d8d12

Browse files
sbernauerdervoeti
andauthored
chore!: Use serde_json::Value instead of String for user-provided JSON configOverrides (#1206)
* chore!: Use `serde_json::Value` instead of `String` for user-provided JSON configOverrides * changelog * Update crates/stackable-operator/CHANGELOG.md Co-authored-by: Lukas Krug <lukas@luvosys.de> * Update crates/stackable-operator/CHANGELOG.md --------- Co-authored-by: Lukas Krug <lukas@luvosys.de>
1 parent a07999f commit 57d8d12

3 files changed

Lines changed: 133 additions & 28 deletions

File tree

crates/stackable-operator/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
### Changed
8+
9+
- BREAKING: Use `serde_json::Value` instead of `String` for user-provided JSON `configOverrides`. This change is marked as breaking, as it causes a breaking change to the CRDs ([#1206]).
10+
11+
[#1206]: https://github.com/stackabletech/operator-rs/pull/1206
12+
713
## [0.111.1] - 2026-04-28
814

915
### Added

crates/stackable-operator/crds/DummyCluster.yaml

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,8 +1007,34 @@ spec:
10071007
type: string
10081008
type: array
10091009
userProvided:
1010-
description: Override the entire config file with the specified String.
1011-
type: string
1010+
description: |-
1011+
Override the entire config file with the specified JSON document.
1012+
1013+
Please note that you can in-line JSON into YAML as follows:
1014+
1015+
```yaml
1016+
# ... other YAML content
1017+
userProvided: {
1018+
"myString": "test",
1019+
"myList": ["test"],
1020+
"myBool": true,
1021+
"my": {"nested.field.with.dots": 42}
1022+
}
1023+
```
1024+
1025+
As an alternative you can also stick to YAML:
1026+
1027+
```yaml
1028+
# ... other YAML content
1029+
userProvided:
1030+
myString: test
1031+
myList: [test]
1032+
myBool: true
1033+
my:
1034+
nested.field.with.dots: 42
1035+
```
1036+
type: object
1037+
x-kubernetes-preserve-unknown-fields: true
10121038
type: object
10131039
dummy.properties:
10141040
additionalProperties:
@@ -1659,8 +1685,34 @@ spec:
16591685
type: string
16601686
type: array
16611687
userProvided:
1662-
description: Override the entire config file with the specified String.
1663-
type: string
1688+
description: |-
1689+
Override the entire config file with the specified JSON document.
1690+
1691+
Please note that you can in-line JSON into YAML as follows:
1692+
1693+
```yaml
1694+
# ... other YAML content
1695+
userProvided: {
1696+
"myString": "test",
1697+
"myList": ["test"],
1698+
"myBool": true,
1699+
"my": {"nested.field.with.dots": 42}
1700+
}
1701+
```
1702+
1703+
As an alternative you can also stick to YAML:
1704+
1705+
```yaml
1706+
# ... other YAML content
1707+
userProvided:
1708+
myString: test
1709+
myList: [test]
1710+
myBool: true
1711+
my:
1712+
nested.field.with.dots: 42
1713+
```
1714+
type: object
1715+
x-kubernetes-preserve-unknown-fields: true
16641716
type: object
16651717
dummy.properties:
16661718
additionalProperties:

crates/stackable-operator/src/config_overrides.rs

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,6 @@ pub enum Error {
2626
source: serde_json::Error,
2727
index: usize,
2828
},
29-
30-
#[snafu(display("failed to parse user-provided JSON content"))]
31-
ParseUserProvidedJson { source: serde_json::Error },
3229
}
3330

3431
/// Trait that allows the product config pipeline to extract flat key-value
@@ -89,8 +86,33 @@ pub enum JsonConfigOverrides {
8986
/// `{"op": "add", "path": "/0/happy", "value": true}`
9087
JsonPatches(Vec<String>),
9188

92-
/// Override the entire config file with the specified String.
93-
UserProvided(String),
89+
/// Override the entire config file with the specified JSON document.
90+
///
91+
/// Please note that you can in-line JSON into YAML as follows:
92+
///
93+
/// ```yaml
94+
/// # ... other YAML content
95+
/// userProvided: {
96+
/// "myString": "test",
97+
/// "myList": ["test"],
98+
/// "myBool": true,
99+
/// "my": {"nested.field.with.dots": 42}
100+
/// }
101+
/// ```
102+
///
103+
/// As an alternative you can also stick to YAML:
104+
///
105+
/// ```yaml
106+
/// # ... other YAML content
107+
/// userProvided:
108+
/// myString: test
109+
/// myList: [test]
110+
/// myBool: true
111+
/// my:
112+
/// nested.field.with.dots: 42
113+
/// ```
114+
#[schemars(schema_with = "raw_object_schema")]
115+
UserProvided(serde_json::Value),
94116
}
95117

96118
impl JsonConfigOverrides {
@@ -123,18 +145,18 @@ impl JsonConfigOverrides {
123145
json_patch::patch(&mut doc, &operations).context(ApplyJsonPatchSnafu)?;
124146
Ok(doc)
125147
}
126-
Self::UserProvided(content) => {
127-
serde_json::from_str(content).context(ParseUserProvidedJsonSnafu)
128-
}
148+
Self::UserProvided(content) => Ok(content.clone()),
129149
}
130150
}
131151
}
132152

133153
#[cfg(test)]
134154
mod tests {
155+
use rstest::rstest;
135156
use serde_json::json;
136157

137158
use super::*;
159+
use crate::utils::yaml_from_str_singleton_map;
138160

139161
#[test]
140162
fn json_merge_patch_add_and_overwrite_fields() {
@@ -244,30 +266,16 @@ mod tests {
244266
#[test]
245267
fn user_provided_ignores_base() {
246268
let base = json!({"foo": "bar"});
247-
let content = "{\"custom\": true}";
269+
let content = json!({"custom": true});
248270

249-
let overrides = JsonConfigOverrides::UserProvided(content.to_owned());
271+
let overrides = JsonConfigOverrides::UserProvided(content);
250272

251273
let result = overrides
252274
.apply(&base)
253275
.expect("user provided should succeed");
254276
assert_eq!(result, json!({"custom": true}));
255277
}
256278

257-
#[test]
258-
fn user_provided_invalid_json_returns_error() {
259-
let base = json!({"foo": "bar"});
260-
261-
let overrides = JsonConfigOverrides::UserProvided("not valid json".to_owned());
262-
263-
let result = overrides.apply(&base);
264-
assert!(
265-
matches!(result.unwrap_err(), Error::ParseUserProvidedJson { source } if source.to_string()
266-
== "expected ident at line 1 column 2"),
267-
"invalid JSON should return an error"
268-
);
269-
}
270-
271279
#[test]
272280
fn key_value_config_overrides_as_product_config_overrides() {
273281
let mut overrides = BTreeMap::new();
@@ -281,4 +289,43 @@ mod tests {
281289
assert_eq!(result.get("key1"), Some(&Some("value1".to_owned())));
282290
assert_eq!(result.get("key2"), Some(&Some("value2".to_owned())));
283291
}
292+
293+
#[rstest]
294+
#[case::inline_json(
295+
r#"
296+
# ... other YAML content
297+
userProvided: {
298+
"myString": "test",
299+
"myList": ["test"],
300+
"myBool": true,
301+
"my": {"nested.field.with.dots": 42}
302+
}
303+
"#
304+
)]
305+
#[case::inline_yaml(
306+
"
307+
# ... other YAML content
308+
userProvided:
309+
myString: test
310+
myList: [test]
311+
myBool: true
312+
my:
313+
nested.field.with.dots: 42
314+
"
315+
)]
316+
fn parse_user_provided_json(#[case] yaml: String) {
317+
let expected = json!({
318+
"myString": "test",
319+
"myList": ["test"],
320+
"myBool": true,
321+
"my": {"nested.field.with.dots": 42}
322+
});
323+
let json_config_overrides: JsonConfigOverrides =
324+
yaml_from_str_singleton_map(&yaml).unwrap();
325+
326+
match json_config_overrides {
327+
JsonConfigOverrides::UserProvided(value) => assert_eq!(value, expected),
328+
_ => panic!("JsonConfigOverrides must be of type UserProvided"),
329+
}
330+
}
284331
}

0 commit comments

Comments
 (0)