Add environment subsition for auth blocks (#5439)

Signed-off-by: Daniel Cooper <daniel@14lines.com>
This commit is contained in:
Daniel Cooper
2025-11-06 04:30:24 +00:00
committed by GitHub
parent b2aa26a0f1
commit 6209a81e1c
+110 -1
View File
@@ -369,15 +369,56 @@ impl ExtensionManager {
timeout,
headers,
name,
envs,
env_keys,
..
} => {
// Merge environment variables from direct envs and keychain-stored env_keys
let all_envs = merge_environments(envs, env_keys, &sanitized_name).await?;
// Helper function to substitute environment variables in a string
// Supports both ${VAR} and $VAR syntax
fn substitute_env_vars(value: &str, env_map: &HashMap<String, String>) -> String {
let mut result = value.to_string();
// First handle ${VAR} syntax (with optional whitespace)
let re_braces = regex::Regex::new(r"\$\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}")
.expect("valid regex");
for cap in re_braces.captures_iter(value) {
if let Some(var_name) = cap.get(1) {
if let Some(env_value) = env_map.get(var_name.as_str()) {
result = result.replace(&cap[0], env_value);
}
}
}
// Then handle $VAR syntax (simple variable without braces)
let re_simple =
regex::Regex::new(r"\$([A-Za-z_][A-Za-z0-9_]*)").expect("valid regex");
for cap in re_simple.captures_iter(&result.clone()) {
if let Some(var_name) = cap.get(1) {
// Only substitute if it wasn't already part of ${VAR} syntax
if !value.contains(&format!("${{{}}}", var_name.as_str())) {
if let Some(env_value) = env_map.get(var_name.as_str()) {
result = result.replace(&cap[0], env_value);
}
}
}
}
result
}
let mut default_headers = HeaderMap::new();
for (key, value) in headers {
// Substitute environment variables in header values
let substituted_value = substitute_env_vars(value, &all_envs);
default_headers.insert(
HeaderName::try_from(key).map_err(|_| {
ExtensionError::ConfigError(format!("invalid header: {}", key))
})?,
value.parse().map_err(|_| {
substituted_value.parse().map_err(|_| {
ExtensionError::ConfigError(format!("invalid header value: {}", key))
})?,
);
@@ -1539,4 +1580,72 @@ mod tests {
assert!(result.is_ok());
}
#[tokio::test]
async fn test_streamable_http_header_env_substitution() {
use std::collections::HashMap;
// Test the substitute_env_vars helper function (which is defined inside add_extension)
// We'll recreate it here for testing purposes
fn substitute_env_vars(value: &str, env_map: &HashMap<String, String>) -> String {
let mut result = value.to_string();
// First handle ${VAR} syntax (with optional whitespace)
let re_braces =
regex::Regex::new(r"\$\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}").expect("valid regex");
for cap in re_braces.captures_iter(value) {
if let Some(var_name) = cap.get(1) {
if let Some(env_value) = env_map.get(var_name.as_str()) {
result = result.replace(&cap[0], env_value);
}
}
}
// Then handle $VAR syntax (simple variable without braces)
let re_simple = regex::Regex::new(r"\$([A-Za-z_][A-Za-z0-9_]*)").expect("valid regex");
for cap in re_simple.captures_iter(&result.clone()) {
if let Some(var_name) = cap.get(1) {
// Only substitute if it wasn't already part of ${VAR} syntax
if !value.contains(&format!("${{{}}}", var_name.as_str())) {
if let Some(env_value) = env_map.get(var_name.as_str()) {
result = result.replace(&cap[0], env_value);
}
}
}
}
result
}
let mut env_map = HashMap::new();
env_map.insert("AUTH_TOKEN".to_string(), "secret123".to_string());
env_map.insert("API_KEY".to_string(), "key456".to_string());
// Test ${VAR} syntax
let result = substitute_env_vars("Bearer ${ AUTH_TOKEN }", &env_map);
assert_eq!(result, "Bearer secret123");
// Test ${VAR} syntax without spaces
let result = substitute_env_vars("Bearer ${AUTH_TOKEN}", &env_map);
assert_eq!(result, "Bearer secret123");
// Test $VAR syntax
let result = substitute_env_vars("Bearer $AUTH_TOKEN", &env_map);
assert_eq!(result, "Bearer secret123");
// Test multiple substitutions
let result = substitute_env_vars("Key: $API_KEY, Token: ${AUTH_TOKEN}", &env_map);
assert_eq!(result, "Key: key456, Token: secret123");
// Test no substitution when variable doesn't exist
let result = substitute_env_vars("Bearer ${UNKNOWN_VAR}", &env_map);
assert_eq!(result, "Bearer ${UNKNOWN_VAR}");
// Test mixed content
let result = substitute_env_vars(
"Authorization: Bearer ${AUTH_TOKEN} and API ${API_KEY}",
&env_map,
);
assert_eq!(result, "Authorization: Bearer secret123 and API key456");
}
}