Skip to content

Commit 6ed7718

Browse files
Tom Faytofay
authored andcommitted
support custom compression
Signed-off-by: Tom Fay <tom@teamfay.co.uk>
1 parent 977bb48 commit 6ed7718

File tree

2 files changed

+139
-67
lines changed

2 files changed

+139
-67
lines changed

examples/custom_compressor.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/// Example that shows how to use a custom compression and media type for image layers.
2+
/// The example below does no compression.
3+
use std::{env, io, path::PathBuf};
4+
5+
use oci_spec::image::Platform;
6+
use ocidir::{cap_std::fs::Dir, new_empty_manifest, BlobWriter, OciDir, WriteComplete};
7+
8+
struct NoCompression<'a>(BlobWriter<'a>);
9+
10+
impl<'a> io::Write for NoCompression<'a> {
11+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
12+
self.0.write(buf)
13+
}
14+
15+
fn flush(&mut self) -> io::Result<()> {
16+
self.0.flush()
17+
}
18+
}
19+
20+
impl<'a> WriteComplete<BlobWriter<'a>> for NoCompression<'a> {
21+
fn complete(self) -> io::Result<BlobWriter<'a>> {
22+
Ok(self.0)
23+
}
24+
}
25+
26+
fn main() {
27+
let dir = Dir::open_ambient_dir(env::temp_dir(), ocidir::cap_std::ambient_authority()).unwrap();
28+
let oci_dir = OciDir::ensure(dir).unwrap();
29+
30+
let mut manifest = new_empty_manifest().build().unwrap();
31+
let mut config = ocidir::oci_spec::image::ImageConfigurationBuilder::default()
32+
.build()
33+
.unwrap();
34+
35+
// Add the src as a layer
36+
let writer = oci_dir
37+
.create_custom_layer(
38+
|bw| Ok(NoCompression(bw)),
39+
oci_spec::image::MediaType::ImageLayer,
40+
)
41+
.unwrap();
42+
let mut builder = tar::Builder::new(writer);
43+
builder.follow_symlinks(false);
44+
45+
builder
46+
.append_dir_all(".", PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src"))
47+
.unwrap();
48+
49+
let layer = builder.into_inner().unwrap().complete().unwrap();
50+
oci_dir.push_layer(&mut manifest, &mut config, layer, "src", None);
51+
52+
println!(
53+
"Created image with manifest: {}",
54+
manifest.to_string_pretty().unwrap()
55+
);
56+
57+
// Add the image manifest
58+
let _descriptor = oci_dir
59+
.insert_manifest_and_config(manifest.clone(), config, None, Platform::default())
60+
.unwrap();
61+
}

src/lib.rs

Lines changed: 78 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use std::collections::{HashMap, HashSet};
1616
use std::fmt::Debug;
1717
use std::fs::File;
1818
use std::io::{prelude::*, BufReader};
19+
use std::marker::PhantomData;
1920
use std::path::{Path, PathBuf};
2021
use std::str::FromStr;
2122
use thiserror::Error;
@@ -154,13 +155,6 @@ impl<'a> Debug for BlobWriter<'a> {
154155
}
155156
}
156157

157-
/// Create an OCI tar+gzip layer.
158-
pub struct GzipLayerWriter<'a>(Sha256Writer<GzEncoder<BlobWriter<'a>>>);
159-
160-
#[cfg(feature = "zstd")]
161-
/// Writer for a OCI tar+zstd layer.
162-
pub struct ZstdLayerWriter<'a>(Sha256Writer<zstd::Encoder<'static, BlobWriter<'a>>>);
163-
164158
#[derive(Debug)]
165159
/// An opened OCI directory.
166160
pub struct OciDir {
@@ -280,17 +274,32 @@ impl OciDir {
280274
BlobWriter::new(&self.dir)
281275
}
282276

277+
/// Create a layer writer with a custom encoder and
278+
/// media type
279+
pub fn create_custom_layer<'a, W: WriteComplete<BlobWriter<'a>>>(
280+
&'a self,
281+
create: impl FnOnce(BlobWriter<'a>) -> std::io::Result<W>,
282+
media_type: MediaType,
283+
) -> Result<LayerWriter<'a, W>> {
284+
let bw = BlobWriter::new(&self.dir)?;
285+
Ok(LayerWriter::new(create(bw)?, media_type))
286+
}
287+
283288
/// Create a writer for a new gzip+tar blob; the contents
284289
/// are not parsed, but are expected to be a tarball.
285-
pub fn create_gzip_layer(&self, c: Option<flate2::Compression>) -> Result<GzipLayerWriter> {
286-
GzipLayerWriter::new(&self.dir, c)
290+
pub fn create_gzip_layer<'a>(
291+
&'a self,
292+
c: Option<flate2::Compression>,
293+
) -> Result<LayerWriter<'a, GzEncoder<BlobWriter>>> {
294+
let creator = |bw: BlobWriter<'a>| Ok(GzEncoder::new(bw, c.unwrap_or_default()));
295+
self.create_custom_layer(creator, MediaType::ImageLayerGzip)
287296
}
288297

