use std::path::PathBuf;
use std::sync::Mutex;
use kovra_core::SecretValue;
use crate::error::WrapperError;
pub struct Command {
pub program: PathBuf,
pub args: Vec<String>,
pub env: Vec<(String, SecretValue)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Output {
pub status: Option<i32>,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
}
pub trait ProcessRunner {
fn run(&self, command: &Command) -> Result<Output, WrapperError>;
}
pub struct SystemRunner;
impl ProcessRunner for SystemRunner {
fn run(&self, command: &Command) -> Result<Output, WrapperError> {
let mut cmd = std::process::Command::new(&command.program);
cmd.args(&command.args);
for (name, value) in &command.env {
let s = std::str::from_utf8(value.expose()).map_err(|_| {
WrapperError::Spawn(format!(
"value for `{name}` is not valid UTF-8 and cannot be injected"
))
})?;
cmd.env(name, s);
}
let out = cmd.output().map_err(|e| {
WrapperError::Spawn(format!("launch {}: {e}", command.program.display()))
})?;
Ok(Output {
status: out.status.code(),
stdout: out.stdout,
stderr: out.stderr,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RecordedRun {
pub program: PathBuf,
pub args: Vec<String>,
pub env: Vec<(String, String)>,
}
impl RecordedRun {
pub fn env_value(&self, name: &str) -> Option<&str> {
self.env
.iter()
.find(|(k, _)| k == name)
.map(|(_, v)| v.as_str())
}
}
pub struct MockRunner {
output: Output,
invocations: Mutex<Vec<RecordedRun>>,
}
impl MockRunner {
pub fn new(output: Output) -> Self {
Self {
output,
invocations: Mutex::new(Vec::new()),
}
}
pub fn ok() -> Self {
Self::new(Output {
status: Some(0),
stdout: Vec::new(),
stderr: Vec::new(),
})
}
pub fn invocations(&self) -> Vec<RecordedRun> {
self.invocations
.lock()
.expect("runner mutex poisoned")
.clone()
}
pub fn was_invoked(&self) -> bool {
!self
.invocations
.lock()
.expect("runner mutex poisoned")
.is_empty()
}
}
impl ProcessRunner for MockRunner {
fn run(&self, command: &Command) -> Result<Output, WrapperError> {
let env = command
.env
.iter()
.map(|(k, v)| (k.clone(), String::from_utf8_lossy(v.expose()).into_owned()))
.collect();
self.invocations
.lock()
.expect("runner mutex poisoned")
.push(RecordedRun {
program: command.program.clone(),
args: command.args.clone(),
env,
});
Ok(self.output.clone())
}
}