Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ Keys supported by image output:
* `dangling-name-prefix=<value>`: name image with `prefix@<digest>`, used for anonymous images
* `name-canonical=true`: add additional canonical name `name@<digest>`
* `compression=<uncompressed|gzip|estargz|zstd>`: choose compression type for layers newly created and cached, gzip is default value. estargz should be used with `oci-mediatypes=true`.
* `compression-variant=<gzip|igzip|pigz>`: choose different compression method for gzip, only worked with `compression=gzip`.
* `compression-level=<value>`: compression level for gzip, estargz (0-9) and zstd (0-22)
* `rewrite-timestamp=true`: rewrite the file timestamps to the `SOURCE_DATE_EPOCH` value.
See [`docs/build-repro.md`](docs/build-repro.md) for how to specify the `SOURCE_DATE_EPOCH` value.
Expand Down Expand Up @@ -467,6 +468,7 @@ buildctl build ... \
* `image-manifest=<true|false>`: whether to export cache manifest as an OCI-compatible image manifest rather than a manifest list/index (default: `true` since BuildKit `v0.21`, must be used with `oci-mediatypes=true`)
* `oci-mediatypes=<true|false>`: whether to use OCI mediatypes in exported manifests (default: `true`, since BuildKit `v0.8`)
* `compression=<uncompressed|gzip|estargz|zstd>`: choose compression type for layers newly created and cached, gzip is default value. estargz and zstd should be used with `oci-mediatypes=true`
* `compression-variant=<gzip|igzip|pigz>`: choose different compression method for gzip, only worked with `compression=gzip`.
* `compression-level=<value>`: choose compression level for gzip, estargz (0-9) and zstd (0-22)
* `force-compression=true`: forcibly apply `compression` option to all layers
* `ignore-error=<false|true>`: specify if error is ignored in case cache export fails (default: `false`)
Expand Down Expand Up @@ -494,6 +496,7 @@ The directory layout conforms to OCI Image Spec v1.0.
* `image-manifest=<true|false>`: whether to export cache manifest as an OCI-compatible image manifest rather than a manifest list/index (default: `true` since BuildKit `v0.21`, must be used with `oci-mediatypes=true`)
* `oci-mediatypes=<true|false>`: whether to use OCI mediatypes in exported manifests (default `true`, since BuildKit `v0.8`)
* `compression=<uncompressed|gzip|estargz|zstd>`: choose compression type for layers newly created and cached, gzip is default value. estargz and zstd should be used with `oci-mediatypes=true`.
* `compression-variant=<gzip|igzip|pigz>`: choose different compression method for gzip, only worked with `compression=gzip`.
* `compression-level=<value>`: compression level for gzip, estargz (0-9) and zstd (0-22)
* `force-compression=true`: forcibly apply `compression` option to all layers
* `ignore-error=<false|true>`: specify if error is ignored in case cache export fails (default: `false`)
Expand Down
10 changes: 7 additions & 3 deletions util/compression/attrs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import (
)

const (
attrLayerCompression = "compression"
attrForceCompression = "force-compression"
attrCompressionLevel = "compression-level"
attrLayerCompression = "compression"
attrForceCompression = "force-compression"
attrCompressionLevel = "compression-level"
attrCompressionVariant = "compression-variant"
)

func ParseAttributes(attrs map[string]string) (Config, error) {
Expand Down Expand Up @@ -44,5 +45,8 @@ func ParseAttributes(attrs map[string]string) (Config, error) {
}
compressionConfig = compressionConfig.SetLevel(int(ii))
}
if v, ok := attrs[attrCompressionVariant]; ok {
compressionConfig = compressionConfig.SetVariant(v)
}
return compressionConfig, nil
}
12 changes: 9 additions & 3 deletions util/compression/compression.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ var (
)

type Config struct {
Type Type
Force bool
Level *int
Type Type
Force bool
Level *int
Variant string
}

func New(t Type) Config {
Expand All @@ -75,6 +76,11 @@ func (c Config) SetLevel(l int) Config {
return c
}

func (c Config) SetVariant(v string) Config {
c.Variant = v
return c
}

const (
mediaTypeDockerSchema2LayerZstd = images.MediaTypeDockerSchema2Layer + ".zstd"
)
Expand Down
51 changes: 51 additions & 0 deletions util/compression/gzip.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package compression

import (
"bytes"
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"os/exec"

"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/core/images"
Expand All @@ -12,6 +16,12 @@ import (

func (c gzipType) Compress(ctx context.Context, comp Config) (compressorFunc Compressor, finalize Finalizer) {
return func(dest io.Writer, _ string) (io.WriteCloser, error) {
switch comp.Variant {
case "igzip":
return gzipCmdWriter(ctx, "igzip", comp)(dest)
case "pigz":
return gzipCmdWriter(ctx, "pigz", comp)(dest)
}
return gzipWriter(comp)(dest)
}, nil
}
Expand Down Expand Up @@ -64,3 +74,44 @@ func gzipWriter(comp Config) func(io.Writer) (io.WriteCloser, error) {
return gzip.NewWriterLevel(dest, level)
}
}

type writeCloserWrapper struct {
io.Writer
closer func() error
}

func (w *writeCloserWrapper) Close() error {
return w.closer()
}

func gzipCmdWriter(ctx context.Context, cmd string, comp Config) func(io.Writer) (io.WriteCloser, error) {
return func(dest io.Writer) (io.WriteCloser, error) {
reader, writer := io.Pipe()
args := []string{"-c"}
if comp.Level != nil {
args = append(args, fmt.Sprintf("-%d", *comp.Level))
}
command := exec.CommandContext(ctx, cmd, args...)
command.Stdin = reader
command.Stdout = dest

var errBuf bytes.Buffer
command.Stderr = &errBuf

if err := command.Start(); err != nil {
return nil, err
}

return &writeCloserWrapper{
Writer: writer,
closer: func() error {
closeErr := writer.Close()
waitErr := command.Wait()
if waitErr != nil {
return fmt.Errorf("%s: %s", waitErr, errBuf.String())
}
return errors.Join(closeErr, waitErr)
},
}, nil
}
}
Loading