use super::*;
use crate::tar as ostree_tar;
use anyhow::Context;
use fn_error_context::context;
use gio::glib;
use ostree::gio;
use std::collections::BTreeMap;
use std::path::Path;
use tracing::{instrument, Level};
#[derive(Debug, Default)]
pub struct Config {
pub labels: Option<BTreeMap<String, String>>,
pub cmd: Option<Vec<String>>,
}
#[context("Writing ostree root to blob")]
fn export_ostree_ref_to_blobdir(
repo: &ostree::Repo,
rev: &str,
ocidir: &openat::Dir,
compression: Option<flate2::Compression>,
) -> Result<oci::Layer> {
let commit = repo.resolve_rev(rev, false)?.unwrap();
let mut w = oci::LayerWriter::new(ocidir, compression)?;
ostree_tar::export_commit(repo, commit.as_str(), &mut w)?;
w.complete()
}
#[context("Building oci")]
fn build_oci(
repo: &ostree::Repo,
rev: &str,
ocidir_path: &Path,
config: &Config,
compression: Option<flate2::Compression>,
) -> Result<ImageReference> {
std::fs::create_dir(ocidir_path).context("Creating OCI dir")?;
let ocidir = &openat::Dir::open(ocidir_path)?;
let writer = &mut oci::OciWriter::new(ocidir)?;
let commit = repo.resolve_rev(rev, false)?.unwrap();
let commit = commit.as_str();
let (commit_v, _) = repo.load_commit(commit)?;
let commit_meta = &commit_v.child_value(0);
let commit_meta = glib::VariantDict::new(Some(commit_meta));
if let Some(version) =
commit_meta.lookup_value("version", Some(glib::VariantTy::new("s").unwrap()))
{
let version = version.str().unwrap();
writer.add_config_annotation("version", version);
writer.add_manifest_annotation("ostree.version", version);
}
writer.add_config_annotation(OSTREE_COMMIT_LABEL, commit);
writer.add_manifest_annotation(OSTREE_COMMIT_LABEL, commit);
for (k, v) in config.labels.iter().map(|k| k.iter()).flatten() {
writer.add_config_annotation(k, v);
}
if let Some(cmd) = config.cmd.as_ref() {
let cmd: Vec<_> = cmd.iter().map(|s| s.as_str()).collect();
writer.set_cmd(&cmd);
}
let rootfs_blob = export_ostree_ref_to_blobdir(repo, commit, ocidir, compression)?;
writer.set_root_layer(rootfs_blob);
writer.complete()?;
Ok(ImageReference {
transport: Transport::OciDir,
name: ocidir_path.to_str().unwrap().to_string(),
})
}
#[instrument(skip(repo))]
async fn build_impl(
repo: &ostree::Repo,
ostree_ref: &str,
config: &Config,
dest: &ImageReference,
) -> Result<ImageReference> {
let compression = if dest.transport == Transport::ContainerStorage {
Some(flate2::Compression::none())
} else {
None
};
if dest.transport == Transport::OciDir {
let _copied: ImageReference = build_oci(
repo,
ostree_ref,
Path::new(dest.name.as_str()),
config,
compression,
)?;
} else {
let tempdir = tempfile::tempdir_in("/var/tmp")?;
let tempdest = tempdir.path().join("d");
let tempdest = tempdest.to_str().unwrap();
let src = build_oci(repo, ostree_ref, Path::new(tempdest), config, compression)?;
let mut cmd = skopeo::new_cmd();
tracing::event!(Level::DEBUG, "Copying {} to {}", src, dest);
cmd.stdout(std::process::Stdio::null())
.arg("copy")
.arg(src.to_string())
.arg(dest.to_string());
let proc = super::skopeo::spawn(cmd)?;
let output = proc.wait_with_output().await?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(anyhow::anyhow!("skopeo failed: {}\n", stderr));
}
}
let info = super::import::fetch_manifest_info(dest).await?;
Ok(dest.with_digest(info.manifest_digest.as_str()))
}
pub async fn export<S: AsRef<str>>(
repo: &ostree::Repo,
ostree_ref: S,
config: &Config,
dest: &ImageReference,
) -> Result<ImageReference> {
build_impl(repo, ostree_ref.as_ref(), config, dest).await
}