docker_wrapper/command/
compose_create.rs1use super::{CommandExecutor, ComposeCommand, ComposeConfig, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7#[derive(Debug, Clone)]
9#[allow(clippy::struct_excessive_bools)] pub struct ComposeCreateCommand {
11 pub executor: CommandExecutor,
13 pub config: ComposeConfig,
15 pub build: bool,
17 pub no_build: bool,
19 pub force_recreate: bool,
21 pub no_recreate: bool,
23 pub pull: Option<PullPolicy>,
25 pub remove_orphans: bool,
27 pub services: Vec<String>,
29}
30
31#[derive(Debug, Clone, Copy)]
33pub enum PullPolicy {
34 Always,
36 Never,
38 Missing,
40 Build,
42}
43
44impl std::fmt::Display for PullPolicy {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 match self {
47 Self::Always => write!(f, "always"),
48 Self::Never => write!(f, "never"),
49 Self::Missing => write!(f, "missing"),
50 Self::Build => write!(f, "build"),
51 }
52 }
53}
54
55#[derive(Debug, Clone)]
57pub struct ComposeCreateResult {
58 pub stdout: String,
60 pub stderr: String,
62 pub success: bool,
64 pub services: Vec<String>,
66}
67
68impl ComposeCreateCommand {
69 #[must_use]
71 pub fn new() -> Self {
72 Self {
73 executor: CommandExecutor::new(),
74 config: ComposeConfig::new(),
75 build: false,
76 no_build: false,
77 force_recreate: false,
78 no_recreate: false,
79 pull: None,
80 remove_orphans: false,
81 services: Vec::new(),
82 }
83 }
84
85 #[must_use]
87 pub fn build(mut self) -> Self {
88 self.build = true;
89 self
90 }
91
92 #[must_use]
94 pub fn no_build(mut self) -> Self {
95 self.no_build = true;
96 self
97 }
98
99 #[must_use]
101 pub fn force_recreate(mut self) -> Self {
102 self.force_recreate = true;
103 self
104 }
105
106 #[must_use]
108 pub fn no_recreate(mut self) -> Self {
109 self.no_recreate = true;
110 self
111 }
112
113 #[must_use]
115 pub fn pull(mut self, policy: PullPolicy) -> Self {
116 self.pull = Some(policy);
117 self
118 }
119
120 #[must_use]
122 pub fn remove_orphans(mut self) -> Self {
123 self.remove_orphans = true;
124 self
125 }
126
127 #[must_use]
129 pub fn service(mut self, service: impl Into<String>) -> Self {
130 self.services.push(service.into());
131 self
132 }
133
134 #[must_use]
136 pub fn services<I, S>(mut self, services: I) -> Self
137 where
138 I: IntoIterator<Item = S>,
139 S: Into<String>,
140 {
141 self.services.extend(services.into_iter().map(Into::into));
142 self
143 }
144}
145
146impl Default for ComposeCreateCommand {
147 fn default() -> Self {
148 Self::new()
149 }
150}
151
152#[async_trait]
153impl DockerCommand for ComposeCreateCommand {
154 type Output = ComposeCreateResult;
155
156 fn get_executor(&self) -> &CommandExecutor {
157 &self.executor
158 }
159
160 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
161 &mut self.executor
162 }
163
164 fn build_command_args(&self) -> Vec<String> {
165 <Self as ComposeCommand>::build_command_args(self)
167 }
168
169 async fn execute(&self) -> Result<Self::Output> {
170 let args = <Self as ComposeCommand>::build_command_args(self);
171 let output = self.execute_command(args).await?;
172
173 Ok(ComposeCreateResult {
174 stdout: output.stdout,
175 stderr: output.stderr,
176 success: output.success,
177 services: self.services.clone(),
178 })
179 }
180}
181
182impl ComposeCommand for ComposeCreateCommand {
183 fn get_config(&self) -> &ComposeConfig {
184 &self.config
185 }
186
187 fn get_config_mut(&mut self) -> &mut ComposeConfig {
188 &mut self.config
189 }
190
191 fn subcommand(&self) -> &'static str {
192 "create"
193 }
194
195 fn build_subcommand_args(&self) -> Vec<String> {
196 let mut args = Vec::new();
197
198 if self.build {
199 args.push("--build".to_string());
200 }
201
202 if self.no_build {
203 args.push("--no-build".to_string());
204 }
205
206 if self.force_recreate {
207 args.push("--force-recreate".to_string());
208 }
209
210 if self.no_recreate {
211 args.push("--no-recreate".to_string());
212 }
213
214 if self.remove_orphans {
215 args.push("--remove-orphans".to_string());
216 }
217
218 if let Some(pull) = self.pull {
220 args.push("--pull".to_string());
221 args.push(pull.to_string());
222 }
223
224 args.extend(self.services.clone());
226
227 args
228 }
229}
230
231impl ComposeCreateResult {
232 #[must_use]
234 pub fn success(&self) -> bool {
235 self.success
236 }
237
238 #[must_use]
240 pub fn services(&self) -> &[String] {
241 &self.services
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn test_compose_create_basic() {
251 let cmd = ComposeCreateCommand::new();
252 let args = cmd.build_subcommand_args();
253 assert!(args.is_empty());
254
255 let full_args = ComposeCommand::build_command_args(&cmd);
256 assert_eq!(full_args[0], "compose");
257 assert!(full_args.contains(&"create".to_string()));
258 }
259
260 #[test]
261 fn test_compose_create_with_build() {
262 let cmd = ComposeCreateCommand::new().build().force_recreate();
263 let args = cmd.build_subcommand_args();
264 assert!(args.contains(&"--build".to_string()));
265 assert!(args.contains(&"--force-recreate".to_string()));
266 }
267
268 #[test]
269 fn test_compose_create_with_pull() {
270 let cmd = ComposeCreateCommand::new()
271 .pull(PullPolicy::Always)
272 .no_recreate();
273 let args = cmd.build_subcommand_args();
274 assert!(args.contains(&"--pull".to_string()));
275 assert!(args.contains(&"always".to_string()));
276 assert!(args.contains(&"--no-recreate".to_string()));
277 }
278
279 #[test]
280 fn test_compose_create_with_services() {
281 let cmd = ComposeCreateCommand::new()
282 .service("web")
283 .service("db")
284 .remove_orphans();
285 let args = cmd.build_subcommand_args();
286 assert!(args.contains(&"web".to_string()));
287 assert!(args.contains(&"db".to_string()));
288 assert!(args.contains(&"--remove-orphans".to_string()));
289 }
290
291 #[test]
292 fn test_pull_policy_display() {
293 assert_eq!(PullPolicy::Always.to_string(), "always");
294 assert_eq!(PullPolicy::Never.to_string(), "never");
295 assert_eq!(PullPolicy::Missing.to_string(), "missing");
296 assert_eq!(PullPolicy::Build.to_string(), "build");
297 }
298
299 #[test]
300 fn test_compose_config_integration() {
301 let cmd = ComposeCreateCommand::new()
302 .file("docker-compose.yml")
303 .project_name("my-project")
304 .build()
305 .service("web");
306
307 let args = ComposeCommand::build_command_args(&cmd);
308 assert!(args.contains(&"--file".to_string()));
309 assert!(args.contains(&"docker-compose.yml".to_string()));
310 assert!(args.contains(&"--project-name".to_string()));
311 assert!(args.contains(&"my-project".to_string()));
312 assert!(args.contains(&"--build".to_string()));
313 assert!(args.contains(&"web".to_string()));
314 }
315}