docker_wrapper/command/compose/
push.rs

1//! Docker Compose push command implementation using unified trait pattern.
2
3use crate::command::{CommandExecutor, ComposeCommand, ComposeConfig, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Docker Compose push command builder
8#[derive(Debug, Clone)]
9pub struct ComposePushCommand {
10    /// Base command executor
11    pub executor: CommandExecutor,
12    /// Base compose configuration
13    pub config: ComposeConfig,
14    /// Services to push images for (empty for all)
15    pub services: Vec<String>,
16    /// Ignore build failures
17    pub ignore_build_failures: bool,
18    /// Include dependencies
19    pub include_deps: bool,
20    /// Quiet mode
21    pub quiet: bool,
22}
23
24/// Result from compose push command
25#[derive(Debug, Clone)]
26pub struct ComposePushResult {
27    /// Raw stdout output
28    pub stdout: String,
29    /// Raw stderr output
30    pub stderr: String,
31    /// Success status
32    pub success: bool,
33    /// Services that were pushed
34    pub services: Vec<String>,
35}
36
37impl ComposePushCommand {
38    /// Create a new compose push command
39    #[must_use]
40    pub fn new() -> Self {
41        Self {
42            executor: CommandExecutor::new(),
43            config: ComposeConfig::new(),
44            services: Vec::new(),
45            ignore_build_failures: false,
46            include_deps: false,
47            quiet: false,
48        }
49    }
50
51    /// Add a service to push
52    #[must_use]
53    pub fn service(mut self, service: impl Into<String>) -> Self {
54        self.services.push(service.into());
55        self
56    }
57
58    /// Add multiple services to push
59    #[must_use]
60    pub fn services<I, S>(mut self, services: I) -> Self
61    where
62        I: IntoIterator<Item = S>,
63        S: Into<String>,
64    {
65        self.services.extend(services.into_iter().map(Into::into));
66        self
67    }
68
69    /// Ignore build failures
70    #[must_use]
71    pub fn ignore_build_failures(mut self) -> Self {
72        self.ignore_build_failures = true;
73        self
74    }
75
76    /// Include dependencies
77    #[must_use]
78    pub fn include_deps(mut self) -> Self {
79        self.include_deps = true;
80        self
81    }
82
83    /// Enable quiet mode
84    #[must_use]
85    pub fn quiet(mut self) -> Self {
86        self.quiet = true;
87        self
88    }
89}
90
91impl Default for ComposePushCommand {
92    fn default() -> Self {
93        Self::new()
94    }
95}
96
97#[async_trait]
98impl DockerCommand for ComposePushCommand {
99    type Output = ComposePushResult;
100
101    fn get_executor(&self) -> &CommandExecutor {
102        &self.executor
103    }
104
105    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
106        &mut self.executor
107    }
108
109    fn build_command_args(&self) -> Vec<String> {
110        <Self as ComposeCommand>::build_command_args(self)
111    }
112
113    async fn execute(&self) -> Result<Self::Output> {
114        let args = <Self as ComposeCommand>::build_command_args(self);
115        let output = self.execute_command(args).await?;
116
117        Ok(ComposePushResult {
118            stdout: output.stdout,
119            stderr: output.stderr,
120            success: output.success,
121            services: self.services.clone(),
122        })
123    }
124}
125
126impl ComposeCommand for ComposePushCommand {
127    fn get_config(&self) -> &ComposeConfig {
128        &self.config
129    }
130
131    fn get_config_mut(&mut self) -> &mut ComposeConfig {
132        &mut self.config
133    }
134
135    fn subcommand(&self) -> &'static str {
136        "push"
137    }
138
139    fn build_subcommand_args(&self) -> Vec<String> {
140        let mut args = Vec::new();
141
142        if self.ignore_build_failures {
143            args.push("--ignore-build-failures".to_string());
144        }
145
146        if self.include_deps {
147            args.push("--include-deps".to_string());
148        }
149
150        if self.quiet {
151            args.push("--quiet".to_string());
152        }
153
154        args.extend(self.services.clone());
155        args
156    }
157}
158
159impl ComposePushResult {
160    /// Check if the command was successful
161    #[must_use]
162    pub fn success(&self) -> bool {
163        self.success
164    }
165
166    /// Get the services that were pushed
167    #[must_use]
168    pub fn services(&self) -> &[String] {
169        &self.services
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn test_compose_push_basic() {
179        let cmd = ComposePushCommand::new();
180        let args = cmd.build_subcommand_args();
181        assert!(args.is_empty());
182
183        let full_args = ComposeCommand::build_command_args(&cmd);
184        assert_eq!(full_args[0], "compose");
185        assert!(full_args.contains(&"push".to_string()));
186    }
187
188    #[test]
189    fn test_compose_push_with_options() {
190        let cmd = ComposePushCommand::new()
191            .ignore_build_failures()
192            .include_deps()
193            .quiet()
194            .service("web");
195
196        let args = cmd.build_subcommand_args();
197        assert!(args.contains(&"--ignore-build-failures".to_string()));
198        assert!(args.contains(&"--include-deps".to_string()));
199        assert!(args.contains(&"--quiet".to_string()));
200        assert!(args.contains(&"web".to_string()));
201    }
202}