intermodal_rs/cmd/image/inspect/
mod.rs

1//! Handling of 'inspect' subcommand of 'image' command
2
3use std::collections::HashMap;
4use std::io;
5use std::string::String;
6
7use serde::Serialize;
8
9use crate::cmd::image::ImageCommands;
10use crate::image::{oci::digest::Digest, transports};
11
12// We use references because, this will be generated from underlying 'image.inspect' struct.
13// which contains 'owned' values, For our case, the underlying struct will 'outlive' this.
14// We try to match the output as closely as 'skopeo inspect'
15#[derive(Serialize)]
16struct InspectOutput<'a> {
17    #[serde(rename = "Name", skip_serializing_if = "Option::is_none")]
18    name: Option<String>,
19
20    #[serde(rename = "Tag", skip_serializing_if = "Option::is_none")]
21    tag: Option<String>,
22
23    #[serde(rename = "Digest")]
24    digest: &'a str,
25
26    #[serde(rename = "RepoTags")]
27    repo_tags: &'a Vec<String>,
28
29    #[serde(rename = "Created")]
30    created: &'a str,
31
32    #[serde(rename = "DockerVersion")]
33    docker_version: &'a str,
34
35    #[serde(rename = "Labels")]
36    labels: &'a HashMap<String, String>,
37
38    #[serde(rename = "Architecture")]
39    architecture: &'a str,
40
41    #[serde(rename = "Os")]
42    os: &'a str,
43
44    #[serde(rename = "Layers")]
45    layers: &'a Vec<String>,
46
47    #[serde(rename = "Env")]
48    env: &'a Vec<String>,
49}
50
51/// Run the 'inspect' subcommand asynchronously.
52pub async fn run_subcmd_inspect(cmd: ImageCommands) -> io::Result<()> {
53    if let ImageCommands::Inspect {
54        name: ref image_name,
55        config,
56        raw,
57    } = cmd
58    {
59        log::debug!("Image Name: {}", image_name);
60
61        if let Ok(image_ref) = transports::parse_image_name(image_name) {
62            log::debug!(
63                "Valid Reference found! {}",
64                image_ref.string_within_transport()
65            );
66
67            let mut image = image_ref.new_image()?;
68
69            log::debug!("calling get_manifest");
70            let manifest = image.manifest().await?;
71
72            let digeststr = Digest::from_bytes(&manifest.manifest).to_string();
73
74            if raw {
75                println!(
76                    "Manifest for {}: {}",
77                    image_name,
78                    std::str::from_utf8(&manifest.manifest).unwrap()
79                );
80            }
81
82            if config {
83                log::debug!("Getting Config for the image.");
84                if raw {
85                    println!(
86                        "Config Blob for Image '{}' : {}",
87                        image_name,
88                        std::str::from_utf8(&image.config_blob().await?).unwrap()
89                    );
90                } else {
91                    let inspect_data = image.inspect().await?;
92                    let tags = image.source_ref().get_repo_tags().await?;
93                    log::debug!("Tags: {:#?}", tags);
94
95                    let docker_ref = image_ref.docker_reference();
96
97                    let mut reference_name: Option<String> = None;
98                    let mut reference_tag: Option<String> = None;
99                    if docker_ref.is_some() {
100                        reference_name = Some(docker_ref.as_ref().unwrap().name());
101                        reference_tag = Some(docker_ref.as_ref().unwrap().tag());
102                    }
103
104                    let output = InspectOutput {
105                        name: reference_name,
106                        tag: reference_tag,
107                        digest: &digeststr,
108                        repo_tags: &tags,
109                        created: &inspect_data.created,
110                        docker_version: &inspect_data.docker_version,
111                        labels: &inspect_data.labels,
112                        architecture: &inspect_data.architecture,
113                        os: &inspect_data.os,
114                        layers: &inspect_data.layers,
115                        env: &inspect_data.env,
116                    };
117                    println!("{}", serde_json::to_string_pretty(&output).unwrap());
118                }
119            }
120
121            Ok(())
122        } else {
123            let err = format!("Invalid Image Name: {}", image_name);
124            log::error!("{}", &err);
125            Err(io::Error::new(io::ErrorKind::InvalidInput, err))
126        }
127    } else {
128        let err = format!("Invalid Command: {:?}", cmd);
129        log::error!("{}", &err);
130        Err(io::Error::new(io::ErrorKind::InvalidInput, err))
131    }
132}
133
134#[cfg(test)]
135mod tests {
136
137    use super::*;
138    use crate::image::transports;
139
140    #[test]
141    fn test_subcommand_inspect_no_name() {
142        let m = add_subcmd_inspect().get_matches_from_safe(vec!["inspect"]);
143        assert!(m.is_err());
144    }
145
146    #[test]
147    fn test_subcommand_image_name() {
148        let m = add_subcmd_inspect()
149            .get_matches_from_safe(vec!["inspect", "fedora"])
150            .unwrap();
151
152        assert_eq!(m.value_of("name"), Some("fedora"));
153    }
154
155    #[test]
156    fn test_unsupported_flag() {
157        let m = add_subcmd_inspect().get_matches_from_safe(vec!["inspect", "--war"]);
158
159        assert!(m.is_err());
160    }
161
162    #[tokio::test]
163    async fn test_subcommand_run_success() {
164        transports::init_transports();
165        let m = add_subcmd_inspect()
166            .get_matches_from_safe(vec!["inspec", "docker://fedora"])
167            .unwrap();
168        let name = m.value_of("name").unwrap();
169
170        assert_eq!(name, "docker://fedora");
171
172        let result = run_subcmd_inspect(&m).await;
173        assert!(result.is_ok());
174    }
175}