docker_image_pusher/image/
parser.rs

1//! Enhanced Docker image parsing with better error handling and progress reporting
2
3use crate::error::Result;
4use crate::logging::Logger;
5use crate::registry::tar_utils::TarUtils;
6use serde::{Deserialize, Serialize};
7use std::path::Path;
8use std::time::Instant;
9
10#[derive(Debug, Deserialize, Serialize, Clone)]
11pub struct LayerInfo {
12    pub digest: String,
13    pub size: u64,
14    pub media_type: String,
15    pub tar_path: String,
16    pub compressed_size: Option<u64>,
17    pub offset: Option<u64>,
18}
19
20#[derive(Debug, Deserialize, Serialize)]
21pub struct ImageConfig {
22    pub architecture: Option<String>,
23    pub os: Option<String>,
24    pub config: Option<serde_json::Value>,
25    pub rootfs: Option<serde_json::Value>,
26    pub history: Option<Vec<serde_json::Value>>,
27    pub created: Option<String>,
28    pub author: Option<String>,
29}
30
31#[derive(Debug, Clone)]
32pub struct ImageInfo {
33    pub config_digest: String,
34    pub config_size: u64,
35    pub layers: Vec<LayerInfo>,
36    pub total_size: u64,
37}
38
39pub struct ImageParser {
40    output: Logger,
41    large_layer_threshold: u64,
42}
43
44impl ImageParser {
45    pub fn new(output: Logger) -> Self {
46        Self {
47            output,
48            large_layer_threshold: 100 * 1024 * 1024, // 100MB
49        }
50    }
51
52    pub fn set_large_layer_threshold(&mut self, threshold: u64) {
53        self.large_layer_threshold = threshold;
54        self.output.detail(&format!(
55            "Large layer threshold set to {}",
56            self.output.format_size(threshold)
57        ));
58    }
59
60    /// Parse Docker image from tar file using TarUtils
61    pub async fn parse_tar_file(&mut self, tar_path: &Path) -> Result<ImageInfo> {
62        let start_time = Instant::now();
63        self.output.section("Parsing Docker Image");
64        self.output.info(&format!("Source: {}", tar_path.display()));
65
66        // Use TarUtils for parsing - single source of truth
67        let image_info = TarUtils::parse_image_info(tar_path)?;
68
69        let elapsed = start_time.elapsed();
70        self.output.success(&format!(
71            "Parsing completed in {} - {} layers, total size: {}",
72            self.output.format_duration(elapsed),
73            image_info.layers.len(),
74            self.output.format_size(image_info.total_size)
75        ));
76
77        if self.output.verbose {
78            self.print_image_summary(&image_info);
79        }
80
81        Ok(image_info)
82    }
83
84    fn print_image_summary(&self, image_info: &ImageInfo) {
85        let empty_layers_count = image_info.layers.iter().filter(|l| l.size == 0).count();
86        let large_layers_count = image_info
87            .layers
88            .iter()
89            .filter(|l| l.size > self.large_layer_threshold)
90            .count();
91
92        let items = vec![
93            ("Layers", image_info.layers.len().to_string()),
94            ("Empty Layers", empty_layers_count.to_string()),
95            (
96                "Large Layers",
97                format!(
98                    "{} (>{})",
99                    large_layers_count,
100                    self.output.format_size(self.large_layer_threshold)
101                ),
102            ),
103            ("Total Size", self.output.format_size(image_info.total_size)),
104            (
105                "Config Digest",
106                format!("{}...", &image_info.config_digest[..23]),
107            ),
108        ];
109
110        self.output.summary_kv("Image Information", &items);
111
112        if self.output.verbose {
113            self.output.subsection("Layer Details");
114            for (i, layer) in image_info.layers.iter().enumerate() {
115                let layer_type = if layer.size == 0 {
116                    " (EMPTY)"
117                } else if layer.size > self.large_layer_threshold {
118                    " (LARGE)"
119                } else {
120                    ""
121                };
122
123                self.output.detail(&format!(
124                    "Layer {}: {}... ({}){}",
125                    i + 1,
126                    &layer.digest[..23],
127                    self.output.format_size(layer.size),
128                    layer_type
129                ));
130            }
131        }
132    }
133}