Skip to content

Commit 6a22f47

Browse files
committed
move to v2::role_utils
1 parent 5a6f9dc commit 6a22f47

2 files changed

Lines changed: 82 additions & 64 deletions

File tree

rust/operator-binary/src/controller/validate.rs

Lines changed: 71 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@ use std::collections::{BTreeMap, HashMap};
33
use snafu::{ResultExt, Snafu};
44
use stackable_operator::{
55
commons::product_image_selection::{self, ResolvedProductImage},
6-
config::merge::Merge,
7-
role_utils::RoleGroupRef,
6+
config::fragment,
7+
kube::ResourceExt,
8+
role_utils::{GenericRoleConfig, RoleGroup},
9+
v2::role_utils::{GenericCommonConfig, with_validated_config},
810
};
911
use strum::IntoEnumIterator;
1012

1113
use super::dereference::DereferencedObjects;
1214
use crate::{
1315
airflow_controller::CONTAINER_IMAGE_BASE_NAME,
1416
crd::{
15-
AirflowConfig, AirflowConfigOverrides, AirflowExecutor, AirflowRole, AirflowRoleType,
16-
authentication::AirflowClientAuthenticationDetailsResolved,
17+
AirflowConfig, AirflowConfigFragment, AirflowConfigOverrides, AirflowExecutor, AirflowRole,
18+
AirflowRoleType, authentication::AirflowClientAuthenticationDetailsResolved,
1719
authorization::AirflowAuthorizationResolved, v1alpha2,
1820
},
1921
};
@@ -25,8 +27,11 @@ pub enum Error {
2527
source: product_image_selection::Error,
2628
},
2729

28-
#[snafu(display("failed to resolve and merge config for role and role group"))]
29-
FailedToResolveConfig { source: crate::crd::Error },
30+
#[snafu(display("failed to resolve and merge config for role group {role_group}"))]
31+
FailedToResolveConfig {
32+
source: fragment::ValidationError,
33+
role_group: String,
34+
},
3035
}
3136

3237
/// Per-role configuration extracted during validation.
@@ -98,29 +103,16 @@ pub fn validate_cluster(
98103
},
99104
);
100105

106+
let default_config = AirflowConfig::default_config(&airflow.name_any(), &role);
107+
101108
let mut group_configs = BTreeMap::new();
102-
for rolegroup_name in resolved_role.role_groups.keys() {
103-
let rolegroup_ref = RoleGroupRef {
104-
cluster: stackable_operator::kube::runtime::reflector::ObjectRef::from_obj(airflow),
105-
role: role.to_string(),
106-
role_group: rolegroup_name.into(),
107-
};
108-
109-
let merged_config = airflow
110-
.merged_config(&role, &rolegroup_ref)
111-
.context(FailedToResolveConfigSnafu)?;
112-
113-
let (config_overrides, env_overrides) =
114-
merge_role_group_overrides(&resolved_role, rolegroup_name);
115-
116-
group_configs.insert(
117-
rolegroup_name.clone(),
118-
ValidatedRoleGroupConfig {
119-
merged_config,
120-
config_overrides,
121-
env_overrides,
122-
},
123-
);
109+
for (rolegroup_name, rolegroup) in &resolved_role.role_groups {
110+
let validated = validate_role_group(&resolved_role, rolegroup, &default_config)
111+
.with_context(|_| FailedToResolveConfigSnafu {
112+
role_group: rolegroup_name.clone(),
113+
})?;
114+
115+
group_configs.insert(rolegroup_name.clone(), validated);
124116
}
125117

126118
role_groups.insert(role, group_configs);
@@ -141,38 +133,42 @@ pub fn validate_cluster(
141133
})
142134
}
143135

