mk_lib/schema/command/
mod.rs1use 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 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}