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