1mod local_executor;
2mod ssh_executor;
3mod utils;
4
5pub type BoxExecutor = Box<dyn Executor + 'static + Send>;
6use local_executor::LocalExecutor;
7use ssh_executor::SshExecutor;
8
9use std::error::Error as StdError;
10
11pub type BoxError = Box<dyn StdError + Send + Sync + 'static>;
12
13use thiserror::Error;
14
15pub enum ExecutorConfig<'a> {
16 Local,
17 Ssh { host: &'a str },
18}
19
20impl ExecutorConfig<'_> {
21 pub async fn into_executor(&self) -> Result<BoxExecutor, ExecutorError> {
22 create_executor(self).await
23 }
24}
25
26#[derive(Error, Debug)]
27pub enum ExecutorError {
28 #[error("failed to execute command")]
29 ExecuteFailed(#[source] BoxError),
30 #[error("failed to create command executor")]
31 CreationFailed(#[source] BoxError),
32 #[error("empty command")]
33 EmptyCommand,
34 #[error("command returned non-zero exit code: {0} {1}")]
35 NonZeroExitCode(u32, String),
36 #[error("command returned no response")]
37 NoResponse(String),
38 #[error("failed to write file")]
39 FileWriteFailed(#[source] BoxError),
40 #[error("failed to create directory")]
41 MkdirsFailed(#[source] BoxError),
42}
43
44use ExecutorError::*;
45
46use crate::utils::BoxFuture;
47
48pub trait Executor {
49 fn execute<'a>(&'a self, cmd: &'a str) -> BoxFuture<'a, Result<String, ExecutorError>>;
50
51 fn mkdirs<'a>(&'a self, path: &'a str) -> BoxFuture<'a, Result<(), ExecutorError>>;
52
53 fn write_file<'a>(
54 &'a self,
55 path: &'a str,
56 content: &'a str,
57 ) -> BoxFuture<'a, Result<(), ExecutorError>>;
58}
59
60pub async fn create_executor<'a>(
61 config: &ExecutorConfig<'a>,
62) -> Result<BoxExecutor, ExecutorError> {
63 Ok(match config {
64 ExecutorConfig::Local => Box::new(LocalExecutor),
65 ExecutorConfig::Ssh { host } => Box::new(
66 SshExecutor::new(host)
67 .await
68 .map_err(|e| CreationFailed(Box::new(e)))?,
69 ),
70 })
71}