spin_oci/
utils.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//! Utilities related to distributing Spin apps via OCI registries

use anyhow::{Context, Result};
use async_compression::tokio::bufread::GzipDecoder;
use async_compression::tokio::write::GzipEncoder;
use async_tar::Archive;
use spin_common::ui::quoted_path;
use std::path::{Path, PathBuf};

/// Create a compressed archive of source, returning its path in working_dir
pub async fn archive(source: &Path, working_dir: &Path) -> Result<PathBuf> {
    // Create tar archive file
    let tar_gz_path = working_dir
        .join(source.file_name().unwrap())
        .with_extension("tar.gz");
    let tar_gz = tokio::fs::File::create(tar_gz_path.as_path())
        .await
        .context(format!(
            "Unable to create tar archive for source {}",
            quoted_path(source)
        ))?;

    // Create encoder
    // TODO: use zstd? May be more performant
    let tar_gz_enc = GzipEncoder::new(tar_gz);

    // Build tar archive
    let mut tar_builder = async_tar::Builder::new(
        tokio_util::compat::TokioAsyncWriteCompatExt::compat_write(tar_gz_enc),
    );
    tar_builder
        .append_dir_all(".", source)
        .await
        .context(format!(
            "Unable to create tar archive for source {}",
            quoted_path(source)
        ))?;
    // Finish writing the archive
    tar_builder.finish().await?;
    // Shutdown the encoder
    use tokio::io::AsyncWriteExt;
    tar_builder
        .into_inner()
        .await?
        .into_inner()
        .shutdown()
        .await?;
    Ok(tar_gz_path)
}

/// Unpack a compressed archive existing at source into dest
pub async fn unarchive(source: &Path, dest: &Path) -> Result<()> {
    let decoder = GzipDecoder::new(tokio::io::BufReader::new(
        tokio::fs::File::open(source).await?,
    ));
    let archive = Archive::new(tokio_util::compat::TokioAsyncReadCompatExt::compat(decoder));
    if let Err(e) = archive.unpack(dest).await {
        return Err(e.into());
    };
    Ok(())
}