Skip to content

Commit

Permalink
Add support for zstd compressed layers
Browse files Browse the repository at this point in the history
  • Loading branch information
darktohka committed Jan 24, 2025
1 parent 925cdd8 commit 28ca3da
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 19 deletions.
71 changes: 52 additions & 19 deletions dive/image/docker/image_archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"path"
"strings"

"github.com/klauspost/compress/zstd"

"github.com/wagoodman/dive/dive/filetree"
"github.com/wagoodman/dive/dive/image"
)
Expand Down Expand Up @@ -77,6 +79,26 @@ func NewImageArchive(tarFile io.ReadCloser) (*ImageArchive, error) {
return img, err
}

// add the layer to the image
img.layerMap[tree.Name] = tree
} else if strings.HasSuffix(name, ".tar.zst") || strings.HasSuffix(name, ".zst") {
currentLayer++

// Add zstd reader
zst, err := zstd.NewReader(tarReader)
if err != nil {
return img, err
}

// Add tar reader
layerReader := tar.NewReader(zst)

// Process layer
tree, err := processLayerTar(name, layerReader)
if err != nil {
return img, err
}

// add the layer to the image
img.layerMap[tree.Name] = tree
} else if strings.HasSuffix(name, ".json") || strings.HasPrefix(name, "sha256:") {
Expand All @@ -89,36 +111,47 @@ func NewImageArchive(tarFile io.ReadCloser) (*ImageArchive, error) {
// For the OCI-compatible image format (used since Docker 25), use mime sniffing
// but limit this to only the blobs/ (containing the config, and the layers)

// The idea here is that we try various formats in turn, and those tries should
// never consume more bytes than this buffer contains so we can start again.
// The idea here is that we read the first few bytes of the file to determine if
// it's encrypted with gzip or zstd. If it is, we'll decompress it first.

// There is one edge case to consider: if the tar file's first file's name begins
// with 0x1f8b or 0x28b52ffd, the file will be erronously decompressed. This is
// very unlikely: filenames usually are not binary, but it's worth mentioning.

// 512 bytes ought to be enough (as that's the size of a TAR entry header),
// but play it safe with 1024 bytes. This should also include very small layers
// (unless they've also been gzipped, but Docker does not appear to do it)
buffer := make([]byte, 1024)
n, err := io.ReadFull(tarReader, buffer)

if err != nil && err != io.ErrUnexpectedEOF {
return img, err
}

// Only try reading a TAR if file is "big enough"
if n == cap(buffer) {
var unwrappedReader io.Reader
var unwrappedReader io.Reader

if n >= 2 && buffer[0] == 0x1f && buffer[1] == 0x8b {
// This is a gzipped file
unwrappedReader, err = gzip.NewReader(io.MultiReader(bytes.NewReader(buffer[:n]), tarReader))
if err != nil {
// Not a gzipped entry
unwrappedReader = io.MultiReader(bytes.NewReader(buffer[:n]), tarReader)
return img, err
}

// Try reading a TAR
layerReader := tar.NewReader(unwrappedReader)
tree, err := processLayerTar(name, layerReader)
if err == nil {
currentLayer++
// add the layer to the image
img.layerMap[tree.Name] = tree
continue
} else if n >= 4 && buffer[0] == 0x28 && buffer[1] == 0xb5 && buffer[2] == 0x2f && buffer[3] == 0xfd {
// This is a zstd file
unwrappedReader, err = zstd.NewReader(io.MultiReader(bytes.NewReader(buffer[:n]), tarReader))
if err != nil {
return img, err
}
} else {
// This is not a compressed file
unwrappedReader = io.MultiReader(bytes.NewReader(buffer[:n]), tarReader)
}

// Try reading a TAR
layerReader := tar.NewReader(unwrappedReader)
tree, err := processLayerTar(name, layerReader)
if err == nil {
currentLayer++
// add the layer to the image
img.layerMap[tree.Name] = tree
continue
}

// Not a TAR (or smaller than our buffer), might be a JSON file
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/dustin/go-humanize v1.0.0
github.com/fatih/color v1.7.0
github.com/google/uuid v1.1.1
github.com/klauspost/compress v1.17.6
github.com/logrusorgru/aurora v0.0.0-20190803045625-94edacc10f9b
github.com/lunixbochs/vtclean v1.0.0
github.com/mitchellh/go-homedir v1.1.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
Expand Down

0 comments on commit 28ca3da

Please sign in to comment.