Skip to content

Commit

Permalink
Merge pull request #22 from ariel-miculas/support-symlinks-for-blob
Browse files Browse the repository at this point in the history
Add support for symlinks in blobs/sha256 path
  • Loading branch information
cgwalters authored Sep 30, 2024
2 parents 9cf9434 + 0a88546 commit 97ffb16
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 13 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ To access an existing OCI directory:
# use anyhow::{anyhow, Result};
# fn main() -> anyhow::Result<()> {
let d = cap_std::fs::Dir::open_ambient_dir("/path/to/ocidir", cap_std::ambient_authority())?;
let d = ocidir::OciDir::open(&d)?;
let d = ocidir::OciDir::open(d)?;
println!("{:?}", d.read_index()?.ok_or_else(|| anyhow!("missing Image Index"))?);
# Ok(())
# }
Expand Down
33 changes: 21 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ impl<'a> Debug for GzipLayerWriter<'a> {
/// An opened OCI directory.
pub struct OciDir {
/// The underlying directory.
pub dir: std::sync::Arc<Dir>,
pub dir: Dir,
pub blobs_dir: Dir,
}

/// Write a serializable data (JSON) as an OCI blob
Expand Down Expand Up @@ -211,7 +212,7 @@ fn sha256_of_descriptor(desc: &Descriptor) -> Result<&str> {
impl OciDir {
/// Open the OCI directory at the target path; if it does not already
/// have the standard OCI metadata, it is created.
pub fn ensure(dir: &Dir) -> Result<Self> {
pub fn ensure(dir: Dir) -> Result<Self> {
let mut db = cap_std::fs::DirBuilder::new();
db.recursive(true).mode(0o755);
dir.ensure_dir_with(BLOBDIR, &db)?;
Expand All @@ -225,8 +226,8 @@ impl OciDir {
pub fn clone_to(&self, destdir: &Dir, p: impl AsRef<Path>) -> Result<Self> {
let p = p.as_ref();
destdir.create_dir(p)?;
let cloned = Self::ensure(&destdir.open_dir(p)?)?;
for blob in self.dir.read_dir(BLOBDIR)? {
let cloned = Self::ensure(destdir.open_dir(p)?)?;
for blob in self.blobs_dir.entries()? {
let blob = blob?;
let path = Path::new(BLOBDIR).join(blob.file_name());
let mut src = self.dir.open(&path).map(BufReader::new)?;
Expand All @@ -237,9 +238,17 @@ impl OciDir {
}

/// Open an existing OCI directory.
pub fn open(dir: &Dir) -> Result<Self> {
let dir = std::sync::Arc::new(dir.try_clone()?);
Ok(Self { dir })
pub fn open(dir: Dir) -> Result<Self> {
let blobs_dir = dir.open_dir(BLOBDIR)?;
Self::open_with_external_blobs(dir, blobs_dir)
}

/// Open an existing OCI directory with a separate cap_std::Dir for blobs/sha256
/// This is useful when `blobs/sha256` might contain symlinks pointing outside the oci
/// directory, e.g. when sharing blobs across OCI repositories. The LXC OCI template uses this
/// feature.
pub fn open_with_external_blobs(dir: Dir, blobs_dir: Dir) -> Result<Self> {
Ok(Self { dir, blobs_dir })
}

/// Write a serializable data (JSON) as an OCI blob
Expand Down Expand Up @@ -334,13 +343,13 @@ impl OciDir {

fn parse_descriptor_to_path(desc: &oci_spec::image::Descriptor) -> Result<PathBuf> {
let digest = sha256_of_descriptor(desc)?;
Ok(Path::new(BLOBDIR).join(digest))
Ok(PathBuf::from(digest))
}

/// Open a blob; its size is validated as a sanity check.
pub fn read_blob(&self, desc: &oci_spec::image::Descriptor) -> Result<File> {
let path = Self::parse_descriptor_to_path(desc)?;
let f = self.dir.open(path).map(|f| f.into_std())?;
let f = self.blobs_dir.open(path).map(|f| f.into_std())?;
let expected: u64 = desc.size();
let found = f.metadata()?.len();
if expected != found {
Expand All @@ -352,7 +361,7 @@ impl OciDir {
/// Returns `true` if the blob with this digest is already present.
pub fn has_blob(&self, desc: &oci_spec::image::Descriptor) -> Result<bool> {
let path = Self::parse_descriptor_to_path(desc)?;
self.dir.try_exists(path).map_err(Into::into)
self.blobs_dir.try_exists(path).map_err(Into::into)
}

/// Returns `true` if the manifest is already present.
Expand Down Expand Up @@ -706,7 +715,7 @@ mod tests {
#[test]
fn test_build() -> Result<()> {
let td = cap_tempfile::tempdir(cap_std::ambient_authority())?;
let w = OciDir::ensure(&td)?;
let w = OciDir::ensure(td.try_clone()?)?;
let mut layerw = w.create_gzip_layer(None)?;
layerw.write_all(b"pretend this is a tarball")?;
let root_layer = layerw.complete()?;
Expand Down Expand Up @@ -791,7 +800,7 @@ mod tests {
#[test]
fn test_complete_verified_as() -> Result<()> {
let td = cap_tempfile::tempdir(cap_std::ambient_authority())?;
let oci_dir = OciDir::ensure(&td)?;
let oci_dir = OciDir::ensure(td.try_clone()?)?;

// Test a successful write
let empty_json_digest = oci_image::DescriptorBuilder::default()
Expand Down

0 comments on commit 97ffb16

Please sign in to comment.