#[cfg(test)]
mod tests;
use super::env;
use crate::{config::compose::ComposeConfig, error::DockerError};
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
#[derive(Default, Clone)]
pub struct ComposeParser {
env_file_path: Option<PathBuf>,
env_vars: Option<HashMap<String, String>>,
}
impl ComposeParser {
pub fn parse_from_path<P: AsRef<Path>>(self, path: P) -> Result<ComposeConfig, DockerError> {
let mut f = File::open(path)?;
self.parse(&mut f)
}
pub fn parse<R>(self, reader: &mut R) -> Result<ComposeConfig, DockerError>
where
R: Read,
{
let mut env_vars = self.env_vars.unwrap_or_default();
if let Some(env_file_path) = self.env_file_path {
let env_content = std::fs::read_to_string(env_file_path).map_err(|e| {
DockerError::ValidationError(format!("Failed to read env file: {}", e))
})?;
env_vars.extend(env::parse_env_file(&env_content));
}
let mut config_bytes = Vec::new();
reader.read_to_end(&mut config_bytes)?;
let compose = String::from_utf8(config_bytes).map_err(|e| {
DockerError::ValidationError(format!("Failed to read compose file: {}", e))
})?;
let processed_content = env::substitute_env_vars(&compose, &env_vars);
let config: ComposeConfig =
serde_yaml::from_str(&processed_content).map_err(DockerError::YamlError)?;
validate_required_env_vars(&config, &env_vars)?;
Ok(config)
}
}
impl ComposeParser {
#[must_use]
pub fn new() -> Self {
Self {
env_file_path: None,
env_vars: None,
}
}
#[must_use]
pub fn env_file<P: AsRef<Path>>(mut self, path: P) -> Self {
self.env_file_path = Some(path.as_ref().to_path_buf());
self
}
#[must_use]
pub fn env_vars(mut self, vars: HashMap<String, String>) -> Self {
self.env_vars = Some(vars);
self
}
}
fn validate_required_env_vars(
config: &ComposeConfig,
env_vars: &HashMap<String, String>,
) -> Result<(), DockerError> {
let mut required_vars = std::collections::HashSet::new();
for service in config.services.values() {
if let Some(env) = &service.environment {
for (key, value) in env {
if value.contains("${") && !value.contains(":-") {
required_vars.insert(key.clone());
}
}
}
}
let env_keys: std::collections::HashSet<_> = env_vars.keys().cloned().collect();
let missing_vars: Vec<_> = required_vars.difference(&env_keys).collect();
if !missing_vars.is_empty() {
return Err(DockerError::ValidationError(format!(
"Missing required environment variables: {:?}",
missing_vars
)));
}
Ok(())
}