docker_image_pusher/
tar_utils.rs

1//! Shared tar processing utilities to eliminate duplication
2
3use crate::error::{Result, PusherError};
4use std::path::Path;
5use std::io::Read;
6use std::fs::File;
7use tar::Archive;
8
9/// Tar processing utilities for layer extraction and offset calculation
10pub struct TarUtils;
11
12impl TarUtils {
13    /// Extract layer data from tar archive
14    pub fn extract_layer_data(tar_path: &Path, layer_path: &str) -> Result<Vec<u8>> {
15        let file = File::open(tar_path)
16            .map_err(|e| PusherError::Io(format!("Failed to open tar file: {}", e)))?;
17        let mut archive = Archive::new(file);
18        archive.set_ignore_zeros(true);
19
20        for entry_result in archive.entries()
21            .map_err(|e| PusherError::ImageParsing(format!("Failed to read tar entries: {}", e)))? {
22            let mut entry = entry_result
23                .map_err(|e| PusherError::ImageParsing(format!("Failed to read tar entry: {}", e)))?;
24
25            let path = entry.path()
26                .map_err(|e| PusherError::ImageParsing(format!("Failed to read entry path: {}", e)))?
27                .to_string_lossy()
28                .to_string();
29
30            if path == layer_path {
31                let mut data = Vec::new();
32                entry.read_to_end(&mut data)
33                    .map_err(|e| PusherError::ImageParsing(format!("Failed to read layer data: {}", e)))?;
34                return Ok(data);
35            }
36        }
37
38        Err(PusherError::ImageParsing(format!("Layer '{}' not found in tar archive", layer_path)))
39    }
40
41    /// Find the offset of a layer within the tar archive
42    pub fn find_layer_offset(tar_path: &Path, layer_path: &str) -> Result<u64> {
43        let file = File::open(tar_path)
44            .map_err(|e| PusherError::Io(format!("Failed to open tar file: {}", e)))?;
45        let mut archive = Archive::new(file);
46        archive.set_ignore_zeros(true);
47
48        let mut current_offset = 0u64;
49
50        for entry_result in archive.entries()
51            .map_err(|e| PusherError::ImageParsing(format!("Failed to read tar entries: {}", e)))? {
52            let entry = entry_result
53                .map_err(|e| PusherError::ImageParsing(format!("Failed to read tar entry: {}", e)))?;
54
55            let path = entry.path()
56                .map_err(|e| PusherError::ImageParsing(format!("Failed to read entry path: {}", e)))?
57                .to_string_lossy()
58                .to_string();
59
60            if path == layer_path {
61                return Ok(current_offset);
62            }
63
64            // Calculate entry size including headers (simplified calculation)
65            let size = entry.header().size()
66                .map_err(|e| PusherError::ImageParsing(format!("Failed to read entry size: {}", e)))?;
67            
68            current_offset += size + 512; // 512 bytes for TAR header (simplified)
69        }
70
71        Err(PusherError::ImageParsing(format!("Layer '{}' not found for offset calculation", layer_path)))
72    }
73
74    /// Get a list of all entries in the tar archive with their sizes
75    pub fn list_tar_entries(tar_path: &Path) -> Result<Vec<(String, u64)>> {
76        let file = File::open(tar_path)
77            .map_err(|e| PusherError::Io(format!("Failed to open tar file: {}", e)))?;
78        let mut archive = Archive::new(file);
79        archive.set_ignore_zeros(true);
80
81        let mut entries = Vec::new();
82
83        for entry_result in archive.entries()
84            .map_err(|e| PusherError::ImageParsing(format!("Failed to read tar entries: {}", e)))? {
85            let entry = entry_result
86                .map_err(|e| PusherError::ImageParsing(format!("Failed to read tar entry: {}", e)))?;
87
88            let path = entry.path()
89                .map_err(|e| PusherError::ImageParsing(format!("Failed to read entry path: {}", e)))?
90                .to_string_lossy()
91                .to_string();
92
93            let size = entry.header().size()
94                .map_err(|e| PusherError::ImageParsing(format!("Failed to read entry size: {}", e)))?;
95
96            entries.push((path, size));
97        }
98
99        Ok(entries)
100    }
101
102    /// Validate that a tar archive is readable and properly formatted
103    pub fn validate_tar_archive(tar_path: &Path) -> Result<()> {
104        let file = File::open(tar_path)
105            .map_err(|e| PusherError::Io(format!("Failed to open tar file: {}", e)))?;
106        let mut archive = Archive::new(file);
107        archive.set_ignore_zeros(true);
108
109        // Try to read the first few entries to validate format
110        let mut entry_count = 0;
111        for entry_result in archive.entries()
112            .map_err(|e| PusherError::ImageParsing(format!("Failed to read tar entries: {}", e)))? {
113            let entry = entry_result
114                .map_err(|e| PusherError::ImageParsing(format!("Failed to read tar entry: {}", e)))?;
115
116            // Validate that we can read the path
117            let _ = entry.path()
118                .map_err(|e| PusherError::ImageParsing(format!("Failed to read entry path: {}", e)))?;
119
120            entry_count += 1;
121            
122            // Only validate the first 10 entries for performance
123            if entry_count >= 10 {
124                break;
125            }
126        }
127
128        if entry_count == 0 {
129            return Err(PusherError::ImageParsing("Tar archive appears to be empty".to_string()));
130        }
131
132        Ok(())
133    }
134}