feat: Add environment variable substitution in config files (#1577)

closes #942 

This PR brings support for environment variable substitution in config
files. This simply uses the [subst](https://github.com/fizyr/subst)
crate to parse all environment variables before parsing the toml, this
allows to define toml values in environment variables for more
flexibility.
This commit is contained in:
Romain de Laage 2025-10-10 13:52:29 +02:00 committed by GitHub
parent 7d7304f713
commit d382d74241
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 24 additions and 2 deletions

11
Cargo.lock generated
View File

@ -4312,6 +4312,7 @@ dependencies = [
"serde_json",
"serde_with",
"simplelog",
"subst",
"tar",
"tempfile",
"thiserror 2.0.16",
@ -5118,6 +5119,16 @@ dependencies = [
"syn 2.0.106",
]
[[package]]
name = "subst"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a9a86e5144f63c2d18334698269a8bfae6eece345c70b64821ea5b35054ec99"
dependencies = [
"memchr",
"unicode-width 0.1.14",
]
[[package]]
name = "subtle"
version = "2.6.1"

View File

@ -139,6 +139,7 @@ opentelemetry = { version = "0.30.0", default-features = false, features = ["met
opentelemetry-otlp = { version = "0.30.0", features = ["metrics"], optional = true }
opentelemetry_sdk = { version = "0.30.0", default-features = false, features = ["metrics"], optional = true }
rhai = { version = "1", features = ["sync", "serde", "no_optimize", "no_module", "no_custom_syntax", "only_i64"], optional = true }
subst = "0.3.8"
[dev-dependencies]
abscissa_core = { version = "0.8.2", default-features = false, features = ["testing"] }

View File

@ -13,7 +13,7 @@ use std::{
path::PathBuf,
};
use abscissa_core::{FrameworkError, config::Config, path::AbsPathBuf};
use abscissa_core::{FrameworkError, FrameworkErrorKind, config::Config, path::AbsPathBuf};
use anyhow::{Result, anyhow};
use clap::{Parser, ValueHint};
use conflate::Merge;
@ -120,7 +120,17 @@ impl RusticConfig {
if let Some(path) = paths.iter().find(|path| path.exists()) {
merge_logs.push((Level::Info, format!("using config {}", path.display())));
let mut config = Self::load_toml_file(AbsPathBuf::canonicalize(path)?)?;
let config_content = subst::substitute(
&std::fs::read_to_string(AbsPathBuf::canonicalize(path)?)?,
&subst::Env,
)
.map_err(|e| {
abscissa_core::error::context::Context::new(
FrameworkErrorKind::ParseError,
Some(Box::new(e)),
)
})?;
let mut config = Self::load_toml(config_content)?;
// if "use_profile" is defined in config file, merge the referenced profiles first
for profile in &config.global.use_profiles.clone() {
config.merge_profile(profile, merge_logs, Level::Warn)?;