docker_wrapper/command/
commit.rs1use super::{CommandExecutor, CommandOutput, DockerCommand};
7use crate::error::Result;
8use async_trait::async_trait;
9
10#[derive(Debug, Clone)]
34pub struct CommitCommand {
35 container: String,
37 repository: Option<String>,
39 tag: Option<String>,
41 message: Option<String>,
43 author: Option<String>,
45 pause: bool,
47 changes: Vec<String>,
49 pub executor: CommandExecutor,
51}
52
53impl CommitCommand {
54 #[must_use]
64 pub fn new(container: impl Into<String>) -> Self {
65 Self {
66 container: container.into(),
67 repository: None,
68 tag: None,
69 message: None,
70 author: None,
71 pause: true, changes: Vec::new(),
73 executor: CommandExecutor::new(),
74 }
75 }
76
77 #[must_use]
88 pub fn repository(mut self, repository: impl Into<String>) -> Self {
89 self.repository = Some(repository.into());
90 self
91 }
92
93 #[must_use]
105 pub fn tag(mut self, tag: impl Into<String>) -> Self {
106 self.tag = Some(tag.into());
107 self
108 }
109
110 #[must_use]
112 pub fn message(mut self, message: impl Into<String>) -> Self {
113 self.message = Some(message.into());
114 self
115 }
116
117 #[must_use]
128 pub fn author(mut self, author: impl Into<String>) -> Self {
129 self.author = Some(author.into());
130 self
131 }
132
133 #[must_use]
135 pub fn no_pause(mut self) -> Self {
136 self.pause = false;
137 self
138 }
139
140 #[must_use]
153 pub fn change(mut self, change: impl Into<String>) -> Self {
154 self.changes.push(change.into());
155 self
156 }
157
158 pub async fn run(&self) -> Result<CommitResult> {
183 let output = self.execute().await?;
184
185 let image_id = output.stdout.trim().to_string();
187
188 Ok(CommitResult { output, image_id })
189 }
190}
191
192#[async_trait]
193impl DockerCommand for CommitCommand {
194 type Output = CommandOutput;
195
196 fn build_command_args(&self) -> Vec<String> {
197 let mut args = vec!["commit".to_string()];
198
199 if let Some(ref author) = self.author {
200 args.push("--author".to_string());
201 args.push(author.clone());
202 }
203
204 for change in &self.changes {
205 args.push("--change".to_string());
206 args.push(change.clone());
207 }
208
209 if let Some(ref message) = self.message {
210 args.push("--message".to_string());
211 args.push(message.clone());
212 }
213
214 if !self.pause {
215 args.push("--pause=false".to_string());
216 }
217
218 args.push(self.container.clone());
220
221 if let Some(ref repo) = self.repository {
223 let mut image_name = repo.clone();
224 if let Some(ref tag) = self.tag {
225 image_name.push(':');
226 image_name.push_str(tag);
227 }
228 args.push(image_name);
229 }
230
231 args.extend(self.executor.raw_args.clone());
232 args
233 }
234
235 fn get_executor(&self) -> &CommandExecutor {
236 &self.executor
237 }
238
239 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
240 &mut self.executor
241 }
242
243 async fn execute(&self) -> Result<Self::Output> {
244 let args = self.build_command_args();
245 let command_name = args[0].clone();
246 let command_args = args[1..].to_vec();
247 self.executor
248 .execute_command(&command_name, command_args)
249 .await
250 }
251}
252
253#[derive(Debug, Clone)]
255pub struct CommitResult {
256 pub output: CommandOutput,
258 pub image_id: String,
260}
261
262impl CommitResult {
263 #[must_use]
265 pub fn success(&self) -> bool {
266 self.output.success && !self.image_id.is_empty()
267 }
268
269 #[must_use]
271 pub fn image_id(&self) -> &str {
272 &self.image_id
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[test]
281 fn test_commit_basic() {
282 let cmd = CommitCommand::new("test-container");
283 let args = cmd.build_command_args();
284 assert_eq!(args, vec!["commit", "test-container"]);
285 }
286
287 #[test]
288 fn test_commit_with_repository() {
289 let cmd = CommitCommand::new("test-container").repository("myapp");
290 let args = cmd.build_command_args();
291 assert_eq!(args, vec!["commit", "test-container", "myapp"]);
292 }
293
294 #[test]
295 fn test_commit_with_repository_and_tag() {
296 let cmd = CommitCommand::new("test-container")
297 .repository("myapp")
298 .tag("v2.0");
299 let args = cmd.build_command_args();
300 assert_eq!(args, vec!["commit", "test-container", "myapp:v2.0"]);
301 }
302
303 #[test]
304 fn test_commit_with_message_and_author() {
305 let cmd = CommitCommand::new("test-container")
306 .message("Updated config")
307 .author("Dev <dev@example.com>")
308 .repository("myapp");
309 let args = cmd.build_command_args();
310 assert_eq!(
311 args,
312 vec![
313 "commit",
314 "--author",
315 "Dev <dev@example.com>",
316 "--message",
317 "Updated config",
318 "test-container",
319 "myapp"
320 ]
321 );
322 }
323
324 #[test]
325 fn test_commit_with_changes() {
326 let cmd = CommitCommand::new("test-container")
327 .change("ENV VERSION=2.0")
328 .change("EXPOSE 8080")
329 .repository("myapp");
330 let args = cmd.build_command_args();
331 assert_eq!(
332 args,
333 vec![
334 "commit",
335 "--change",
336 "ENV VERSION=2.0",
337 "--change",
338 "EXPOSE 8080",
339 "test-container",
340 "myapp"
341 ]
342 );
343 }
344
345 #[test]
346 fn test_commit_no_pause() {
347 let cmd = CommitCommand::new("test-container")
348 .no_pause()
349 .repository("myapp");
350 let args = cmd.build_command_args();
351 assert_eq!(
352 args,
353 vec!["commit", "--pause=false", "test-container", "myapp"]
354 );
355 }
356
357 #[test]
358 fn test_commit_all_options() {
359 let cmd = CommitCommand::new("test-container")
360 .repository("myapp")
361 .tag("v2.0")
362 .message("Commit message")
363 .author("Author Name")
364 .no_pause()
365 .change("ENV FOO=bar");
366 let args = cmd.build_command_args();
367 assert_eq!(
368 args,
369 vec![
370 "commit",
371 "--author",
372 "Author Name",
373 "--change",
374 "ENV FOO=bar",
375 "--message",
376 "Commit message",
377 "--pause=false",
378 "test-container",
379 "myapp:v2.0"
380 ]
381 );
382 }
383}