use super::check::pipeline_script;
use super::helpers::{service_rid, timeout_wrapped, NOT_CONVERGED_MARKER};
use super::query::{gather_script, scatter_script};
use crate::core::shell_escape::sh_squote;
use crate::core::types::{Resource, TaskMode};
pub fn apply_script(resource: &Resource) -> String {
if !resource.stages.is_empty() {
return pipeline_script(resource);
}
match resource.task_mode.as_ref().unwrap_or(&TaskMode::Batch) {
TaskMode::Service => return service_script(resource),
TaskMode::Dispatch => return dispatch_script(resource),
TaskMode::Pipeline | TaskMode::Batch => {} }
batch_script(resource)
}
fn batch_script(resource: &Resource) -> String {
let command = resource.command.as_deref().unwrap_or("true");
let mut script = String::from("set -euo pipefail\n");
if let Some(scatter) = scatter_script(resource) {
script.push_str(&scatter);
}
if let Some(ref dir) = resource.working_dir {
script.push_str(&format!("cd {}\n", sh_squote(dir)));
}
if let Some(timeout_secs) = resource.timeout {
script.push_str(&timeout_wrapped(command, timeout_secs));
} else {
script.push_str(command);
script.push('\n');
}
if let Some(gather) = gather_script(resource) {
script.push_str(&gather);
}
if let Some(ref check) = resource.completion_check {
script.push_str("if ! {\n");
script.push_str(check.trim_end());
script.push_str("\n}\nthen\n");
script.push_str(&format!(" echo '{NOT_CONVERGED_MARKER}' >&2\n"));
script.push_str(" echo 'task=not-converged: the declared state was not reached' >&2\n");
script.push_str(" exit 1\n");
script.push_str("fi\n");
}
script
}
fn service_script(resource: &Resource) -> String {
let command = resource.command.as_deref().unwrap_or("true");
let rid = service_rid(resource);
let pidfile = sh_squote(&format!("/tmp/forjar-svc-{rid}.pid"));
let logfile = sh_squote(&format!("/tmp/forjar-svc-{rid}.log"));
let mut script = String::from("set -euo pipefail\n");
if let Some(ref dir) = resource.working_dir {
script.push_str(&format!("cd {}\n", sh_squote(dir)));
}
script.push_str(&format!(
"if [ -f {pidfile} ] && kill -0 \"$(cat {pidfile})\" 2>/dev/null; then\n\
\x20 echo 'service={rid} already running (pid='\"$(cat {pidfile})\"')'\n\
\x20 exit 0\nfi\n"
));
script.push_str(&format!(
"nohup bash -c '{command}' > {logfile} 2>&1 &\n\
FORJAR_SVC_PID=$!\n\
echo $FORJAR_SVC_PID > {pidfile}\n\
echo 'service={rid} started (pid='$FORJAR_SVC_PID')'\n"
));
if let Some(ref hc) = resource.health_check {
let timeout = hc
.timeout
.as_deref()
.and_then(|t| t.strip_suffix('s'))
.unwrap_or("5");
let retries = hc.retries.unwrap_or(3);
script.push_str(&format!(
"sleep 1\nfor _i in $(seq 1 {retries}); do\n\
\x20 if ! kill -0 \"$FORJAR_SVC_PID\" 2>/dev/null; then\n\
\x20\x20\x20 echo 'service={rid} DIED during startup (pid='$FORJAR_SVC_PID')'\n\
\x20\x20\x20 tail -20 {logfile} 2>/dev/null || true\n\
\x20\x20\x20 rm -f {pidfile}\n\
\x20\x20\x20 exit 1\n\
\x20 fi\n\
\x20 if timeout {timeout} bash -c '{}'; then\n\
\x20\x20\x20 echo 'service={rid} healthy'\n\
\x20\x20\x20 exit 0\n\
\x20 fi\n\
\x20 sleep 1\ndone\n\
echo 'service={rid} started but health check pending'\n",
hc.command
));
}
script
}
fn dispatch_script(resource: &Resource) -> String {
let command = resource.command.as_deref().unwrap_or("true");
let mut script = String::from("set -euo pipefail\n");
if let Some(ref dir) = resource.working_dir {
script.push_str(&format!("cd {}\n", sh_squote(dir)));
}
if let Some(ref gate) = resource.quality_gate {
if let Some(ref gate_cmd) = gate.command {
let msg = gate
.message
.as_deref()
.unwrap_or("dispatch gate check failed");
script.push_str(&format!(
"if ! bash -c '{gate_cmd}'; then\n\
\x20 echo {}\n\
\x20 exit 1\nfi\n",
sh_squote(&format!("DISPATCH BLOCKED: {msg}"))
));
}
}
if let Some(scatter) = scatter_script(resource) {
script.push_str(&scatter);
}
if let Some(timeout_secs) = resource.timeout {
script.push_str(&timeout_wrapped(command, timeout_secs));
} else {
script.push_str(command);
script.push('\n');
}
if let Some(gather) = gather_script(resource) {
script.push_str(&gather);
}
script
}