Skip to main content

spin_oci/
utils.rs

1//! Utilities related to distributing Spin apps via OCI registries
2
3use anyhow::{Context, Result};
4use async_compression::tokio::bufread::GzipDecoder;
5use async_compression::tokio::write::GzipEncoder;
6use spin_common::ui::quoted_path;
7use std::path::{Path, PathBuf};
8use tokio_tar::Archive;
9
10/// Create a compressed archive of source, returning its path in working_dir
11pub async fn archive(source: &Path, working_dir: &Path) -> Result<PathBuf> {
12    let source = source.to_owned();
13    let working_dir = working_dir.to_owned();
14
15    // Create tar archive file
16    let tar_gz_path = working_dir
17        .join(source.file_name().unwrap())
18        .with_extension("tar.gz");
19    let tar_gz = tokio::fs::File::create(tar_gz_path.as_path())
20        .await
21        .context(format!(
22            "Unable to create tar archive for source {}",
23            quoted_path(&source)
24        ))?;
25
26    // Create encoder
27    // TODO: use zstd? May be more performant
28    let tar_gz_enc = GzipEncoder::new(tar_gz);
29
30    // Build tar archive
31    let mut tar_builder = tokio_tar::Builder::new(tar_gz_enc);
32    tar_builder
33        .append_dir_all(".", &source)
34        .await
35        .context(format!(
36            "Unable to create tar archive for source {}",
37            quoted_path(&source)
38        ))?;
39
40    // Finish writing the archive
41    tar_builder.finish().await?;
42
43    // Shut down the encoder
44    use tokio::io::AsyncWriteExt;
45    tar_builder
46        .into_inner()
47        .await?
48        .into_inner()
49        .shutdown()
50        .await?;
51
52    Ok(tar_gz_path)
53}
54
55/// Unpack a compressed archive existing at source into dest
56pub async fn unarchive(source: &Path, dest: &Path) -> Result<()> {
57    let source = source.to_owned();
58    let dest = dest.to_owned();
59
60    let decoder = GzipDecoder::new(tokio::io::BufReader::new(
61        tokio::fs::File::open(&source).await?,
62    ));
63    let mut archive = Archive::new(decoder);
64    if let Err(e) = archive.unpack(&dest).await {
65        return Err(e.into());
66    };
67    Ok(())
68}