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