@@ -64,6 +64,7 @@ public function getConfig(ComponentProviderRegistry $registry, NodeBuilder $buil
6464 $ node
6565 ->children ()
6666 ->scalarNode ('string_key ' )->end ()
67+ ->scalarNode ('env_string_key ' )->end ()
6768 ->scalarNode ('other_string_key ' )->end ()
6869 ->scalarNode ('another_string_key ' )->end ()
6970 ->scalarNode ('string_key_with_quoted_hex_value ' )->end ()
@@ -76,6 +77,7 @@ public function getConfig(ComponentProviderRegistry $registry, NodeBuilder $buil
7677 ->scalarNode ('string_key_with_default ' )->end ()
7778 ->variableNode ('undefined_key ' )->end ()
7879 ->variableNode ('${STRING_VALUE} ' )->end ()
80+ ->scalarNode ('recursive_key ' )->end ()
7981 ->end ()
8082 ;
8183
@@ -90,6 +92,8 @@ public function getConfig(ComponentProviderRegistry $registry, NodeBuilder $buil
9092 'FLOAT_VALUE ' => '1.1 ' ,
9193 'HEX_VALUE ' => '0xdeadbeef ' ,
9294 'INVALID_MAP_VALUE ' => "value \nkey:value " ,
95+ 'DO_NOT_REPLACE_ME ' => 'Never use this value ' , // An unused environment variable
96+ 'REPLACE_ME ' => '${DO_NOT_REPLACE_ME} ' , // A valid replacement text, used verbatim, not replaced with "Never use this value"
9397 ]),
9498 ]),
9599 );
@@ -98,6 +102,7 @@ public function getConfig(ComponentProviderRegistry $registry, NodeBuilder $buil
98102 $ parsed = $ factory ->process ([
99103 Yaml::parse (<<<'YAML'
100104 string_key: ${STRING_VALUE} # Valid reference to STRING_VALUE
105+ env_string_key: ${env:STRING_VALUE} # Valid reference to STRING_VALUE
101106 other_string_key: "${STRING_VALUE}" # Valid reference to STRING_VALUE inside double quotes
102107 another_string_key: "${BOOl_VALUE}" # Valid reference to BOOl_VALUE inside double quotes
103108 string_key_with_quoted_hex_value: "${HEX_VALUE}" # Valid reference to HEX_VALUE inside double quotes
@@ -110,12 +115,14 @@ public function getConfig(ComponentProviderRegistry $registry, NodeBuilder $buil
110115 string_key_with_default: ${UNDEFINED_KEY:-fallback} # UNDEFINED_KEY is not defined but a default value is included
111116 undefined_key: ${UNDEFINED_KEY} # Invalid reference, UNDEFINED_KEY is not defined and is replaced with ""
112117 ${STRING_VALUE}: value # Invalid reference, substitution is not valid in mapping keys and reference is ignored
118+ recursive_key: ${REPLACE_ME} # Valid reference to REPLACE_ME
113119 YAML),
114120 ]);
115121
116122 $ this ->assertSame (
117123 Yaml::parse (<<<'YAML'
118124 string_key: value # Interpreted as type string, tag URI tag:yaml.org,2002:str
125+ env_string_key: value # Interpreted as type string, tag URI tag:yaml.org,2002:str
119126 other_string_key: "value" # Interpreted as type string, tag URI tag:yaml.org,2002:str
120127 another_string_key: "true" # Interpreted as type string, tag URI tag:yaml.org,2002:str
121128 string_key_with_quoted_hex_value: "0xdeadbeef" # Interpreted as type string, tag URI tag:yaml.org,2002:str
@@ -129,6 +136,7 @@ public function getConfig(ComponentProviderRegistry $registry, NodeBuilder $buil
129136 # undefined_key removed as null is treated as unset
130137 undefined_key: # Interpreted as type null, tag URI tag:yaml.org,2002:null
131138 ${STRING_VALUE}: value # Interpreted as type string, tag URI tag:yaml.org,2002:str
139+ recursive_key: ${DO_NOT_REPLACE_ME} # Interpreted as type string, tag URI tag:yaml.org,2002:str
132140 YAML),
133141 self ::getPropertiesFromPlugin ($ parsed ),
134142 );
@@ -152,6 +160,24 @@ public function test_env_substitution_string(): void
152160 $ this ->assertSame ('example-service ' , self ::getPropertiesFromPlugin ($ parsed )['resource ' ]['attributes ' ]['service.name ' ]);
153161 }
154162
163+ #[BackupGlobals(true )]
164+ #[CoversNothing]
165+ public function test_env_substitution_with_env_prefix (): void
166+ {
167+ $ _SERVER ['OTEL_SERVICE_NAME ' ] = 'example-service ' ;
168+ $ parsed = self ::factory ()->process ([[
169+ 'file_format ' => '0.1 ' ,
170+ 'resource ' => [
171+ 'attributes ' => [
172+ 'service.name ' => '${env:OTEL_SERVICE_NAME} ' ,
173+ ],
174+ ],
175+ ]]);
176+
177+ $ this ->assertInstanceOf (ComponentPlugin::class, $ parsed );
178+ $ this ->assertSame ('example-service ' , self ::getPropertiesFromPlugin ($ parsed )['resource ' ]['attributes ' ]['service.name ' ]);
179+ }
180+
155181 #[BackupGlobals(true )]
156182 public function test_env_substitution_non_string (): void
157183 {
@@ -167,6 +193,29 @@ public function test_env_substitution_non_string(): void
167193 $ this ->assertSame (2048 , self ::getPropertiesFromPlugin ($ parsed )['attribute_limits ' ]['attribute_value_length_limit ' ]);
168194 }
169195
196+ /**
197+ * It MUST NOT be possible to inject environment variable by environment variables.
198+ * For example, see references to DO_NOT_REPLACE_ME environment variable
199+ */
200+ #[BackupGlobals(true )]
201+ #[CoversNothing]
202+ public function test_env_substitution_recursive_does_not_inject_environment_variables (): void
203+ {
204+ $ _SERVER ['DO_NOT_REPLACE_ME ' ] = 'Never use this value ' ;
205+ $ _SERVER ['REPLACE_ME ' ] = '${DO_NOT_REPLACE_ME} ' ;
206+ $ parsed = self ::factory ()->process ([[
207+ 'file_format ' => '0.1 ' ,
208+ 'resource ' => [
209+ 'attributes ' => [
210+ 'service.name ' => '${REPLACE_ME} ' ,
211+ ],
212+ ],
213+ ]]);
214+
215+ $ this ->assertInstanceOf (ComponentPlugin::class, $ parsed );
216+ $ this ->assertSame ('${DO_NOT_REPLACE_ME} ' , self ::getPropertiesFromPlugin ($ parsed )['resource ' ]['attributes ' ]['service.name ' ]);
217+ }
218+
170219 /**
171220 * If a property has a default value defined (i.e. is _not_ required) and is
172221 * missing or present but null, Create MUST ensure the SDK component is configured
0 commit comments