@@ -27,7 +27,7 @@ use crate::{
2727 statefulset:: build_server_rolegroup_statefulset,
2828 } ,
2929 } ,
30- crd:: { AirflowConfigOverrides , Container } ,
30+ crd:: { AirflowConfigOverrides , AirflowRole , Container } ,
3131} ;
3232
3333pub mod graceful_shutdown;
@@ -93,23 +93,27 @@ pub fn build(cluster: &ValidatedCluster) -> Result<KubernetesResources<Prepared>
9393 config_maps. push ( executor_template_config_map) ;
9494 }
9595
96- for ( role, role_group_configs) in & cluster. role_groups {
97- if let Some ( role_config) = cluster. role_configs . get ( role) {
98- if let Some ( pdb_config) = & role_config. pdb {
99- pod_disruption_budgets. extend ( build_pdb ( pdb_config, cluster, role) ) ;
100- }
101- if let Some ( listener_class) = & role_config. listener_class
102- && let Some ( group_listener_name) = & role_config. group_listener_name
103- {
104- listeners. push ( build_group_listener (
105- cluster,
106- role,
107- listener_class. clone ( ) ,
108- group_listener_name. clone ( ) ,
109- ) ) ;
110- }
111- }
112-
96+ // One entry per role, in `AirflowRole` declaration order. Each role's groups come from its own
97+ // field, so the role and its groups cannot be paired up wrongly here.
98+ for ( role, role_group_configs) in [
99+ (
100+ & AirflowRole :: Webserver ,
101+ & cluster. webserver_role_group_configs ,
102+ ) ,
103+ (
104+ & AirflowRole :: Scheduler ,
105+ & cluster. scheduler_role_group_configs ,
106+ ) ,
107+ ( & AirflowRole :: Worker , & cluster. worker_role_group_configs ) ,
108+ (
109+ & AirflowRole :: DagProcessor ,
110+ & cluster. dagprocessor_role_group_configs ,
111+ ) ,
112+ (
113+ & AirflowRole :: Triggerer ,
114+ & cluster. triggerer_role_group_configs ,
115+ ) ,
116+ ] {
113117 for ( role_group_name, rg_config) in role_group_configs {
114118 let logging = & rg_config. config . logging ;
115119
@@ -149,6 +153,21 @@ pub fn build(cluster: &ValidatedCluster) -> Result<KubernetesResources<Prepared>
149153 } ) ?,
150154 ) ;
151155 }
156+
157+ if let Some ( pdb) = cluster. pdb ( role) {
158+ pod_disruption_budgets. extend ( build_pdb ( pdb, cluster, role) ) ;
159+ }
160+ }
161+
162+ // Only the webserver serves the web UI, so it is the only role with a group listener; it is
163+ // built once here rather than inside the role loop.
164+ if let Some ( webserver) = & cluster. webserver_config {
165+ listeners. push ( build_group_listener (
166+ cluster,
167+ & AirflowRole :: Webserver ,
168+ webserver. listener_class . clone ( ) ,
169+ webserver. group_listener_name . clone ( ) ,
170+ ) ) ;
152171 }
153172
154173 Ok ( KubernetesResources {
@@ -335,18 +354,22 @@ pub(crate) mod test_support {
335354 serde_yaml:: with:: singleton_map_recursive:: deserialize ( cluster_value)
336355 . expect ( "the test CR deserialises" ) ;
337356
338- let dereferenced = DereferencedObjects {
357+ validate_cluster ( & cluster, "oci.stackable.tech/sdp" , dereferenced_objects ( ) )
358+ . expect ( "test cluster validates" )
359+ }
360+
361+ /// The resolved external objects a test cluster is validated against: no AuthenticationClasses
362+ /// and no OPA authorization, so validation depends on the CR alone.
363+ pub fn dereferenced_objects ( ) -> DereferencedObjects {
364+ DereferencedObjects {
339365 authentication_config : AirflowClientAuthenticationDetailsResolved {
340366 authentication_classes_resolved : vec ! [ ] ,
341367 user_registration : true ,
342368 user_registration_role : "Public" . to_string ( ) ,
343369 sync_roles_at : FlaskRolesSyncMoment :: default ( ) ,
344370 } ,
345371 authorization_config : AirflowAuthorizationResolved { opa : None } ,
346- } ;
347-
348- validate_cluster ( & cluster, "oci.stackable.tech/sdp" , dereferenced)
349- . expect ( "test cluster validates" )
372+ }
350373 }
351374
352375 /// A Celery-executor cluster whose webserver trusts the given reverse proxies.
0 commit comments