docker_wrapper/command/
export.rs

1//! Docker export command implementation.
2//!
3//! This module provides the `docker export` command for exporting containers to tarballs.
4
5use super::{CommandExecutor, CommandOutput, DockerCommand};
6use crate::error::Result;
7use async_trait::async_trait;
8
9/// Docker export command builder
10///
11/// Export a container's filesystem as a tar archive.
12///
13/// # Example
14///
15/// ```no_run
16/// use docker_wrapper::ExportCommand;
17///
18/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
19/// // Export container to file
20/// let result = ExportCommand::new("my-container")
21///     .output("container.tar")
22///     .run()
23///     .await?;
24///
25/// if result.success() {
26///     println!("Container exported to container.tar");
27/// }
28/// # Ok(())
29/// # }
30/// ```
31#[derive(Debug, Clone)]
32pub struct ExportCommand {
33    /// Container name or ID to export
34    container: String,
35    /// Output file path
36    output: Option<String>,
37    /// Command executor
38    pub executor: CommandExecutor,
39}
40
41impl ExportCommand {
42    /// Create a new export command
43    ///
44    /// # Example
45    ///
46    /// ```
47    /// use docker_wrapper::ExportCommand;
48    ///
49    /// let cmd = ExportCommand::new("my-container");
50    /// ```
51    #[must_use]
52    pub fn new(container: impl Into<String>) -> Self {
53        Self {
54            container: container.into(),
55            output: None,
56            executor: CommandExecutor::new(),
57        }
58    }
59
60    /// Set output file for the export
61    ///
62    /// # Example
63    ///
64    /// ```
65    /// use docker_wrapper::ExportCommand;
66    ///
67    /// let cmd = ExportCommand::new("my-container")
68    ///     .output("backup.tar");
69    /// ```
70    #[must_use]
71    pub fn output(mut self, output: impl Into<String>) -> Self {
72        self.output = Some(output.into());
73        self
74    }
75
76    /// Execute the export command
77    ///
78    /// # Errors
79    /// Returns an error if:
80    /// - The Docker daemon is not running
81    /// - The container doesn't exist
82    /// - File I/O errors occur during export
83    /// - Insufficient disk space
84    ///
85    /// # Example
86    ///
87    /// ```no_run
88    /// use docker_wrapper::ExportCommand;
89    ///
90    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
91    /// let result = ExportCommand::new("web-server")
92    ///     .output("web-backup.tar")
93    ///     .run()
94    ///     .await?;
95    ///
96    /// if result.success() {
97    ///     println!("Container '{}' exported to '{}'",
98    ///              result.container(), result.output_file().unwrap_or("stdout"));
99    /// }
100    /// # Ok(())
101    /// # }
102    /// ```
103    pub async fn run(&self) -> Result<ExportResult> {
104        let output = self.execute().await?;
105
106        Ok(ExportResult {
107            output,
108            container: self.container.clone(),
109            output_file: self.output.clone(),
110        })
111    }
112}
113
114#[async_trait]
115impl DockerCommand for ExportCommand {
116    type Output = CommandOutput;
117
118    fn build_command_args(&self) -> Vec<String> {
119        let mut args = vec!["export".to_string()];
120
121        if let Some(ref output) = self.output {
122            args.push("--output".to_string());
123            args.push(output.clone());
124        }
125
126        args.push(self.container.clone());
127        args.extend(self.executor.raw_args.clone());
128        args
129    }
130
131    fn get_executor(&self) -> &CommandExecutor {
132        &self.executor
133    }
134
135    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
136        &mut self.executor
137    }
138
139    async fn execute(&self) -> Result<Self::Output> {
140        let args = self.build_command_args();
141        let command_name = args[0].clone();
142        let command_args = args[1..].to_vec();
143        self.executor
144            .execute_command(&command_name, command_args)
145            .await
146    }
147}
148
149/// Result from the export command
150#[derive(Debug, Clone)]
151pub struct ExportResult {
152    /// Raw command output
153    pub output: CommandOutput,
154    /// Container that was exported
155    pub container: String,
156    /// Output file path (if specified)
157    pub output_file: Option<String>,
158}
159
160impl ExportResult {
161    /// Check if the export was successful
162    #[must_use]
163    pub fn success(&self) -> bool {
164        self.output.success
165    }
166
167    /// Get the container name
168    #[must_use]
169    pub fn container(&self) -> &str {
170        &self.container
171    }
172
173    /// Get the output file path
174    #[must_use]
175    pub fn output_file(&self) -> Option<&str> {
176        self.output_file.as_deref()
177    }
178
179    /// Get the raw command output
180    #[must_use]
181    pub fn output(&self) -> &CommandOutput {
182        &self.output
183    }
184
185    /// Check if export was written to a file
186    #[must_use]
187    pub fn exported_to_file(&self) -> bool {
188        self.output_file.is_some()
189    }
190
191    /// Check if export was written to stdout
192    #[must_use]
193    pub fn exported_to_stdout(&self) -> bool {
194        self.output_file.is_none()
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn test_export_basic() {
204        let cmd = ExportCommand::new("test-container");
205        let args = cmd.build_command_args();
206        assert_eq!(args, vec!["export", "test-container"]);
207    }
208
209    #[test]
210    fn test_export_with_output() {
211        let cmd = ExportCommand::new("test-container").output("backup.tar");
212        let args = cmd.build_command_args();
213        assert_eq!(
214            args,
215            vec!["export", "--output", "backup.tar", "test-container"]
216        );
217    }
218
219    #[test]
220    fn test_export_with_path() {
221        let cmd = ExportCommand::new("web-server").output("/tmp/exports/web.tar");
222        let args = cmd.build_command_args();
223        assert_eq!(
224            args,
225            vec!["export", "--output", "/tmp/exports/web.tar", "web-server"]
226        );
227    }
228
229    #[test]
230    fn test_export_result() {
231        let result = ExportResult {
232            output: CommandOutput {
233                stdout: String::new(),
234                stderr: String::new(),
235                exit_code: 0,
236                success: true,
237            },
238            container: "my-container".to_string(),
239            output_file: Some("backup.tar".to_string()),
240        };
241
242        assert!(result.success());
243        assert_eq!(result.container(), "my-container");
244        assert_eq!(result.output_file(), Some("backup.tar"));
245        assert!(result.exported_to_file());
246        assert!(!result.exported_to_stdout());
247    }
248
249    #[test]
250    fn test_export_result_stdout() {
251        let result = ExportResult {
252            output: CommandOutput {
253                stdout: "tar data...".to_string(),
254                stderr: String::new(),
255                exit_code: 0,
256                success: true,
257            },
258            container: "my-container".to_string(),
259            output_file: None,
260        };
261
262        assert!(result.success());
263        assert_eq!(result.container(), "my-container");
264        assert_eq!(result.output_file(), None);
265        assert!(!result.exported_to_file());
266        assert!(result.exported_to_stdout());
267    }
268}