docker_wrapper/command/compose/
images.rs1use crate::command::{CommandExecutor, ComposeCommand, ComposeConfig, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6use serde::Deserialize;
7
8#[derive(Debug, Clone)]
10pub struct ComposeImagesCommand {
11 pub executor: CommandExecutor,
13 pub config: ComposeConfig,
15 pub format: Option<ImagesFormat>,
17 pub quiet: bool,
19 pub services: Vec<String>,
21}
22
23#[derive(Debug, Clone, Copy)]
25pub enum ImagesFormat {
26 Table,
28 Json,
30}
31
32impl std::fmt::Display for ImagesFormat {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 match self {
35 Self::Table => write!(f, "table"),
36 Self::Json => write!(f, "json"),
37 }
38 }
39}
40
41#[derive(Debug, Clone, Deserialize)]
43pub struct ImageInfo {
44 pub container: String,
46 pub repository: String,
48 pub tag: String,
50 #[serde(rename = "ID")]
52 pub id: String,
53 pub size: String,
55}
56
57#[derive(Debug, Clone)]
59pub struct ComposeImagesResult {
60 pub stdout: String,
62 pub stderr: String,
64 pub success: bool,
66 pub images: Vec<ImageInfo>,
68 pub services: Vec<String>,
70}
71
72impl ComposeImagesCommand {
73 #[must_use]
75 pub fn new() -> Self {
76 Self {
77 executor: CommandExecutor::new(),
78 config: ComposeConfig::new(),
79 format: None,
80 quiet: false,
81 services: Vec::new(),
82 }
83 }
84
85 #[must_use]
87 pub fn format(mut self, format: ImagesFormat) -> Self {
88 self.format = Some(format);
89 self
90 }
91
92 #[must_use]
94 pub fn format_json(mut self) -> Self {
95 self.format = Some(ImagesFormat::Json);
96 self
97 }
98
99 #[must_use]
101 pub fn format_table(mut self) -> Self {
102 self.format = Some(ImagesFormat::Table);
103 self
104 }
105
106 #[must_use]
108 pub fn quiet(mut self) -> Self {
109 self.quiet = true;
110 self
111 }
112
113 #[must_use]
115 pub fn service(mut self, service: impl Into<String>) -> Self {
116 self.services.push(service.into());
117 self
118 }
119
120 #[must_use]
122 pub fn services<I, S>(mut self, services: I) -> Self
123 where
124 I: IntoIterator<Item = S>,
125 S: Into<String>,
126 {
127 self.services.extend(services.into_iter().map(Into::into));
128 self
129 }
130}
131
132impl Default for ComposeImagesCommand {
133 fn default() -> Self {
134 Self::new()
135 }
136}
137
138#[async_trait]
139impl DockerCommand for ComposeImagesCommand {
140 type Output = ComposeImagesResult;
141
142 fn get_executor(&self) -> &CommandExecutor {
143 &self.executor
144 }
145
146 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
147 &mut self.executor
148 }
149
150 fn build_command_args(&self) -> Vec<String> {
151 <Self as ComposeCommand>::build_command_args(self)
152 }
153
154 async fn execute(&self) -> Result<Self::Output> {
155 let args = <Self as ComposeCommand>::build_command_args(self);
156 let output = self.execute_command(args).await?;
157
158 let images = if matches!(self.format, Some(ImagesFormat::Json)) {
159 serde_json::from_str(&output.stdout).unwrap_or_default()
160 } else {
161 Vec::new()
162 };
163
164 Ok(ComposeImagesResult {
165 stdout: output.stdout,
166 stderr: output.stderr,
167 success: output.success,
168 images,
169 services: self.services.clone(),
170 })
171 }
172}
173
174impl ComposeCommand for ComposeImagesCommand {
175 fn get_config(&self) -> &ComposeConfig {
176 &self.config
177 }
178
179 fn get_config_mut(&mut self) -> &mut ComposeConfig {
180 &mut self.config
181 }
182
183 fn subcommand(&self) -> &'static str {
184 "images"
185 }
186
187 fn build_subcommand_args(&self) -> Vec<String> {
188 let mut args = Vec::new();
189
190 if let Some(format) = self.format {
191 args.push("--format".to_string());
192 args.push(format.to_string());
193 }
194
195 if self.quiet {
196 args.push("--quiet".to_string());
197 }
198
199 args.extend(self.services.clone());
200 args
201 }
202}
203
204impl ComposeImagesResult {
205 #[must_use]
207 pub fn success(&self) -> bool {
208 self.success
209 }
210
211 #[must_use]
213 pub fn images(&self) -> &[ImageInfo] {
214 &self.images
215 }
216
217 #[must_use]
219 pub fn services(&self) -> &[String] {
220 &self.services
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn test_compose_images_basic() {
230 let cmd = ComposeImagesCommand::new();
231 let args = cmd.build_subcommand_args();
232 assert!(args.is_empty());
233
234 let full_args = ComposeCommand::build_command_args(&cmd);
235 assert_eq!(full_args[0], "compose");
236 assert!(full_args.contains(&"images".to_string()));
237 }
238
239 #[test]
240 fn test_compose_images_with_format() {
241 let cmd = ComposeImagesCommand::new().format_json();
242 let args = cmd.build_subcommand_args();
243 assert!(args.contains(&"--format".to_string()));
244 assert!(args.contains(&"json".to_string()));
245 }
246
247 #[test]
248 fn test_compose_images_quiet() {
249 let cmd = ComposeImagesCommand::new().quiet();
250 let args = cmd.build_subcommand_args();
251 assert!(args.contains(&"--quiet".to_string()));
252 }
253
254 #[test]
255 fn test_compose_images_with_services() {
256 let cmd = ComposeImagesCommand::new().services(vec!["web", "db"]);
257 let args = cmd.build_subcommand_args();
258 assert!(args.contains(&"web".to_string()));
259 assert!(args.contains(&"db".to_string()));
260 }
261}