Skip to content

Commit f2a18f2

Browse files
committed
Merge branch 'main' into feat/crd-versioning-type-change
2 parents cb395c6 + 004b9bd commit f2a18f2

File tree

2 files changed

+203
-52
lines changed

2 files changed

+203
-52
lines changed

crates/stackable-operator/CHANGELOG.md

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

55
## [Unreleased]
66

7-
- BREAKING: Replace `lazy_static` with `std::cell::LazyCell` (the original implementation was done in [#827] and reverted in [#835]) ([#840]).
8-
97
### Added
108

119
- `iter::reverse_if` helper ([#838]).
10+
- Add two new constants `CONFIG_OVERRIDE_FILE_HEADER_KEY` and `CONFIG_OVERRIDE_FILE_FOOTER_KEY` ([#843]).
11+
12+
### Changed
13+
14+
- 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]).
1216

1317
[#838]: https://github.com/stackabletech/operator-rs/pull/838
18+
[#841]: https://github.com/stackabletech/operator-rs/pull/841
19+
[#843]: https://github.com/stackabletech/operator-rs/pull/843
1420

1521
## [0.73.0] - 2024-08-09
1622

crates/stackable-operator/src/product_config_utils.rs

Lines changed: 195 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ use tracing::{debug, error, warn};
88

99
use crate::role_utils::{CommonConfiguration, Role};
1010

11+
pub const CONFIG_OVERRIDE_FILE_HEADER_KEY: &str = "EXPERIMENTAL_FILE_HEADER";
12+
pub const CONFIG_OVERRIDE_FILE_FOOTER_KEY: &str = "EXPERIMENTAL_FILE_FOOTER";
13+
1114
type Result<T, E = Error> = std::result::Result<T, E>;
1215

1316
#[derive(Debug, PartialEq, Snafu)]
@@ -345,7 +348,7 @@ fn process_validation_result(
345348
/// with the highest priority. The merge priority chain looks like this where '->' means
346349
/// "overwrites if existing or adds":
347350
///
348-
/// group overrides -> group config -> role overrides -> role config (TODO: -> common_config)
351+
/// group overrides -> role overrides -> group config -> role config (TODO: -> common_config)
349352
///
350353
/// The output is a map where the [`crate::role_utils::RoleGroup] name points to another map of
351354
/// [`product_config::types::PropertyValidationResult`] that points to the mapped configuration
@@ -368,24 +371,42 @@ where
368371
{
369372
let mut result = HashMap::new();
370373

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

373378
// for each role group ...
374379
for (role_group_name, role_group) in &role.role_groups {
375-
// ... 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.
376383
let role_group_properties =
377384
parse_role_config(resource, role_name, &role_group.config, property_kinds)?;
378-
379-
// ... and merge them with the role properties.
380-
let mut role_properties_copy = role_properties.clone();
381385
for (property_kind, properties) in role_group_properties {
382-
role_properties_copy
386+
role_group_properties_merged
383387
.entry(property_kind)
384388
.or_default()
385389
.extend(properties);
386390
}
387391

388-
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);
389410
}
390411

391412
Ok(result)
@@ -411,86 +432,80 @@ where
411432
T: Configuration,
412433
{
413434
let mut result = HashMap::new();
414-
415435
for property_kind in property_kinds {
416436
match property_kind {
417437
PropertyNameKind::File(file) => result.insert(
418438
property_kind.clone(),
419-
parse_file_properties(resource, role_name, config, file)?,
439+
config.config.compute_files(resource, role_name, file)?,
420440
),
421441
PropertyNameKind::Env => result.insert(
422442
property_kind.clone(),
423-
parse_env_properties(resource, role_name, config)?,
443+
config.config.compute_env(resource, role_name)?,
424444
),
425445
PropertyNameKind::Cli => result.insert(
426446
property_kind.clone(),
427-
parse_cli_properties(resource, role_name, config)?,
447+
config.config.compute_cli(resource, role_name)?,
428448
),
429449
};
430450
}
431451

432452
Ok(result)
433453
}
434454

435-
fn parse_cli_properties<T>(
436-
resource: &<T as Configuration>::Configurable,
437-
role_name: &str,
438-
config: &CommonConfiguration<T>,
439-
) -> Result<BTreeMap<String, Option<String>>>
440-
where
441-
T: Configuration,
442-
{
443-
// Properties from the role have the lowest priority, so they are computed and added first...
444-
let mut final_properties = config.config.compute_cli(resource, role_name)?;
445-
446-
// ...followed by config_overrides from the role
447-
for (key, value) in &config.cli_overrides {
448-
final_properties.insert(key.clone(), Some(value.clone()));
449-
}
450-
451-
Ok(final_properties)
452-
}
453-
454-
fn parse_env_properties<T>(
455-
resource: &<T as Configuration>::Configurable,
456-
role_name: &str,
455+
fn parse_role_overrides<T>(
457456
config: &CommonConfiguration<T>,
458-
) -> Result<BTreeMap<String, Option<String>>>
457+
property_kinds: &[PropertyNameKind],
458+
) -> Result<HashMap<PropertyNameKind, BTreeMap<String, Option<String>>>>
459459
where
460460
T: Configuration,
461461
{
462-
// Properties from the role have the lowest priority, so they are computed and added first...
463-
let mut final_properties = config.config.compute_env(resource, role_name)?;
464-
465-
// ...followed by config_overrides from the role
466-
for (key, value) in &config.env_overrides {
467-
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+
};
468487
}
469488

470-
Ok(final_properties)
489+
Ok(result)
471490
}
472491

473-
fn parse_file_properties<T>(
474-
resource: &<T as Configuration>::Configurable,
475-
role_name: &str,
492+
fn parse_file_overrides<T>(
476493
config: &CommonConfiguration<T>,
477494
file: &str,
478495
) -> Result<BTreeMap<String, Option<String>>>
479496
where
480497
T: Configuration,
481498
{
482-
// Properties from the role have the lowest priority, so they are computed and added first...
483-
let mut final_properties = config.config.compute_files(resource, role_name, file)?;
499+
let mut final_overrides: BTreeMap<String, Option<String>> = BTreeMap::new();
484500

485-
// ...followed by config_overrides from the role
486501
// For Conf files only process overrides that match our file name
487502
if let Some(config) = config.config_overrides.get(file) {
488503
for (key, value) in config {
489-
final_properties.insert(key.clone(), Some(value.clone()));
504+
final_overrides.insert(key.clone(), Some(value.clone()));
490505
}
491506
}
492507

493-
Ok(final_properties)
508+
Ok(final_overrides)
494509
}
495510

496511
#[cfg(test)]
@@ -1046,6 +1061,136 @@ mod tests {
10461061
assert_eq!(config, expected);
10471062
}
10481063

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+
10491194
#[test]
10501195
fn test_transform_role_to_config_overrides() {
10511196
let role_group = "role_group";
@@ -1085,7 +1230,7 @@ mod tests {
10851230
),
10861231
PropertyNameKind::Cli =>
10871232
collection!(
1088-
"cli".to_string() => Some(GROUP_CLI.to_string()),
1233+
"cli".to_string() => Some("cli".to_string()),
10891234
),
10901235
}
10911236
};

0 commit comments

Comments
 (0)