use anyhow::{Context, Result};
use tracing::info;
use crate::jobstore::{JobDir, generate_job_id, resolve_root};
use crate::run::{
mask_env_vars, materialize_stdin_for_job, pre_create_log_files, resolve_effective_cwd,
validate_stdin_source,
};
use crate::schema::{CreateData, JobMeta, JobMetaJob, Response};
use crate::tag::dedup_tags;
#[derive(Debug)]
pub struct CreateOpts<'a> {
pub command: Vec<String>,
pub root: Option<&'a str>,
pub timeout_ms: u64,
pub kill_after_ms: u64,
pub cwd: Option<&'a str>,
pub env_vars: Vec<String>,
pub env_files: Vec<String>,
pub inherit_env: bool,
pub mask: Vec<String>,
pub stdin: Option<crate::run::StdinSource>,
pub stdin_max_bytes: u64,
pub progress_every_ms: u64,
pub notify_command: Option<String>,
pub notify_file: Option<String>,
pub shell_wrapper: Vec<String>,
pub tags: Vec<String>,
pub output_pattern: Option<String>,
pub output_match_type: Option<String>,
pub output_stream: Option<String>,
pub output_command: Option<String>,
pub output_file: Option<String>,
}
pub fn execute(opts: CreateOpts) -> Result<()> {
if opts.command.is_empty() {
anyhow::bail!("no command specified for create");
}
let root = resolve_root(opts.root);
std::fs::create_dir_all(&root)
.with_context(|| format!("create jobs root {}", root.display()))?;
let job_id = generate_job_id(&root)?;
let created_at = crate::run::now_rfc3339_pub();
let env_keys: Vec<String> = opts
.env_vars
.iter()
.map(|kv| kv.split('=').next().unwrap_or(kv.as_str()).to_string())
.collect();
let masked_env_vars = mask_env_vars(&opts.env_vars, &opts.mask);
let effective_cwd = resolve_effective_cwd(opts.cwd);
let on_output_match = crate::notify::build_output_match_config(
opts.output_pattern,
opts.output_match_type,
opts.output_stream,
opts.output_command,
opts.output_file,
None,
);
let notification =
if opts.notify_command.is_some() || opts.notify_file.is_some() || on_output_match.is_some()
{
Some(crate::schema::NotificationConfig {
notify_command: opts.notify_command.clone(),
notify_file: opts.notify_file.clone(),
on_output_match,
})
} else {
None
};
let tags = dedup_tags(opts.tags)?;
let stdin_source = opts.stdin.clone();
validate_stdin_source(stdin_source.as_ref())?;
let meta = JobMeta {
job: JobMetaJob { id: job_id.clone() },
schema_version: crate::schema::SCHEMA_VERSION.to_string(),
command: opts.command.clone(),
created_at: created_at.clone(),
root: root.display().to_string(),
env_keys,
env_vars: masked_env_vars,
env_vars_runtime: opts.env_vars.clone(),
mask: opts.mask.clone(),
cwd: Some(effective_cwd),
notification,
tags,
inherit_env: opts.inherit_env,
env_files: opts.env_files.clone(),
timeout_ms: opts.timeout_ms,
kill_after_ms: opts.kill_after_ms,
progress_every_ms: opts.progress_every_ms,
shell_wrapper: Some(opts.shell_wrapper.clone()),
stdin_file: None,
};
let job_dir = JobDir::create(&root, &job_id, &meta)?;
let stdin_file =
materialize_stdin_for_job(&job_dir, stdin_source.as_ref(), opts.stdin_max_bytes)?;
if stdin_file.is_some() {
let mut meta_with_stdin = meta.clone();
meta_with_stdin.stdin_file = stdin_file;
job_dir.write_meta_atomic(&meta_with_stdin)?;
}
info!(job_id = %job_id, "created job directory (created state)");
pre_create_log_files(&job_dir)?;
job_dir.init_state_created()?;
let stdout_log_path = job_dir.stdout_path().display().to_string();
let stderr_log_path = job_dir.stderr_path().display().to_string();
Response::new(
"create",
CreateData {
job_id,
state: "created".to_string(),
stdout_log_path,
stderr_log_path,
},
)
.print();
Ok(())
}