batuta/playbook/
executor_command.rs1use crate::playbook::cache;
4use crate::playbook::dag;
5use crate::playbook::parser;
6use crate::playbook::types::*;
7use anyhow::{Context, Result};
8use std::path::Path;
9
10#[derive(Debug, thiserror::Error)]
12#[error("command failed (exit code: {exit_code:?}): {stderr}")]
13pub(super) struct CommandError {
14 pub(super) exit_code: Option<i32>,
15 pub(super) stderr: String,
16}
17
18pub(super) async fn execute_command(cmd: &str) -> Result<()> {
20 let output = tokio::process::Command::new("sh")
21 .arg("-c")
22 .arg(cmd)
23 .output()
24 .await
25 .context("failed to spawn command")?;
26
27 if !output.status.success() {
28 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
29 return Err(CommandError {
30 exit_code: output.status.code(),
31 stderr: if stderr.is_empty() {
32 format!("exit code {}", output.status.code().unwrap_or(-1))
33 } else {
34 stderr
35 },
36 }
37 .into());
38 }
39
40 Ok(())
41}
42
43pub fn validate_only(playbook_path: &Path) -> Result<(Playbook, Vec<ValidationWarning>)> {
45 let playbook = parser::parse_playbook_file(playbook_path)?;
46 let warnings = parser::validate_playbook(&playbook)?;
47 let _ = dag::build_dag(&playbook)?; Ok((playbook, warnings))
49}
50
51pub fn show_status(playbook_path: &Path) -> Result<()> {
53 let playbook = parser::parse_playbook_file(playbook_path)?;
54 let lock = cache::load_lock_file(playbook_path)?;
55
56 println!("Playbook: {} ({})", playbook.name, playbook_path.display());
57 println!("Version: {}", playbook.version);
58 println!("Stages: {}", playbook.stages.len());
59
60 if let Some(ref lock) = lock {
61 println!("\nLock file: {} ({})", lock.generator, lock.generated_at);
62 println!("{}", "-".repeat(60));
63 for (name, _stage) in &playbook.stages {
64 if let Some(stage_lock) = lock.stages.get(name) {
65 let status_str = match stage_lock.status {
66 StageStatus::Completed => "COMPLETED",
67 StageStatus::Failed => "FAILED",
68 StageStatus::Cached => "CACHED",
69 StageStatus::Running => "RUNNING",
70 StageStatus::Pending => "PENDING",
71 StageStatus::Hashing => "HASHING",
72 StageStatus::Validating => "VALIDATING",
73 };
74 let duration = stage_lock
75 .duration_seconds
76 .map(|d| format!("{:.1}s", d))
77 .unwrap_or_else(|| "-".to_string());
78 println!(" {:20} {:12} {}", name, status_str, duration);
79 } else {
80 println!(" {:20} {:12}", name, "NOT RUN");
81 }
82 }
83 } else {
84 println!("\nNo lock file found (pipeline has not been run)");
85 }
86
87 Ok(())
88}