push_from_cache_tar_demo/
push_from_cache_tar_demo.rs

1//! Example: Push from Cache using Tar - 从缓存推送镜像(使用tar引用)
2//!
3//! 此示例展示如何从本地缓存推送Docker镜像到远程registry(使用tar文件作为引用)。
4//! 这是4种核心操作模式中的第4种:PushFromCacheUsingTar
5//! 注意:此模式实际上与模式3相同,因为缓存格式是统一的
6
7use docker_image_pusher::{
8    AuthConfig, cli::operation_mode::OperationMode, error::Result,
9    image::image_manager::ImageManager, registry::RegistryClientBuilder,
10};
11
12#[tokio::main]
13async fn main() -> Result<()> {
14    println!("📦 Docker Image Pusher - Push from Cache (Tar Reference) Demo");
15    println!("================================================================");
16
17    // 配置参数
18    let target_registry = "registry.cn-beijing.aliyuncs.com";
19    let target_repository = "yoce/cblt"; // 推送到相同的repository
20    let target_reference = "yoce"; // 推送到相同的reference
21    let cache_dir = ".cache_demo"; // 使用已有的缓存
22
23    println!("📥 Configuration:");
24    println!("  Cache Directory: {}", cache_dir);
25    println!("  Target Registry: {}", target_registry);
26    println!("  Target Repository: {}", target_repository);
27    println!("  Target Reference: {}", target_reference);
28    println!();
29
30    println!(
31        "ℹ️  Note: This mode pushes cached image {}/{} to target registry",
32        target_repository, target_reference
33    );
34    println!("   The implementation is identical to manifest-based push");
35    println!();
36
37    // 1. 检查缓存是否存在
38    check_cache_exists(cache_dir, target_repository, target_reference).await?;
39
40    // 2. 创建 ImageManager
41    println!("🔧 Creating ImageManager...");
42    let mut image_manager = ImageManager::new(Some(cache_dir), true)?;
43    println!("✅ ImageManager created successfully");
44
45    // 3. 构建 Registry Client
46    println!("🌐 Building Registry Client for Aliyun registry...");
47    let client = RegistryClientBuilder::new(format!("https://{}", target_registry))
48        .with_timeout(3600)
49        .with_skip_tls(false) // Aliyun uses TLS
50        .with_verbose(true)
51        .build()?;
52    println!("✅ Registry Client built successfully");
53
54    // 4. 获取认证(Aliyun credentials)
55    println!("🔐 Authenticating with Aliyun registry...");
56    let auth_config = AuthConfig::new("canny_best@163.com".to_string(), "ra201222".to_string());
57    let auth_token = client
58        .authenticate_for_repository(&auth_config, target_repository)
59        .await?;
60    println!(
61        "✅ Authentication successful with user: {}",
62        auth_config.username
63    );
64    println!("🔑 Token scope: repository:{}:pull,push", target_repository);
65
66    // 5. 定义操作模式 - 使用 tar 引用方式推送(实际上与manifest模式相同)
67    let mode = OperationMode::PushFromCacheUsingTar {
68        repository: target_repository.to_string(),
69        reference: target_reference.to_string(),
70    };
71
72    println!("📋 Operation Mode: {}", mode.description());
73    println!("🔄 Internal Process: Reading from unified cache format (same as manifest mode)");
74    println!();
75
76    // 6. 执行推送操作
77    println!("🔄 Starting push from cache operation...");
78    match image_manager
79        .execute_operation(&mode, Some(&client), auth_token.as_deref())
80        .await
81    {
82        Ok(()) => {
83            println!("✅ Push from cache (tar reference) operation completed successfully!");
84            println!();
85            println!(
86                "🎯 Image pushed to: {}/{}/{}",
87                target_registry, target_repository, target_reference
88            );
89            println!("🔍 You can now verify the upload:");
90            println!(
91                "   curl http://{}/v2/{}/manifests/{}",
92                target_registry, target_repository, target_reference
93            );
94            println!(
95                "   curl http://{}/v2/{}/tags/list",
96                target_registry, target_repository
97            );
98
99            // 显示模式差异说明
100            show_mode_explanation();
101        }
102        Err(e) => {
103            eprintln!("❌ Push from cache (tar reference) operation failed: {}", e);
104            eprintln!("💡 Possible solutions:");
105            eprintln!(
106                "   - Check if target registry is running: docker run -d -p 5000:5000 registry:2"
107            );
108            eprintln!("   - Verify cache contains the source image");
109            eprintln!("   - Check network connectivity to target registry");
110            std::process::exit(1);
111        }
112    }
113
114    // 7. 验证推送结果并对比两种模式
115    verify_push_result_and_compare(&client, target_repository, target_reference, &auth_token).await;
116
117    Ok(())
118}
119
120async fn check_cache_exists(cache_dir: &str, repository: &str, reference: &str) -> Result<()> {
121    println!("🔍 Checking cache for source image...");
122
123    // 检查缓存目录
124    if !std::path::Path::new(cache_dir).exists() {
125        eprintln!("❌ Cache directory not found: {}", cache_dir);
126        eprintln!("💡 Please run extract_and_cache_demo.rs or pull_and_cache_demo.rs first");
127        std::process::exit(1);
128    }
129
130    // 检查索引文件
131    let index_path = format!("{}/index.json", cache_dir);
132    if !std::path::Path::new(&index_path).exists() {
133        eprintln!("❌ Cache index not found: {}", index_path);
134        eprintln!("💡 Cache appears to be empty or corrupted");
135        std::process::exit(1);
136    }
137
138    // 检查特定镜像的manifest
139    let manifest_path = format!("{}/manifests/{}/{}", cache_dir, repository, reference);
140    if !std::path::Path::new(&manifest_path).exists() {
141        eprintln!("❌ Manifest not found in cache: {}", manifest_path);
142        eprintln!("💡 Available images in cache:");
143
144        // 列出缓存中的镜像
145        if let Ok(index_content) = std::fs::read_to_string(&index_path) {
146            if let Ok(index) = serde_json::from_str::<serde_json::Value>(&index_content) {
147                if let Some(obj) = index.as_object() {
148                    for key in obj.keys() {
149                        eprintln!("   - {}", key);
150                    }
151                }
152            }
153        }
154        std::process::exit(1);
155    }
156
157    println!(
158        "✅ Source image found in cache: {}/{}",
159        repository, reference
160    );
161    Ok(())
162}
163
164fn show_mode_explanation() {
165    println!();
166    println!("📚 Mode Comparison: Manifest vs Tar Reference");
167    println!("===============================================");
168    println!("🎯 PushFromCacheUsingManifest:");
169    println!("   - Directly references cached manifest");
170    println!("   - Fastest approach for cache-based pushes");
171    println!("   - Standard Docker Registry API workflow");
172    println!();
173    println!("📦 PushFromCacheUsingTar:");
174    println!("   - Uses tar file as reference but reads from cache");
175    println!("   - Unified cache format makes both modes identical");
176    println!("   - Maintains compatibility with tar-based workflows");
177    println!();
178    println!("💡 Key Insight: Both modes use the same optimized cache format!");
179    println!("   The cache abstracts away the original source (manifest vs tar)");
180}
181
182async fn verify_push_result_and_compare(
183    client: &docker_image_pusher::registry::RegistryClient,
184    repository: &str,
185    reference: &str,
186    auth_token: &Option<String>,
187) {
188    println!();
189    println!("🔍 Verifying push result...");
190
191    // 检查manifest是否存在
192    match client
193        .pull_manifest(repository, reference, auth_token)
194        .await
195    {
196        Ok(manifest_data) => {
197            println!("✅ Manifest successfully retrieved from target registry");
198            println!("📊 Manifest size: {} bytes", manifest_data.len());
199
200            // 解析manifest获取详细信息
201            if let Ok(manifest) = serde_json::from_slice::<serde_json::Value>(&manifest_data) {
202                println!("📄 Manifest details:");
203
204                if let Some(media_type) = manifest.get("mediaType").and_then(|v| v.as_str()) {
205                    println!("   - Media Type: {}", media_type);
206                }
207
208                if let Some(layers) = manifest.get("layers").and_then(|v| v.as_array()) {
209                    println!("   - Number of layers: {}", layers.len());
210                    for (i, layer) in layers.iter().enumerate() {
211                        if let Some(digest) = layer.get("digest").and_then(|v| v.as_str()) {
212                            let short_digest = &digest[7..19]; // sha256: 前缀后的前12个字符
213                            println!("   - Layer {}: {}...", i + 1, short_digest);
214                        }
215                    }
216                }
217
218                if let Some(config) = manifest.get("config") {
219                    if let Some(digest) = config.get("digest").and_then(|v| v.as_str()) {
220                        let short_digest = &digest[7..19];
221                        println!("   - Config: {}...", short_digest);
222                    }
223                }
224            }
225        }
226        Err(e) => {
227            eprintln!("⚠️  Could not verify manifest: {}", e);
228        }
229    }
230
231    // 尝试检查标签列表
232    match client.list_tags(repository, auth_token).await {
233        Ok(tags) => {
234            println!("🏷️  Available tags in repository: {:?}", tags);
235        }
236        Err(e) => {
237            eprintln!("⚠️  Could not list tags: {}", e);
238        }
239    }
240
241    // 显示性能对比
242    println!();
243    println!("⚡ Performance Notes:");
244    println!("   - Both manifest and tar reference modes use the same cache");
245    println!("   - No performance difference between the two approaches");
246    println!("   - Cache format optimized for fast reads and minimal memory usage");
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    #[tokio::test]
254    async fn test_push_from_cache_tar_creation() {
255        // 测试 ImageManager 创建
256        let result = ImageManager::new(Some(".test_push_tar_cache"), false);
257        assert!(result.is_ok());
258
259        // 清理测试缓存
260        let _ = std::fs::remove_dir_all(".test_push_tar_cache");
261    }
262
263    #[test]
264    fn test_operation_mode_description() {
265        let mode = OperationMode::PushFromCacheUsingTar {
266            repository: "demo/test".to_string(),
267            reference: "v1.0".to_string(),
268        };
269        assert_eq!(mode.description(), "Push from cache using tar reference");
270    }
271
272    #[test]
273    fn test_mode_equivalence() {
274        // 测试两种推送模式的描述不同但实现相同
275        let manifest_mode = OperationMode::PushFromCacheUsingManifest {
276            repository: "test/repo".to_string(),
277            reference: "latest".to_string(),
278        };
279
280        let tar_mode = OperationMode::PushFromCacheUsingTar {
281            repository: "test/repo".to_string(),
282            reference: "latest".to_string(),
283        };
284
285        // 描述应该不同
286        assert_ne!(manifest_mode.description(), tar_mode.description());
287
288        // 但repository和reference应该相同
289        if let (
290            OperationMode::PushFromCacheUsingManifest {
291                repository: r1,
292                reference: ref1,
293            },
294            OperationMode::PushFromCacheUsingTar {
295                repository: r2,
296                reference: ref2,
297            },
298        ) = (&manifest_mode, &tar_mode)
299        {
300            assert_eq!(r1, r2);
301            assert_eq!(ref1, ref2);
302        }
303    }
304}