Skip to main content

crankshaft_engine/task/
execution.rs

1//! A unit of executable work.
2
3use std::collections::BTreeMap;
4use std::fmt::Display;
5use std::process::ExitStatus;
6
7use bon::Builder;
8use indexmap::IndexMap;
9use nonempty::NonEmpty;
10
11/// An error used in [`Builder::images()`] when no images are specified.
12#[derive(Debug)]
13pub struct NoImageError;
14
15impl Display for NoImageError {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        write!(f, "no image specified")
18    }
19}
20
21impl std::error::Error for NoImageError {}
22
23/// An execution.
24#[derive(Builder, Clone, Debug)]
25#[builder(builder_type = Builder)]
26pub struct Execution {
27    /// The container images.
28    ///
29    /// For backends that support it, multiple images can be specified to act as
30    /// fallbacks in the event that the previous fails to pull.
31    ///
32    /// NOTE: Images will be tried in the order provided.
33    #[builder(with = |iter: impl IntoIterator<Item = impl Into<String>>| -> Result<_, NoImageError> {
34        NonEmpty::collect(iter.into_iter().map(Into::into)).ok_or(NoImageError)
35    })]
36    pub(crate) images: NonEmpty<String>,
37
38    /// The program to execute.
39    #[builder(into)]
40    pub(crate) program: String,
41
42    /// The arguments to the program.
43    #[builder(into, default)]
44    pub(crate) args: Vec<String>,
45
46    /// The working directory, if configured.
47    #[builder(into)]
48    pub(crate) work_dir: Option<String>,
49
50    /// The path inside the container to a file whose contents will be piped to
51    /// the standard input, if configured.
52    #[builder(into)]
53    pub(crate) stdin: Option<String>,
54
55    /// The path inside the container to a file where the contents of the
56    /// standard output stream will be written, if configured.
57    #[builder(into)]
58    pub(crate) stdout: Option<String>,
59
60    /// The path inside the container to a file where the contents of the
61    /// standard error stream will be written, if configured.
62    #[builder(into)]
63    pub(crate) stderr: Option<String>,
64
65    /// A map of environment variables, if configured.
66    #[builder(into, default)]
67    pub(crate) env: IndexMap<String, String>,
68}
69
70impl Execution {
71    /// The images for the execution to run within.
72    pub fn images(&self) -> &NonEmpty<String> {
73        &self.images
74    }
75
76    /// The program to execute.
77    pub fn program(&self) -> &str {
78        &self.program
79    }
80
81    /// The arguments to the execution.
82    pub fn args(&self) -> &[String] {
83        &self.args
84    }
85
86    /// The working directory.
87    pub fn work_dir(&self) -> Option<&str> {
88        self.work_dir.as_deref()
89    }
90
91    /// The file to pipe the standard input stream from.
92    pub fn stdin(&self) -> Option<&str> {
93        self.stdin.as_deref()
94    }
95
96    /// The file to pipe the standard output stream to.
97    pub fn stdout(&self) -> Option<&str> {
98        self.stdout.as_deref()
99    }
100
101    /// The file to pipe the standard error stream to.
102    pub fn stderr(&self) -> Option<&str> {
103        self.stderr.as_deref()
104    }
105
106    /// The environment variables for the execution.
107    pub fn env(&self) -> &IndexMap<String, String> {
108        &self.env
109    }
110}
111
112impl From<Execution> for tes::v1::types::task::Executor {
113    fn from(execution: Execution) -> Self {
114        let env = execution
115            .env
116            .into_iter()
117            .collect::<BTreeMap<String, String>>();
118
119        let env = if env.is_empty() { None } else { Some(env) };
120
121        let mut command = Vec::with_capacity(execution.args.len() + 1);
122        command.push(execution.program);
123        command.extend(execution.args);
124
125        tes::v1::types::task::Executor {
126            image: execution.images.first().into(),
127            command,
128            workdir: execution.work_dir,
129            stdin: execution.stdin,
130            stdout: execution.stdout,
131            stderr: execution.stderr,
132            env,
133            ignore_error: Some(true),
134        }
135    }
136}
137
138/// The result of an [`Execution`].
139#[derive(Clone, Debug)]
140pub struct ExecutionResult {
141    /// The name of the container image that was used in this execution.
142    ///
143    /// NOTE: While [`Execution`]s require an image, a backend is not
144    /// necessarily required to make use of it.
145    pub image: Option<String>,
146    /// The exit status of the execution.
147    pub status: ExitStatus,
148}