Skip to content

Commit caf2bb8

Browse files
committed
fix: passing environment variables to containers via temmporary --env-file
1 parent ce5ea1b commit caf2bb8

3 files changed

Lines changed: 115 additions & 4 deletions

File tree

src/book/mod.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,34 @@ impl Container {
8282
.to_string(),
8383
app_in_path: true,
8484
args: vec!["run".to_string(), "--rm".to_string()],
85+
env: BTreeMap::new(),
86+
temp_env_file: None,
8587
};
8688

89+
// handle environment variables if present
90+
if !cmdline.env.is_empty() {
91+
let mut env_contents = String::new();
92+
for (key, value) in &cmdline.env {
93+
env_contents.push_str(&format!("{}={}\n", key, value));
94+
}
95+
96+
// create temp file
97+
let temp_file = tempfile::NamedTempFile::new()
98+
.map_err(|e| anyhow::anyhow!("failed to create temp env file: {}", e))?;
99+
100+
// write env vars
101+
std::fs::write(temp_file.path(), env_contents)
102+
.map_err(|e| anyhow::anyhow!("failed to write env file: {}", e))?;
103+
104+
// add env-file arg
105+
dockerized
106+
.args
107+
.push(format!("--env-file={}", temp_file.path().display()));
108+
109+
// keep temp file alive until docker run completes
110+
dockerized.temp_env_file = Some(temp_file);
111+
}
112+
87113
// add volumes if any
88114
if let Some(volumes) = &self.volumes {
89115
for volume in volumes {
@@ -397,6 +423,8 @@ mod tests {
397423
app: "original_app".to_string(),
398424
app_in_path: true,
399425
args: vec!["arg1".to_string(), "arg2".to_string()],
426+
env: BTreeMap::new(),
427+
temp_env_file: None,
400428
};
401429

402430
let wrapped_cmdline = container.wrap(original_cmdline).unwrap();
@@ -515,4 +543,54 @@ functions:
515543
assert!(result.get_function("function1").is_ok());
516544
assert!(result.get_function("function2").is_err());
517545
}
546+
547+
#[test]
548+
fn test_wrap_with_env() {
549+
let env: BTreeMap<String, String> = {
550+
let mut env = BTreeMap::new();
551+
env.insert("TEST_VAR".to_string(), "test_value".to_string());
552+
env
553+
};
554+
555+
let command_line =
556+
CommandLine::from_vec_with_env(&vec!["echo".to_string(), "test".to_string()], env)
557+
.unwrap();
558+
559+
let container = Container {
560+
source: ContainerSource::Image("test_image".to_string()),
561+
args: None,
562+
volumes: None,
563+
force: false,
564+
preserve_app: true,
565+
platform: None,
566+
};
567+
568+
let wrapped = container.wrap(command_line).unwrap();
569+
570+
// Find the env-file argument
571+
let env_file_arg = wrapped
572+
.args
573+
.iter()
574+
.find(|arg| arg.starts_with("--env-file="))
575+
.expect("--env-file argument not found")
576+
.clone();
577+
578+
// Extract the file path
579+
let env_file_path = env_file_arg
580+
.strip_prefix("--env-file=")
581+
.expect("Failed to strip --env-file= prefix");
582+
583+
let env_file = std::path::Path::new(env_file_path);
584+
assert!(env_file.exists());
585+
586+
// Read the env file contents
587+
let env_file_contents = std::fs::read_to_string(env_file).expect("Failed to read env file");
588+
589+
// Verify it contains the expected environment variable
590+
assert!(env_file_contents.contains("TEST_VAR=test_value"));
591+
592+
// Clean up the env file
593+
drop(wrapped);
594+
assert!(!env_file.exists(), "env file was not deleted");
595+
}
518596
}

