Skip to main content

mk_lib/schema/command/
mod.rs

1use std::io::{
2  BufRead as _,
3  BufReader,
4};
5use std::thread;
6
7use crate::handle_output;
8use crate::schema::get_output_handler;
9use anyhow::Context;
10
11use super::TaskContext;
12use serde::Deserialize;
13
14mod container_build;
15mod container_run;
16mod container_runtime;
17mod local_run;
18mod task_run;
19
20pub use container_runtime::ContainerRuntime;
21pub use local_run::LocalRun;
22
23#[derive(Debug, Deserialize, Clone)]
24#[serde(untagged)]
25pub enum CommandRunner {
26  ContainerBuild(container_build::ContainerBuild),
27  ContainerRun(container_run::ContainerRun),
28  LocalRun(local_run::LocalRun),
29  TaskRun(task_run::TaskRun),
30  CommandRun(String),
31}
32
33impl CommandRunner {
34  pub fn execute(&self, context: &TaskContext) -> anyhow::Result<()> {
35    context.emit_event(&serde_json::json!({
36      "event": "command_started",
37      "task": context.current_task_name.clone().unwrap_or_else(|| "<task>".to_string()),
38      "kind": self.kind(),
39    }))?;
40
41    let result = match self {
42      CommandRunner::ContainerBuild(container_build) => container_build.execute(context),
43      CommandRunner::ContainerRun(container_run) => container_run.execute(context),
44      CommandRunner::LocalRun(local_run) => local_run.execute(context),
45      CommandRunner::TaskRun(task_run) => task_run.execute(context),
46      CommandRunner::CommandRun(command) => self.execute_command(context, command),
47    };
48
49    context.emit_event(&serde_json::json!({
50      "event": "command_finished",
51      "task": context.current_task_name.clone().unwrap_or_else(|| "<task>".to_string()),
52      "kind": self.kind(),
53      "success": result.is_ok(),
54    }))?;
55
56    result
57  }
58
59  fn execute_command(&self, context: &TaskContext, command: &str) -> anyhow::Result<()> {
60    assert!(!command.is_empty());
61
62    let ignore_errors = context.ignore_errors();
63    let verbose = context.verbose();
64    let shell = context.shell();
65
66    let stdout = get_output_handler(verbose);
67    let stderr = get_output_handler(verbose);
68
69    let mut cmd = shell.proc();
70    cmd.arg(command).stdout(stdout).stderr(stderr);
71
72    // Inject environment variables
73    for (key, value) in context.env_vars.iter() {
74      cmd.env(key, value);
75    }
76
77    let mut cmd = cmd.spawn()?;
78    if verbose {
79      handle_output!(cmd.stdout, context);
80      handle_output!(cmd.stderr, context);
81    }
82
83    let status = cmd.wait()?;
84    if !status.success() && !ignore_errors {
85      anyhow::bail!("Command failed - {}", command);
86    }
87
88    Ok(())
89  }
90
91  fn kind(&self) -> &'static str {
92    match self {
93      CommandRunner::ContainerBuild(_) => "container_build",
94      CommandRunner::ContainerRun(_) => "container_run",
95      CommandRunner::LocalRun(_) => "local_run",
96      CommandRunner::TaskRun(_) => "task_run",
97      CommandRunner::CommandRun(_) => "command_run",
98    }
99  }
100}
101
102#[cfg(test)]
103mod test {
104  use super::*;
105
106  #[test]
107  fn test_command_1() -> anyhow::Result<()> {
108    {
109      let yaml = "
110        command: 'echo \"Hello, World!\"'
111        ignore_errors: false
112        verbose: false
113      ";
114      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
115
116      if let CommandRunner::LocalRun(local_run) = command {
117        assert_eq!(local_run.command, "echo \"Hello, World!\"");
118        assert_eq!(local_run.work_dir, None);
119        assert_eq!(local_run.ignore_errors, Some(false));
120        assert_eq!(local_run.verbose, Some(false));
121      } else {
122        panic!("Expected CommandRunner::LocalRun");
123      }
124
125      Ok(())
126    }
127  }
128
129  #[test]
130  fn test_command_2() -> anyhow::Result<()> {
131    {
132      let yaml = "
133        command: 'echo \"Hello, World!\"'
134      ";
135      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
136
137      if let CommandRunner::LocalRun(local_run) = command {
138        assert_eq!(local_run.command, "echo \"Hello, World!\"");
139        assert_eq!(local_run.work_dir, None);
140        assert_eq!(local_run.ignore_errors, None);
141        assert_eq!(local_run.verbose, None);
142      } else {
143        panic!("Expected CommandRunner::LocalRun");
144      }
145
146      Ok(())
147    }
148  }
149
150  #[test]
151  fn test_command_3() -> anyhow::Result<()> {
152    {
153      let yaml = "
154        command: 'echo \"Hello, World!\"'
155        ignore_errors: true
156      ";
157      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
158      if let CommandRunner::LocalRun(local_run) = command {
159        assert_eq!(local_run.command, "echo \"Hello, World!\"");
160        assert_eq!(local_run.work_dir, None);
161        assert_eq!(local_run.ignore_errors, Some(true));
162        assert_eq!(local_run.verbose, None);
163      } else {
164        panic!("Expected CommandRunner::LocalRun");
165      }
166
167      Ok(())
168    }
169  }
170
171  #[test]
172  fn test_command_4() -> anyhow::Result<()> {
173    {
174      let yaml = "
175        command: 'echo \"Hello, World!\"'
176        verbose: false
177      ";
178      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
179      if let CommandRunner::LocalRun(local_run) = command {
180        assert_eq!(local_run.command, "echo \"Hello, World!\"");
181        assert_eq!(local_run.work_dir, None);
182        assert_eq!(local_run.ignore_errors, None);
183        assert_eq!(local_run.verbose, Some(false));
184      } else {
185        panic!("Expected CommandRunner::LocalRun");
186      }
187
188      Ok(())
189    }
190  }
191
192  #[test]
193  fn test_command_5() -> anyhow::Result<()> {
194    {
195      let yaml = "
196        command: 'echo \"Hello, World!\"'
197        work_dir: /tmp
198      ";
199      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
200      if let CommandRunner::LocalRun(local_run) = command {
201        assert_eq!(local_run.command, "echo \"Hello, World!\"");
202        assert_eq!(local_run.work_dir, Some("/tmp".into()));
203        assert_eq!(local_run.ignore_errors, None);
204        assert_eq!(local_run.verbose, None);
205      } else {
206        panic!("Expected CommandRunner::LocalRun");
207      }
208
209      Ok(())
210    }
211  }
212
213  #[test]
214  fn test_command_6() -> anyhow::Result<()> {
215    {
216      let yaml = "
217        echo 'Hello, World!'
218      ";
219      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
220      if let CommandRunner::CommandRun(command) = command {
221        assert_eq!(command, "echo 'Hello, World!'");
222      } else {
223        panic!("Expected CommandRunner::CommandRun");
224      }
225
226      Ok(())
227    }
228  }
229}