1mod r2configs;
2mod v2;
3mod hash_utils;
4
5use std::fs;
6use std::path::{Path, PathBuf};
7use std::process::Command;
8use anyhow::{bail, Context, Result};
9use tempfile::TempDir;
10
11pub async fn run(image: String, tag: String) -> Result<()> {
12 let script_dir = Path::new("--").parent().unwrap().to_owned();
13 let tmp_dir = TempDir::new_in(&script_dir)?;
14
15 check_skopeo("skopeo")?;
16
17 let env_vars = r2configs::parse_r2configs()?;
18
19 let status = convert_oci(&image, &tag, &tmp_dir)?;
20 if !status.success() {
21 bail!("Failed to convert image");
22 }
23
24 let (image_manifests_dir, image_blobs_dir) = prepare_dir(&script_dir, &image)?;
25
26 move_files(&tmp_dir, &image_manifests_dir, &image_blobs_dir)?;
27
28 let client = v2::s3_upload::prepare_s3_client(&env_vars)?;
29
30 v2::s3_upload::upload_blobs(&image, &image_blobs_dir, &client, &env_vars.r2_bucket).await?;
31
32 v2::s3_upload::upload_manifests(&image, &image_manifests_dir, &client, &env_vars.r2_bucket).await?;
33
34 cleanup(tmp_dir, &script_dir, &image)?;
35
36 Ok(())
37}
38
39fn check_skopeo(cmd: &str) -> Result<()> {
40 if Command::new(cmd).output().is_err() {
41 bail!("{} is not installed", cmd);
42 }
43
44 Ok(())
45}
46
47fn convert_oci(image: &str, tag: &str, tmp_dir: &TempDir) -> Result<std::process::ExitStatus> {
48 Command::new("skopeo")
49 .arg("copy")
50 .arg("--all")
51 .arg(format!("docker-daemon:{}:{}", image, tag))
52 .arg(format!("dir:{}", tmp_dir.path().display()))
53 .status()
54 .context("Failed to execute skopeo command")
55}
56
57fn prepare_dir(script_dir: &Path, image: &str) -> Result<(PathBuf, PathBuf)> {
58 let v2_dir = script_dir.join("v2");
59 fs::create_dir_all(&v2_dir)?;
60
61 let image_manifests_dir = v2_dir.join(&image).join("manifests");
62 let image_blobs_dir = v2_dir.join(&image).join("blobs");
63 fs::create_dir_all(&image_manifests_dir)?;
64 fs::create_dir_all(&image_blobs_dir)?;
65
66 Ok((image_manifests_dir, image_blobs_dir))
67}
68
69fn move_files(tmp_dir: &TempDir, image_manifests_dir: &Path, image_blobs_dir: &Path) -> Result<()> {
70 for entry in fs::read_dir(tmp_dir.path())? {
71 let src = entry?.path();
72 let file_name = src.file_name().unwrap().to_string_lossy().into_owned();
73
74 if file_name == "version" {
75 fs::remove_file(src)?;
76 continue;
77 }
78
79 let dst_dir = if file_name.ends_with(".manifest.json") {
80 &image_manifests_dir
81 } else {
82 &image_blobs_dir
83 };
84
85 let hash = hash_utils::compute_blake3(&src)?;
86 let dst = dst_dir.join(hash);
87
88 fs::rename(&src, &dst)?;
89 }
90
91 Ok(())
92}
93
94fn cleanup(tmp_dir: TempDir, script_dir: &Path, image: &str) -> Result<()> {
95 tmp_dir.close()?;
96 let v2_dir = script_dir.join("v2").join(image);
97 fs::remove_dir_all(&v2_dir)?;
98
99 Ok(())
100}