use crate::core::types::Resource;
pub fn check_script(resource: &Resource) -> String {
let name = resource.name.as_deref().unwrap_or("unknown");
let user = resource.owner.as_deref().unwrap_or("root");
format!(
"crontab -u '{user}' -l 2>/dev/null | grep -qF '# forjar:{name}' && echo 'exists:{name}' || echo 'missing:{name}'"
)
}
pub fn apply_script(resource: &Resource) -> String {
let name = resource.name.as_deref().unwrap_or("unknown");
let user = resource.owner.as_deref().unwrap_or("root");
let state = resource.state.as_deref().unwrap_or("present");
match state {
"absent" => format!(
"set -euo pipefail\n\
SUDO=\"\"\n\
[ \"$(id -u)\" -ne 0 ] && SUDO=\"sudo\"\n\
EXISTING=$($SUDO crontab -u '{user}' -l 2>/dev/null || true)\n\
echo \"$EXISTING\" | grep -v '# forjar:{name}' | grep -v '# forjar-cmd:{name}' | $SUDO crontab -u '{user}' -"
),
_ => {
let schedule = resource.schedule.as_deref().unwrap_or("* * * * *");
let command = resource.command.as_deref().unwrap_or("true");
format!(
"set -euo pipefail\n\
SUDO=\"\"\n\
[ \"$(id -u)\" -ne 0 ] && SUDO=\"sudo\"\n\
EXISTING=$($SUDO crontab -u '{user}' -l 2>/dev/null | grep -v '# forjar:{name}' | grep -v '# forjar-cmd:{name}' || true)\n\
{{\n\
echo \"$EXISTING\"\n\
echo '# forjar:{name}'\n\
echo '# forjar-cmd:{name}' \n\
echo '{schedule} {command}'\n\
}} | $SUDO crontab -u '{user}' -"
)
}
}
}
pub fn state_query_script(resource: &Resource) -> String {
let name = resource.name.as_deref().unwrap_or("unknown");
let user = resource.owner.as_deref().unwrap_or("root");
format!(
"crontab -u '{user}' -l 2>/dev/null | grep -A1 '# forjar:{name}' || echo 'cron=MISSING:{name}'"
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::types::{MachineTarget, ResourceType};
fn make_cron_resource(name: &str) -> Resource {
Resource {
resource_type: ResourceType::Cron,
machine: MachineTarget::Single("m1".to_string()),
state: None,
depends_on: vec![],
provider: None,
packages: vec![],
version: None,
path: None,
content: None,
source: None,
target: None,
owner: Some("root".to_string()),
group: None,
mode: None,
name: Some(name.to_string()),
enabled: None,
restart_on: vec![],
triggers: vec![],
fs_type: None,
options: None,
uid: None,
shell: None,
home: None,
groups: vec![],
ssh_authorized_keys: vec![],
system_user: false,
schedule: Some("0 * * * *".to_string()),
command: Some("/usr/local/bin/backup.sh".to_string()),
image: None,
ports: vec![],
environment: vec![],
volumes: vec![],
restart: None,
protocol: None,
port: None,
action: None,
from_addr: None,
recipe: None,
inputs: std::collections::HashMap::new(),
arch: vec![],
tags: vec![],
resource_group: None,
when: None,
count: None,
for_each: None,
chroot_dir: None,
namespace_uid: None,
namespace_gid: None,
seccomp: false,
netns: false,
cpuset: None,
memory_limit: None,
overlay_lower: None,
overlay_upper: None,
overlay_work: None,
overlay_merged: None,
format: None,
quantization: None,
checksum: None,
cache_dir: None,
gpu_backend: None,
driver_version: None,
cuda_version: None,
rocm_version: None,
devices: vec![],
persistence_mode: None,
compute_mode: None,
gpu_memory_limit_mb: None,
output_artifacts: vec![],
completion_check: None,
timeout: None,
working_dir: None,
task_mode: None,
task_inputs: vec![],
stages: vec![],
cache: false,
gpu_device: None,
restart_delay: None,
quality_gate: None,
health_check: None,
restart_policy: None,
pre_apply: None,
post_apply: None,
lifecycle: None,
store: false,
sudo: false,
script: None,
gather: vec![],
scatter: vec![],
build_machine: None,
repo: None,
tag: None,
asset_pattern: None,
binary: None,
install_dir: None,
}
}
#[test]
fn test_fj033_check_cron() {
let r = make_cron_resource("backup");
let script = check_script(&r);
assert!(script.contains("crontab -u 'root' -l"));
assert!(script.contains("forjar:backup"));
assert!(script.contains("exists:backup"));
assert!(script.contains("missing:backup"));
}
#[test]
fn test_fj033_apply_present() {
let r = make_cron_resource("backup");
let script = apply_script(&r);
assert!(script.contains("set -euo pipefail"));
assert!(script.contains("crontab -u 'root'"));
assert!(script.contains("0 * * * *"));
assert!(script.contains("/usr/local/bin/backup.sh"));
assert!(script.contains("# forjar:backup"));
}
#[test]
fn test_fj033_apply_absent() {
let mut r = make_cron_resource("old-job");
r.state = Some("absent".to_string());
let script = apply_script(&r);
assert!(script.contains("grep -v '# forjar:old-job'"));
}
#[test]
fn test_fj033_state_query() {
let r = make_cron_resource("backup");
let script = state_query_script(&r);
assert!(script.contains("crontab -u 'root' -l"));
assert!(script.contains("forjar:backup"));
assert!(script.contains("cron=MISSING:backup"));
}
#[test]
fn test_fj033_custom_user() {
let mut r = make_cron_resource("deploy-sync");
r.owner = Some("deploy".to_string());
let script = apply_script(&r);
assert!(script.contains("crontab -u 'deploy'"));
}
#[test]
fn test_fj033_sudo_detection() {
let r = make_cron_resource("job");
let script = apply_script(&r);
assert!(script.contains("SUDO=\"\""));
assert!(script.contains("$SUDO crontab"));
}
#[test]
fn test_fj033_unique_tagging() {
let r1 = make_cron_resource("job-a");
let r2 = make_cron_resource("job-b");
let s1 = apply_script(&r1);
let s2 = apply_script(&r2);
assert!(s1.contains("# forjar:job-a"));
assert!(s2.contains("# forjar:job-b"));
assert!(!s1.contains("# forjar:job-b"));
}
#[test]
fn test_fj033_apply_preserves_existing_entries() {
let r = make_cron_resource("backup");
let script = apply_script(&r);
assert!(
script.contains("grep -v '# forjar:backup'"),
"must remove old entry before re-adding"
);
}
#[test]
fn test_fj033_absent_preserves_other_entries() {
let mut r = make_cron_resource("old-job");
r.state = Some("absent".to_string());
let script = apply_script(&r);
assert!(script.contains("grep -v '# forjar:old-job'"));
assert!(script.contains("grep -v '# forjar-cmd:old-job'"));
assert!(script.contains("crontab -u 'root' -"));
}
#[test]
fn test_fj033_default_schedule_and_command() {
let mut r = make_cron_resource("defaults");
r.schedule = None;
r.command = None;
let script = apply_script(&r);
assert!(
script.contains("* * * * *"),
"default schedule should be every minute"
);
assert!(script.contains("true"), "default command should be 'true'");
}
#[test]
fn test_fj033_check_custom_user() {
let mut r = make_cron_resource("sync");
r.owner = Some("www-data".to_string());
let script = check_script(&r);
assert!(script.contains("crontab -u 'www-data' -l"));
}
#[test]
fn test_fj033_state_query_custom_user() {
let mut r = make_cron_resource("sync");
r.owner = Some("deploy".to_string());
let script = state_query_script(&r);
assert!(script.contains("crontab -u 'deploy' -l"));
}
#[test]
fn test_fj033_no_name_defaults_to_unknown() {
let mut r = make_cron_resource("placeholder");
r.name = None;
let check = check_script(&r);
assert!(check.contains("forjar:unknown"));
let apply = apply_script(&r);
assert!(apply.contains("# forjar:unknown"));
let query = state_query_script(&r);
assert!(query.contains("forjar:unknown"));
}
#[test]
fn test_fj033_no_owner_defaults_to_root() {
let mut r = make_cron_resource("job");
r.owner = None;
let script = apply_script(&r);
assert!(script.contains("crontab -u 'root'"));
}
#[test]
fn test_fj033_absent_ignores_schedule_and_command() {
let mut r = make_cron_resource("old-job");
r.state = Some("absent".to_string());
r.schedule = Some("0 3 * * *".to_string());
r.command = Some("/bin/cleanup".to_string());
let script = apply_script(&r);
assert!(
!script.contains("0 3 * * *"),
"absent should not include schedule"
);
assert!(
!script.contains("/bin/cleanup"),
"absent should not include command"
);
assert!(script.contains("grep -v '# forjar:old-job'"));
}
#[test]
fn test_fj033_apply_cmd_tag_idempotency() {
let r = make_cron_resource("backup");
let script = apply_script(&r);
assert!(script.contains("grep -v '# forjar-cmd:backup'"));
assert!(script.contains("echo '# forjar-cmd:backup'"));
}
#[test]
fn test_fj132_check_script_default_name() {
let mut r = make_cron_resource("placeholder");
r.name = None;
let script = check_script(&r);
assert!(script.contains("forjar:unknown"));
}
#[test]
fn test_fj132_state_query_default_owner() {
let mut r = make_cron_resource("job");
r.owner = None;
let script = state_query_script(&r);
assert!(script.contains("crontab -u 'root' -l"));
}
#[test]
fn test_fj132_apply_special_chars_in_command() {
let mut r = make_cron_resource("log-rotate");
r.command = Some("/usr/sbin/logrotate /etc/logrotate.d/*.conf".to_string());
let script = apply_script(&r);
assert!(script.contains("/usr/sbin/logrotate /etc/logrotate.d/*.conf"));
}
#[test]
fn test_fj132_apply_five_field_schedule() {
let mut r = make_cron_resource("weekly");
r.schedule = Some("0 3 * * 0".to_string());
let script = apply_script(&r);
assert!(script.contains("0 3 * * 0"));
}
#[test]
fn test_fj132_check_and_query_same_user() {
let mut r = make_cron_resource("sync");
r.owner = Some("www-data".to_string());
let check = check_script(&r);
let query = state_query_script(&r);
assert!(check.contains("crontab -u 'www-data'"));
assert!(query.contains("crontab -u 'www-data'"));
}
#[test]
fn test_fj036_cron_apply_contains_schedule() {
let mut r = make_cron_resource("nightly-backup");
r.schedule = Some("30 2 * * *".to_string());
r.command = Some("/opt/backup/run.sh".to_string());
let script = apply_script(&r);
assert!(
script.contains("30 2 * * *"),
"apply script must include the schedule string"
);
assert!(
script.contains("/opt/backup/run.sh"),
"apply script must include the command"
);
}
#[test]
fn test_fj036_cron_apply_absent_removes() {
let mut r = make_cron_resource("stale-job");
r.state = Some("absent".to_string());
let script = apply_script(&r);
assert!(
script.contains("grep -v '# forjar:stale-job'"),
"absent state must generate crontab removal via grep -v"
);
assert!(
script.contains("crontab -u 'root' -"),
"absent state must reinstall filtered crontab"
);
assert!(
!script.contains("0 * * * *"),
"absent should not emit schedule"
);
}
#[test]
fn test_fj153_cron_all_defaults() {
let mut r = make_cron_resource("job");
r.schedule = None;
r.command = None;
r.owner = None;
r.name = None;
let script = apply_script(&r);
assert!(
script.contains("* * * * *"),
"default schedule should be every minute"
);
assert!(script.contains("true"), "default command should be 'true'");
assert!(
script.contains("crontab -u 'root'"),
"default user should be root"
);
}
#[test]
fn test_fj153_cron_no_name_check() {
let mut r = make_cron_resource("placeholder");
r.name = None;
let script = check_script(&r);
assert!(script.contains("forjar:unknown"));
}
#[test]
fn test_fj153_cron_absent_all_defaults() {
let mut r = make_cron_resource("old");
r.state = Some("absent".to_string());
r.owner = None;
let script = apply_script(&r);
assert!(script.contains("grep -v '# forjar:old'"));
assert!(script.contains("crontab -u 'root' -"));
assert!(!script.contains("echo '"));
}
#[test]
fn test_fj153_cron_custom_user() {
let mut r = make_cron_resource("deploy-sync");
r.owner = Some("deploy".to_string());
r.schedule = Some("*/5 * * * *".to_string());
r.command = Some("/opt/sync.sh".to_string());
let script = apply_script(&r);
assert!(script.contains("crontab -u 'deploy'"));
assert!(script.contains("*/5 * * * *"));
assert!(script.contains("forjar:deploy-sync"));
}
#[test]
fn test_fj036_cron_state_query_lists_crontab() {
let r = make_cron_resource("hourly-sync");
let script = state_query_script(&r);
assert!(
script.contains("crontab -u 'root' -l"),
"state_query must use 'crontab -l' to list entries"
);
assert!(
script.contains("forjar:hourly-sync"),
"state_query must filter by forjar tag"
);
}
}