ambient_ci/action_impl/
dput.rs1use std::{
2 path::{Path, PathBuf},
3 process::Command,
4};
5
6use clingwrap::runner::CommandRunner;
7use serde::{Deserialize, Serialize};
8use walkdir::WalkDir;
9
10use crate::{
11 action::{ActionError, Context},
12 action_impl::ActionImpl,
13};
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub struct Dput {
21 artifacts: PathBuf,
22 dput_target: Option<String>,
23}
24
25impl Dput {
26 pub fn new<P: AsRef<Path>>(artifacts: P, dput_target: Option<String>) -> Self {
28 Self {
29 artifacts: artifacts.as_ref().to_path_buf(),
30 dput_target,
31 }
32 }
33}
34
35impl ActionImpl for Dput {
36 fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
37 if let Some(target) = &self.dput_target {
38 let changes = changes_file(context.artifacts_dir())?;
39 dput(target, &changes)?;
40 }
41 Ok(())
42 }
43}
44
45fn changes_file(dir: &Path) -> Result<PathBuf, DputError> {
46 let mut result = None;
47 for entry in WalkDir::new(dir).into_iter() {
48 let path = entry.map_err(DputError::WalkDir)?.path().to_path_buf();
49 if path.display().to_string().ends_with(".changes") {
50 if result.is_some() {
51 return Err(DputError::ManyChanges);
52 }
53 result = Some(path);
54 }
55 }
56
57 if let Some(path) = result {
58 Ok(path)
59 } else {
60 Err(DputError::NoChanges)
61 }
62}
63
64fn dput(dest: &str, changes: &Path) -> Result<(), DputError> {
65 let mut cmd = Command::new("dput");
66 cmd.arg(dest).arg(changes);
67
68 let mut runner = CommandRunner::new(cmd);
69 runner.capture_stdout();
70 runner.capture_stderr();
71
72 let output = runner
73 .execute()
74 .map_err(|err| DputError::Execute("dput", err))?;
75
76 if output.status.code() != Some(0) {
77 let stderr = String::from_utf8_lossy(&output.stderr);
78 return Err(DputError::Dput(
79 dest.into(),
80 changes.to_path_buf(),
81 stderr.into(),
82 ));
83 }
84
85 Ok(())
86}
87
88#[derive(Debug, thiserror::Error)]
90pub enum DputError {
91 #[error("dput failed to upload {1} to {0}:\n{2}")]
93 Dput(String, PathBuf, String),
94
95 #[error("failed to run program {0}")]
97 Execute(&'static str, #[source] clingwrap::runner::CommandError),
98
99 #[error("failed to list contents of upload directory")]
101 WalkDir(#[source] walkdir::Error),
102
103 #[error("no *.changes file built for deb project")]
105 NoChanges,
106
107 #[error("more than one *.changes file built for deb project")]
109 ManyChanges,
110}
111
112impl From<DputError> for ActionError {
113 fn from(value: DputError) -> Self {
114 Self::Dput(value)
115 }
116}