Skip to content

Commit

Permalink
render: Add filter_unpack
Browse files Browse the repository at this point in the history
This lets users supply a callback receiving the path of each file in
the image to selectively filter which files are unpacked into the
destination directory.
  • Loading branch information
jeckersb authored and vrutkovs committed Jun 2, 2023
1 parent 1508534 commit 0642b1c
Showing 1 changed file with 56 additions and 4 deletions.
60 changes: 56 additions & 4 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// https://github.com/moby/moby/blob/v17.05.0-ce/image/spec/v1.md

use libflate::gzip;
use std::{fs, path};
use std::{fs, io, path};

#[derive(Debug, thiserror::Error)]
pub enum RenderError {
Expand All @@ -19,6 +19,42 @@ pub enum RenderError {
/// Layers must be provided as gzip-compressed tar archives, with lower layers
/// coming first. Target directory must be an existing absolute path.
pub fn unpack(layers: &[Vec<u8>], target_dir: &path::Path) -> Result<(), RenderError> {
_unpack(layers, target_dir, |mut archive, target_dir| {
Ok(archive.unpack(target_dir)?)
})
}

/// Unpack an ordered list of layers to a target directory, filtering
/// file entries by path.
///
/// Layers must be provided as gzip-compressed tar archives, with lower layers
/// coming first. Target directory must be an existing absolute path.
pub fn filter_unpack<P>(
layers: &[Vec<u8>],
target_dir: &path::Path,
predicate: P,
) -> Result<(), RenderError>
where
P: Fn(&path::Path) -> bool,
{
_unpack(layers, target_dir, |mut archive, target_dir| {
for entry in archive.entries()? {
let mut entry = entry?;
let path = entry.path()?;

if predicate(&path) {
entry.unpack_in(target_dir)?;
}
}

Ok(())
})
}

fn _unpack<U>(layers: &[Vec<u8>], target_dir: &path::Path, unpacker: U) -> Result<(), RenderError>
where
U: Fn(tar::Archive<gzip::Decoder<&[u8]>>, &path::Path) -> Result<(), RenderError>,
{
if !target_dir.is_absolute() || !target_dir.exists() || !target_dir.is_dir() {
return Err(RenderError::WrongTargetPath(target_dir.to_path_buf()));
}
Expand All @@ -28,7 +64,7 @@ pub fn unpack(layers: &[Vec<u8>], target_dir: &path::Path) -> Result<(), RenderE
let mut archive = tar::Archive::new(gz_dec);
archive.set_preserve_permissions(true);
archive.set_unpack_xattrs(true);
archive.unpack(target_dir)?;
unpacker(archive, target_dir)?;

// Clean whiteouts
let gz_dec = gzip::Decoder::new(l.as_slice())?;
Expand All @@ -48,14 +84,30 @@ pub fn unpack(layers: &[Vec<u8>], target_dir: &path::Path) -> Result<(), RenderE
// Remove real file behind whiteout
let real_name = wh_name.trim_start_matches(".wh.");
let abs_real_path = target_dir.join(&rel_parent).join(real_name);
fs::remove_dir_all(abs_real_path)?;
remove_whiteout(abs_real_path)?;

// Remove whiteout place-holder
let abs_wh_path = target_dir.join(&rel_parent).join(fname);
fs::remove_dir_all(abs_wh_path)?;
remove_whiteout(abs_wh_path)?;
};
}
}
}
Ok(())
}

// Whiteout files in archive may not exist on filesystem if they were
// filtered out via filter_unpack. If not found, that's ok and the
// error is non-fatal. Otherwise still return error for other
// failures.
fn remove_whiteout(path: path::PathBuf) -> io::Result<()> {
let res = fs::remove_dir_all(path);

match res {
Ok(_) => res,
Err(ref e) => match e.kind() {
io::ErrorKind::NotFound => Ok(()),
_ => res,
},
}
}

0 comments on commit 0642b1c

Please sign in to comment.