use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::fs::OpenOptions;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Serialize};
use crate::constants::DEFAULT_ORCHESTRATION_CONFIG_PATH;
use crate::helpers::{has_flag, option_value};
mod cli;
mod command;
mod config;
mod execution;
mod planner;
mod runtime_state;
use self::cli::parse_cli_options;
use self::config::{default_orchestration_config, load_resolved_config, validate_config};
use self::execution::{
resolve_max_parallel, run_oneshot_parallel, run_oneshot_task, spawn_service_task,
};
use self::planner::{build_task_index, filtered_ordered_tasks, topological_order};
use self::runtime_state::{
load_runtime_state, load_runtime_state_or_default, now_unix_ms, process_alive, sanitize_name,
save_runtime_state, terminate_process,
};
const ORCHESTRATION_CONFIG_API_VERSION: &str = "robotrt.orchestrate.v1";
const ORCHESTRATION_RUNTIME_STATE_API_VERSION: &str = "robotrt.orchestrate.runtime.v1";
const DEFAULT_RUNTIME_STATE_FILE: &str = "artifacts/orchestrate/runtime-state.json";
const DEFAULT_RUN_LOG_DIR: &str = "artifacts/orchestrate/logs";
#[derive(Clone, Debug, Serialize, Deserialize)]
struct OrchestrationConfig {
api_version: String,
#[serde(default)]
project: String,
#[serde(default = "default_max_parallel")]
max_parallel: usize,
#[serde(default)]
profiles: BTreeMap<String, OrchestrationProfile>,
tasks: Vec<OrchestrationTask>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct OrchestrationTask {
name: String,
#[serde(default)]
command: Vec<String>,
#[serde(default)]
depends_on: Vec<String>,
#[serde(default)]
cwd: Option<PathBuf>,
#[serde(default)]
env: BTreeMap<String, String>,
#[serde(default)]
continue_on_error: bool,
#[serde(default = "default_enabled")]
enabled: bool,
#[serde(default)]
group: Option<String>,
#[serde(default)]
run_mode: TaskRunMode,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
struct OrchestrationProfile {
#[serde(default)]
description: Option<String>,
#[serde(default)]
max_parallel: Option<usize>,
#[serde(default)]
env: BTreeMap<String, String>,
#[serde(default)]
tasks: BTreeMap<String, TaskOverride>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
struct TaskOverride {
#[serde(default)]
command: Option<Vec<String>>,
#[serde(default)]
depends_on: Option<Vec<String>>,
#[serde(default)]
cwd: Option<PathBuf>,
#[serde(default)]
env: BTreeMap<String, String>,
#[serde(default)]
continue_on_error: Option<bool>,
#[serde(default)]
enabled: Option<bool>,
#[serde(default)]
group: Option<String>,
#[serde(default)]
run_mode: Option<TaskRunMode>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
struct OrchestrationOverlay {
#[serde(default)]
project: Option<String>,
#[serde(default)]
max_parallel: Option<usize>,
#[serde(default)]
env: BTreeMap<String, String>,
#[serde(default)]
tasks: BTreeMap<String, TaskOverride>,
#[serde(default)]
add_tasks: Vec<OrchestrationTask>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
enum TaskRunMode {
#[default]
Oneshot,
Service,
}
#[derive(Debug, Serialize)]
struct TaskRunResult {
name: String,
status: String,
exit_code: Option<i32>,
elapsed_ms: u64,
log_file: Option<PathBuf>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct RuntimeState {
api_version: String,
config_file: PathBuf,
profile: Option<String>,
updated_at_unix_ms: u64,
processes: Vec<ServiceProcessRecord>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct ServiceProcessRecord {
task: String,
group: Option<String>,
pid: u32,
command: Vec<String>,
cwd: Option<PathBuf>,
log_file: PathBuf,
started_at_unix_ms: u64,
}
#[derive(Debug)]
struct RunningTask {
child: Child,
started_at: Instant,
log_file: PathBuf,
}
#[derive(Default)]
struct CliOrchestrateOptions {
config_path: PathBuf,
profile: Option<String>,
overlay_files: Vec<PathBuf>,
json: bool,
target_task: Option<String>,
group: Option<String>,
dry_run: bool,
continue_on_error: bool,
max_parallel_override: Option<usize>,
state_file: PathBuf,
prune: bool,
}
fn default_max_parallel() -> usize {
1
}
fn default_enabled() -> bool {
true
}
pub fn orchestrate_cmd(args: &[String]) -> Result<(), String> {
command::orchestrate_cmd(args)
}