push_to_aliyun_demo/
push_to_aliyun_demo.rs

1//! Example: Push to Aliyun Registry - 推送镜像到阿里云容器镜像服务
2//!
3//! 此示例展示如何将缓存中的镜像推送到阿里云容器镜像服务
4
5use docker_image_pusher::{
6    AuthConfig, cli::operation_mode::OperationMode, error::Result,
7    image::image_manager::ImageManager, registry::RegistryClientBuilder,
8};
9
10#[tokio::main]
11async fn main() -> Result<()> {
12    println!("🚀 Docker Image Pusher - Push to Aliyun Registry Demo");
13    println!("=======================================================");
14
15    // 阿里云配置
16    let aliyun_registry = "registry.cn-beijing.aliyuncs.com";
17    let aliyun_username = "canny_best@163.com";
18    let aliyun_password = "ra201222";
19
20    // 源镜像(从缓存)
21    let source_repository = "yoce/cblt";
22    let source_reference = "yoce";
23
24    // 目标镜像(推送到阿里云)- 推送回同一个仓库,不同tag
25    let target_repository = "yoce/cblt";
26    let target_reference = "push-test";
27    let cache_dir = ".cache_demo";
28
29    println!("📥 Configuration:");
30    println!(
31        "  Source (Cache): {}/{}",
32        source_repository, source_reference
33    );
34    println!("  Target Registry: {}", aliyun_registry);
35    println!("  Target Repository: {}", target_repository);
36    println!("  Target Reference: {}", target_reference);
37    println!("  Cache Directory: {}", cache_dir);
38    println!("  Username: {}", aliyun_username);
39    println!();
40
41    // 1. 检查缓存是否存在
42    check_cache_exists(cache_dir, source_repository, source_reference).await?;
43
44    // 2. 创建 ImageManager
45    println!("🔧 Creating ImageManager...");
46    let mut image_manager = ImageManager::new(Some(cache_dir), true)?;
47    println!("✅ ImageManager created successfully");
48
49    // 3. 复制缓存中的镜像到目标仓库名称
50    println!("📋 Copying cached image to target repository name...");
51    copy_image_in_cache(
52        cache_dir,
53        source_repository,
54        source_reference,
55        target_repository,
56        target_reference,
57    )
58    .await?;
59    println!("✅ Image copied in cache");
60
61    // 4. 构建 Registry Client for 阿里云
62    println!("🌐 Building Registry Client for Aliyun...");
63    let client = RegistryClientBuilder::new(format!("https://{}", aliyun_registry))
64        .with_timeout(3600)
65        .with_skip_tls(false)
66        .with_verbose(true)
67        .build()?;
68    println!("✅ Registry Client built successfully");
69
70    // 4. 认证到阿里云
71    println!("🔐 Authenticating with Aliyun registry...");
72    let auth_config = AuthConfig::new(aliyun_username.to_string(), aliyun_password.to_string());
73    let auth_token = client
74        .authenticate_for_repository(&auth_config, target_repository)
75        .await?;
76    println!("✅ Authentication successful");
77
78    // 5. 定义操作模式 - 推送缓存中的镜像到阿里云
79    let mode = OperationMode::PushFromCacheUsingManifest {
80        repository: target_repository.to_string(),
81        reference: target_reference.to_string(),
82    };
83
84    println!("📋 Operation Mode: {}", mode.description());
85    println!();
86
87    // 6. 执行推送操作
88    println!("🔄 Starting push to Aliyun operation...");
89    println!(
90        "🎯 Target: {}/{}/{}",
91        aliyun_registry, target_repository, target_reference
92    );
93
94    match image_manager
95        .execute_operation(&mode, Some(&client), auth_token.as_deref())
96        .await
97    {
98        Ok(()) => {
99            println!("✅ Push to Aliyun operation completed successfully!");
100            println!(
101                "🎯 Image pushed to: {}/{}/{}",
102                aliyun_registry, target_repository, target_reference
103            );
104            println!("🔍 You can verify the upload in Aliyun Console:");
105            println!("   https://cr.console.aliyun.com");
106
107            // 验证推送结果
108            verify_push_result(&client, target_repository, target_reference, &auth_token).await;
109        }
110        Err(e) => {
111            eprintln!("❌ Push to Aliyun operation failed: {}", e);
112            eprintln!("💡 Possible solutions:");
113            eprintln!("   - Check Aliyun credentials and permissions");
114            eprintln!("   - Verify repository name format (namespace/repo)");
115            eprintln!("   - Check network connectivity to Aliyun registry");
116            eprintln!("   - Ensure the repository exists in Aliyun console");
117            eprintln!("   - Check if the namespace 'yoce' exists");
118            std::process::exit(1);
119        }
120    }
121
122    Ok(())
123}
124
125async fn check_cache_exists(cache_dir: &str, repository: &str, reference: &str) -> Result<()> {
126    println!("🔍 Checking cache for source image...");
127
128    if !std::path::Path::new(cache_dir).exists() {
129        eprintln!("❌ Cache directory not found: {}", cache_dir);
130        std::process::exit(1);
131    }
132
133    let index_path = format!("{}/index.json", cache_dir);
134    if !std::path::Path::new(&index_path).exists() {
135        eprintln!("❌ Cache index not found: {}", index_path);
136        std::process::exit(1);
137    }
138
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        std::process::exit(1);
143    }
144
145    println!(
146        "✅ Source image found in cache: {}/{}",
147        repository, reference
148    );
149    Ok(())
150}
151
152async fn verify_push_result(
153    client: &docker_image_pusher::registry::RegistryClient,
154    repository: &str,
155    reference: &str,
156    auth_token: &Option<String>,
157) {
158    println!();
159    println!("🔍 Verifying push result...");
160
161    // 检查manifest是否存在
162    match client
163        .pull_manifest(repository, reference, auth_token)
164        .await
165    {
166        Ok(manifest_data) => {
167            println!("✅ Manifest successfully retrieved from Aliyun registry");
168            println!("📊 Manifest size: {} bytes", manifest_data.len());
169
170            // 解析manifest获取layer信息
171            if let Ok(manifest) = serde_json::from_slice::<serde_json::Value>(&manifest_data) {
172                if let Some(layers) = manifest.get("layers").and_then(|v| v.as_array()) {
173                    println!("📦 Number of layers: {}", layers.len());
174                }
175                if let Some(media_type) = manifest.get("mediaType").and_then(|v| v.as_str()) {
176                    println!("📋 Media type: {}", media_type);
177                }
178            }
179        }
180        Err(e) => {
181            eprintln!("⚠️  Could not verify manifest: {}", e);
182        }
183    }
184
185    // 尝试检查标签列表
186    match client.list_tags(repository, auth_token).await {
187        Ok(tags) => {
188            println!("🏷️  Available tags: {:?}", tags);
189        }
190        Err(e) => {
191            eprintln!("⚠️  Could not list tags: {}", e);
192        }
193    }
194}
195
196async fn copy_image_in_cache(
197    cache_dir: &str,
198    src_repo: &str,
199    src_ref: &str,
200    dst_repo: &str,
201    dst_ref: &str,
202) -> Result<()> {
203    use std::fs;
204    use std::path::Path;
205
206    // 读取index.json
207    let index_path = format!("{}/index.json", cache_dir);
208    let index_content = fs::read_to_string(&index_path)?;
209    let mut index: serde_json::Value = serde_json::from_str(&index_content)?;
210
211    let src_key = format!("{}/{}", src_repo, src_ref);
212    let dst_key = format!("{}/{}", dst_repo, dst_ref);
213
214    // 复制源镜像条目到目标
215    if let Some(src_entry) = index.get(&src_key).cloned() {
216        let mut dst_entry = src_entry;
217
218        // 更新路径
219        if let Some(obj) = dst_entry.as_object_mut() {
220            obj.insert(
221                "repository".to_string(),
222                serde_json::Value::String(dst_repo.to_string()),
223            );
224            obj.insert(
225                "reference".to_string(),
226                serde_json::Value::String(dst_ref.to_string()),
227            );
228
229            let new_manifest_path = format!("{}/manifests/{}/{}", cache_dir, dst_repo, dst_ref);
230            obj.insert(
231                "manifest_path".to_string(),
232                serde_json::Value::String(new_manifest_path.clone()),
233            );
234
235            // 创建目标manifest目录
236            if let Some(parent) = Path::new(&new_manifest_path).parent() {
237                fs::create_dir_all(parent)?;
238            }
239
240            // 复制manifest文件
241            let src_manifest_path = format!("{}/manifests/{}/{}", cache_dir, src_repo, src_ref);
242            fs::copy(&src_manifest_path, &new_manifest_path)?;
243        }
244
245        // 添加到index
246        index.as_object_mut().unwrap().insert(dst_key, dst_entry);
247
248        // 保存index.json
249        let updated_index = serde_json::to_string_pretty(&index)?;
250        fs::write(&index_path, updated_index)?;
251
252        println!(
253            "✅ Copied {}/{} -> {}/{} in cache",
254            src_repo, src_ref, dst_repo, dst_ref
255        );
256        Ok(())
257    } else {
258        Err(docker_image_pusher::error::RegistryError::Validation(
259            format!("Source image {}/{} not found in cache", src_repo, src_ref),
260        ))
261    }
262}