hecate_executor/
lib.rs

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}