diff --git a/binaries/daemon/src/spawn.rs b/binaries/daemon/src/spawn.rs index 113db901..d192c3ec 100644 --- a/binaries/daemon/src/spawn.rs +++ b/binaries/daemon/src/spawn.rs @@ -22,6 +22,7 @@ use dora_message::{ common::{LogLevel, LogMessage}, daemon_to_coordinator::{DataMessage, NodeExitStatus, Timestamped}, daemon_to_node::{NodeConfig, RuntimeConfig}, + descriptor::EnvValue, id::NodeId, }; use dora_node_api::{ @@ -31,6 +32,7 @@ use dora_node_api::{ }; use eyre::{ContextCompat, WrapErr, bail}; use std::{ + collections::BTreeMap, future::Future, path::{Path, PathBuf}, process::Stdio, @@ -127,7 +129,8 @@ impl Spawner { let (command, error_msg) = match &node.kind { dora_core::descriptor::CoreNodeKind::Custom(n) => { let mut command = - path_spawn_command(&node_working_dir, self.uv, logger, n, true).await?; + path_spawn_command(&node_working_dir, self.uv, logger, n, &node.env, true) + .await?; if let Some(command) = &mut command { command.current_dir(&node_working_dir); @@ -626,6 +629,7 @@ async fn path_spawn_command( uv: bool, logger: &mut NodeLogger<'_>, node: &dora_core::descriptor::CustomNode, + env: &Option>, permit_url: bool, ) -> eyre::Result> { let cmd = match node.path.as_str() { @@ -652,7 +656,44 @@ async fn path_spawn_command( .await .wrap_err("failed to download custom node")? } else { - resolve_path(source, working_dir) + let replacements: Vec> = source + .find('$') + .map(|start| { + let end = source[start..].find('/').unwrap_or(source.len()); + let var = &source[start + 1..start + end]; + if let Some(envs) = env { + if let Some(val) = envs.get(var) { + Ok((var.to_string(), val.to_string())) + } else { + eyre::bail!( + "environment variable `{}` for node `{}` not found", + var, + source + ) + } + } else { + eyre::bail!( + "environment variable `{}` for node `{}` not found", + var, + source + ) + } + }) + .into_iter() + .collect(); + let mut source = String::from(source); + for kv in replacements.into_iter() { + match kv { + Ok((var, value)) => { + source = source.replace(&format!("${}", var), &value); + } + Err(err) => { + return Err(err); + } + } + } + + resolve_path(&source, working_dir) .wrap_err_with(|| format!("failed to resolve node source `{source}`"))? }; diff --git a/libraries/core/src/build/build_command.rs b/libraries/core/src/build/build_command.rs index aed83e89..79231611 100644 --- a/libraries/core/src/build/build_command.rs +++ b/libraries/core/src/build/build_command.rs @@ -19,12 +19,27 @@ pub fn run_build_command( let lines = build.lines().collect::>(); for build_line in lines { - let mut split = build_line.split_whitespace(); + let quote: Vec<&str> = build_line.split('"').collect(); + if quote.len() % 2 == 0 { + return Err(eyre!( + "build command `{build_line}`. quote(s) are not in pair" + )); + } + let mut split_vec: Vec<&str> = vec![]; + quote.iter().enumerate().for_each(|(i, part)| { + if i % 2 == 0 { + let mut split_with_white: Vec<&str> = part.split_whitespace().collect(); + split_vec.append(&mut split_with_white); + } else { + split_vec.push(part); + } + }); + let mut split = split_vec.iter(); let program = split .next() .ok_or_else(|| eyre!("build command is empty"))?; - let mut cmd = if uv && (program == "pip" || program == "pip3") { + let mut cmd = if uv && (*program == "pip" || *program == "pip3") { let mut cmd = Command::new("uv"); cmd.arg("pip"); cmd