Skip to content

Commit 004b9bd

Browse files
xeniapeTechassi
andauthored
fix: swap priority order of role group config and role overrides in configuration merging (#841)
* change evaluation order of product configurations * add changelog * rename variable and move clone * add unit test for config override order * remove dublicate heading * Update crates/stackable-operator/src/product_config_utils.rs Co-authored-by: Techassi <git@techassi.dev> --------- Co-authored-by: Techassi <git@techassi.dev>
1 parent 1d56e60 commit 004b9bd

File tree

2 files changed

+194
-50
lines changed

2 files changed

+194
-50
lines changed

crates/stackable-operator/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ All notable changes to this project will be documented in this file.
1212
### Changed
1313

1414
- BREAKING: Replace `lazy_static` with `std::cell::LazyCell` (the original implementation was done in [#827] and reverted in [#835]) ([#840]).
15+
- BREAKING: Swap priority order of role group config and role overrides in configuration merging to prioritize overrides in general ([#841]).
1516

1617
[#838]: https://github.com/stackabletech/operator-rs/pull/838
18+
[#841]: https://github.com/stackabletech/operator-rs/pull/841
1719
[#843]: https://github.com/stackabletech/operator-rs/pull/843
1820

1921
## [0.73.0] - 2024-08-09

crates/stackable-operator/src/product_config_utils.rs

Lines changed: 192 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ fn process_validation_result(
348348
/// with the highest priority. The merge priority chain looks like this where '->' means
349349
/// "overwrites if existing or adds":
350350
///
351-
/// group overrides -> group config -> role overrides -> role config (TODO: -> common_config)
351+
/// group overrides -> role overrides -> group config -> role config (TODO: -> common_config)
352352
///
353353
/// The output is a map where the [`crate::role_utils::RoleGroup] name points to another map of
354354
/// [`product_config::types::PropertyValidationResult`] that points to the mapped configuration
@@ -371,24 +371,42 @@ where
371371
{
372372
let mut result = HashMap::new();
373373

374+
// Properties from the role have the lowest priority, so they are computed first...
374375
let role_properties = parse_role_config(resource, role_name, &role.config, property_kinds)?;
376+
let role_overrides = parse_role_overrides(&role.config, property_kinds)?;
375377

376378
// for each role group ...
377379
for (role_group_name, role_group) in &role.role_groups {
378-
// ... compute the group properties ...
380+
let mut role_group_properties_merged = role_properties.clone();
381+
382+
// ... compute the group properties and merge them into role properties.
379383
let role_group_properties =
380384
parse_role_config(resource, role_name, &role_group.config, property_kinds)?;
381-
382-
// ... and merge them with the role properties.
383-
let mut role_properties_copy = role_properties.clone();
384385
for (property_kind, properties) in role_group_properties {
385-
role_properties_copy
386+
role_group_properties_merged
386387
.entry(property_kind)
387388
.or_default()
388389
.extend(properties);
389390
}
390391

391-
result.insert(role_group_name.clone(), role_properties_copy);
392+
// ... copy role overrides and merge them into `role_group_properties_merged`.
393+
for (property_kind, property_overrides) in role_overrides.clone() {
394+
role_group_properties_merged
395+
.entry(property_kind)
396+
.or_default()
397+
.extend(property_overrides);
398+
}
399+
400+
// ... compute the role group overrides and merge them into `role_group_properties_merged`.
401+
let role_group_overrides = parse_role_overrides(&role_group.config, property_kinds)?;
402+
for (property_kind, property_overrides) in role_group_overrides {
403+
role_group_properties_merged
404+
.entry(property_kind)
405+
.or_default()
406+
.extend(property_overrides);
407+
}
408+
409+
result.insert(role_group_name.clone(), role_group_properties_merged);
392410
}
393411

394412
Ok(result)
@@ -414,86 +432,80 @@ where
414432
T: Configuration,
415433
{
416434
let mut result = HashMap::new();
417-
418435
for property_kind in property_kinds {
419436
match property_kind {
420437
PropertyNameKind::File(file) => result.insert(
421438
property_kind.clone(),
422-
parse_file_properties(resource, role_name, config, file)?,
439+
config.config.compute_files(resource, role_name, file)?,
423440
),
424441
PropertyNameKind::Env => result.insert(
425442
property_kind.clone(),
426-
parse_env_properties(resource, role_name, config)?,
443+
config.config.compute_env(resource, role_name)?,
427444
),
428445
PropertyNameKind::Cli => result.insert(
429446
property_kind.clone(),
430-
parse_cli_properties(resource, role_name, config)?,
447+
config.config.compute_cli(resource, role_name)?,
431448
),
432449
};
433450
}
434451

435452
Ok(result)
436453
}
437454

438-
fn parse_cli_properties<T>(
439-
resource: &<T as Configuration>::Configurable,
440-
role_name: &str,
441-
config: &CommonConfiguration<T>,
442-
) -> Result<BTreeMap<String, Option<String>>>
443-
where
444-
T: Configuration,
445-
{
446-
// Properties from the role have the lowest priority, so they are computed and added first...
447-
let mut final_properties = config.config.compute_cli(resource, role_name)?;
448-
449-
// ...followed by config_overrides from the role
450-
for (key, value) in &config.cli_overrides {
451-
final_properties.insert(key.clone(), Some(value.clone()));
452-
}
453-
454-
Ok(final_properties)
455-
}
456-
457-
fn parse_env_properties<T>(
458-
resource: &<T as Configuration>::Configurable,
459-
role_name: &str,
455+
fn parse_role_overrides<T>(
460456
config: &CommonConfiguration<T>,
461-
) -> Result<BTreeMap<String, Option<String>>>
457+
property_kinds: &[PropertyNameKind],
458+
) -> Result<HashMap<PropertyNameKind, BTreeMap<String, Option<String>>>>
462459
where
463460
T: Configuration,
464461
{
465-
// Properties from the role have the lowest priority, so they are computed and added first...
466-
let mut final_properties = config.config.compute_env(resource, role_name)?;
467-
468-
// ...followed by config_overrides from the role
469-
for (key, value) in &config.env_overrides {
470-
final_properties.insert(key.clone(), Some(value.clone()));
462+
let mut result = HashMap::new();
463+
for property_kind in property_kinds {
464+
match property_kind {
465+
PropertyNameKind::File(file) => {
466+
result.insert(property_kind.clone(), parse_file_overrides(config, file)?)
467+
}
468+
PropertyNameKind::Env => result.insert(
469+
property_kind.clone(),
470+
config
471+
.env_overrides
472+
.clone()
473+
.into_iter()
474+
.map(|(k, v)| (k, Some(v)))
475+
.collect(),
476+
),
477+
PropertyNameKind::Cli => result.insert(
478+
property_kind.clone(),
479+
config
480+
.cli_overrides
481+
.clone()
482+
.into_iter()
483+
.map(|(k, v)| (k, Some(v)))
484+
.collect(),
485+
),
486+
};
471487
}
472488

473-
Ok(final_properties)
489+
Ok(result)
474490
}
475491

476-
fn parse_file_properties<T>(
477-
resource: &<T as Configuration>::Configurable,
478-
role_name: &str,
492+
fn parse_file_overrides<T>(
479493
config: &CommonConfiguration<T>,
480494
file: &str,
481495
) -> Result<BTreeMap<String, Option<String>>>
482496
where
483497
T: Configuration,
484498
{
485-
// Properties from the role have the lowest priority, so they are computed and added first...
486-
let mut final_properties = config.config.compute_files(resource, role_name, file)?;
499+
let mut final_overrides: BTreeMap<String, Option<String>> = BTreeMap::new();
487500

488-
// ...followed by config_overrides from the role
489501
// For Conf files only process overrides that match our file name
490502
if let Some(config) = config.config_overrides.get(file) {
491503
for (key, value) in config {
492-
final_properties.insert(key.clone(), Some(value.clone()));
504+
final_overrides.insert(key.clone(), Some(value.clone()));
493505
}
494506
}
495507

496-
Ok(final_properties)
508+
Ok(final_overrides)
497509
}
498510

499511
#[cfg(test)]
@@ -1049,6 +1061,136 @@ mod tests {
10491061
assert_eq!(config, expected);
10501062
}
10511063

1064+
#[rstest]
1065+
#[case(
1066+
HashMap::from([
1067+
("env".to_string(), ROLE_ENV_OVERRIDE.to_string()),
1068+
]),
1069+
HashMap::from([
1070+
("env".to_string(), GROUP_ENV_OVERRIDE.to_string()),
1071+
]),
1072+
BTreeMap::from([
1073+
("cli".to_string(), ROLE_CLI_OVERRIDE.to_string()),
1074+
]),
1075+
BTreeMap::from([
1076+
("cli".to_string(), GROUP_CLI_OVERRIDE.to_string()),
1077+
]),
1078+
HashMap::from([
1079+
("file".to_string(), HashMap::from([
1080+
("file".to_string(), ROLE_CONF_OVERRIDE.to_string())
1081+
]))
1082+
]),
1083+
HashMap::from([
1084+
("file".to_string(), HashMap::from([
1085+
("file".to_string(), GROUP_CONF_OVERRIDE.to_string())
1086+
]))
1087+
]),
1088+
collection ! {
1089+
ROLE_GROUP.to_string() => collection ! {
1090+
PropertyNameKind::Env => collection ! {
1091+
"env".to_string() => Some(GROUP_ENV_OVERRIDE.to_string()),
1092+
},
1093+
PropertyNameKind::Cli => collection ! {
1094+
"cli".to_string() => Some(GROUP_CLI_OVERRIDE.to_string()),
1095+
},
1096+
PropertyNameKind::File("file".to_string()) => collection ! {
1097+
"file".to_string() => Some(GROUP_CONF_OVERRIDE.to_string()),
1098+
}
1099+
}
1100+
}
1101+
)]
1102+
#[case(
1103+
HashMap::from([
1104+
("env".to_string(), ROLE_ENV_OVERRIDE.to_string()),
1105+
]),
1106+
HashMap::from([]),
1107+
BTreeMap::from([
1108+
("cli".to_string(), ROLE_CLI_OVERRIDE.to_string()),
1109+
]),
1110+
BTreeMap::from([]),
1111+
HashMap::from([
1112+
("file".to_string(), HashMap::from([
1113+
("file".to_string(), ROLE_CONF_OVERRIDE.to_string())
1114+
]))
1115+
]),
1116+
HashMap::from([]),
1117+
collection ! {
1118+
ROLE_GROUP.to_string() => collection ! {
1119+
PropertyNameKind::Env => collection ! {
1120+
"env".to_string() => Some(ROLE_ENV_OVERRIDE.to_string()),
1121+
},
1122+
PropertyNameKind::Cli => collection ! {
1123+
"cli".to_string() => Some(ROLE_CLI_OVERRIDE.to_string()),
1124+
},
1125+
PropertyNameKind::File("file".to_string()) => collection ! {
1126+
"file".to_string() => Some(ROLE_CONF_OVERRIDE.to_string()),
1127+
}
1128+
}
1129+
}
1130+
)]
1131+
#[case(
1132+
HashMap::from([]),
1133+
HashMap::from([]),
1134+
BTreeMap::from([]),
1135+
BTreeMap::from([]),
1136+
HashMap::from([]),
1137+
HashMap::from([]),
1138+
collection ! {
1139+
ROLE_GROUP.to_string() => collection ! {
1140+
PropertyNameKind::Env => collection ! {
1141+
"env".to_string() => Some(GROUP_ENV.to_string()),
1142+
},
1143+
PropertyNameKind::Cli => collection ! {
1144+
"cli".to_string() => Some(GROUP_CLI.to_string()),
1145+
},
1146+
PropertyNameKind::File("file".to_string()) => collection ! {
1147+
"file".to_string() => Some(GROUP_CONFIG.to_string()),
1148+
}
1149+
}
1150+
}
1151+
)]
1152+
fn test_order_in_transform_role_to_config(
1153+
#[case] role_env_override: HashMap<String, String>,
1154+
#[case] group_env_override: HashMap<String, String>,
1155+
#[case] role_cli_override: BTreeMap<String, String>,
1156+
#[case] group_cli_override: BTreeMap<String, String>,
1157+
#[case] role_conf_override: HashMap<String, HashMap<String, String>>,
1158+
#[case] group_conf_override: HashMap<String, HashMap<String, String>>,
1159+
#[case] expected: HashMap<
1160+
String,
1161+
HashMap<PropertyNameKind, BTreeMap<String, Option<String>>>,
1162+
>,
1163+
) {
1164+
let role: Role<Box<TestConfig>, TestRoleConfig> = Role {
1165+
config: build_common_config(
1166+
build_test_config(ROLE_CONFIG, ROLE_ENV, ROLE_CLI),
1167+
Some(role_conf_override),
1168+
Some(role_env_override),
1169+
Some(role_cli_override),
1170+
),
1171+
role_config: Default::default(),
1172+
role_groups: collection! {"role_group".to_string() => RoleGroup {
1173+
replicas: Some(1),
1174+
config: build_common_config(
1175+
build_test_config(GROUP_CONFIG, GROUP_ENV, GROUP_CLI),
1176+
Some(group_conf_override),
1177+
Some(group_env_override),
1178+
Some(group_cli_override)),
1179+
}},
1180+
};
1181+
1182+
let property_kinds = vec![
1183+
PropertyNameKind::Env,
1184+
PropertyNameKind::Cli,
1185+
PropertyNameKind::File("file".to_string()),
1186+
];
1187+
1188+
let config =
1189+
transform_role_to_config(&String::new(), ROLE_GROUP, &role, &property_kinds).unwrap();
1190+
1191+
assert_eq!(config, expected);
1192+
}
1193+
10521194
#[test]
10531195
fn test_transform_role_to_config_overrides() {
10541196
let role_group = "role_group";
@@ -1088,7 +1230,7 @@ mod tests {
10881230
),
10891231
PropertyNameKind::Cli =>
10901232
collection!(
1091-
"cli".to_string() => Some(GROUP_CLI.to_string()),
1233+
"cli".to_string() => Some("cli".to_string()),
10921234
),
10931235
}
10941236
};

0 commit comments

Comments
 (0)