use crate::core::shell_escape::{is_valid_host, sh_squote};
use crate::core::types::Resource;
pub fn check_script(resource: &Resource) -> String {
let deploy_path = resource.target.as_deref().unwrap_or("/dev/null");
let mut script = format!(
"test -x {} && echo 'installed:build' || echo 'missing:build'",
sh_squote(deploy_path)
);
if let Some(ref check) = resource.completion_check {
script = format!(
"if {check} >/dev/null 2>&1; then echo 'installed:build'; else echo 'missing:build'; fi"
);
}
script
}
fn build_command_body(resource: &Resource) -> String {
let command = resource.command.as_deref().unwrap_or("true");
match resource.working_dir {
Some(ref dir) => format!("cd {} && {command}", sh_squote(dir)),
None => command.to_string(),
}
}
pub fn apply_script(resource: &Resource) -> String {
let artifact = resource.source.as_deref().unwrap_or("/dev/null");
let deploy_path = resource.target.as_deref().unwrap_or("/tmp/build-artifact");
let build_machine = resource.build_machine.as_deref().unwrap_or("localhost");
if build_machine != "localhost" && !is_valid_host(build_machine) {
return format!(
"echo {} >&2; exit 1",
sh_squote(&format!("ERROR: invalid build_machine: {build_machine}"))
);
}
let bm = sh_squote(build_machine);
let dp = sh_squote(deploy_path);
let build_cmd = build_command_body(resource);
let mut script = String::from("set -euo pipefail\n");
if build_machine == "localhost" {
script.push_str(&format!("# Phase 1: build locally\n{build_cmd}\n"));
} else {
script.push_str(&format!(
"# Phase 1: build on {build_machine}\n\
ssh -o BatchMode=yes -o ConnectTimeout=10 {bm} bash -s <<'FORJAR_BUILD'\n\
{build_cmd}\n\
FORJAR_BUILD\n"
));
}
if build_machine != "localhost" {
let remote_spec = sh_squote(&format!("{build_machine}:{artifact}"));
script.push_str(&format!(
"# Phase 2: transfer artifact\n\
mkdir -p \"$(dirname {dp})\"\n\
scp -o BatchMode=yes {remote_spec} {dp}\n\
chmod +x {dp}\n"
));
} else {
script.push_str(&format!(
"# Phase 2: copy artifact locally\n\
mkdir -p \"$(dirname {dp})\"\n\
cp {} {dp}\n\
chmod +x {dp}\n",
sh_squote(artifact)
));
}
if let Some(ref check) = resource.completion_check {
script.push_str(&format!("# Phase 3: verify\n{check}\n"));
}
script
}
pub fn state_query_script(resource: &Resource) -> String {
let deploy_path = sh_squote(resource.target.as_deref().unwrap_or("/dev/null"));
format!(
"if [ -f {deploy_path} ]; then \
sha256sum {deploy_path} | awk '{{print $1}}'; \
else echo 'MISSING'; fi"
)
}
#[cfg(test)]
mod fj154_tests {
use super::*;
use crate::core::types::{MachineTarget, ResourceType};
fn build_resource() -> Resource {
Resource {
resource_type: ResourceType::Build,
machine: MachineTarget::Single("jetson".to_string()),
build_machine: Some("intel".to_string()),
command: Some("cargo build --release".to_string()),
working_dir: Some("~/src/aprender".to_string()),
source: Some("/tmp/cross/release/apr".to_string()),
target: Some("/home/user/.cargo/bin/apr".to_string()),
..Default::default()
}
}
#[test]
fn fj154_remote_build_uses_stdin_heredoc() {
let r = build_resource();
let script = apply_script(&r);
assert!(
script.contains(
"ssh -o BatchMode=yes -o ConnectTimeout=10 'intel' bash -s <<'FORJAR_BUILD'"
),
"{script}"
);
assert!(script.contains("FORJAR_BUILD\n"), "{script}");
assert!(!script.contains("'intel' 'cd"), "{script}");
}
#[test]
fn fj154_command_with_quotes_survives_in_heredoc() {
let mut r = build_resource();
r.command = Some("sed 's/a/b/' Cargo.toml".to_string());
let script = apply_script(&r);
assert!(script.contains("sed 's/a/b/' Cargo.toml"), "{script}");
let after = script.split("<<'FORJAR_BUILD'").nth(1).unwrap_or("");
assert!(after.contains("sed 's/a/b/'"), "{script}");
}
#[test]
fn fj154_invalid_build_machine_rejected() {
let mut r = build_resource();
r.build_machine = Some("intel';reboot;'".to_string());
let script = apply_script(&r);
assert!(script.contains("ERROR: invalid build_machine"), "{script}");
assert!(script.contains("exit 1"), "{script}");
assert!(!script.contains("ssh"), "{script}");
}
#[test]
fn fj154_scp_remote_spec_quoted() {
let r = build_resource();
let script = apply_script(&r);
assert!(
script.contains(
"scp -o BatchMode=yes 'intel:/tmp/cross/release/apr' '/home/user/.cargo/bin/apr'"
),
"{script}"
);
}
#[test]
fn fj154_working_dir_quote_neutralized() {
let mut r = build_resource();
r.build_machine = Some("localhost".to_string());
r.working_dir = Some("~/x';reboot;'".to_string());
let script = apply_script(&r);
assert!(script.contains("'\\''"), "{script}");
assert!(!script.contains("cd '~/x';reboot"), "{script}");
}
}