66use std:: {
77 borrow:: Cow ,
88 collections:: { BTreeMap , HashMap } ,
9+ str:: FromStr ,
910} ;
1011
1112use snafu:: { ResultExt , Snafu } ;
@@ -18,6 +19,7 @@ use stackable_operator::{
1819 kube:: { Resource , api:: ObjectMeta } ,
1920 v2:: {
2021 HasName , HasUid ,
22+ builder:: pod:: container:: { self , EnvVarName , EnvVarSet } ,
2123 controller_utils:: { get_cluster_name, get_namespace, get_uid} ,
2224 types:: {
2325 kubernetes:: { NamespaceName , Uid } ,
@@ -76,6 +78,9 @@ pub enum Error {
7678 InvalidMetadataDatabaseConnection {
7779 source : stackable_operator:: database_connections:: Error ,
7880 } ,
81+
82+ #[ snafu( display( "invalid environment variable override name" ) ) ]
83+ ParseEnvVarName { source : container:: Error } ,
7984}
8085
8186type Result < T , E = Error > = std:: result:: Result < T , E > ;
@@ -92,8 +97,9 @@ pub struct DruidRoleGroupConfig {
9297 pub runtime_config : BTreeMap < String , String > ,
9398 /// The security.properties "validated config".
9499 pub security_config : BTreeMap < String , String > ,
95- /// Merged env overrides (role <- rolegroup). Druid has no computed env vars.
96- pub env : BTreeMap < String , String > ,
100+ /// Merged env overrides (role <- rolegroup). Druid has no computed env vars. Names are
101+ /// validated here so the build step can render them directly into the container.
102+ pub env : EnvVarSet ,
97103 /// The fully rendered `jvm.config` (operator defaults merged with the role/rolegroup JVM
98104 /// argument overrides). Precomputed here so the config-map builder no longer needs the raw
99105 /// cluster's `get_role`.
@@ -228,6 +234,25 @@ fn key_value_overrides(
228234 . collect ( )
229235}
230236
237+ /// Merges the role-level and rolegroup-level env overrides into a validated [`EnvVarSet`].
238+ ///
239+ /// The role is processed first, then the rolegroup, so that rolegroup overrides win on key
240+ /// collisions ([`EnvVarSet::with_value`] overrides earlier entries with the same name). The
241+ /// override names are validated here so the build step can render them directly.
242+ fn merged_env_overrides (
243+ role_env_overrides : & HashMap < String , String > ,
244+ rg_env_overrides : & HashMap < String , String > ,
245+ ) -> Result < EnvVarSet > {
246+ let mut env = EnvVarSet :: new ( ) ;
247+ for ( name, value) in role_env_overrides. iter ( ) . chain ( rg_env_overrides. iter ( ) ) {
248+ env = env. with_value (
249+ & EnvVarName :: from_str ( name) . context ( ParseEnvVarNameSnafu ) ?,
250+ value. clone ( ) ,
251+ ) ;
252+ }
253+ Ok ( env)
254+ }
255+
231256/// Builds the precomputed per-file config for a single rolegroup. Pure assembly: combines the
232257/// role-level overrides with the rolegroup-level overrides (rolegroup wins) on top of the
233258/// computed defaults. No behavior change vs. the inline loop body it was extracted from.
@@ -269,11 +294,7 @@ fn build_role_group_config(
269294 security_config. extend ( security_properties:: build ( & security_overrides) ) ;
270295
271296 // ----- env -----
272- let mut env: BTreeMap < String , String > = role_env_overrides
273- . iter ( )
274- . map ( |( k, v) | ( k. clone ( ) , v. clone ( ) ) )
275- . collect ( ) ;
276- env. extend ( rg_env_overrides. iter ( ) . map ( |( k, v) | ( k. clone ( ) , v. clone ( ) ) ) ) ;
297+ let env = merged_env_overrides ( role_env_overrides, rg_env_overrides) ?;
277298
278299 // ----- jvm.config -----
279300 let ( heap, direct) = merged_config
0 commit comments