comprehensive_demo/
comprehensive_demo.rs

1//! Comprehensive Demo - 完整的4种操作模式演示
2//!
3//! 此示例展示Docker Image Pusher的完整工作流程,包括4种核心操作模式:
4//! 1. PullAndCache - 从registry拉取并缓存
5//! 2. ExtractAndCache - 从tar文件提取并缓存
6//! 3. PushFromCacheUsingManifest - 从缓存推送(manifest方式)
7//! 4. PushFromCacheUsingTar - 从缓存推送(tar引用方式)
8
9use docker_image_pusher::{
10    cli::operation_mode::OperationMode, error::Result, image::image_manager::ImageManager,
11    registry::RegistryClientBuilder,
12};
13use std::env;
14use std::time::Instant;
15
16#[tokio::main]
17async fn main() -> Result<()> {
18    println!("🚀 Docker Image Pusher - Comprehensive 4-Mode Demo");
19    println!("===================================================");
20    println!("This demo showcases all 4 core operation modes:");
21    println!("1. Pull and Cache from registry");
22    println!("2. Extract and Cache from tar file");
23    println!("3. Push from Cache using Manifest");
24    println!("4. Push from Cache using Tar reference");
25    println!();
26
27    let start_time = Instant::now();
28
29    // 配置
30    let cache_dir = ".cache_comprehensive_demo";
31    let target_registry =
32        env::var("TARGET_REGISTRY").unwrap_or_else(|_| "localhost:5000".to_string());
33
34    // 清理之前的演示缓存
35    let _ = std::fs::remove_dir_all(cache_dir);
36
37    // 运行完整演示
38    match run_comprehensive_demo(cache_dir, &target_registry).await {
39        Ok(()) => {
40            let duration = start_time.elapsed();
41            println!("🎉 Comprehensive demo completed successfully!");
42            println!("⏱️  Total time: {:.2}s", duration.as_secs_f64());
43
44            // 显示最终统计
45            show_final_statistics(cache_dir).await;
46        }
47        Err(e) => {
48            eprintln!("❌ Demo failed: {}", e);
49            std::process::exit(1);
50        }
51    }
52
53    Ok(())
54}
55
56async fn run_comprehensive_demo(cache_dir: &str, target_registry: &str) -> Result<()> {
57    // =========================================================================
58    // 模式1: PullAndCache - 从registry拉取并缓存
59    // =========================================================================
60    println!("📥 MODE 1: Pull and Cache from Registry");
61    println!("========================================");
62
63    let pull_start = Instant::now();
64
65    // 创建ImageManager
66    let mut image_manager = ImageManager::new(Some(cache_dir), true)?;
67
68    // 构建registry client
69    let source_client = RegistryClientBuilder::new("https://registry-1.docker.io".to_string())
70        .with_timeout(3600)
71        .with_verbose(true)
72        .build()?;
73
74    // 定义拉取操作
75    let pull_mode = OperationMode::PullAndCache {
76        repository: "library/hello-world".to_string(),
77        reference: "latest".to_string(),
78    };
79
80    println!("🔄 Executing: {}", pull_mode.description());
81    image_manager
82        .execute_operation(&pull_mode, Some(&source_client), None)
83        .await?;
84
85    let pull_duration = pull_start.elapsed();
86    println!("✅ Mode 1 completed in {:.2}s", pull_duration.as_secs_f64());
87    println!();
88
89    // =========================================================================
90    // 模式2: ExtractAndCache - 从tar文件提取并缓存
91    // =========================================================================
92    println!("📦 MODE 2: Extract and Cache from Tar File");
93    println!("===========================================");
94
95    let extract_start = Instant::now();
96
97    // 创建示例tar文件
98    let tar_file = "nginx-demo.tar";
99    create_demo_tar_file(tar_file, "nginx:alpine").await?;
100
101    // 定义提取操作
102    let extract_mode = OperationMode::ExtractAndCache {
103        tar_file: tar_file.to_string(),
104        repository: "local/nginx".to_string(),
105        reference: "alpine".to_string(),
106    };
107
108    println!("🔄 Executing: {}", extract_mode.description());
109    image_manager
110        .execute_operation(&extract_mode, None, None)
111        .await?;
112
113    let extract_duration = extract_start.elapsed();
114    println!(
115        "✅ Mode 2 completed in {:.2}s",
116        extract_duration.as_secs_f64()
117    );
118    println!();
119
120    // =========================================================================
121    // 准备目标registry客户端
122    // =========================================================================
123    println!("🌐 Setting up target registry...");
124    ensure_target_registry_running(target_registry).await;
125
126    let target_client = RegistryClientBuilder::new(format!("http://{}", target_registry))
127        .with_timeout(3600)
128        .with_skip_tls(true)
129        .with_verbose(true)
130        .build()?;
131
132    // =========================================================================
133    // 模式3: PushFromCacheUsingManifest - 从缓存推送(manifest方式)
134    // =========================================================================
135    println!("🚀 MODE 3: Push from Cache using Manifest");
136    println!("==========================================");
137
138    let push_manifest_start = Instant::now();
139
140    // 定义推送操作(manifest方式)
141    let push_manifest_mode = OperationMode::PushFromCacheUsingManifest {
142        repository: "demo/hello-world-manifest".to_string(),
143        reference: "v1.0".to_string(),
144    };
145
146    println!("🔄 Executing: {}", push_manifest_mode.description());
147    image_manager
148        .execute_operation(&push_manifest_mode, Some(&target_client), None)
149        .await?;
150
151    let push_manifest_duration = push_manifest_start.elapsed();
152    println!(
153        "✅ Mode 3 completed in {:.2}s",
154        push_manifest_duration.as_secs_f64()
155    );
156    println!();
157
158    // =========================================================================
159    // 模式4: PushFromCacheUsingTar - 从缓存推送(tar引用方式)
160    // =========================================================================
161    println!("📦 MODE 4: Push from Cache using Tar Reference");
162    println!("===============================================");
163
164    let push_tar_start = Instant::now();
165
166    // 定义推送操作(tar引用方式)
167    let push_tar_mode = OperationMode::PushFromCacheUsingTar {
168        repository: "demo/nginx-tar-ref".to_string(),
169        reference: "alpine".to_string(),
170    };
171
172    println!("🔄 Executing: {}", push_tar_mode.description());
173    image_manager
174        .execute_operation(&push_tar_mode, Some(&target_client), None)
175        .await?;
176
177    let push_tar_duration = push_tar_start.elapsed();
178    println!(
179        "✅ Mode 4 completed in {:.2}s",
180        push_tar_duration.as_secs_f64()
181    );
182    println!();
183
184    // 验证所有推送结果
185    verify_all_pushes(&target_client, target_registry).await;
186
187    Ok(())
188}
189
190async fn create_demo_tar_file(tar_file: &str, image: &str) -> Result<()> {
191    if std::path::Path::new(tar_file).exists() {
192        println!("✅ Using existing tar file: {}", tar_file);
193        return Ok(());
194    }
195
196    println!("🛠️  Creating demo tar file: {}", tar_file);
197
198    // 尝试拉取镜像并保存为tar
199    let pull_output = tokio::process::Command::new("docker")
200        .args(&["pull", image])
201        .output()
202        .await;
203
204    if let Ok(result) = pull_output {
205        if result.status.success() {
206            let save_output = tokio::process::Command::new("docker")
207                .args(&["save", image, "-o", tar_file])
208                .output()
209                .await;
210
211            if let Ok(save_result) = save_output {
212                if save_result.status.success() {
213                    println!("✅ Tar file created: {}", tar_file);
214                    return Ok(());
215                }
216            }
217        }
218    }
219
220    eprintln!("⚠️  Could not create tar file, using alternative approach");
221    Ok(())
222}
223
224async fn ensure_target_registry_running(registry: &str) {
225    println!("🔍 Checking if target registry is running...");
226
227    // 尝试连接到registry
228    let client = reqwest::Client::new();
229    let url = format!("http://{}/v2/", registry);
230
231    match client.get(&url).send().await {
232        Ok(response) => {
233            if response.status().is_success() {
234                println!("✅ Target registry is running: {}", registry);
235            } else {
236                println!("⚠️  Registry responded with status: {}", response.status());
237            }
238        }
239        Err(_) => {
240            println!("❌ Target registry not accessible: {}", registry);
241            println!("💡 To start a local registry:");
242            println!("   docker run -d -p 5000:5000 --name registry registry:2");
243            println!("   Continuing with demo anyway...");
244        }
245    }
246}
247
248async fn verify_all_pushes(client: &docker_image_pusher::registry::RegistryClient, registry: &str) {
249    println!("🔍 Verifying all push operations...");
250    println!("=====================================");
251
252    let test_repositories = vec![
253        ("demo/hello-world-manifest", "v1.0"),
254        ("demo/nginx-tar-ref", "alpine"),
255    ];
256
257    for (repo, tag) in test_repositories {
258        println!("📋 Checking {}/{}...", repo, tag);
259
260        match client.pull_manifest(repo, tag, &None).await {
261            Ok(manifest_data) => {
262                println!("   ✅ Manifest found ({} bytes)", manifest_data.len());
263
264                // 解析manifest获取层信息
265                if let Ok(manifest) = serde_json::from_slice::<serde_json::Value>(&manifest_data) {
266                    if let Some(layers) = manifest.get("layers").and_then(|v| v.as_array()) {
267                        println!("   📦 Layers: {}", layers.len());
268                    }
269                }
270            }
271            Err(e) => {
272                println!("   ❌ Manifest not found: {}", e);
273            }
274        }
275
276        // 检查标签列表
277        match client.list_tags(repo, &None).await {
278            Ok(tags) => {
279                println!("   🏷️  Tags: {:?}", tags);
280            }
281            Err(_) => {
282                println!("   ⚠️  Could not list tags");
283            }
284        }
285
286        println!();
287    }
288
289    println!("🌐 Registry endpoints to test manually:");
290    println!("   curl http://{}/v2/_catalog", registry);
291    println!(
292        "   curl http://{}/v2/demo/hello-world-manifest/tags/list",
293        registry
294    );
295    println!(
296        "   curl http://{}/v2/demo/nginx-tar-ref/tags/list",
297        registry
298    );
299}
300
301async fn show_final_statistics(cache_dir: &str) {
302    println!();
303    println!("📊 Final Demo Statistics");
304    println!("========================");
305
306    // 缓存统计
307    if let Ok(entries) = std::fs::read_dir(format!("{}/blobs/sha256", cache_dir)) {
308        let blob_count = entries.count();
309        println!("📦 Total cached blobs: {}", blob_count);
310    }
311
312    // 缓存的镜像数量
313    if let Ok(index_content) = std::fs::read_to_string(format!("{}/index.json", cache_dir)) {
314        if let Ok(index) = serde_json::from_str::<serde_json::Value>(&index_content) {
315            if let Some(obj) = index.as_object() {
316                println!("🖼️  Cached images: {}", obj.len());
317                for key in obj.keys() {
318                    println!("   - {}", key);
319                }
320            }
321        }
322    }
323
324    // 计算缓存目录大小
325    if let Ok(metadata) = std::fs::metadata(cache_dir) {
326        if metadata.is_dir() {
327            if let Ok(_entries) = std::fs::read_dir(cache_dir) {
328                let mut total_size = 0u64;
329                count_dir_size(&format!("{}/blobs", cache_dir), &mut total_size);
330                println!(
331                    "💾 Cache size: {} bytes ({:.2} MB)",
332                    total_size,
333                    total_size as f64 / 1024.0 / 1024.0
334                );
335            }
336        }
337    }
338
339    println!();
340    println!("🎯 Demo Results Summary:");
341    println!("   ✅ Mode 1 (Pull and Cache): SUCCESS");
342    println!("   ✅ Mode 2 (Extract and Cache): SUCCESS");
343    println!("   ✅ Mode 3 (Push from Cache - Manifest): SUCCESS");
344    println!("   ✅ Mode 4 (Push from Cache - Tar Ref): SUCCESS");
345    println!();
346    println!(
347        "🧹 Cleanup: Run 'rm -rf {}' to remove demo cache",
348        cache_dir
349    );
350}
351
352fn count_dir_size(dir: &str, total: &mut u64) {
353    if let Ok(entries) = std::fs::read_dir(dir) {
354        for entry in entries.flatten() {
355            if let Ok(metadata) = entry.metadata() {
356                if metadata.is_file() {
357                    *total += metadata.len();
358                } else if metadata.is_dir() {
359                    count_dir_size(&entry.path().to_string_lossy(), total);
360                }
361            }
362        }
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369
370    #[tokio::test]
371    async fn test_comprehensive_demo_setup() {
372        // 测试基本组件创建
373        let result = ImageManager::new(Some(".test_comprehensive"), false);
374        assert!(result.is_ok());
375
376        let client_result =
377            RegistryClientBuilder::new("https://registry-1.docker.io".to_string()).build();
378        assert!(client_result.is_ok());
379
380        // 清理
381        let _ = std::fs::remove_dir_all(".test_comprehensive");
382    }
383
384    #[test]
385    fn test_all_operation_modes() {
386        let modes = vec![
387            OperationMode::PullAndCache {
388                repository: "test/repo".to_string(),
389                reference: "latest".to_string(),
390            },
391            OperationMode::ExtractAndCache {
392                tar_file: "test.tar".to_string(),
393                repository: "local/test".to_string(),
394                reference: "latest".to_string(),
395            },
396            OperationMode::PushFromCacheUsingManifest {
397                repository: "demo/test".to_string(),
398                reference: "v1.0".to_string(),
399            },
400            OperationMode::PushFromCacheUsingTar {
401                repository: "demo/test-tar".to_string(),
402                reference: "v1.0".to_string(),
403            },
404        ];
405
406        // 验证所有模式都有描述
407        for mode in modes {
408            assert!(!mode.description().is_empty());
409        }
410    }
411}