1
use anyhow::Context as _;
2
use serde::Deserialize;
3
use std::io::{
4
  BufRead as _,
5
  BufReader,
6
};
7
use std::process::Command as ProcessCommand;
8
use std::thread;
9

            
10
use super::TaskContext;
11
use crate::defaults::{
12
  default_shell,
13
  default_verbose,
14
};
15
use crate::handle_output;
16
use crate::schema::get_output_handler;
17

            
18
/// This struct represents a precondition that must be met before a task can be
19
/// executed.
20
#[derive(Debug, Default, Deserialize)]
21
pub struct Precondition {
22
  /// The command to run
23
  pub command: String,
24

            
25
  //// The message to display if the command fails
26
  #[serde(default)]
27
  pub message: Option<String>,
28

            
29
  /// The shell to use to run the command
30
  #[serde(default = "default_shell")]
31
  pub shell: String,
32

            
33
  /// The working directory to run the command in
34
  #[serde(default)]
35
  pub work_dir: Option<String>,
36

            
37
  /// Show verbose output
38
  #[serde(default)]
39
  pub verbose: Option<bool>,
40
}
41

            
42
impl Precondition {
43
  pub fn execute(&self, context: &TaskContext) -> anyhow::Result<()> {
44
    assert!(!self.command.is_empty());
45
    assert!(!self.shell.is_empty());
46

            
47
    let verbose = self.verbose(context);
48

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

            
52
    let shell = &self.shell;
53
    let mut cmd = ProcessCommand::new(shell);
54
    cmd
55
      .arg("-c")
56
      .arg(self.command.clone())
57
      .stdout(stdout)
58
      .stderr(stderr);
59

            
60
    if self.work_dir.is_some() {
61
      cmd.current_dir(self.work_dir.as_ref().with_context(|| "Failed to get work_dir")?);
62
    }
63

            
64
    // Inject environment variables
65
    for (key, value) in context.env_vars.iter() {
66
      cmd.env(key, value);
67
    }
68

            
69
    let mut cmd = cmd.spawn()?;
70

            
71
    if verbose {
72
      handle_output!(cmd.stdout, context);
73
      handle_output!(cmd.stderr, context);
74
    }
75

            
76
    let status = cmd.wait()?;
77
    if !status.success() {
78
      if let Some(message) = &self.message {
79
        anyhow::bail!("Precondition failed - {}", message);
80
      } else {
81
        anyhow::bail!("Precondition failed - {}", self.command);
82
      }
83
    }
84

            
85
    Ok(())
86
  }
87

            
88
  fn verbose(&self, context: &TaskContext) -> bool {
89
    self.verbose.or(context.verbose).unwrap_or(default_verbose())
90
  }
91
}
92

            
93
#[cfg(test)]
94
mod test {
95
  use super::*;
96

            
97
  #[test]
98
20
  fn test_precondition_1() -> anyhow::Result<()> {
99
20
    {
100
20
      let yaml = "
101
20
        command: 'echo \"Hello, World!\"'
102
20
        message: 'This is a message'
103
20
      ";
104
20
      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
105

            
106
20
      assert_eq!(precondition.command, "echo \"Hello, World!\"");
107
20
      assert_eq!(precondition.message, Some("This is a message".into()));
108
20
      assert_eq!(precondition.shell, "sh");
109
20
      assert_eq!(precondition.work_dir, None);
110
20
      assert_eq!(precondition.verbose, None);
111

            
112
20
      Ok(())
113
    }
114
20
  }
115

            
116
  #[test]
117
20
  fn test_precondition_2() -> anyhow::Result<()> {
118
20
    {
119
20
      let yaml = "
120
20
        command: 'echo \"Hello, World!\"'
121
20
      ";
122
20
      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
123

            
124
20
      assert_eq!(precondition.command, "echo \"Hello, World!\"");
125
20
      assert_eq!(precondition.message, None);
126
20
      assert_eq!(precondition.shell, "sh");
127
20
      assert_eq!(precondition.work_dir, None);
128
20
      assert_eq!(precondition.verbose, None);
129

            
130
20
      Ok(())
131
    }
132
20
  }
133

            
134
  #[test]
135
20
  fn test_precondition_3() -> anyhow::Result<()> {
136
20
    {
137
20
      let yaml = "
138
20
        command: 'echo \"Hello, World!\"'
139
20
        message: null
140
20
      ";
141
20
      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
142

            
143
20
      assert_eq!(precondition.command, "echo \"Hello, World!\"");
144
20
      assert_eq!(precondition.message, None);
145
20
      assert_eq!(precondition.shell, "sh");
146
20
      assert_eq!(precondition.work_dir, None);
147
20
      assert_eq!(precondition.verbose, None);
148

            
149
20
      Ok(())
150
    }
151
20
  }
152

            
153
  #[test]
154
20
  fn test_precondition_4() -> anyhow::Result<()> {
155
20
    {
156
20
      let yaml = "
157
20
        command: 'echo \"Hello, World!\"'
158
20
        work_dir: /tmp
159
20
      ";
160
20
      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
161

            
162
20
      assert_eq!(precondition.command, "echo \"Hello, World!\"");
163
20
      assert_eq!(precondition.message, None);
164
20
      assert_eq!(precondition.shell, "sh");
165
20
      assert_eq!(precondition.work_dir, Some("/tmp".into()));
166
20
      assert_eq!(precondition.verbose, None);
167

            
168
20
      Ok(())
169
    }
170
20
  }
171

            
172
  #[test]
173
20
  fn test_precondition_5() -> anyhow::Result<()> {
174
20
    {
175
20
      let yaml = "
176
20
        command: 'echo \"Hello, World!\"'
177
20
        verbose: true
178
20
      ";
179
20
      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
180

            
181
20
      assert_eq!(precondition.command, "echo \"Hello, World!\"");
182
20
      assert_eq!(precondition.message, None);
183
20
      assert_eq!(precondition.shell, "sh");
184
20
      assert_eq!(precondition.work_dir, None);
185
20
      assert_eq!(precondition.verbose, Some(true));
186

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