1use 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 let cache_dir = ".cache_comprehensive_demo";
31 let target_registry =
32 env::var("TARGET_REGISTRY").unwrap_or_else(|_| "localhost:5000".to_string());
33
34 let _ = std::fs::remove_dir_all(cache_dir);
36
37 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 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 println!("📥 MODE 1: Pull and Cache from Registry");
61 println!("========================================");
62
63 let pull_start = Instant::now();
64
65 let mut image_manager = ImageManager::new(Some(cache_dir), true)?;
67
68 let source_client = RegistryClientBuilder::new("https://registry-1.docker.io".to_string())
70 .with_timeout(3600)
71 .with_verbose(true)
72 .build()?;
73
74 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 println!("📦 MODE 2: Extract and Cache from Tar File");
93 println!("===========================================");
94
95 let extract_start = Instant::now();
96
97 let tar_file = "nginx-demo.tar";
99 create_demo_tar_file(tar_file, "nginx:alpine").await?;
100
101 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 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 println!("🚀 MODE 3: Push from Cache using Manifest");
136 println!("==========================================");
137
138 let push_manifest_start = Instant::now();
139
140 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 println!("📦 MODE 4: Push from Cache using Tar Reference");
162 println!("===============================================");
163
164 let push_tar_start = Instant::now();
165
166 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 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 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 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 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 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 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 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 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 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 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 for mode in modes {
408 assert!(!mode.description().is_empty());
409 }
410 }
411}