docker_wrapper/command/
save.rs1use super::{CommandExecutor, CommandOutput, DockerCommand};
6use crate::error::Result;
7use async_trait::async_trait;
8use std::path::Path;
9
10#[derive(Debug, Clone)]
36pub struct SaveCommand {
37 images: Vec<String>,
39 output: Option<String>,
41 pub executor: CommandExecutor,
43}
44
45impl SaveCommand {
46 #[must_use]
56 pub fn new(image: impl Into<String>) -> Self {
57 Self {
58 images: vec![image.into()],
59 output: None,
60 executor: CommandExecutor::new(),
61 }
62 }
63
64 #[must_use]
74 pub fn new_multiple(images: Vec<impl Into<String>>) -> Self {
75 Self {
76 images: images.into_iter().map(Into::into).collect(),
77 output: None,
78 executor: CommandExecutor::new(),
79 }
80 }
81
82 #[must_use]
84 pub fn image(mut self, image: impl Into<String>) -> Self {
85 self.images.push(image.into());
86 self
87 }
88
89 #[must_use]
101 pub fn output(mut self, path: &Path) -> Self {
102 self.output = Some(path.to_string_lossy().into_owned());
103 self
104 }
105
106 pub async fn run(&self) -> Result<SaveResult> {
133 let output = self.execute().await?;
134 Ok(SaveResult {
135 output,
136 images: self.images.clone(),
137 output_file: self.output.clone(),
138 })
139 }
140}
141
142#[async_trait]
143impl DockerCommand for SaveCommand {
144 type Output = CommandOutput;
145
146 fn build_command_args(&self) -> Vec<String> {
147 let mut args = vec!["save".to_string()];
148
149 if let Some(ref output_file) = self.output {
150 args.push("--output".to_string());
151 args.push(output_file.clone());
152 }
153
154 args.extend(self.images.clone());
156
157 args.extend(self.executor.raw_args.clone());
158 args
159 }
160
161 fn get_executor(&self) -> &CommandExecutor {
162 &self.executor
163 }
164
165 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
166 &mut self.executor
167 }
168
169 async fn execute(&self) -> Result<Self::Output> {
170 let args = self.build_command_args();
171 let command_name = args[0].clone();
172 let command_args = args[1..].to_vec();
173 self.executor
174 .execute_command(&command_name, command_args)
175 .await
176 }
177}
178
179#[derive(Debug, Clone)]
181pub struct SaveResult {
182 pub output: CommandOutput,
184 pub images: Vec<String>,
186 pub output_file: Option<String>,
188}
189
190impl SaveResult {
191 #[must_use]
193 pub fn success(&self) -> bool {
194 self.output.success
195 }
196
197 #[must_use]
199 pub fn images(&self) -> &[String] {
200 &self.images
201 }
202
203 #[must_use]
205 pub fn output_file(&self) -> Option<&str> {
206 self.output_file.as_deref()
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_save_single_image() {
216 let cmd = SaveCommand::new("alpine:latest");
217 let args = cmd.build_command_args();
218 assert_eq!(args, vec!["save", "alpine:latest"]);
219 }
220
221 #[test]
222 fn test_save_multiple_images() {
223 let cmd = SaveCommand::new_multiple(vec!["alpine:latest", "nginx:latest", "redis:latest"]);
224 let args = cmd.build_command_args();
225 assert_eq!(
226 args,
227 vec!["save", "alpine:latest", "nginx:latest", "redis:latest"]
228 );
229 }
230
231 #[test]
232 fn test_save_with_output() {
233 let cmd = SaveCommand::new("alpine:latest").output(Path::new("alpine.tar"));
234 let args = cmd.build_command_args();
235 assert_eq!(
236 args,
237 vec!["save", "--output", "alpine.tar", "alpine:latest"]
238 );
239 }
240
241 #[test]
242 fn test_save_multiple_with_output() {
243 let cmd = SaveCommand::new_multiple(vec!["alpine", "nginx"])
244 .image("redis")
245 .output(Path::new("/tmp/images.tar"));
246 let args = cmd.build_command_args();
247 assert_eq!(
248 args,
249 vec![
250 "save",
251 "--output",
252 "/tmp/images.tar",
253 "alpine",
254 "nginx",
255 "redis"
256 ]
257 );
258 }
259}