144-
/// Merge a role group's config overrides over the role-level ones (role-group wins per key) via
145-
/// the `Merge` impl on [`AirflowConfigOverrides`], and combine env overrides (role first, then
146-
/// role-group on top). Mirrors hdfs-operator's `validate_role_group_config`.
136+
/// Validate and merge one role group against its role, via the shared
137+
/// [`with_validated_config`] from `operator-rs`.
147138
///
148-
/// The merged overrides are returned *typed*; flattening into the rendered `webserver_config.py`
149-
/// happens later, in the build step. Note the `Merge` semantics: a role-group `null` inherits the
150-
/// role-level value rather than unsetting it.
151-
fn merge_role_group_overrides(
139+
/// This performs the full `default → role → role-group` merge of the config fragment (then
140+
/// validates it) *and* the role←role-group merge of the overrides in one step. The config
141+
/// overrides are kept *typed* ([`AirflowConfigOverrides`]); flattening into the rendered
142+
/// `webserver_config.py` happens later, in the build step.
143+
///
144+
/// Note the override `Merge` semantics: a role-group `null` inherits the role-level value rather
145+
/// than unsetting it (config overrides), and env overrides layer role-group on top of role.
146+
fn validate_role_group(
152147
role: &AirflowRoleType,
153-
rolegroup_name: &str,
154-
) -> (AirflowConfigOverrides, HashMap<String, String>) {
155-
let rolegroup = role.role_groups.get(rolegroup_name);
156-
157-
let mut config_overrides = rolegroup
158-
.map(|rg| rg.config.config_overrides.clone())
159-
.unwrap_or_default();
160-
config_overrides.merge(&role.config.config_overrides);
161-
162-
let mut env_overrides = role.config.env_overrides.clone();
163-
if let Some(rg) = rolegroup {
164-
env_overrides.extend(rg.config.env_overrides.clone());
165-
}
166-
167-
(config_overrides, env_overrides)
148+
rolegroup: &RoleGroup<AirflowConfigFragment, GenericCommonConfig, AirflowConfigOverrides>,
149+
default_config: &AirflowConfigFragment,
150+
) -> Result<ValidatedRoleGroupConfig, fragment::ValidationError> {
151+
let validated = with_validated_config::<
152+
AirflowConfig,
153+
GenericCommonConfig,
154+
AirflowConfigFragment,
155+
GenericRoleConfig,
156+
AirflowConfigOverrides,
157+
>(rolegroup, role, default_config)?;
158+
159+
Ok(ValidatedRoleGroupConfig {
160+
merged_config: validated.config.config,
161+
config_overrides: validated.config.config_overrides,
162+
env_overrides: validated.config.env_overrides,
163+
})
168164
}
169165

170166
#[cfg(test)]
171167
mod tests {
172168
use std::collections::BTreeMap;
173169

174-
use super::merge_role_group_overrides;
175-
use crate::crd::{AirflowRole, v1alpha2};
170+
use super::validate_role_group;
171+
use crate::crd::{AirflowConfig, AirflowRole, v1alpha2};
176172

177173
fn test_cluster() -> v1alpha2::AirflowCluster {
178174
let cluster_yaml = r#"
@@ -227,8 +223,13 @@ mod tests {
227223
let role = cluster
228224
.get_role(&AirflowRole::Webserver)
229225
.expect("webserver role");
226+
let default_config = AirflowConfig::default_config("airflow", &AirflowRole::Webserver);
227+
let rolegroup = role.role_groups.get("default").expect("default role group");
230228

231-
let (config_overrides, env_overrides) = merge_role_group_overrides(&role, "default");
229+
let validated =
230+
validate_role_group(&role, rolegroup, &default_config).expect("validated role group");
231+
let config_overrides = validated.config_overrides;
232+
let env_overrides = validated.env_overrides;
232233

233234
// configOverrides are kept typed (values are `Option<String>`). The role-group AUTH_TYPE
234235
// overrides the role-level one; both role-only and group-only keys are kept.
@@ -330,7 +331,11 @@ mod tests {
330331

331332
// What we do now (Merge): the role-group `null` inherits the role-level value, so
332333
// AUTH_TYPE survives as the role's "AUTH_OID".
333-
let (config_overrides, _env_overrides) = merge_role_group_overrides(&role, "default");
334+
let default_config = AirflowConfig::default_config("airflow", &AirflowRole::Webserver);
335+
let rolegroup = role.role_groups.get("default").expect("default role group");
336+
let config_overrides = validate_role_group(&role, rolegroup, &default_config)
337+
.expect("validated role group")
338+
.config_overrides;
334339
assert_eq!(
335340
config_overrides
336341
.webserver_config_py
@@ -347,10 +352,19 @@ mod tests {
347352
let role = cluster
348353
.get_role(&AirflowRole::Scheduler)
349354
.expect("scheduler role");
355+
let default_config = AirflowConfig::default_config("airflow", &AirflowRole::Scheduler);
356+
let rolegroup = role.role_groups.get("default").expect("default role group");
350357

351-
let (config_overrides, env_overrides) = merge_role_group_overrides(&role, "default");
358+
let validated =
359+
validate_role_group(&role, rolegroup, &default_config).expect("validated role group");
352360

353-
assert!(config_overrides.webserver_config_py.overrides.is_empty());
354-
assert!(env_overrides.is_empty());
361+
assert!(
362+
validated
363+
.config_overrides
364+
.webserver_config_py
365+
.overrides
366+
.is_empty()
367+
);
368+
assert!(validated.env_overrides.is_empty());
355369
}
356370
}

rust/operator-binary/src/crd/mod.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,15 @@ use stackable_operator::{
3232
framework::{create_vector_shutdown_file_command, remove_vector_shutdown_file_command},
3333
spec::Logging,
3434
},
35-
role_utils::{
36-
CommonConfiguration, GenericCommonConfig, GenericRoleConfig, Role, RoleGroup, RoleGroupRef,
37-
},
35+
role_utils::{CommonConfiguration, GenericRoleConfig, Role, RoleGroup, RoleGroupRef},
3836
schemars::{self, JsonSchema},
3937
shared::time::Duration,
4038
status::condition::{ClusterCondition, HasStatusCondition},
4139
utils::{COMMON_BASH_TRAP_FUNCTIONS, crds::raw_object_list_schema},
4240
v2::{
4341
config_overrides::KeyValueConfigOverrides,
4442
flask_config_writer::{FlaskAppConfigOptions, PythonType},
43+
role_utils::GenericCommonConfig,
4544
},
4645
versioned::versioned,
4746
};
@@ -96,13 +95,18 @@ pub const MAX_LOG_FILES_SIZE: MemoryQuantity = MemoryQuantity {
9695
unit: BinaryMultiple::Mebi,
9796
};
9897

99-
pub type AirflowRoleType = Role<AirflowConfigFragment, AirflowConfigOverrides>;
98+
pub type AirflowRoleType =
99+
Role<AirflowConfigFragment, AirflowConfigOverrides, GenericRoleConfig, GenericCommonConfig>;
100100

101101
pub type AirflowExecutorCommonConfiguration =
102102
CommonConfiguration<ExecutorConfigFragment, GenericCommonConfig, AirflowConfigOverrides>;
103103

104-
pub type AirflowWebserverRoleType =
105-
Role<AirflowConfigFragment, AirflowConfigOverrides, v1alpha2::WebserverRoleConfig>;
104+
pub type AirflowWebserverRoleType = Role<
105+
AirflowConfigFragment,
106+
AirflowConfigOverrides,
107+
v1alpha2::WebserverRoleConfig,
108+
GenericCommonConfig,
109+
>;
106110

107111
#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, Merge, PartialEq, Serialize)]
108112
#[serde(rename_all = "camelCase")]
@@ -971,7 +975,7 @@ pub struct AirflowConfig {
971975
impl AirflowConfig {
972976
pub const GIT_CREDENTIALS_SECRET_PROPERTY: &'static str = "gitCredentialsSecret";
973977

974-
fn default_config(cluster_name: &str, role: &AirflowRole) -> AirflowConfigFragment {
978+
pub(crate) fn default_config(cluster_name: &str, role: &AirflowRole) -> AirflowConfigFragment {
975979
AirflowConfigFragment {
976980
resources: default_resources(role),
977981
logging: product_logging::spec::default_logging(),

0 commit comments

Comments
 (0)