289298
/// Create a tar output stream, backed by a blob
290299
pub fn create_layer(
291300
&self,
292301
c: Option<flate2::Compression>,
293-
) -> Result<tar::Builder<GzipLayerWriter>> {
302+
) -> Result<tar::Builder<LayerWriter<GzEncoder<BlobWriter>>>> {
294303
Ok(tar::Builder::new(self.create_gzip_layer(c)?))
295304
}
296305

@@ -299,8 +308,12 @@ impl OciDir {
299308
/// are not parsed, but are expected to be a tarball.
300309
///
301310
/// This method is only available when the `zstd` feature is enabled.
302-
pub fn create_layer_zstd(&self, compression_level: Option<i32>) -> Result<ZstdLayerWriter> {
303-
ZstdLayerWriter::new(&self.dir, compression_level)
311+
pub fn create_layer_zstd<'a>(
312+
&'a self,
313+
compression_level: Option<i32>,
314+
) -> Result<LayerWriter<'a, zstd::Encoder<'static, BlobWriter<'a>>>> {
315+
let creator = |bw: BlobWriter<'a>| zstd::Encoder::new(bw, compression_level.unwrap_or(0));
316+
Ok(self.create_custom_layer(creator, MediaType::ImageLayerZstd)?)
304317
}
305318

306319
#[cfg(feature = "zstdmt")]
@@ -312,12 +325,17 @@ impl OciDir {
312325
/// [zstd::Encoder::multithread]]
313326
///
314327
/// This method is only available when the `zstdmt` feature is enabled.
315-
pub fn create_layer_zstd_multithread(
316-
&self,
328+
pub fn create_layer_zstd_multithread<'a>(
329+
&'a self,
317330
compression_level: Option<i32>,
318331
n_workers: u32,
319-
) -> Result<ZstdLayerWriter> {
320-
ZstdLayerWriter::multithread(&self.dir, compression_level, n_workers)
332+
) -> Result<LayerWriter<'a, zstd::Encoder<'static, BlobWriter<'a>>>> {
333+
let creator = |bw: BlobWriter<'a>| {
334+
let mut encoder = zstd::Encoder::new(bw, compression_level.unwrap_or(0))?;
335+
encoder.multithread(n_workers)?;
336+
Ok(encoder)
337+
};
338+
Ok(self.create_custom_layer(creator, MediaType::ImageLayerZstd)?)
321339
}
322340

323341
/// Add a layer to the top of the image stack. The firsh pushed layer becomes the root.
@@ -682,79 +700,72 @@ impl<'a> std::io::Write for BlobWriter<'a> {
682700
}
683701
}
684702

