Skip to main content

ambient_ci/action_impl/
rsync.rs

1use std::{
2    path::{Path, PathBuf},
3    process::Command,
4};
5
6use clingwrap::runner::CommandRunner;
7use serde::{Deserialize, Serialize};
8
9use crate::{
10    action::{ActionError, Context},
11    action_impl::ActionImpl,
12};
13
14/// Upload build artifacts using `rsync` to configured server.
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct Rsync {
17    artifacts: PathBuf,
18    rsync_target: Option<String>,
19}
20
21impl Rsync {
22    /// Create a new `Rsync` action.
23    pub fn new<P: AsRef<Path>>(artifacts: P, rsync_target: Option<String>) -> Self {
24        Self {
25            artifacts: artifacts.as_ref().to_path_buf(),
26            rsync_target,
27        }
28    }
29}
30
31impl ActionImpl for Rsync {
32    fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
33        if let Some(target) = &self.rsync_target {
34            rsync_server(&context.artifacts_dir().join(&self.artifacts), target)?;
35        }
36        Ok(())
37    }
38}
39
40fn rsync_server(src: &Path, target: &str) -> Result<(), RsyncError> {
41    let src = src.join(".");
42    let target = format!("{target}/.");
43    let mut cmd = Command::new("rsync");
44    cmd.arg("-a").arg("--del").arg(&src).arg(&target);
45
46    let mut runner = CommandRunner::new(cmd);
47    runner.capture_stdout();
48    runner.capture_stderr();
49
50    let output = runner
51        .execute()
52        .map_err(|err| RsyncError::Execute("rsync", err))?;
53
54    if output.status.code() != Some(0) {
55        let stderr = String::from_utf8_lossy(&output.stderr);
56        return Err(RsyncError::Rsync(
57            src.clone(),
58            target.clone(),
59            stderr.into(),
60        ));
61    }
62
63    Ok(())
64}
65
66/// Errors from Rsync action.
67#[derive(Debug, thiserror::Error)]
68pub enum RsyncError {
69    /// `rsync` failed.
70    #[error("rsync failed to synchronize {0} to {1}:\n{2}")]
71    Rsync(PathBuf, String, String),
72
73    /// Can't run program.
74    #[error("failed to run program {0}")]
75    Execute(&'static str, #[source] clingwrap::runner::CommandError),
76}
77
78impl From<RsyncError> for ActionError {
79    fn from(value: RsyncError) -> Self {
80        Self::Rsync(value)
81    }
82}