docker_wrapper/compose/
push.rs

1//! Docker Compose push command implementation.
2
3use crate::compose::{ComposeCommandV2 as ComposeCommand, ComposeConfig};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Docker Compose push command
8///
9/// Push service images to registry.
10#[derive(Debug, Clone, Default)]
11pub struct ComposePushCommand {
12    /// Base configuration
13    pub config: ComposeConfig,
14    /// Include all tags when pushing
15    pub include_deps: bool,
16    /// Ignore push failures
17    pub ignore_push_failures: bool,
18    /// Don't print progress
19    pub quiet: bool,
20    /// Services to push
21    pub services: Vec<String>,
22}
23
24/// Result from push command
25#[derive(Debug, Clone)]
26pub struct PushResult {
27    /// Output from the command
28    pub output: String,
29    /// Whether the operation succeeded
30    pub success: bool,
31}
32
33impl ComposePushCommand {
34    /// Create a new push command
35    #[must_use]
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    /// Add a compose file
41    #[must_use]
42    pub fn file<P: Into<std::path::PathBuf>>(mut self, file: P) -> Self {
43        self.config.files.push(file.into());
44        self
45    }
46
47    /// Set project name
48    #[must_use]
49    pub fn project_name(mut self, name: impl Into<String>) -> Self {
50        self.config.project_name = Some(name.into());
51        self
52    }
53
54    /// Include all dependencies
55    #[must_use]
56    pub fn include_deps(mut self) -> Self {
57        self.include_deps = true;
58        self
59    }
60
61    /// Ignore push failures
62    #[must_use]
63    pub fn ignore_push_failures(mut self) -> Self {
64        self.ignore_push_failures = true;
65        self
66    }
67
68    /// Quiet mode
69    #[must_use]
70    pub fn quiet(mut self) -> Self {
71        self.quiet = true;
72        self
73    }
74
75    /// Add a service to push
76    #[must_use]
77    pub fn service(mut self, service: impl Into<String>) -> Self {
78        self.services.push(service.into());
79        self
80    }
81
82    /// Add multiple services to push
83    #[must_use]
84    pub fn services<I, S>(mut self, services: I) -> Self
85    where
86        I: IntoIterator<Item = S>,
87        S: Into<String>,
88    {
89        self.services.extend(services.into_iter().map(Into::into));
90        self
91    }
92
93    fn build_args(&self) -> Vec<String> {
94        let mut args = vec!["push".to_string()];
95
96        // Add flags
97        if self.include_deps {
98            args.push("--include-deps".to_string());
99        }
100        if self.ignore_push_failures {
101            args.push("--ignore-push-failures".to_string());
102        }
103        if self.quiet {
104            args.push("--quiet".to_string());
105        }
106
107        // Add services
108        args.extend(self.services.clone());
109
110        args
111    }
112}
113
114#[async_trait]
115impl ComposeCommand for ComposePushCommand {
116    type Output = PushResult;
117
118    fn get_config(&self) -> &ComposeConfig {
119        &self.config
120    }
121
122    fn get_config_mut(&mut self) -> &mut ComposeConfig {
123        &mut self.config
124    }
125
126    async fn execute_compose(&self, args: Vec<String>) -> Result<Self::Output> {
127        let output = self.execute_compose_command(args).await?;
128
129        Ok(PushResult {
130            output: output.stdout,
131            success: output.success,
132        })
133    }
134
135    async fn execute(&self) -> Result<Self::Output> {
136        let args = self.build_args();
137        self.execute_compose(args).await
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_push_command_basic() {
147        let cmd = ComposePushCommand::new();
148        let args = cmd.build_args();
149        assert_eq!(args[0], "push");
150    }
151
152    #[test]
153    fn test_push_command_with_flags() {
154        let cmd = ComposePushCommand::new()
155            .include_deps()
156            .ignore_push_failures()
157            .quiet();
158        let args = cmd.build_args();
159        assert!(args.contains(&"--include-deps".to_string()));
160        assert!(args.contains(&"--ignore-push-failures".to_string()));
161        assert!(args.contains(&"--quiet".to_string()));
162    }
163
164    #[test]
165    fn test_push_command_with_services() {
166        let cmd = ComposePushCommand::new().service("web").service("api");
167        let args = cmd.build_args();
168        assert!(args.contains(&"web".to_string()));
169        assert!(args.contains(&"api".to_string()));
170    }
171}