Skip to content

Commit

Permalink
Change cache key calculation to be more reproducible.
Browse files Browse the repository at this point in the history
Before we were using the full image digest, but that contains a timestamp. Now
we only use the layers themselves and the image config (env vars, etc.).

Also fix a bug in unpacking the layers themselves. mtimes can change during unpacking,
so set them all once at the end.
  • Loading branch information
dlorenc committed Jan 11, 2019
1 parent 170e0a2 commit 177dae1
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 9 deletions.
15 changes: 11 additions & 4 deletions integration/dockerfiles/Dockerfile_test_cache
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@
# If the image is built twice, /date should be the same in both images
# if the cache is implemented correctly

FROM gcr.io/google-appengine/debian9@sha256:1d6a9a6d106bd795098f60f4abb7083626354fa6735e81743c7f8cfca11259f0
RUN date > /date
COPY context/foo /foo
RUN echo hey
FROM alpine as base_stage

RUN mkdir foo && echo base_stage > foo/base

FROM base_stage as cached_stage

RUN echo cached_stage > foo/cache

FROM cached_stage as bug_stage

RUN echo bug_stage > foo/bug
7 changes: 6 additions & 1 deletion pkg/executor/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func (s *stageBuilder) optimize(compositeKey CompositeCache, cfg v1.Config, cmds
img, err := layerCache.RetrieveLayer(ck)
if err != nil {
logrus.Infof("No cached layer found for cmd %s", command.String())
logrus.Debugf("Key missing was: %s", compositeKey.Key())
break
}

Expand All @@ -153,7 +154,11 @@ func (s *stageBuilder) optimize(compositeKey CompositeCache, cfg v1.Config, cmds

func (s *stageBuilder) build() error {
// Set the initial cache key to be the base image digest, the build args and the SrcContext.
compositeKey := NewCompositeCache(s.baseImageDigest)
dgst, err := util.ReproducibleDigest(s.image)
if err != nil {
return err
}
compositeKey := NewCompositeCache(dgst)
compositeKey.AddKey(s.opts.BuildArgs...)

cmds := []commands.DockerCommand{}
Expand Down
20 changes: 16 additions & 4 deletions pkg/util/fs_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ func GetFSFromImage(root string, img v1.Image) ([]string, error) {
if err != nil {
return nil, err
}
extractedFiles := []string{}

// Store a map of files to their mtime. We need to set mtimes in a second pass because creating files
// can change the mtime of a directory.
extractedFiles := map[string]time.Time{}

for i, l := range layers {
logrus.Debugf("Extracting layer %d", i)
Expand Down Expand Up @@ -106,10 +109,17 @@ func GetFSFromImage(root string, img v1.Image) ([]string, error) {
if err := extractFile(root, hdr, tr); err != nil {
return nil, err
}
extractedFiles = append(extractedFiles, filepath.Join(root, filepath.Clean(hdr.Name)))
extractedFiles[filepath.Join(root, filepath.Clean(hdr.Name))] = hdr.ModTime
}
}
return extractedFiles, nil

fileNames := []string{}
for f, t := range extractedFiles {
fileNames = append(fileNames, f)
os.Chtimes(f, time.Time{}, t)
}

return fileNames, nil
}

// DeleteFilesystem deletes the extracted image file system
Expand Down Expand Up @@ -272,6 +282,7 @@ func extractFile(dest string, hdr *tar.Header, tr io.Reader) error {
return err
}
}

return nil
}

Expand Down Expand Up @@ -382,7 +393,8 @@ func RelativeFiles(fp string, root string) ([]string, error) {
}

// ParentDirectories returns a list of paths to all parent directories
// Ex. /some/temp/dir -> [/, /some, /some/temp, /some/temp/dir]
// Ex. /some/temp/dir -> [/some, /some/temp, /some/temp/dir]
// This purposefully excludes the /.
func ParentDirectories(path string) []string {
path = filepath.Clean(path)
dirs := strings.Split(path, "/")
Expand Down
1 change: 1 addition & 0 deletions pkg/util/tar_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func (t *Tar) Close() {

// AddFileToTar adds the file at path p to the tar
func (t *Tar) AddFileToTar(p string) error {
logrus.Debugf("Adding file %s to tar", p)
i, err := os.Lstat(p)
if err != nil {
return fmt.Errorf("Failed to get file info for %s: %s", p, err)
Expand Down
28 changes: 28 additions & 0 deletions pkg/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import (
"crypto/md5"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"os"
"strconv"
"syscall"

"github.com/google/go-containerregistry/pkg/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -127,3 +129,29 @@ func SHA256(r io.Reader) (string, error) {
}
return hex.EncodeToString(hasher.Sum(make([]byte, 0, hasher.Size()))), nil
}

type ReproducibleManifest struct {
Layers []v1.Descriptor
Config v1.Config
}

func ReproducibleDigest(img v1.Image) (string, error) {
mfst, err := img.Manifest()
if err != nil {
return "", err
}
cfg, err := img.ConfigFile()
if err != nil {
return "", err
}
rm := ReproducibleManifest{
Layers: mfst.Layers,
Config: cfg.Config,
}

b, err := json.Marshal(rm)
if err != nil {
return "", err
}
return string(b), nil
}

0 comments on commit 177dae1

Please sign in to comment.