use super::local::LocalCas;
use super::reapi_exec::{
ActionResult, ActionSpec, CommandSpec, DigestVal, OutputFile, ReapiExecClient,
};
use anyhow::{Context, Result, bail};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Instant;
pub struct RemoteWorker {
pub worker_id: String,
pub workspace_root: PathBuf,
pub cas: LocalCas,
}
impl RemoteWorker {
pub fn new(worker_id: String, workspace_root: PathBuf, cas: LocalCas) -> Result<Self> {
fs::create_dir_all(&workspace_root)?;
Ok(Self {
worker_id,
workspace_root,
cas,
})
}
pub fn validate_path_security(path_str: &str) -> Result<()> {
if path_str.contains("..") || path_str.starts_with('/') || path_str.starts_with('\\') {
bail!(
"Security Violation: Path traversal escape attempt in path '{}'",
path_str
);
}
Ok(())
}
pub fn execute_action(
&self,
action: &ActionSpec,
command: &CommandSpec,
) -> Result<ActionResult> {
let start_time = Instant::now();
for file in &command.output_files {
Self::validate_path_security(file)?;
}
for dir in &command.output_directories {
Self::validate_path_security(dir)?;
}
let action_dir = self
.workspace_root
.join(format!("work_{}", &action.command_digest.hash[7..15]));
fs::create_dir_all(&action_dir)?;
if command.arguments.is_empty() {
bail!("Command arguments cannot be empty");
}
let mut cmd = Command::new(&command.arguments[0]);
if command.arguments.len() > 1 {
cmd.args(&command.arguments[1..]);
}
cmd.current_dir(&action_dir);
for (k, v) in &command.environment_variables {
cmd.env(k, v);
}
let output = cmd
.output()
.with_context(|| format!("Remote worker failed executing {:?}", command.arguments))?;
let mut output_files = Vec::new();
for target_rel in &command.output_files {
let target_path = action_dir.join(target_rel);
if target_path.exists() && target_path.is_file() {
let content = fs::read(&target_path)?;
let digest = ReapiExecClient::compute_digest(&content);
self.cas.store_file(&digest.hash, &target_path)?;
output_files.push(OutputFile {
path: target_rel.clone(),
digest,
});
}
}
let _ = fs::remove_dir_all(&action_dir);
let duration_ms = start_time.elapsed().as_millis() as u64;
Ok(ActionResult {
exit_code: output.status.code().unwrap_or(-1),
stdout_raw: output.stdout,
stderr_raw: output.stderr,
output_files,
output_directories: Vec::new(),
execution_duration_ms: duration_ms,
})
}
}