Skip to content

Commit

Permalink
Add the option to exclude certain layers from the tarball.
Browse files Browse the repository at this point in the history
This should be considered a relatively advanced option, but for folks that know
what they are doing you can reduce the amount of data that you need to encode in
the tarball for the daemon to load it.

The ultimate use case of this option will be from `daemon.Write`, which
currently uses the `docker load` interface to pull image into the daemon,
however, this currently reuploads (and redownloads) the base image on each write
in context like `ko`. If we can determine the set of layers that already exist
in the daemon we can elide these from the tarball to dramatically improve
performance.

Related: google#205
  • Loading branch information
mattmoor committed Jun 10, 2018
1 parent dc0a777 commit f2696f7
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 0 deletions.
25 changes: 25 additions & 0 deletions pkg/v1/tarball/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@ import (
"github.com/google/go-containerregistry/pkg/v1"
)

// LayerFilter defines a function for filtering layers.
// True - indicates the layer should be kept,
// False - indicates the layer should be excluded.
type LayerFilter func(v1.Layer) (bool, error)

// WriteOptions are used to expose optional information to guide or
// control the image write.
type WriteOptions struct {
LayerFilter LayerFilter
// TODO(mattmoor): Whether to store things compressed?
}

Expand Down Expand Up @@ -66,6 +72,16 @@ func Write(tag name.Tag, img v1.Image, wo *WriteOptions, w io.Writer) error {
return err
}

// Default filter function
filter := func(_ v1.Layer) (bool, error) {
return true, nil
}
if wo != nil {
if wo.LayerFilter != nil {
filter = wo.LayerFilter
}
}

// Write the layers.
layers, err := img.Layers()
if err != nil {
Expand All @@ -89,6 +105,15 @@ func Write(tag name.Tag, img v1.Image, wo *WriteOptions, w io.Writer) error {
// https://www.gnu.org/software/gzip/manual/html_node/Overview.html
layerFiles[i] = fmt.Sprintf("%s.tar.gz", hex)

// We filter late because the lenght of layerFiles must match the diff_ids
// in config file. It is ok if the file doesn't exist when the daemon
// already has a given layer, since it won't try to read it.
if keep, err := filter(l); err != nil {
return err
} else if !keep {
continue
}

r, err := l.Compressed()
if err != nil {
return err
Expand Down
69 changes: 69 additions & 0 deletions pkg/v1/tarball/write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
package tarball

import (
"archive/tar"
"io"
"io/ioutil"
"os"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -84,6 +87,72 @@ func TestWrite(t *testing.T) {
}
}

func TestFilteredWrite(t *testing.T) {
// Make a tempfile for tarball writes.
fp, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("Error creating temp file.")
}
t.Log(fp.Name())
defer fp.Close()
defer os.Remove(fp.Name())

// Make a random image
randImage, err := random.Image(256, 8)
if err != nil {
t.Fatalf("Error creating random image.")
}
tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation)
if err != nil {
t.Fatalf("Error creating test tag.")
}

layers, err := randImage.Layers()
if err != nil {
t.Fatalf("Layers() = %v", err)
}
rld, err := layers[0].Digest()
if err != nil {
t.Fatalf("Digest() = %v", err)
}

wo := &WriteOptions{
LayerFilter: func(l v1.Layer) (bool, error) {
// Filter the first layer in the image.
if ld, err := l.Digest(); err != nil {
return false, err
} else {
return ld != rld, nil
}
},
}

if err := WriteToFile(fp.Name(), tag, randImage, wo); err != nil {
t.Fatalf("Unexpected error writing tarball: %v", err)
}

f, err := os.Open(fp.Name())
if err != nil {
t.Fatalf("os.Open() = %v", err)
}
defer f.Close()

tarReader := tar.NewReader(f)
for {
header, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("scanning tarfile: %v", err)
}

if strings.Contains(header.Name, rld.Hex) {
t.Errorf("Saw file %v in tarball, want %v elided.", header.Name, rld)
}
}
}

func assertImageLayersMatchManifestLayers(t *testing.T, i v1.Image) {
t.Helper()

Expand Down

0 comments on commit f2696f7

Please sign in to comment.