diff --git a/lib/src/container/encapsulate.rs b/lib/src/container/encapsulate.rs index c19b3bd3..475bbff9 100644 --- a/lib/src/container/encapsulate.rs +++ b/lib/src/container/encapsulate.rs @@ -7,6 +7,7 @@ use crate::chunking::{Chunk, Chunking, ObjectMetaSized}; use crate::container::skopeo; use crate::tar as ostree_tar; use anyhow::{anyhow, Context, Result}; +use camino::Utf8Path; use cap_std::fs::Dir; use cap_std_ext::cap_std; use chrono::NaiveDateTime; @@ -19,7 +20,6 @@ use ostree::gio; use std::borrow::Cow; use std::collections::{BTreeMap, HashMap}; use std::num::NonZeroU32; -use std::path::Path; use tracing::instrument; /// The label which may be used in addition to the standard OCI label. @@ -160,17 +160,11 @@ fn export_chunked( fn build_oci( repo: &ostree::Repo, rev: &str, - ocidir_path: &Path, + writer: &mut OciDir, tag: Option<&str>, config: &Config, opts: ExportOpts, -) -> Result { - if !ocidir_path.exists() { - std::fs::create_dir(ocidir_path).context("Creating OCI dir")?; - } - let ocidir = Dir::open_ambient_dir(ocidir_path, cap_std::ambient_authority())?; - let mut writer = ocidir::OciDir::ensure(&ocidir)?; - +) -> Result<()> { let commit = repo.require_rev(rev)?; let commit = commit.as_str(); let (commit_v, _) = repo.load_commit(commit)?; @@ -246,7 +240,7 @@ fn build_oci( export_chunked( repo, commit, - &mut writer, + writer, &mut manifest, &mut imgcfg, labels, @@ -276,10 +270,7 @@ fn build_oci( writer.replace_with_single_manifest(manifest, platform)?; } - Ok(ImageReference { - transport: Transport::OciDir, - name: ocidir_path.to_str().unwrap().to_string(), - }) + Ok(()) } /// Interpret a filesystem path as optionally including a tag. Paths @@ -308,19 +299,37 @@ async fn build_impl( let digest = if dest.transport == Transport::OciDir { let (path, tag) = parse_oci_path_and_tag(dest.name.as_str()); tracing::debug!("using OCI path={path} tag={tag:?}"); - let _copied: ImageReference = - build_oci(repo, ostree_ref, Path::new(path), tag, config, opts)?; + if !Utf8Path::new(path).exists() { + std::fs::create_dir(path)?; + } + let ocidir = Dir::open_ambient_dir(path, cap_std::ambient_authority())?; + let mut ocidir = OciDir::ensure(&ocidir)?; + build_oci(repo, ostree_ref, &mut ocidir, tag, config, opts)?; None } else { - let tempdir = tempfile::tempdir_in("/var/tmp")?; - let tempdest = tempdir.path().join("d"); - let tempdest = tempdest.to_str().unwrap(); + let tempdir = { + let vartmp = Dir::open_ambient_dir("/var/tmp", cap_std::ambient_authority())?; + cap_std_ext::cap_tempfile::tempdir_in(&vartmp)? + }; + let mut ocidir = OciDir::ensure(&tempdir)?; // Minor TODO: refactor to avoid clone let authfile = opts.authfile.clone(); - let tempoci = build_oci(repo, ostree_ref, Path::new(tempdest), None, config, opts)?; + build_oci(repo, ostree_ref, &mut ocidir, None, config, opts)?; + drop(ocidir); - let digest = skopeo::copy(&tempoci, dest, authfile.as_deref()).await?; + // Pass the temporary oci directory as the current working directory for the skopeo process + let tempoci = ImageReference { + transport: Transport::OciDir, + name: ".".into(), + }; + let digest = skopeo::copy( + &tempoci, + dest, + authfile.as_deref(), + Some(tempdir.try_clone()?), + ) + .await?; Some(digest) }; if let Some(digest) = digest { diff --git a/lib/src/container/ocidir.rs b/lib/src/container/ocidir.rs index e144ce07..8d93dda1 100644 --- a/lib/src/container/ocidir.rs +++ b/lib/src/container/ocidir.rs @@ -154,6 +154,7 @@ pub fn new_empty_manifest() -> oci_image::ImageManifestBuilder { impl OciDir { /// Open the OCI directory at the target path; if it does not already /// have the standard OCI metadata, it is created. + #[context("Opening OCI dir")] pub fn ensure(dir: &Dir) -> Result { let mut db = cap_std::fs::DirBuilder::new(); db.recursive(true).mode(0o755); diff --git a/lib/src/container/skopeo.rs b/lib/src/container/skopeo.rs index 99489c0f..df247cfe 100644 --- a/lib/src/container/skopeo.rs +++ b/lib/src/container/skopeo.rs @@ -2,6 +2,9 @@ use super::ImageReference; use anyhow::{Context, Result}; +use cap_std_ext::cap_std::fs::Dir; +use cap_std_ext::cmdext::CapStdExtCommandExt; +use fn_error_context::context; use serde::Deserialize; use std::io::Read; use std::path::Path; @@ -45,10 +48,9 @@ pub(crate) fn container_policy_is_default_insecure() -> Result { } /// Create a Command builder for skopeo. -pub(crate) fn new_cmd() -> tokio::process::Command { - let mut cmd = Command::new("skopeo"); +pub(crate) fn new_cmd() -> std::process::Command { + let mut cmd = std::process::Command::new("skopeo"); cmd.stdin(Stdio::null()); - cmd.kill_on_drop(true); cmd } @@ -59,21 +61,28 @@ pub(crate) fn spawn(mut cmd: Command) -> Result { } /// Use skopeo to copy a container image. +#[context("Skopeo copy")] pub(crate) async fn copy( src: &ImageReference, dest: &ImageReference, authfile: Option<&Path>, + cwd: Option, ) -> Result { let digestfile = tempfile::NamedTempFile::new()?; let mut cmd = new_cmd(); cmd.stdout(std::process::Stdio::null()).arg("copy"); cmd.arg("--digestfile"); cmd.arg(digestfile.path()); + if let Some(cwd) = cwd { + cmd.cwd_dir(cwd); + } if let Some(authfile) = authfile { cmd.arg("--authfile"); cmd.arg(authfile); } cmd.args(&[src.to_string(), dest.to_string()]); + let mut cmd = tokio::process::Command::from(cmd); + cmd.kill_on_drop(true); let proc = super::skopeo::spawn(cmd)?; let output = proc.wait_with_output().await?; if !output.status.success() { diff --git a/lib/src/container/update_detachedmeta.rs b/lib/src/container/update_detachedmeta.rs index 23aa5fb1..8aad8bde 100644 --- a/lib/src/container/update_detachedmeta.rs +++ b/lib/src/container/update_detachedmeta.rs @@ -29,7 +29,7 @@ pub async fn update_detached_metadata( }; // Full copy of the source image - let pulled_digest: String = skopeo::copy(src, &tempsrc_ref, None) + let pulled_digest: String = skopeo::copy(src, &tempsrc_ref, None, None) .await .context("Creating temporary copy to OCI dir")?; @@ -124,7 +124,7 @@ pub async fn update_detached_metadata( // Finally, copy the mutated image back to the target. For chunked images, // because we only changed one layer, skopeo should know not to re-upload shared blobs. - crate::container::skopeo::copy(&tempsrc_ref, dest, None) + crate::container::skopeo::copy(&tempsrc_ref, dest, None, None) .await .context("Copying to destination") }