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

            
7
use std::thread;
8

            
9
use crate::handle_output;
10
use crate::schema::get_output_handler;
11
use anyhow::Context;
12

            
13
use super::TaskContext;
14
use serde::Deserialize;
15

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

            
21
#[derive(Debug, Deserialize)]
22
#[serde(untagged)]
23
pub enum CommandRunner {
24
  ContainerBuild(container_build::ContainerBuild),
25
  ContainerRun(container_run::ContainerRun),
26
  LocalRun(local_run::LocalRun),
27
  TaskRun(task_run::TaskRun),
28
  CommandRun(String),
29
}
30

            
31
impl CommandRunner {
32
113
  pub fn execute(&self, context: &mut TaskContext) -> anyhow::Result<()> {
33
113
    match self {
34
      CommandRunner::ContainerBuild(container_build) => container_build.execute(context),
35
      CommandRunner::ContainerRun(container_run) => container_run.execute(context),
36
113
      CommandRunner::LocalRun(local_run) => local_run.execute(context),
37
      CommandRunner::TaskRun(task_run) => task_run.execute(context),
38
      CommandRunner::CommandRun(command) => self.execute_command(context, command),
39
    }
40
113
  }
41

            
42
  fn execute_command(&self, context: &TaskContext, command: &str) -> anyhow::Result<()> {
43
    assert!(!command.is_empty());
44

            
45
    let ignore_errors = context.ignore_errors();
46
    let verbose = context.verbose();
47
    let shell: &str = &context.shell();
48

            
49
    let stdout = get_output_handler(verbose);
50
    let stderr = get_output_handler(verbose);
51

            
52
    let mut cmd = ProcessCommand::new(shell);
53
    cmd.arg("-c").arg(command).stdout(stdout).stderr(stderr);
54

            
55
    // Inject environment variables
56
    for (key, value) in context.env_vars.iter() {
57
      cmd.env(key, value);
58
    }
59

            
60
    let mut cmd = cmd.spawn()?;
61
    if verbose {
62
      handle_output!(cmd.stdout, context);
63
      handle_output!(cmd.stderr, context);
64
    }
65

            
66
    let status = cmd.wait()?;
67
    if !status.success() && !ignore_errors {
68
      anyhow::bail!("Command failed - {}", command);
69
    }
70

            
71
    Ok(())
72
  }
73
}
74

            
75
#[cfg(test)]
76
mod test {
77
  use super::*;
78

            
79
  #[test]
80
20
  fn test_command_1() -> anyhow::Result<()> {
81
20
    {
82
20
      let yaml = "
83
20
        command: 'echo \"Hello, World!\"'
84
20
        ignore_errors: false
85
20
        verbose: false
86
20
      ";
87
20
      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
88

            
89
20
      if let CommandRunner::LocalRun(local_run) = command {
90
20
        assert_eq!(local_run.command, "echo \"Hello, World!\"");
91
20
        assert_eq!(local_run.shell, "sh");
92
20
        assert_eq!(local_run.work_dir, None);
93
20
        assert_eq!(local_run.ignore_errors, Some(false));
94
20
        assert_eq!(local_run.verbose, Some(false));
95
      } else {
96
        panic!("Expected CommandRunner::LocalRun");
97
      }
98

            
99
20
      Ok(())
100
    }
101
20
  }
102

            
103
  #[test]
104
20
  fn test_command_2() -> anyhow::Result<()> {
105
20
    {
106
20
      let yaml = "
107
20
        command: 'echo \"Hello, World!\"'
108
20
      ";
109
20
      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
110

            
111
20
      if let CommandRunner::LocalRun(local_run) = command {
112
20
        assert_eq!(local_run.command, "echo \"Hello, World!\"");
113
20
        assert_eq!(local_run.shell, "sh");
114
20
        assert_eq!(local_run.work_dir, None);
115
20
        assert_eq!(local_run.ignore_errors, None);
116
20
        assert_eq!(local_run.verbose, None);
117
      } else {
118
        panic!("Expected CommandRunner::LocalRun");
119
      }
120

            
121
20
      Ok(())
122
    }
123
20
  }
124

            
125
  #[test]
126
20
  fn test_command_3() -> anyhow::Result<()> {
127
20
    {
128
20
      let yaml = "
129
20
        command: 'echo \"Hello, World!\"'
130
20
        ignore_errors: true
131
20
      ";
132
20
      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
133
20
      if let CommandRunner::LocalRun(local_run) = command {
134
20
        assert_eq!(local_run.command, "echo \"Hello, World!\"");
135
20
        assert_eq!(local_run.shell, "sh");
136
20
        assert_eq!(local_run.work_dir, None);
137
20
        assert_eq!(local_run.ignore_errors, Some(true));
138
20
        assert_eq!(local_run.verbose, None);
139
      } else {
140
        panic!("Expected CommandRunner::LocalRun");
141
      }
142

            
143
20
      Ok(())
144
    }
145
20
  }
146

            
147
  #[test]
148
20
  fn test_command_4() -> anyhow::Result<()> {
149
20
    {
150
20
      let yaml = "
151
20
        command: 'echo \"Hello, World!\"'
152
20
        verbose: false
153
20
      ";
154
20
      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
155
20
      if let CommandRunner::LocalRun(local_run) = command {
156
20
        assert_eq!(local_run.command, "echo \"Hello, World!\"");
157
20
        assert_eq!(local_run.shell, "sh");
158
20
        assert_eq!(local_run.work_dir, None);
159
20
        assert_eq!(local_run.ignore_errors, None);
160
20
        assert_eq!(local_run.verbose, Some(false));
161
      } else {
162
        panic!("Expected CommandRunner::LocalRun");
163
      }
164

            
165
20
      Ok(())
166
    }
167
20
  }
168

            
169
  #[test]
170
20
  fn test_command_5() -> anyhow::Result<()> {
171
20
    {
172
20
      let yaml = "
173
20
        command: 'echo \"Hello, World!\"'
174
20
        work_dir: /tmp
175
20
      ";
176
20
      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
177
20
      if let CommandRunner::LocalRun(local_run) = command {
178
20
        assert_eq!(local_run.command, "echo \"Hello, World!\"");
179
20
        assert_eq!(local_run.shell, "sh");
180
20
        assert_eq!(local_run.work_dir, Some("/tmp".into()));
181
20
        assert_eq!(local_run.ignore_errors, None);
182
20
        assert_eq!(local_run.verbose, None);
183
      } else {
184
        panic!("Expected CommandRunner::LocalRun");
185
      }
186

            
187
20
      Ok(())
188
    }
189
20
  }
190

            
191
  #[test]
192
20
  fn test_command_6() -> anyhow::Result<()> {
193
20
    {
194
20
      let yaml = "
195
20
        echo 'Hello, World!'
196
20
      ";
197
20
      let command = serde_yaml::from_str::<CommandRunner>(yaml)?;
198
20
      if let CommandRunner::CommandRun(command) = command {
199
20
        assert_eq!(command, "echo 'Hello, World!'");
200
      } else {
201
        panic!("Expected CommandRunner::CommandRun");
202
      }
203

            
204
20
      Ok(())
205
    }
206
20
  }
207
}