1
use std::io::{
2
  BufRead as _,
3
  BufReader,
4
};
5
use std::thread;
6

            
7
use crate::handle_output;
8
use crate::schema::get_output_handler;
9
use anyhow::Context;
10
use schemars::JsonSchema;
11
use serde::Deserialize;
12

            
13
use super::TaskContext;
14

            
15
mod container_build;
16
mod container_run;
17
mod container_runtime;
18
mod local_run;
19
mod task_run;
20

            
21
pub use container_runtime::ContainerRuntime;
22
pub use local_run::LocalRun;
23

            
24
#[derive(Debug, Deserialize, Clone, JsonSchema)]
25
#[serde(untagged)]
26
/// A single command entry in a task. Can be a shell string or a structured runner object.
27
pub 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

            
35
impl CommandRunner {
36
66
  pub fn execute(&self, context: &TaskContext) -> anyhow::Result<()> {
37
66
    self.execute_inner(context, false)
38
66
  }
39

            
40
4
  pub fn execute_cancellable(&self, context: &TaskContext) -> anyhow::Result<()> {
41
4
    self.execute_inner(context, true)
42
4
  }
43

            
44
70
  fn execute_inner(&self, context: &TaskContext, allow_cancellation: bool) -> anyhow::Result<()> {
45
70
    context.emit_event(&serde_json::json!({
46
70
      "event": "command_started",
47
70
      "task": context.current_task_name.clone().unwrap_or_else(|| "<task>".to_string()),
48
70
      "kind": self.kind(),
49
    }))?;
50

            
51
70
    let result = match self {
52
      CommandRunner::ContainerBuild(container_build) => container_build.execute(context),
53
      CommandRunner::ContainerRun(container_run) => container_run.execute(context),
54
70
      CommandRunner::LocalRun(local_run) if allow_cancellation => local_run.execute_cancellable(context),
55
66
      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
70
    context.emit_event(&serde_json::json!({
61
70
      "event": "command_finished",
62
70
      "task": context.current_task_name.clone().unwrap_or_else(|| "<task>".to_string()),
63
70
      "kind": self.kind(),
64
70
      "success": result.is_ok(),
65
    }))?;
66

            
67
70
    result
68
70
  }
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
    // Inject environment variables
84
    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
140
  fn kind(&self) -> &'static str {
103
140
    match self {
104
      CommandRunner::ContainerBuild(_) => "container_build",
105
      CommandRunner::ContainerRun(_) => "container_run",
106
140
      CommandRunner::LocalRun(_) => "local_run",
107
      CommandRunner::TaskRun(_) => "task_run",
108
      CommandRunner::CommandRun(_) => "command_run",
109
    }
110
140
  }
111
}
112

            
113
#[cfg(test)]
114
mod test {
115
  use super::*;
116

            
117
  #[test]
118
2
  fn test_command_1() -> anyhow::Result<()> {
119
    {
120
2
      let yaml = "
121
2
        command: 'echo \"Hello, World!\"'
122
2
        ignore_errors: false
123
2
        verbose: false
124
2
      ";
125
2
      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
126

            
127
2
      if let CommandRunner::LocalRun(local_run) = command {
128
2
        assert_eq!(local_run.command, "echo \"Hello, World!\"");
129
2
        assert_eq!(local_run.work_dir, None);
130
2
        assert_eq!(local_run.ignore_errors, Some(false));
131
2
        assert_eq!(local_run.verbose, Some(false));
132
      } else {
133
        panic!("Expected CommandRunner::LocalRun");
134
      }
135

            
136
2
      Ok(())
137
    }
138
2
  }
139

            
140
  #[test]
141
2
  fn test_command_2() -> anyhow::Result<()> {
142
    {
143
2
      let yaml = "
144
2
        command: 'echo \"Hello, World!\"'
145
2
      ";
146
2
      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
147

            
148
2
      if let CommandRunner::LocalRun(local_run) = command {
149
2
        assert_eq!(local_run.command, "echo \"Hello, World!\"");
150
2
        assert_eq!(local_run.work_dir, None);
151
2
        assert_eq!(local_run.ignore_errors, None);
152
2
        assert_eq!(local_run.verbose, None);
153
      } else {
154
        panic!("Expected CommandRunner::LocalRun");
155
      }
156

            
157
2
      Ok(())
158
    }
159
2
  }
160

            
161
  #[test]
162
2
  fn test_command_3() -> anyhow::Result<()> {
163
    {
164
2
      let yaml = "
165
2
        command: 'echo \"Hello, World!\"'
166
2
        ignore_errors: true
167
2
      ";
168
2
      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
169
2
      if let CommandRunner::LocalRun(local_run) = command {
170
2
        assert_eq!(local_run.command, "echo \"Hello, World!\"");
171
2
        assert_eq!(local_run.work_dir, None);
172
2
        assert_eq!(local_run.ignore_errors, Some(true));
173
2
        assert_eq!(local_run.verbose, None);
174
      } else {
175
        panic!("Expected CommandRunner::LocalRun");
176
      }
177

            
178
2
      Ok(())
179
    }
180
2
  }
181

            
182
  #[test]
183
2
  fn test_command_4() -> anyhow::Result<()> {
184
    {
185
2
      let yaml = "
186
2
        command: 'echo \"Hello, World!\"'
187
2
        verbose: false
188
2
      ";
189
2
      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
190
2
      if let CommandRunner::LocalRun(local_run) = command {
191
2
        assert_eq!(local_run.command, "echo \"Hello, World!\"");
192
2
        assert_eq!(local_run.work_dir, None);
193
2
        assert_eq!(local_run.ignore_errors, None);
194
2
        assert_eq!(local_run.verbose, Some(false));
195
      } else {
196
        panic!("Expected CommandRunner::LocalRun");
197
      }
198

            
199
2
      Ok(())
200
    }
201
2
  }
202

            
203
  #[test]
204
2
  fn test_command_5() -> anyhow::Result<()> {
205
    {
206
2
      let yaml = "
207
2
        command: 'echo \"Hello, World!\"'
208
2
        work_dir: /tmp
209
2
      ";
210
2
      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
211
2
      if let CommandRunner::LocalRun(local_run) = command {
212
2
        assert_eq!(local_run.command, "echo \"Hello, World!\"");
213
2
        assert_eq!(local_run.work_dir, Some("/tmp".into()));
214
2
        assert_eq!(local_run.ignore_errors, None);
215
2
        assert_eq!(local_run.verbose, None);
216
      } else {
217
        panic!("Expected CommandRunner::LocalRun");
218
      }
219

            
220
2
      Ok(())
221
    }
222
2
  }
223

            
224
  #[test]
225
2
  fn test_command_6() -> anyhow::Result<()> {
226
    {
227
2
      let yaml = "
228
2
        echo 'Hello, World!'
229
2
      ";
230
2
      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
231
2
      if let CommandRunner::CommandRun(command) = command {
232
2
        assert_eq!(command, "echo 'Hello, World!'");
233
      } else {
234
        panic!("Expected CommandRunner::CommandRun");
235
      }
236

            
237
2
      Ok(())
238
    }
239
2
  }
240
}