@@ -3,17 +3,19 @@ use std::collections::{BTreeMap, HashMap};
33use snafu:: { ResultExt , Snafu } ;
44use 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} ;
911use strum:: IntoEnumIterator ;
1012
1113use super :: dereference:: DereferencedObjects ;
1214use 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) ]
171167mod 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}
0 commit comments