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