@@ -4,6 +4,7 @@ use snafu::{ResultExt, Snafu};
44use stackable_operator:: {
55 commons:: product_image_selection:: { self , ResolvedProductImage } ,
66 config:: fragment,
7+ k8s_openapi:: api:: core:: v1:: PodTemplateSpec ,
78 kube:: ResourceExt ,
89 role_utils:: { GenericRoleConfig , RoleGroup } ,
910 v2:: role_utils:: { GenericCommonConfig , with_validated_config} ,
@@ -52,6 +53,8 @@ pub struct ValidatedRoleGroupConfig {
5253 pub merged_config : AirflowConfig ,
5354 pub config_overrides : AirflowConfigOverrides ,
5455 pub env_overrides : HashMap < String , String > ,
56+ pub replicas : Option < u16 > ,
57+ pub pod_overrides : PodTemplateSpec ,
5558}
5659
5760/// The validated cluster: proves that config merging succeeded for every role and
@@ -160,6 +163,8 @@ fn validate_role_group(
160163 merged_config : validated. config . config ,
161164 config_overrides : validated. config . config_overrides ,
162165 env_overrides : validated. config . env_overrides ,
166+ replicas : validated. replicas ,
167+ pod_overrides : validated. config . pod_overrides ,
163168 } )
164169}
165170
@@ -367,4 +372,78 @@ mod tests {
367372 ) ;
368373 assert ! ( validated. env_overrides. is_empty( ) ) ;
369374 }
375+
376+ /// `replicas` and the role←role-group merged `pod_overrides` are produced by
377+ /// `with_validated_config` and must be carried on `ValidatedRoleGroupConfig`, so the build
378+ /// step reads them from here rather than re-deriving from the raw cluster.
379+ #[ test]
380+ fn role_group_carries_merged_pod_overrides_and_replicas ( ) {
381+ let cluster_yaml = r#"
382+ apiVersion: airflow.stackable.tech/v1alpha2
383+ kind: AirflowCluster
384+ metadata:
385+ name: airflow
386+ spec:
387+ image:
388+ productVersion: 3.1.6
389+ clusterConfig:
390+ loadExamples: false
391+ exposeConfig: false
392+ credentialsSecretName: airflow-admin-credentials
393+ metadataDatabase:
394+ postgresql:
395+ host: airflow-postgresql
396+ database: airflow
397+ credentialsSecretName: airflow-postgresql-credentials
398+ webservers:
399+ config: {}
400+ podOverrides:
401+ metadata:
402+ labels:
403+ role-label: role
404+ shared: role
405+ roleGroups:
406+ default:
407+ replicas: 3
408+ config: {}
409+ podOverrides:
410+ metadata:
411+ labels:
412+ rg-label: rg
413+ shared: rg
414+ schedulers:
415+ config: {}
416+ roleGroups:
417+ default:
418+ config: {}
419+ kubernetesExecutors:
420+ config: {}
421+ "# ;
422+ let deserializer = serde_yaml:: Deserializer :: from_str ( cluster_yaml) ;
423+ let cluster: v1alpha2:: AirflowCluster =
424+ serde_yaml:: with:: singleton_map_recursive:: deserialize ( deserializer) . unwrap ( ) ;
425+ let role = cluster
426+ . get_role ( & AirflowRole :: Webserver )
427+ . expect ( "webserver role" ) ;
428+ let default_config = AirflowConfig :: default_config ( "airflow" , & AirflowRole :: Webserver ) ;
429+ let rolegroup = role. role_groups . get ( "default" ) . expect ( "default role group" ) ;
430+
431+ let validated =
432+ validate_role_group ( & role, rolegroup, & default_config) . expect ( "validated role group" ) ;
433+
434+ // replicas is carried through from the role group.
435+ assert_eq ! ( validated. replicas, Some ( 3 ) ) ;
436+
437+ // pod_overrides is merged role←role-group (role-group wins on shared keys, both levels'
438+ // unique keys survive).
439+ let labels = validated
440+ . pod_overrides
441+ . metadata
442+ . expect ( "pod override metadata" )
443+ . labels
444+ . expect ( "pod override labels" ) ;
445+ assert_eq ! ( labels. get( "role-label" ) , Some ( & "role" . to_string( ) ) ) ;
446+ assert_eq ! ( labels. get( "rg-label" ) , Some ( & "rg" . to_string( ) ) ) ;
447+ assert_eq ! ( labels. get( "shared" ) , Some ( & "rg" . to_string( ) ) ) ;
448+ }
370449}
0 commit comments