blackjack/
script.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
use crate::error::{Error, Result};
use envsubst;
use shell_words;
use std::collections::HashMap;
use std::path::PathBuf;
use std::process::{ExitStatus, Stdio};
use tokio::io::AsyncReadExt;
use tokio::process::Command;

pub async fn execute_script(
    command_line: &str,
    wd: PathBuf,
    namespace: &str,
) -> Result<(ExitStatus, String, String)> {
    let argv = shell_words::split(&envsubst::substitute(
        command_line,
        &HashMap::from([("BLACKJACK_NAMESPACE".to_string(), namespace.to_string())]),
    )?)?;
    (!argv.is_empty())
        .then_some(())
        .ok_or(Error::Other("empty command line".to_string()))?;
    let command = &argv[0];
    let args = &argv[1..];

    let mut child = Command::new(command)
        .args(args)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .current_dir(wd)
        .spawn()?;

    let stdout = child
        .stdout
        .take()
        .ok_or(Error::Other("unable to capture script stdout".to_string()))?;
    let stderr = child
        .stderr
        .take()
        .ok_or(Error::Other("unable to capture script stderr".to_string()))?;

    let stdout_future: tokio::task::JoinHandle<
        std::result::Result<std::vec::Vec<u8>, futures::io::Error>,
    > = tokio::spawn(async move {
        let mut stdout = stdout;
        let mut stdout_buf: Vec<u8> = Vec::new();
        stdout.read_to_end(&mut stdout_buf).await?;
        Ok(stdout_buf)
    });
    let stderr_future: tokio::task::JoinHandle<
        std::result::Result<std::vec::Vec<u8>, futures::io::Error>,
    > = tokio::spawn(async move {
        let mut stderr = stderr;
        let mut stderr_buf: Vec<u8> = Vec::new();
        stderr.read_to_end(&mut stderr_buf).await?;
        Ok(stderr_buf)
    });

    let status = child.wait().await?;
    let stdout_result = stdout_future.await??;
    let stderr_result = stderr_future.await??;

    Ok((
        status,
        String::from_utf8_lossy(&stdout_result).to_string(),
        String::from_utf8_lossy(&stderr_result).to_string(),
    ))
}