src/book/runtime.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ impl<'a> FunctionRef<'a> {
182182
) -> anyhow::Result<CommandLine> {
183183
// determine the command line to execute
184184
let command_line = self.function.execution.get_command_line()?;
185+
let mut env = BTreeMap::new();
185186

186187
// interpolate the arguments
187188
let command_line = {
@@ -199,7 +200,7 @@ impl<'a> FunctionRef<'a> {
199200
if var_name.starts_with("env.") || var_name.starts_with("ENV.") {
200201
let env_var_name = var_name.replace("env.", "").replace("ENV.", "");
201202
let env_var = std::env::var(&env_var_name);
202-
if let Ok(value) = env_var {
203+
let env_var_value = if let Ok(value) = env_var {
203204
value
204205
} else if var_default.is_some() {
205206
var_default.unwrap().to_string()
@@ -208,7 +209,12 @@ impl<'a> FunctionRef<'a> {
208209
"environment variable {} not set",
209210
env_var_name
210211
));
211-
}
212+
};
213+
214+
// add the environment variable to the command line for later use
215+
env.insert(env_var_name, env_var_value.to_owned());
216+
217+
env_var_value
212218
} else if let Some(value) = arguments.get(var_name) {
213219
// if the value is empty and there's a default value, use the default value
214220
if value.is_empty() && var_default.is_some() {
@@ -231,7 +237,7 @@ impl<'a> FunctionRef<'a> {
231237
};
232238

233239
// final parsing
234-
CommandLine::from_vec(&command_line)
240+
CommandLine::from_vec_with_env(&command_line, env)
235241
}
236242
}
237243

src/runtime/cmd.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
use std::fmt;
1+
use std::{collections::BTreeMap, fmt};
22

33
#[derive(Debug)]
44
pub struct CommandLine {
55
pub sudo: bool,
66
pub app: String,
77
pub app_in_path: bool,
88
pub args: Vec<String>,
9+
pub env: BTreeMap<String, String>,
10+
11+
// used to keep a valid reference to this while the command is running
12+
pub temp_env_file: Option<tempfile::NamedTempFile>,
913
}
1014

1115
impl CommandLine {
@@ -47,9 +51,20 @@ impl CommandLine {
4751
app,
4852
args,
4953
app_in_path,
54+
env: BTreeMap::new(),
55+
temp_env_file: None,
5056
})
5157
}
5258

59+
pub fn from_vec_with_env(
60+
vec: &Vec<String>,
61+
env: BTreeMap<String, String>,
62+
) -> anyhow::Result<Self> {
63+
let mut cmd = Self::from_vec(vec)?;
64+
cmd.env = env;
65+
Ok(cmd)
66+
}
67+
5368
pub async fn execute(&self) -> anyhow::Result<String> {
5469
let output = tokio::process::Command::new(&self.app)
5570
.args(&self.args)
@@ -111,6 +126,8 @@ mod tests {
111126
app: "ls".to_string(),
112127
args: vec!["-l".to_string(), "-a".to_string()],
113128
app_in_path: true,
129+
env: BTreeMap::new(),
130+
temp_env_file: None,
114131
};
115132
assert_eq!(format!("{}", cmd), "ls -l -a");
116133

@@ -119,6 +136,8 @@ mod tests {
119136
app: "apt".to_string(),
120137
args: vec!["install".to_string(), "package".to_string()],
121138
app_in_path: true,
139+
env: BTreeMap::new(),
140+
temp_env_file: None,
122141
};
123142
assert_eq!(format!("{}", cmd_with_sudo), "sudo apt install package");
124143
}
@@ -130,6 +149,8 @@ mod tests {
130149
app: "echo".to_string(),
131150
args: vec!["-n".to_string(), "Hello, World!".to_string()],
132151
app_in_path: true,
152+
env: BTreeMap::new(),
153+
temp_env_file: None,
133154
};
134155
let result = cmd.execute().await.unwrap();
135156
assert_eq!(result, "Hello, World!");
@@ -142,6 +163,8 @@ mod tests {
142163
app: "ls".to_string(),
143164
args: vec!["nonexistent_file".to_string()],
144165
app_in_path: true,
166+
env: BTreeMap::new(),
167+
temp_env_file: None,
145168
};
146169
let result = cmd.execute().await.unwrap();
147170
assert!(result.contains("EXIT CODE:"));
@@ -158,6 +181,8 @@ mod tests {
158181
"echo 'Hello' && echo 'Error' >&2".to_string(),
159182
],
160183
app_in_path: true,
184+
env: BTreeMap::new(),
185+
temp_env_file: None,
161186
};
162187
let result = cmd.execute().await.unwrap();
163188
assert!(result.contains("Hello"));
@@ -171,6 +196,8 @@ mod tests {
171196
app: "".to_string(),
172197
args: vec!["arg1".to_string(), "arg2".to_string()],
173198
app_in_path: true,
199+
env: BTreeMap::new(),
200+
temp_env_file: None,
174201
};
175202
let result = cmd.execute().await;
176203
assert!(result.is_err());

0 commit comments

Comments
 (0)