docker_wrapper/command/
cp.rs1use super::{CommandExecutor, CommandOutput, DockerCommand};
7use crate::error::Result;
8use async_trait::async_trait;
9use std::path::Path;
10
11#[derive(Debug, Clone)]
38pub struct CpCommand {
39 source: String,
41 destination: String,
43 archive: bool,
45 follow_link: bool,
47 quiet: bool,
49 pub executor: CommandExecutor,
51}
52
53impl CpCommand {
54 #[must_use]
66 pub fn from_container(container: impl Into<String>, path: impl Into<String>) -> Self {
67 let source = format!("{}:{}", container.into(), path.into());
68 Self {
69 source,
70 destination: String::new(),
71 archive: false,
72 follow_link: false,
73 quiet: false,
74 executor: CommandExecutor::new(),
75 }
76 }
77
78 #[must_use]
90 pub fn from_host(path: &Path) -> Self {
91 Self {
92 source: path.to_string_lossy().into_owned(),
93 destination: String::new(),
94 archive: false,
95 follow_link: false,
96 quiet: false,
97 executor: CommandExecutor::new(),
98 }
99 }
100
101 #[must_use]
103 pub fn to_host(mut self, path: &Path) -> Self {
104 self.destination = path.to_string_lossy().into_owned();
105 self
106 }
107
108 #[must_use]
110 pub fn to_container(mut self, container: impl Into<String>, path: impl Into<String>) -> Self {
111 self.destination = format!("{}:{}", container.into(), path.into());
112 self
113 }
114
115 #[must_use]
128 pub fn archive(mut self) -> Self {
129 self.archive = true;
130 self
131 }
132
133 #[must_use]
135 pub fn follow_link(mut self) -> Self {
136 self.follow_link = true;
137 self
138 }
139
140 #[must_use]
142 pub fn quiet(mut self) -> Self {
143 self.quiet = true;
144 self
145 }
146
147 pub async fn run(&self) -> Result<CpResult> {
156 let output = self.execute().await?;
157 Ok(CpResult {
158 output,
159 source: self.source.clone(),
160 destination: self.destination.clone(),
161 })
162 }
163}
164
165#[async_trait]
166impl DockerCommand for CpCommand {
167 type Output = CommandOutput;
168
169 fn build_command_args(&self) -> Vec<String> {
170 let mut args = vec!["cp".to_string()];
171
172 if self.archive {
173 args.push("--archive".to_string());
174 }
175
176 if self.follow_link {
177 args.push("--follow-link".to_string());
178 }
179
180 if self.quiet {
181 args.push("--quiet".to_string());
182 }
183
184 args.push(self.source.clone());
186 args.push(self.destination.clone());
187
188 args.extend(self.executor.raw_args.clone());
189 args
190 }
191
192 fn get_executor(&self) -> &CommandExecutor {
193 &self.executor
194 }
195
196 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
197 &mut self.executor
198 }
199
200 async fn execute(&self) -> Result<Self::Output> {
201 if self.destination.is_empty() {
202 return Err(crate::error::Error::invalid_config(
203 "Destination not specified",
204 ));
205 }
206
207 let args = self.build_command_args();
208 let command_name = args[0].clone();
209 let command_args = args[1..].to_vec();
210 self.executor
211 .execute_command(&command_name, command_args)
212 .await
213 }
214}
215
216#[derive(Debug, Clone)]
218pub struct CpResult {
219 pub output: CommandOutput,
221 pub source: String,
223 pub destination: String,
225}
226
227impl CpResult {
228 #[must_use]
230 pub fn success(&self) -> bool {
231 self.output.success
232 }
233
234 #[must_use]
236 pub fn source(&self) -> &str {
237 &self.source
238 }
239
240 #[must_use]
242 pub fn destination(&self) -> &str {
243 &self.destination
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_cp_from_container_to_host() {
253 let cmd = CpCommand::from_container("test-container", "/app/file.txt")
254 .to_host(Path::new("./file.txt"));
255 let args = cmd.build_command_args();
256 assert_eq!(
257 args,
258 vec!["cp", "test-container:/app/file.txt", "./file.txt"]
259 );
260 }
261
262 #[test]
263 fn test_cp_from_host_to_container() {
264 let cmd = CpCommand::from_host(Path::new("./data.txt"))
265 .to_container("test-container", "/data/data.txt");
266 let args = cmd.build_command_args();
267 assert_eq!(
268 args,
269 vec!["cp", "./data.txt", "test-container:/data/data.txt"]
270 );
271 }
272
273 #[test]
274 fn test_cp_with_archive() {
275 let cmd = CpCommand::from_container("test-container", "/app")
276 .to_host(Path::new("./backup"))
277 .archive();
278 let args = cmd.build_command_args();
279 assert_eq!(
280 args,
281 vec!["cp", "--archive", "test-container:/app", "./backup"]
282 );
283 }
284
285 #[test]
286 fn test_cp_with_follow_link() {
287 let cmd = CpCommand::from_container("test-container", "/link")
288 .to_host(Path::new("./file"))
289 .follow_link();
290 let args = cmd.build_command_args();
291 assert_eq!(
292 args,
293 vec!["cp", "--follow-link", "test-container:/link", "./file"]
294 );
295 }
296
297 #[test]
298 fn test_cp_with_all_options() {
299 let cmd = CpCommand::from_host(Path::new("./src"))
300 .to_container("test-container", "/dest")
301 .archive()
302 .follow_link()
303 .quiet();
304 let args = cmd.build_command_args();
305 assert_eq!(
306 args,
307 vec![
308 "cp",
309 "--archive",
310 "--follow-link",
311 "--quiet",
312 "./src",
313 "test-container:/dest"
314 ]
315 );
316 }
317}