oci_r2_uploader/
lib.rs

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}