@@ -87,6 +87,32 @@ pub async fn start() -> Result<()> {
8787 Ok ( ( ) )
8888}
8989
90+ /// Extract environment variables from deployment metadata.
91+ pub fn env_from_metadata ( metadata : & BTreeMap < String , String > ) -> BTreeMap < String , String > {
92+ metadata
93+ . iter ( )
94+ . filter_map ( |( key, value) | {
95+ parse_env_metadata_key ( key) . map ( |name| ( name. to_string ( ) , value. clone ( ) ) )
96+ } )
97+ . collect ( )
98+ }
99+
100+ /// Parse a metadata key that follows the environment variable convention.
101+ ///
102+ /// The canonical format is `env.<VAR>`, where `<VAR>` environment variable
103+ /// consists of a non-empty ASCII alphanumeric characters or `_`.
104+ ///
105+ /// Dotted forms are intentionally rejected so `env.<SERVICE>.<VAR>` can be added
106+ /// later without being breaking.
107+ fn parse_env_metadata_key ( key : & str ) -> Option < & str > {
108+ let name = key. strip_prefix ( "env." ) ?;
109+ if name. is_empty ( ) || !name. chars ( ) . all ( |c| c. is_ascii_alphanumeric ( ) || c == '_' ) {
110+ return None ;
111+ }
112+
113+ Some ( name)
114+ }
115+
90116static GLOBAL_ENVIRONMENT : LazyLock < Environment > = LazyLock :: new ( Environment :: new) ;
91117
92118/// Management of environment variables to expose to the compose file.
@@ -117,3 +143,63 @@ impl Environment {
117143 vars. clone ( )
118144 }
119145}
146+
147+ #[ cfg( test) ]
148+ mod test {
149+ use super :: * ;
150+
151+ #[ test]
152+ fn test_parse_env_metadata_key ( ) {
153+ let tcs = vec ! [
154+ ( "foo" , None ) ,
155+ ( "env." , None ) ,
156+ ( "env.MY_VAR" , Some ( "MY_VAR" ) ) ,
157+ ( "env.MY_VAR_1" , Some ( "MY_VAR_1" ) ) ,
158+ ( "env.service_A.MY_VAR" , None ) ,
159+ ( "env.MY-VAR" , None ) ,
160+ ( "env.MY VAR" , None ) ,
161+ ( "env.my_var" , Some ( "my_var" ) ) ,
162+ ] ;
163+ for tc in tcs {
164+ assert_eq ! ( parse_env_metadata_key( tc. 0 ) , tc. 1 ) ;
165+ }
166+ }
167+
168+ #[ test]
169+ fn test_env_from_empty_metadata ( ) {
170+ assert_eq ! ( env_from_metadata( & BTreeMap :: new( ) ) , BTreeMap :: new( ) ) ;
171+ }
172+
173+ #[ test]
174+ fn test_env_from_metadata_with_non_env_keys ( ) {
175+ let metadata = BTreeMap :: from ( [
176+ ( ".env" . to_string ( ) , "ignored" . to_string ( ) ) ,
177+ ( "env.MY_VAR" . to_string ( ) , "my value" . to_string ( ) ) ,
178+ ] ) ;
179+
180+ assert_eq ! (
181+ env_from_metadata( & metadata) ,
182+ BTreeMap :: from( [ ( "MY_VAR" . to_string( ) , "my value" . to_string( ) ) ] )
183+ ) ;
184+ }
185+
186+ #[ test]
187+ fn test_env_from_metadata ( ) {
188+ let metadata = BTreeMap :: from ( [
189+ ( "env." . to_string ( ) , "ignored" . to_string ( ) ) ,
190+ ( "env.MY_VAR" . to_string ( ) , "my value" . to_string ( ) ) ,
191+ ( "env.OTHER_VAR" . to_string ( ) , "other value" . to_string ( ) ) ,
192+ ( "env.service_A.MY_VAR" . to_string ( ) , "reserved" . to_string ( ) ) ,
193+ ( "env.MY-VAR" . to_string ( ) , "ignored" . to_string ( ) ) ,
194+ ( "net.oasis.foo" . to_string ( ) , "ignored" . to_string ( ) ) ,
195+ ] ) ;
196+
197+ assert_eq ! (
198+ env_from_metadata( & metadata) ,
199+ BTreeMap :: from( [
200+ ( "MY_VAR" . to_string( ) , "my value" . to_string( ) ) ,
201+ ( "OTHER_VAR" . to_string( ) , "other value" . to_string( ) ) ,
202+ ] )
203+ ) ;
204+ }
205+ }
0 commit comments