docker_wrapper/command/manifest/
inspect.rs1use crate::command::{CommandExecutor, CommandOutput, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ManifestPlatform {
11 pub architecture: Option<String>,
13 pub os: Option<String>,
15 #[serde(rename = "os.version")]
17 pub os_version: Option<String>,
18 pub variant: Option<String>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct ManifestInfo {
25 #[serde(rename = "schemaVersion")]
27 pub schema_version: Option<i32>,
28 #[serde(rename = "mediaType")]
30 pub media_type: Option<String>,
31 pub digest: Option<String>,
33 pub size: Option<i64>,
35 pub platform: Option<ManifestPlatform>,
37 pub manifests: Option<Vec<ManifestInfo>>,
39 #[serde(skip)]
41 pub raw_json: String,
42}
43
44impl ManifestInfo {
45 fn parse(output: &CommandOutput) -> Self {
47 let stdout = output.stdout.trim();
48 if stdout.is_empty() {
49 return Self {
50 schema_version: None,
51 media_type: None,
52 digest: None,
53 size: None,
54 platform: None,
55 manifests: None,
56 raw_json: String::new(),
57 };
58 }
59
60 let mut info: ManifestInfo =
61 serde_json::from_str(stdout).unwrap_or_else(|_| ManifestInfo {
62 schema_version: None,
63 media_type: None,
64 digest: None,
65 size: None,
66 platform: None,
67 manifests: None,
68 raw_json: String::new(),
69 });
70 info.raw_json = stdout.to_string();
71 info
72 }
73}
74
75#[derive(Debug, Clone)]
103pub struct ManifestInspectCommand {
104 manifest_list: Option<String>,
106 manifest: String,
108 insecure: bool,
110 verbose: bool,
112 pub executor: CommandExecutor,
114}
115
116impl ManifestInspectCommand {
117 #[must_use]
123 pub fn new(manifest: impl Into<String>) -> Self {
124 Self {
125 manifest_list: None,
126 manifest: manifest.into(),
127 insecure: false,
128 verbose: false,
129 executor: CommandExecutor::new(),
130 }
131 }
132
133 #[must_use]
135 pub fn manifest_list(mut self, manifest_list: impl Into<String>) -> Self {
136 self.manifest_list = Some(manifest_list.into());
137 self
138 }
139
140 #[must_use]
142 pub fn insecure(mut self) -> Self {
143 self.insecure = true;
144 self
145 }
146
147 #[must_use]
149 pub fn verbose(mut self) -> Self {
150 self.verbose = true;
151 self
152 }
153
154 fn build_args(&self) -> Vec<String> {
156 let mut args = vec!["manifest".to_string(), "inspect".to_string()];
157
158 if self.insecure {
159 args.push("--insecure".to_string());
160 }
161
162 if self.verbose {
163 args.push("--verbose".to_string());
164 }
165
166 if let Some(ref list) = self.manifest_list {
167 args.push(list.clone());
168 }
169
170 args.push(self.manifest.clone());
171
172 args.extend(self.executor.raw_args.clone());
173
174 args
175 }
176}
177
178#[async_trait]
179impl DockerCommand for ManifestInspectCommand {
180 type Output = ManifestInfo;
181
182 fn get_executor(&self) -> &CommandExecutor {
183 &self.executor
184 }
185
186 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
187 &mut self.executor
188 }
189
190 fn build_command_args(&self) -> Vec<String> {
191 self.build_args()
192 }
193
194 async fn execute(&self) -> Result<Self::Output> {
195 let args = self.build_args();
196 let output = self.execute_command(args).await?;
197 Ok(ManifestInfo::parse(&output))
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn test_manifest_inspect_basic() {
207 let cmd = ManifestInspectCommand::new("myapp:latest");
208 let args = cmd.build_args();
209 assert_eq!(args, vec!["manifest", "inspect", "myapp:latest"]);
210 }
211
212 #[test]
213 fn test_manifest_inspect_with_list() {
214 let cmd = ManifestInspectCommand::new("myapp:latest-amd64").manifest_list("myapp:latest");
215 let args = cmd.build_args();
216 assert_eq!(
217 args,
218 vec!["manifest", "inspect", "myapp:latest", "myapp:latest-amd64"]
219 );
220 }
221
222 #[test]
223 fn test_manifest_inspect_with_insecure() {
224 let cmd = ManifestInspectCommand::new("myapp:latest").insecure();
225 let args = cmd.build_args();
226 assert!(args.contains(&"--insecure".to_string()));
227 }
228
229 #[test]
230 fn test_manifest_inspect_with_verbose() {
231 let cmd = ManifestInspectCommand::new("myapp:latest").verbose();
232 let args = cmd.build_args();
233 assert!(args.contains(&"--verbose".to_string()));
234 }
235
236 #[test]
237 fn test_manifest_inspect_all_options() {
238 let cmd = ManifestInspectCommand::new("myapp:latest")
239 .insecure()
240 .verbose();
241 let args = cmd.build_args();
242 assert!(args.contains(&"--insecure".to_string()));
243 assert!(args.contains(&"--verbose".to_string()));
244 }
245
246 #[test]
247 fn test_manifest_info_parse_empty() {
248 let output = CommandOutput {
249 stdout: String::new(),
250 stderr: String::new(),
251 exit_code: 0,
252 success: true,
253 };
254 let info = ManifestInfo::parse(&output);
255 assert!(info.schema_version.is_none());
256 }
257
258 #[test]
259 fn test_manifest_info_parse_json() {
260 let json = r#"{"schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json"}"#;
261 let output = CommandOutput {
262 stdout: json.to_string(),
263 stderr: String::new(),
264 exit_code: 0,
265 success: true,
266 };
267 let info = ManifestInfo::parse(&output);
268 assert_eq!(info.schema_version, Some(2));
269 assert_eq!(
270 info.media_type,
271 Some("application/vnd.docker.distribution.manifest.list.v2+json".to_string())
272 );
273 }
274}