determine gzip archives from magic values
instead of guessing.
This commit is contained in:
parent
8d298610f2
commit
f1566184cf
@ -5,9 +5,9 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -19,6 +19,8 @@ const (
|
|||||||
_whPrefix = ".wh."
|
_whPrefix = ".wh."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _gzipMagic = []byte{0x1f, 0x8b}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
dockerManifestJSON []struct {
|
dockerManifestJSON []struct {
|
||||||
Layers []string `json:"Layers"`
|
Layers []string `json:"Layers"`
|
||||||
@ -33,9 +35,10 @@ type (
|
|||||||
// Flatten flattens a docker image to a tarball. The underlying io.Writer
|
// Flatten flattens a docker image to a tarball. The underlying io.Writer
|
||||||
// should be an open file handle, which the caller is responsible for closing
|
// should be an open file handle, which the caller is responsible for closing
|
||||||
// themselves
|
// themselves
|
||||||
func Flatten(rd io.ReadSeeker, w io.Writer) (err error) {
|
func Flatten(rd io.ReadSeeker, w io.Writer) (_err error) {
|
||||||
tr := tar.NewReader(rd)
|
tr := tar.NewReader(rd)
|
||||||
var closer func() error
|
var closer func() error
|
||||||
|
var err error
|
||||||
|
|
||||||
// layerOffsets maps a layer name (a9b123c0daa/layer.tar) to it's offset
|
// layerOffsets maps a layer name (a9b123c0daa/layer.tar) to it's offset
|
||||||
layerOffsets := map[string]int64{}
|
layerOffsets := map[string]int64{}
|
||||||
@ -95,7 +98,10 @@ func Flatten(rd io.ReadSeeker, w io.Writer) (err error) {
|
|||||||
if _, err := rd.Seek(no.offset, io.SeekStart); err != nil {
|
if _, err := rd.Seek(no.offset, io.SeekStart); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tr, closer = openTargz(rd)
|
tr, closer, err = openTargz(rd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
hdr, err := tr.Next()
|
hdr, err := tr.Next()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
@ -138,8 +144,8 @@ func Flatten(rd io.ReadSeeker, w io.Writer) (err error) {
|
|||||||
// Avoiding use of multierr: if error is present, return
|
// Avoiding use of multierr: if error is present, return
|
||||||
// that. Otherwise return whatever `Close` returns.
|
// that. Otherwise return whatever `Close` returns.
|
||||||
err1 := tw.Close()
|
err1 := tw.Close()
|
||||||
if err == nil {
|
if _err == nil {
|
||||||
err = err1
|
_err = err1
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
// iterate through all layers, all files, and write files.
|
// iterate through all layers, all files, and write files.
|
||||||
@ -147,7 +153,10 @@ func Flatten(rd io.ReadSeeker, w io.Writer) (err error) {
|
|||||||
if _, err := rd.Seek(no.offset, io.SeekStart); err != nil {
|
if _, err := rd.Seek(no.offset, io.SeekStart); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tr, closer = openTargz(rd)
|
tr, closer, err = openTargz(rd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
hdr, err := tr.Next()
|
hdr, err := tr.Next()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
@ -242,29 +251,31 @@ func validateManifest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// openTargz creates a tar reader from a targzip or tar.
|
// openTargz creates a tar reader from a targzip or tar.
|
||||||
//
|
func openTargz(rs io.ReadSeeker) (*tar.Reader, func() error, error) {
|
||||||
// It will try to open a gzip stream, and, if that fails, silently fall back to
|
// find out whether the given file is targz or tar
|
||||||
// tar. I will accept a cleaner implementation looking at magic values.
|
head := make([]byte, 2)
|
||||||
func openTargz(r io.Reader) (*tar.Reader, func() error) {
|
_, err := io.ReadFull(rs, head)
|
||||||
hdrbuf := &bytes.Buffer{}
|
switch {
|
||||||
hdrw := &proxyWriter{w: hdrbuf}
|
case err == io.ErrUnexpectedEOF:
|
||||||
gz, err := gzip.NewReader(io.TeeReader(r, hdrw))
|
return nil, nil, errors.New("tarball or gzipfile too small")
|
||||||
if err == nil {
|
case err != nil:
|
||||||
hdrw.w = ioutil.Discard
|
return nil, nil, fmt.Errorf("read error: %w", err)
|
||||||
hdrbuf = nil
|
|
||||||
return tar.NewReader(gz), gz.Close
|
|
||||||
}
|
|
||||||
return tar.NewReader(io.MultiReader(hdrbuf, r)), func() error { return nil }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// proxyWriter is a pass-through writer. Its underlying writer can be changed
|
if _, err := rs.Seek(-2, io.SeekCurrent); err != nil {
|
||||||
// on-the-fly. Useful when there is a stream that needs to be discarded (change
|
return nil, nil, fmt.Errorf("seek: %w", err)
|
||||||
// the underlying writer to, say, ioutil.Discard).
|
|
||||||
type proxyWriter struct {
|
|
||||||
w io.Writer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes a slice to the underlying w.
|
r := rs.(io.Reader)
|
||||||
func (pw *proxyWriter) Write(p []byte) (int, error) {
|
closer := func() error { return nil }
|
||||||
return pw.w.Write(p)
|
if bytes.Equal(head, _gzipMagic) {
|
||||||
|
gzipr, err := gzip.NewReader(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("gzip.NewReader: %w", err)
|
||||||
|
}
|
||||||
|
closer = gzipr.Close
|
||||||
|
r = gzipr
|
||||||
|
}
|
||||||
|
|
||||||
|
return tar.NewReader(r), closer, nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user