push_from_cache_tar_demo/
push_from_cache_tar_demo.rs1use 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 let target_registry = "registry.cn-beijing.aliyuncs.com";
19 let target_repository = "yoce/cblt"; let target_reference = "yoce"; let cache_dir = ".cache_demo"; 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 check_cache_exists(cache_dir, target_repository, target_reference).await?;
39
40 println!("🔧 Creating ImageManager...");
42 let mut image_manager = ImageManager::new(Some(cache_dir), true)?;
43 println!("✅ ImageManager created successfully");
44
45 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) .with_verbose(true)
51 .build()?;
52 println!("✅ Registry Client built successfully");
53
54 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 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 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 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 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 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 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 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 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 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 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]; 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 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 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 let result = ImageManager::new(Some(".test_push_tar_cache"), false);
257 assert!(result.is_ok());
258
259 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 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 assert_ne!(manifest_mode.description(), tar_mode.description());
287
288 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}