push_from_cache_manifest_demo/
push_from_cache_manifest_demo.rs

1//! Example: Push from Cache using Manifest - 从缓存推送镜像(使用manifest)
2//!
3//! 此示例展示如何从本地缓存推送Docker镜像到远程registry。
4//! 这是4种核心操作模式中的第3种:PushFromCacheUsingManifest
5
6use docker_image_pusher::{
7    AuthConfig, cli::operation_mode::OperationMode, error::Result,
8    image::image_manager::ImageManager, registry::RegistryClientBuilder,
9};
10use std::env;
11
12#[tokio::main]
13async fn main() -> Result<()> {
14    println!("🚀 Docker Image Pusher - Push from Cache (Manifest) Demo");
15    println!("============================================================");
16
17    // 配置参数 - 使用Aliyun registry,推送到已存在的repository
18    let source_repository = "yoce/cblt"; // 从缓存中读取
19    let source_reference = "yoce";
20    let target_registry = "registry.cn-beijing.aliyuncs.com";
21    let target_repository = "yoce/cblt"; // 推送回同一个repository
22    let target_reference = "test-push"; // 使用新的tag
23    let cache_dir = ".cache_demo";
24
25    println!("📥 Configuration:");
26    println!(
27        "  Source (Cache): {}/{}",
28        source_repository, source_reference
29    );
30    println!("  Target Registry: {}", target_registry);
31    println!("  Target Repository: {}", target_repository);
32    println!("  Target Reference: {}", target_reference);
33    println!("  Cache Directory: {}", cache_dir);
34    println!();
35
36    // 1. 检查缓存是否存在
37    check_cache_exists(cache_dir, source_repository, source_reference).await?;
38
39    // 2. 创建 ImageManager
40    println!("🔧 Creating ImageManager...");
41    let mut image_manager = ImageManager::new(Some(cache_dir), true)?;
42    println!("✅ ImageManager created successfully");
43
44    // 3. 构建 Registry Client - 配置为Aliyun registry
45    println!("🌐 Building Registry Client for Aliyun registry...");
46    let client = RegistryClientBuilder::new(format!("https://{}", target_registry))
47        .with_timeout(3600)
48        .with_skip_tls(false) // Aliyun registry使用TLS
49        .with_verbose(true)
50        .build()?;
51    println!("✅ Registry Client built successfully");
52
53    // 4. 获取认证 - 使用Aliyun registry凭据
54    println!("🔐 Authenticating with Aliyun registry...");
55    let username = env::var("ALIYUN_USERNAME").unwrap_or_else(|_| "canny_best@163.com".to_string());
56    let password = env::var("ALIYUN_PASSWORD").unwrap_or_else(|_| "ra201222".to_string());
57
58    let auth_config = AuthConfig::new(username.clone(), password.clone());
59    let auth_token = client
60        .authenticate_for_repository(&auth_config, target_repository)
61        .await?;
62    println!("✅ Authentication successful with user: {}", username);
63    println!("🔑 Token scope: repository:{}:pull,push", target_repository);
64
65    // 5. 定义操作模式 - 使用 manifest 方式推送
66    let mode = OperationMode::PushFromCacheUsingManifest {
67        repository: target_repository.to_string(),
68        reference: target_reference.to_string(),
69    };
70
71    println!("📋 Operation Mode: {}", mode.description());
72    println!();
73
74    // 6. 执行推送操作
75    println!("🔄 Starting push from cache operation...");
76    match image_manager
77        .execute_operation(&mode, Some(&client), auth_token.as_deref())
78        .await
79    {
80        Ok(()) => {
81            println!("✅ Push from cache operation completed successfully!");
82            println!();
83            println!(
84                "🎯 Image pushed to: {}/{}/{}",
85                target_registry, target_repository, target_reference
86            );
87            println!("🔍 You can now verify the upload:");
88            println!(
89                "   curl -H \"Authorization: Bearer <token>\" https://{}/v2/{}/manifests/{}",
90                target_registry, target_repository, target_reference
91            );
92            println!(
93                "   curl -H \"Authorization: Bearer <token>\" https://{}/v2/{}/tags/list",
94                target_registry, target_repository
95            );
96        }
97        Err(e) => {
98            eprintln!("❌ Push from cache operation failed: {}", e);
99            eprintln!("💡 Possible solutions:");
100            eprintln!(
101                "   - Check if source image exists in cache: {}/{}",
102                source_repository, source_reference
103            );
104            eprintln!("   - Verify Aliyun registry credentials");
105            eprintln!("   - Check network connectivity to Aliyun registry");
106            std::process::exit(1);
107        }
108    }
109
110    // 7. 验证推送结果
111    verify_push_result(&client, target_repository, target_reference, &auth_token).await;
112
113    Ok(())
114}
115
116async fn check_cache_exists(cache_dir: &str, repository: &str, reference: &str) -> Result<()> {
117    println!("🔍 Checking cache for source image...");
118
119    // 检查缓存目录
120    if !std::path::Path::new(cache_dir).exists() {
121        eprintln!("❌ Cache directory not found: {}", cache_dir);
122        eprintln!("💡 Please run extract_and_cache_demo.rs or pull_and_cache_demo.rs first");
123        std::process::exit(1);
124    }
125
126    // 检查索引文件
127    let index_path = format!("{}/index.json", cache_dir);
128    if !std::path::Path::new(&index_path).exists() {
129        eprintln!("❌ Cache index not found: {}", index_path);
130        eprintln!("💡 Cache appears to be empty or corrupted");
131        std::process::exit(1);
132    }
133
134    // 检查特定镜像的manifest
135    let manifest_path = format!("{}/manifests/{}/{}", cache_dir, repository, reference);
136    if !std::path::Path::new(&manifest_path).exists() {
137        eprintln!("❌ Manifest not found in cache: {}", manifest_path);
138        eprintln!("💡 Available images in cache:");
139
140        // 列出缓存中的镜像
141        if let Ok(index_content) = std::fs::read_to_string(&index_path) {
142            if let Ok(index) = serde_json::from_str::<serde_json::Value>(&index_content) {
143                if let Some(obj) = index.as_object() {
144                    for key in obj.keys() {
145                        eprintln!("   - {}", key);
146                    }
147                }
148            }
149        }
150        std::process::exit(1);
151    }
152
153    println!(
154        "✅ Source image found in cache: {}/{}",
155        repository, reference
156    );
157    Ok(())
158}
159
160async fn verify_push_result(
161    client: &docker_image_pusher::registry::RegistryClient,
162    repository: &str,
163    reference: &str,
164    auth_token: &Option<String>,
165) {
166    println!();
167    println!("🔍 Verifying push result...");
168
169    // 检查manifest是否存在
170    match client
171        .pull_manifest(repository, reference, auth_token)
172        .await
173    {
174        Ok(manifest_data) => {
175            println!("✅ Manifest successfully retrieved from target registry");
176            println!("📊 Manifest size: {} bytes", manifest_data.len());
177
178            // 解析manifest获取layer信息
179            if let Ok(manifest) = serde_json::from_slice::<serde_json::Value>(&manifest_data) {
180                if let Some(layers) = manifest.get("layers").and_then(|v| v.as_array()) {
181                    println!("📦 Number of layers: {}", layers.len());
182                }
183            }
184        }
185        Err(e) => {
186            eprintln!("⚠️  Could not verify manifest: {}", e);
187        }
188    }
189
190    // 尝试检查标签列表
191    match client.list_tags(repository, auth_token).await {
192        Ok(tags) => {
193            println!("🏷️  Available tags: {:?}", tags);
194        }
195        Err(e) => {
196            eprintln!("⚠️  Could not list tags: {}", e);
197        }
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    #[tokio::test]
206    async fn test_push_from_cache_creation() {
207        // 测试 ImageManager 创建
208        let result = ImageManager::new(Some(".test_push_cache"), false);
209        assert!(result.is_ok());
210
211        // 清理测试缓存
212        let _ = std::fs::remove_dir_all(".test_push_cache");
213    }
214
215    #[test]
216    fn test_operation_mode_description() {
217        let mode = OperationMode::PushFromCacheUsingManifest {
218            repository: "demo/test".to_string(),
219            reference: "v1.0".to_string(),
220        };
221        assert_eq!(mode.description(), "Push from cache using manifest");
222    }
223
224    #[test]
225    fn test_cache_path_construction() {
226        let cache_dir = ".test_cache";
227        let repository = "local/hello-world";
228        let reference = "latest";
229        let manifest_path = format!("{}/manifests/{}/{}", cache_dir, repository, reference);
230        assert_eq!(
231            manifest_path,
232            ".test_cache/manifests/local/hello-world/latest"
233        );
234    }
235}