Skip to content

Commit cf9fa76

Browse files
committed
feat: handle env var syntax for infra configs
1 parent 3d655aa commit cf9fa76

File tree

1 file changed

+38
-1
lines changed

1 file changed

+38
-1
lines changed

agent-control/src/config_migrate/migration/converter.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::config_migrate::migration::{
66
use crate::sub_agent::effective_agents_assembler::AgentTypeDefinitionError;
77
use fs::LocalFile;
88
use fs::file_reader::{FileReader, FileReaderError};
9+
use regex::Regex;
910
use std::collections::{HashMap, HashSet};
1011
use std::path::Path;
1112
use thiserror::Error;
@@ -125,20 +126,35 @@ fn retrieve_dir_mapping_values<F: FileReader>(
125126

126127
let read_files = read_files.try_fold(HashMap::new(), |mut acc, read_file| {
127128
let (filepath, content) = read_file?;
128-
let parsed = serde_yaml::from_str::<serde_yaml::Value>(&content)?;
129+
let parsed = serde_yaml::from_str::<serde_yaml::Value>(&process_config_input(content))?;
129130
acc.insert(filepath, parsed);
130131
Ok::<_, ConversionError>(acc)
131132
})?;
132133

133134
Ok(serde_yaml::to_value(read_files)?)
134135
}
135136

137+
/// Handles the usage of environment variables in the YAML config files via the special
138+
/// `{{VAR_NAME}}` syntax, by replacing them with a YAML-compatible syntax `'{{VAR_NAME}}'`.
139+
/// (just adding quotes to make it a string). If this pattern is not quoted, the resulting YAML
140+
/// would evaluate to a nested mapping with a single key-null pair, which is not what we want.
141+
///
142+
/// This is a simple, non-performant approach that may not cover all edge cases, but works for
143+
/// the common scenarios we expect (small config strings).
144+
fn process_config_input(input: String) -> String {
145+
// This regex matches {{VAR_NAME}} not already inside quotes
146+
let re = Regex::new(r#"(?P<pre>[^'"]|^)\{\{([A-Za-z0-9_]+)\}\}(?P<post>[^'"]|$)"#).unwrap();
147+
re.replace_all(&input, "${pre}'{{${2}}}'${post}")
148+
.to_string()
149+
}
150+
136151
#[cfg(test)]
137152
mod tests {
138153
use std::path::{Path, PathBuf};
139154

140155
use fs::mock::MockLocalFile;
141156
use mockall::{PredicateBooleanExt, predicate};
157+
use rstest::rstest;
142158

143159
use crate::config_migrate::migration::config::DirInfo;
144160

@@ -189,6 +205,27 @@ logs:
189205
pattern: WARN|ERROR
190206
"#;
191207

208+
#[rstest]
209+
#[case::no_templates("license_key: {{MY_ENV_VAR}}", "license_key: '{{MY_ENV_VAR}}'")]
210+
#[case::multiple_templates(
211+
"license_key: {{MY_ENV_VAR}} other {{ANOTHER_ENV}}",
212+
"license_key: '{{MY_ENV_VAR}}' other '{{ANOTHER_ENV}}'"
213+
)]
214+
#[case::no_templates_at_all(
215+
"license_key: my_real_license_key",
216+
"license_key: my_real_license_key"
217+
)]
218+
#[case::multiline_yaml_syntax(
219+
"license_key: {{MY_ENV_VAR}}\nother_key: value",
220+
"license_key: '{{MY_ENV_VAR}}'\nother_key: value"
221+
)]
222+
#[case::already_quoted("license_key: '{{MY_ENV_VAR}}'", "license_key: '{{MY_ENV_VAR}}'")]
223+
#[case::double_quoted(r#"license_key: "{{MY_ENV_VAR}}""#, "license_key: \"{{MY_ENV_VAR}}\"")]
224+
fn env_var_interpolation(#[case] input: &str, #[case] output: &str) {
225+
let result = process_config_input(input.to_string());
226+
assert_eq!(result, output);
227+
}
228+
192229
#[test]
193230
fn from_migration_config_to_conversion() {
194231
// Sample config

0 commit comments

Comments
 (0)