Skip to main content

astro_runner/
astro_runner.rs

1use crate::{
2  executors::{DockerExecutor, Executor, HostExecutor},
3  Plugin, PluginDriver, SharedPluginDriver,
4};
5use astro_run::{
6  stream, Context, Error, HookNoopResult, Result, RunResponse, RunResult, Runner, TriggerEvent,
7  WorkflowId,
8};
9use parking_lot::Mutex;
10use std::{collections::HashMap, env, fs, path::PathBuf, sync::Arc};
11
12struct RunnerState {
13  workflow_events: HashMap<WorkflowId, TriggerEvent>,
14}
15
16#[derive(Clone)]
17pub struct AstroRunner {
18  working_directory: PathBuf,
19  state: Arc<Mutex<RunnerState>>,
20  plugin_driver: SharedPluginDriver,
21}
22
23impl AstroRunner {
24  pub fn builder() -> AstroRunnerBuilder {
25    AstroRunnerBuilder::new()
26  }
27}
28
29#[astro_run::async_trait]
30impl Runner for AstroRunner {
31  async fn on_workflow_completed(&self, result: astro_run::WorkflowRunResult) -> HookNoopResult {
32    if let Err(err) = self.cleanup_workflow_working_directory(result) {
33      log::error!("AstroRunner: cleanup error: {}", err);
34    }
35
36    Ok(())
37  }
38
39  async fn run(&self, ctx: Context) -> RunResponse {
40    let (sender, receiver) = stream();
41
42    let ctx = self.plugin_driver.on_before_run(ctx).await;
43
44    let executor = self.create_executor(&ctx);
45
46    let event = ctx.event.clone();
47    if let Some(event) = &ctx.event {
48      self
49        .state
50        .lock()
51        .workflow_events
52        .insert(ctx.command.id.workflow_id(), event.clone());
53    }
54
55    let plugins = Arc::clone(&self.plugin_driver);
56
57    tokio::spawn(async move {
58      if let Err(err) = executor.execute(ctx.clone(), sender.clone(), event).await {
59        log::error!("AstroRunner: execute error: {}", err);
60      }
61
62      if !sender.is_ended() {
63        sender.end(RunResult::Failed { exit_code: 1 });
64      }
65
66      plugins.on_after_run(ctx).await;
67    });
68
69    Ok(receiver)
70  }
71}
72
73impl AstroRunner {
74  fn create_executor(&self, ctx: &Context) -> Box<dyn Executor> {
75    let os_name = std::env::consts::OS;
76    let architecture = std::env::consts::ARCH;
77    let container = ctx.command.container.clone();
78    if let Some(container) = container {
79      // Example: host/windows
80      let host_name = format!("host/{}", os_name);
81      // Example: host/windows-x86_64, host/linux-x86_64
82      let host_name_with_arch = format!("host/{}-{}", os_name, architecture);
83
84      if container.name == host_name_with_arch || container.name == host_name {
85        let executor = HostExecutor {
86          working_directory: self.working_directory.clone(),
87        };
88
89        return Box::new(executor);
90      }
91    }
92
93    let executor = DockerExecutor {
94      working_directory: self.working_directory.clone(),
95    };
96
97    Box::new(executor)
98  }
99
100  fn cleanup_workflow_working_directory(&self, result: astro_run::WorkflowRunResult) -> Result<()> {
101    let event = self.state.lock().workflow_events.get(&result.id).cloned();
102
103    let mut directory = self.working_directory.clone();
104
105    if let Some(event) = event {
106      directory = directory.join(&event.repo_owner).join(&event.repo_name);
107    }
108
109    directory = directory.join(result.id.inner());
110
111    if directory.exists() {
112      fs::remove_dir_all(directory)?;
113    }
114
115    Ok(()) as Result<()>
116  }
117}
118
119#[derive(Default)]
120pub struct AstroRunnerBuilder {
121  working_directory: Option<PathBuf>,
122  plugins: Vec<Box<dyn Plugin>>,
123}
124
125impl AstroRunnerBuilder {
126  pub fn new() -> Self {
127    Self::default()
128  }
129
130  pub fn plugin<P: Plugin + 'static>(mut self, plugin: P) -> Self {
131    self.plugins.push(Box::new(plugin));
132
133    self
134  }
135
136  pub fn working_directory(mut self, working_directory: PathBuf) -> Self {
137    self.working_directory = Some(working_directory);
138    self
139  }
140
141  pub fn build(self) -> Result<AstroRunner> {
142    let working_directory = self.working_directory.map(Ok).unwrap_or_else(|| {
143      #[allow(deprecated)]
144      env::home_dir()
145        .map(|home| home.join("astro-run"))
146        .ok_or_else(|| Error::init_error("AstroRunnerBuilder: working_directory is required"))
147    })?;
148
149    let runner = AstroRunner {
150      working_directory,
151      state: Arc::new(Mutex::new(RunnerState {
152        workflow_events: HashMap::new(),
153      })),
154      plugin_driver: Arc::new(PluginDriver::new(self.plugins)),
155    };
156
157    Ok(runner)
158  }
159}