ambient_ci/action_impl/
rsync.rs1use 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct Rsync {
17 artifacts: PathBuf,
18 rsync_target: Option<String>,
19}
20
21impl Rsync {
22 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#[derive(Debug, thiserror::Error)]
68pub enum RsyncError {
69 #[error("rsync failed to synchronize {0} to {1}:\n{2}")]
71 Rsync(PathBuf, String, String),
72
73 #[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}