Skip to content

Commit 96ecbea

Browse files
Tom Faytofay
authored andcommitted
support custom compression
Signed-off-by: Tom Fay <[email protected]>
1 parent 21b2e82 commit 96ecbea

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, BlobWriter, OciDir, WriteComplete};
7+
8+
struct NoCompression<'a>(BlobWriter<'a>);
9+
10+
impl io::Write for NoCompression<'_> {
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 = oci_dir.new_empty_manifest().unwrap().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 Debug for BlobWriter<'_> {
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<'a>>>> {
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+
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+
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.
@@ -715,79 +733,72 @@ impl std::io::Write for BlobWriter<'_> {
715733
}
716734
}
717735

718-
impl<'a> GzipLayerWriter<'a> {
719-
/// Create a writer for a gzip compressed layer blob.
720-
fn new(ocidir: &'a Dir, c: Option<flate2::Compression>) -> Result<Self> {
721-
let bw = BlobWriter::new(ocidir)?;
722-
let enc = flate2::write::GzEncoder::new(bw, c.unwrap_or_default());
723-
Ok(Self(Sha256Writer::new(enc)))
724-
}
736+
/// A writer that can be finalized to return an inner writer.
737+
pub trait WriteComplete<W>: Write {
738+
fn complete(self) -> std::io::Result<W>;
739+
}
725740

726-
/// Consume this writer, flushing buffered data and put the blob in place.
727-
pub fn complete(self) -> Result<Layer> {
728-
let (uncompressed_sha256, enc) = self.0.finish();
729-
let blob = enc.finish()?.complete()?;
730-
Ok(Layer {
731-
blob,
732-
uncompressed_sha256,
733-
media_type: MediaType::ImageLayerGzip,
734-
})
741+
impl<W> WriteComplete<W> for GzEncoder<W>
742+
where
743+
W: Write,
744+
{
745+
fn complete(self) -> std::io::Result<W> {
746+
self.finish()
735747
}
736748
}
737749

738-
impl std::io::Write for GzipLayerWriter<'_> {
739-
fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
740-
self.0.write(data)
750+
#[cfg(feature = "zstd")]
751+
impl<W> WriteComplete<W> for zstd::Encoder<'_, W>
752+
where
753+
W: Write,
754+
{
755+
fn complete(self) -> std::io::Result<W> {
756+
self.finish()
741757
}
758+
}
742759

743-
fn flush(&mut self) -> std::io::Result<()> {
744-
self.0.flush()
745-
}
760+
pub struct LayerWriter<'a, W>
761+
where
762+
W: WriteComplete<BlobWriter<'a>>,
763+
{
764+
inner: Sha256Writer<W>,
765+
media_type: MediaType,
766+
marker: PhantomData<&'a ()>,
746767
}
747768

748-
#[cfg(feature = "zstd")]
749-
impl<'a> ZstdLayerWriter<'a> {
750-
/// Create a writer for a gzip compressed layer blob.
751-
fn new(ocidir: &'a Dir, c: Option<i32>) -> Result<Self> {
752-
let bw = BlobWriter::new(ocidir)?;
753-
let encoder = zstd::Encoder::new(bw, c.unwrap_or(0))?;
754-
Ok(Self(Sha256Writer::new(encoder)))
769+
impl<'a, W> LayerWriter<'a, W>
770+
where
771+
W: WriteComplete<BlobWriter<'a>>,
772+
{
773+
pub fn new(inner: W, media_type: oci_image::MediaType) -> Self {
774+
Self {
775+
inner: Sha256Writer::new(inner),
776+
media_type,
777+
marker: PhantomData,
778+
}
755779
}
756780

757-
/// Consume this writer, flushing buffered data and put the blob in place.
758781
pub fn complete(self) -> Result<Layer> {
759-
let (uncompressed_sha256, enc) = self.0.finish();
760-
let blob = enc.finish()?.complete()?;
782+
let (uncompressed_sha256, enc) = self.inner.finish();
783+
let blob = enc.complete()?.complete()?;
761784
Ok(Layer {
762785
blob,
763786
uncompressed_sha256,
764-
media_type: MediaType::ImageLayerZstd,
787+
media_type: self.media_type,
765788
})
766789
}
767790
}
768791

769-
#[cfg(feature = "zstdmt")]
770-
impl<'a> ZstdLayerWriter<'a> {
771-
/// Create a writer for a zstd compressed layer blob, with multithreaded compression enabled.
772-
///
773-
/// The `n_workers` parameter specifies the number of threads to use for compression, per
774-
/// [Encoder::multithread]]
775-
fn multithread(ocidir: &'a Dir, c: Option<i32>, n_workers: u32) -> Result<Self> {
776-
let bw = BlobWriter::new(ocidir)?;
777-
let mut encoder = zstd::Encoder::new(bw, c.unwrap_or(0))?;
778-
encoder.multithread(n_workers)?;
779-
Ok(Self(Sha256Writer::new(encoder)))
780-
}
781-
}
782-
783-
#[cfg(feature = "zstd")]
784-
impl std::io::Write for ZstdLayerWriter<'_> {
792+
impl<'a, W> std::io::Write for LayerWriter<'a, W>
793+
where
794+
W: WriteComplete<BlobWriter<'a>>,
795+
{
785796
fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
786-
self.0.write(data)
797+
self.inner.write(data)
787798
}
788799

789800
fn flush(&mut self) -> std::io::Result<()> {
790-
self.0.flush()
801+
self.inner.flush()
791802
}
792803
}
793804

0 commit comments

Comments
 (0)