docker_wrapper/command/compose/
pull.rs1use crate::command::{CommandExecutor, ComposeCommand, ComposeConfig, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7#[derive(Debug, Clone, Copy)]
9pub enum ComposePullPolicy {
10 Always,
12 Missing,
14}
15
16impl std::fmt::Display for ComposePullPolicy {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 match self {
19 Self::Always => write!(f, "always"),
20 Self::Missing => write!(f, "missing"),
21 }
22 }
23}
24
25#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)]
28pub struct ComposePullCommand {
29 pub executor: CommandExecutor,
31 pub config: ComposeConfig,
33 pub services: Vec<String>,
35 pub ignore_buildable: bool,
37 pub ignore_pull_failures: bool,
39 pub include_deps: bool,
41 pub policy: Option<ComposePullPolicy>,
43 pub quiet: bool,
45}
46
47#[derive(Debug, Clone)]
49pub struct ComposePullResult {
50 pub stdout: String,
52 pub stderr: String,
54 pub success: bool,
56 pub services: Vec<String>,
58}
59
60impl ComposePullCommand {
61 #[must_use]
63 pub fn new() -> Self {
64 Self {
65 executor: CommandExecutor::new(),
66 config: ComposeConfig::new(),
67 services: Vec::new(),
68 ignore_buildable: false,
69 ignore_pull_failures: false,
70 include_deps: false,
71 policy: None,
72 quiet: false,
73 }
74 }
75
76 #[must_use]
78 pub fn service(mut self, service: impl Into<String>) -> Self {
79 self.services.push(service.into());
80 self
81 }
82
83 #[must_use]
85 pub fn services<I, S>(mut self, services: I) -> Self
86 where
87 I: IntoIterator<Item = S>,
88 S: Into<String>,
89 {
90 self.services.extend(services.into_iter().map(Into::into));
91 self
92 }
93
94 #[must_use]
96 pub fn ignore_buildable(mut self) -> Self {
97 self.ignore_buildable = true;
98 self
99 }
100
101 #[must_use]
103 pub fn ignore_pull_failures(mut self) -> Self {
104 self.ignore_pull_failures = true;
105 self
106 }
107
108 #[must_use]
110 pub fn include_deps(mut self) -> Self {
111 self.include_deps = true;
112 self
113 }
114
115 #[must_use]
117 pub fn policy(mut self, policy: ComposePullPolicy) -> Self {
118 self.policy = Some(policy);
119 self
120 }
121
122 #[must_use]
124 pub fn quiet(mut self) -> Self {
125 self.quiet = true;
126 self
127 }
128}
129
130impl Default for ComposePullCommand {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136#[async_trait]
137impl DockerCommand for ComposePullCommand {
138 type Output = ComposePullResult;
139
140 fn get_executor(&self) -> &CommandExecutor {
141 &self.executor
142 }
143
144 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
145 &mut self.executor
146 }
147
148 fn build_command_args(&self) -> Vec<String> {
149 <Self as ComposeCommand>::build_command_args(self)
150 }
151
152 async fn execute(&self) -> Result<Self::Output> {
153 let args = <Self as ComposeCommand>::build_command_args(self);
154 let output = self.execute_command(args).await?;
155
156 Ok(ComposePullResult {
157 stdout: output.stdout,
158 stderr: output.stderr,
159 success: output.success,
160 services: self.services.clone(),
161 })
162 }
163}
164
165impl ComposeCommand for ComposePullCommand {
166 fn get_config(&self) -> &ComposeConfig {
167 &self.config
168 }
169
170 fn get_config_mut(&mut self) -> &mut ComposeConfig {
171 &mut self.config
172 }
173
174 fn subcommand(&self) -> &'static str {
175 "pull"
176 }
177
178 fn build_subcommand_args(&self) -> Vec<String> {
179 let mut args = Vec::new();
180
181 if self.ignore_buildable {
182 args.push("--ignore-buildable".to_string());
183 }
184
185 if self.ignore_pull_failures {
186 args.push("--ignore-pull-failures".to_string());
187 }
188
189 if self.include_deps {
190 args.push("--include-deps".to_string());
191 }
192
193 if let Some(ref policy) = self.policy {
194 args.push("--policy".to_string());
195 args.push(policy.to_string());
196 }
197
198 if self.quiet {
199 args.push("--quiet".to_string());
200 }
201
202 args.extend(self.services.clone());
203 args
204 }
205}
206
207impl ComposePullResult {
208 #[must_use]
210 pub fn success(&self) -> bool {
211 self.success
212 }
213
214 #[must_use]
216 pub fn services(&self) -> &[String] {
217 &self.services
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn test_compose_pull_basic() {
227 let cmd = ComposePullCommand::new();
228 let args = cmd.build_subcommand_args();
229 assert!(args.is_empty());
230
231 let full_args = ComposeCommand::build_command_args(&cmd);
232 assert_eq!(full_args[0], "compose");
233 assert!(full_args.contains(&"pull".to_string()));
234 }
235
236 #[test]
237 fn test_compose_pull_with_options() {
238 let cmd = ComposePullCommand::new()
239 .ignore_buildable()
240 .ignore_pull_failures()
241 .include_deps()
242 .quiet()
243 .service("web");
244
245 let args = cmd.build_subcommand_args();
246 assert!(args.contains(&"--ignore-buildable".to_string()));
247 assert!(args.contains(&"--ignore-pull-failures".to_string()));
248 assert!(args.contains(&"--include-deps".to_string()));
249 assert!(args.contains(&"--quiet".to_string()));
250 assert!(args.contains(&"web".to_string()));
251 }
252
253 #[test]
254 fn test_compose_pull_with_policy() {
255 let cmd = ComposePullCommand::new()
256 .policy(ComposePullPolicy::Always)
257 .service("db");
258
259 let args = cmd.build_subcommand_args();
260 assert!(args.contains(&"--policy".to_string()));
261 assert!(args.contains(&"always".to_string()));
262 assert!(args.contains(&"db".to_string()));
263 }
264
265 #[test]
266 fn test_compose_pull_with_missing_policy() {
267 let cmd = ComposePullCommand::new().policy(ComposePullPolicy::Missing);
268
269 let args = cmd.build_subcommand_args();
270 assert!(args.contains(&"--policy".to_string()));
271 assert!(args.contains(&"missing".to_string()));
272 }
273
274 #[test]
275 fn test_compose_pull_multiple_services() {
276 let cmd = ComposePullCommand::new()
277 .service("web")
278 .service("db")
279 .service("redis");
280
281 let args = cmd.build_subcommand_args();
282 assert!(args.contains(&"web".to_string()));
283 assert!(args.contains(&"db".to_string()));
284 assert!(args.contains(&"redis".to_string()));
285 }
286
287 #[test]
288 fn test_compose_pull_services_batch() {
289 let cmd = ComposePullCommand::new().services(vec!["web", "db"]);
290
291 let args = cmd.build_subcommand_args();
292 assert!(args.contains(&"web".to_string()));
293 assert!(args.contains(&"db".to_string()));
294 }
295
296 #[test]
297 fn test_compose_pull_config_integration() {
298 let cmd = ComposePullCommand::new()
299 .file("docker-compose.yml")
300 .project_name("myapp")
301 .service("api");
302
303 let args = ComposeCommand::build_command_args(&cmd);
304 assert!(args.contains(&"--file".to_string()));
305 assert!(args.contains(&"docker-compose.yml".to_string()));
306 assert!(args.contains(&"--project-name".to_string()));
307 assert!(args.contains(&"myapp".to_string()));
308 assert!(args.contains(&"pull".to_string()));
309 assert!(args.contains(&"api".to_string()));
310 }
311}