685-
impl<'a> GzipLayerWriter<'a> {
686-
/// Create a writer for a gzip compressed layer blob.
687-
fn new(ocidir: &'a Dir, c: Option<flate2::Compression>) -> Result<Self> {
688-
let bw = BlobWriter::new(ocidir)?;
689-
let enc = flate2::write::GzEncoder::new(bw, c.unwrap_or_default());
690-
Ok(Self(Sha256Writer::new(enc)))
691-
}
703+
/// A writer that can be finalized to return an inner writer.
704+
pub trait WriteComplete<W>: Write {
705+
fn complete(self) -> std::io::Result<W>;
706+
}
692707

693-
/// Consume this writer, flushing buffered data and put the blob in place.
694-
pub fn complete(self) -> Result<Layer> {
695-
let (uncompressed_sha256, enc) = self.0.finish();
696-
let blob = enc.finish()?.complete()?;
697-
Ok(Layer {
698-
blob,
699-
uncompressed_sha256,
700-
media_type: MediaType::ImageLayerGzip,
701-
})
708+
impl<W> WriteComplete<W> for GzEncoder<W>
709+
where
710+
W: Write,
711+
{
712+
fn complete(self) -> std::io::Result<W> {
713+
self.finish()
702714
}
703715
}
704716

705-
impl<'a> std::io::Write for GzipLayerWriter<'a> {
706-
fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
707-
self.0.write(data)
717+
#[cfg(feature = "zstd")]
718+
impl<'a, W> WriteComplete<W> for zstd::Encoder<'a, W>
719+
where
720+
W: Write,
721+
{
722+
fn complete(self) -> std::io::Result<W> {
723+
self.finish()
708724
}
725+
}
709726

710-
fn flush(&mut self) -> std::io::Result<()> {
711-
self.0.flush()
712-
}
727+
pub struct LayerWriter<'a, W>
728+
where
729+
W: WriteComplete<BlobWriter<'a>>,
730+
{
731+
inner: Sha256Writer<W>,
732+
media_type: MediaType,
733+
marker: PhantomData<&'a ()>,
713734
}
714735

715-
#[cfg(feature = "zstd")]
716-
impl<'a> ZstdLayerWriter<'a> {
717-
/// Create a writer for a gzip compressed layer blob.
718-
fn new(ocidir: &'a Dir, c: Option<i32>) -> Result<Self> {
719-
let bw = BlobWriter::new(ocidir)?;
720-
let encoder = zstd::Encoder::new(bw, c.unwrap_or(0))?;
721-
Ok(Self(Sha256Writer::new(encoder)))
736+
impl<'a, W> LayerWriter<'a, W>
737+
where
738+
W: WriteComplete<BlobWriter<'a>>,
739+
{
740+
pub fn new(inner: W, media_type: oci_image::MediaType) -> Self {
741+
Self {
742+
inner: Sha256Writer::new(inner),
743+
media_type,
744+
marker: PhantomData,
745+
}
722746
}
723747

724-
/// Consume this writer, flushing buffered data and put the blob in place.
725748
pub fn complete(self) -> Result<Layer> {
726-
let (uncompressed_sha256, enc) = self.0.finish();
727-
let blob = enc.finish()?.complete()?;
749+
let (uncompressed_sha256, enc) = self.inner.finish();
750+
let blob = enc.complete()?.complete()?;
728751
Ok(Layer {
729752
blob,
730753
uncompressed_sha256,
731-
media_type: MediaType::ImageLayerZstd,
754+
media_type: self.media_type,
732755
})
733756
}
734757
}
735758

736-
#[cfg(feature = "zstdmt")]
737-
impl<'a> ZstdLayerWriter<'a> {
738-
/// Create a writer for a zstd compressed layer blob, with multithreaded compression enabled.
739-
///
740-
/// The `n_workers` parameter specifies the number of threads to use for compression, per
741-
/// [Encoder::multithread]]
742-
fn multithread(ocidir: &'a Dir, c: Option<i32>, n_workers: u32) -> Result<Self> {
743-
let bw = BlobWriter::new(ocidir)?;
744-
let mut encoder = zstd::Encoder::new(bw, c.unwrap_or(0))?;
745-
encoder.multithread(n_workers)?;
746-
Ok(Self(Sha256Writer::new(encoder)))
747-
}
748-
}
749-
750-
#[cfg(feature = "zstd")]
751-
impl<'a> std::io::Write for ZstdLayerWriter<'a> {
759+
impl<'a, W> std::io::Write for LayerWriter<'a, W>
760+
where
761+
W: WriteComplete<BlobWriter<'a>>,
762+
{
752763
fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
753-
self.0.write(data)
764+
self.inner.write(data)
754765
}
755766

756767
fn flush(&mut self) -> std::io::Result<()> {
757-
self.0.flush()
768+
self.inner.flush()
758769
}
759770
}
760771

0 commit comments

Comments
 (0)