From 123dcaf83e8bbf70adeedb9e26729e2aa147007f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Wanzenb=C3=B6ck?= Date: Mon, 11 Nov 2019 17:48:03 +0100 Subject: [PATCH 1/4] Fix caching to respect .dockerignore Previously kaniko would compute the cache key for any copy command by computing the combined hash of all files in a directory, even if they were listed as ignored. With this change, the cache key creation was updated to be aware of ignored files. Related issues: * https://github.com/GoogleContainerTools/kaniko/issues/594 --- pkg/executor/build.go | 4 +++- pkg/executor/composite_cache.go | 18 +++++++++++++++--- pkg/util/command_util.go | 4 ++-- pkg/util/fs_util.go | 10 +++++----- pkg/util/fs_util_test.go | 4 ++-- 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/pkg/executor/build.go b/pkg/executor/build.go index d1c17cf1d2..7bfb28b41b 100644 --- a/pkg/executor/build.go +++ b/pkg/executor/build.go @@ -158,8 +158,10 @@ func (s *stageBuilder) populateCompositeKey(command fmt.Stringer, files []string compositeKey = s.populateCopyCmdCompositeKey(command, v.From(), compositeKey) } + srcCtx := s.opts.SrcContext + for _, f := range files { - if err := compositeKey.AddPath(f); err != nil { + if err := compositeKey.AddPath(f, srcCtx); err != nil { return compositeKey, err } } diff --git a/pkg/executor/composite_cache.go b/pkg/executor/composite_cache.go index ae8ff06099..53118397d4 100644 --- a/pkg/executor/composite_cache.go +++ b/pkg/executor/composite_cache.go @@ -54,14 +54,18 @@ func (s *CompositeCache) Hash() (string, error) { return util.SHA256(strings.NewReader(s.Key())) } -func (s *CompositeCache) AddPath(p string) error { +func (s *CompositeCache) AddPath(p, context string) error { sha := sha256.New() fi, err := os.Lstat(p) if err != nil { return err } + if util.ExcludeFile(p, context) { + return os.ErrNotExist + } + if fi.Mode().IsDir() { - k, err := HashDir(p) + k, err := HashDir(p, context) if err != nil { return err } @@ -81,12 +85,20 @@ func (s *CompositeCache) AddPath(p string) error { } // HashDir returns a hash of the directory. -func HashDir(p string) (string, error) { +func HashDir(p, context string) (string, error) { sha := sha256.New() if err := filepath.Walk(p, func(path string, fi os.FileInfo, err error) error { if err != nil { return err } + exclude := util.ExcludeFile(path, context) + if fi.IsDir() && exclude { + return filepath.SkipDir + } + if exclude { + return nil + } + fileHash, err := util.CacheHasher()(path) if err != nil { return err diff --git a/pkg/util/command_util.go b/pkg/util/command_util.go index 563d1a08d8..6f391e1335 100644 --- a/pkg/util/command_util.go +++ b/pkg/util/command_util.go @@ -213,7 +213,7 @@ func IsSrcsValid(srcsAndDest instructions.SourcesAndDest, resolvedSources []stri if !ContainsWildcards(srcs) { totalSrcs := 0 for _, src := range srcs { - if excludeFile(src, root) { + if ExcludeFile(src, root) { continue } totalSrcs++ @@ -250,7 +250,7 @@ func IsSrcsValid(srcsAndDest instructions.SourcesAndDest, resolvedSources []stri return errors.Wrap(err, "failed to get relative files") } for _, file := range files { - if excludeFile(file, root) { + if ExcludeFile(file, root) { continue } totalFiles++ diff --git a/pkg/util/fs_util.go b/pkg/util/fs_util.go index 7aa91adffc..93a7b83758 100644 --- a/pkg/util/fs_util.go +++ b/pkg/util/fs_util.go @@ -561,7 +561,7 @@ func CopyDir(src, dest, buildcontext string) ([]string, error) { if err != nil { return nil, err } - if excludeFile(fullPath, buildcontext) { + if ExcludeFile(fullPath, buildcontext) { logrus.Debugf("%s found in .dockerignore, ignoring", src) continue } @@ -594,7 +594,7 @@ func CopyDir(src, dest, buildcontext string) ([]string, error) { // CopySymlink copies the symlink at src to dest func CopySymlink(src, dest, buildcontext string) (bool, error) { - if excludeFile(src, buildcontext) { + if ExcludeFile(src, buildcontext) { logrus.Debugf("%s found in .dockerignore, ignoring", src) return true, nil } @@ -612,7 +612,7 @@ func CopySymlink(src, dest, buildcontext string) (bool, error) { // CopyFile copies the file at src to dest func CopyFile(src, dest, buildcontext string) (bool, error) { - if excludeFile(src, buildcontext) { + if ExcludeFile(src, buildcontext) { logrus.Debugf("%s found in .dockerignore, ignoring", src) return true, nil } @@ -656,8 +656,8 @@ func GetExcludedFiles(dockerfilepath string, buildcontext string) error { return err } -// excludeFile returns true if the .dockerignore specified this file should be ignored -func excludeFile(path, buildcontext string) bool { +// ExcludeFile returns true if the .dockerignore specified this file should be ignored +func ExcludeFile(path, buildcontext string) bool { if HasFilepathPrefix(path, buildcontext, false) { var err error path, err = filepath.Rel(buildcontext, path) diff --git a/pkg/util/fs_util_test.go b/pkg/util/fs_util_test.go index 2919426ce7..7eea83945d 100644 --- a/pkg/util/fs_util_test.go +++ b/pkg/util/fs_util_test.go @@ -919,14 +919,14 @@ func Test_correctDockerignoreFileIsUsed(t *testing.T) { } for _, excl := range tt.args.excluded { t.Run(tt.name+" to exclude "+excl, func(t *testing.T) { - if !excludeFile(excl, tt.args.buildcontext) { + if !ExcludeFile(excl, tt.args.buildcontext) { t.Errorf("'%v' not excluded", excl) } }) } for _, incl := range tt.args.included { t.Run(tt.name+" to include "+incl, func(t *testing.T) { - if excludeFile(incl, tt.args.buildcontext) { + if ExcludeFile(incl, tt.args.buildcontext) { t.Errorf("'%v' not included", incl) } }) From ef692e55f3d70e73a28d8f9c987874cb57cbe1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Wanzenb=C3=B6ck?= Date: Sun, 17 Nov 2019 21:05:49 +0100 Subject: [PATCH 2/4] composite_cache: add unit tests Add unit tests that check the behaviour of CompositeCache on adding filesytem resources. It checks that * 2 identical directory trees produces the same hash * an extra file produces a different hash * an extra directry produces a different hash * an extra file that is excluded does not alter the hash * an extra directory that is excluded does not alter the hash --- pkg/executor/composite_cache_test.go | 433 +++++++++++++++++++++++++++ 1 file changed, 433 insertions(+) diff --git a/pkg/executor/composite_cache_test.go b/pkg/executor/composite_cache_test.go index a3c7f55af6..095c3ae649 100644 --- a/pkg/executor/composite_cache_test.go +++ b/pkg/executor/composite_cache_test.go @@ -19,9 +19,12 @@ package executor import ( "io/ioutil" "os" + "path" "path/filepath" "reflect" "testing" + + "github.com/GoogleContainerTools/kaniko/pkg/util" ) func Test_NewCompositeCache(t *testing.T) { @@ -135,3 +138,433 @@ func Test_CompositeCache_AddPath_file(t *testing.T) { t.Errorf("expected hash %v to equal hash %v", hash1, hash2) } } + +func createFilesystemStructure(root string, directories, files []string) error { + for _, d := range directories { + dirPath := path.Join(root, d) + if err := os.MkdirAll(dirPath, 0755); err != nil { + return err + } + } + + for _, fileName := range files { + filePath := path.Join(root, fileName) + err := ioutil.WriteFile(filePath, []byte(fileName), 0644) + if err != nil { + return err + } + } + + return nil +} + +func setIgnoreContext(content string) error { + dockerIgnoreDir, err := ioutil.TempDir("", "") + if err != nil { + return err + } + defer os.RemoveAll(dockerIgnoreDir) + err = ioutil.WriteFile(dockerIgnoreDir+".dockerignore", []byte(content), 0644) + if err != nil { + return err + } + err = util.GetExcludedFiles(dockerIgnoreDir, "") + if err != nil { + return err + } + return nil +} + +func hashDirectory(dirpath string) (string, error) { + cache1 := NewCompositeCache() + err := cache1.AddPath(dirpath, dirpath) + if err != nil { + return "", err + } + + hash, err := cache1.Hash() + if err != nil { + return "", err + } + return hash, nil +} + +func Test_CompositeKey_AddPath_Works(t *testing.T) { + tests := []struct { + name string + directories []string + files []string + }{ + { + name: "empty", + directories: []string{}, + files: []string{}, + }, + { + name: "dirs", + directories: []string{"foo", "bar", "foobar", "f/o/o"}, + files: []string{}, + }, + { + name: "files", + directories: []string{}, + files: []string{"foo", "bar", "foobar"}, + }, + { + name: "all", + directories: []string{"foo", "bar"}, + files: []string{"foo/bar", "bar/baz", "foobar"}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + testDir1, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Error creating tempdir: %s", err) + } + defer os.RemoveAll(testDir1) + err = createFilesystemStructure(testDir1, test.directories, test.files) + if err != nil { + t.Fatalf("Error creating filesytem structure: %s", err) + } + + testDir2, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Error creating tempdir: %s", err) + } + defer os.RemoveAll(testDir2) + err = createFilesystemStructure(testDir2, test.directories, test.files) + if err != nil { + t.Fatalf("Error creating filesytem structure: %s", err) + } + + hash1, err := hashDirectory(testDir1) + if err != nil { + t.Fatalf("Failed to calculate hash: %s", err) + } + hash2, err := hashDirectory(testDir2) + if err != nil { + t.Fatalf("Failed to calculate hash: %s", err) + } + + if hash1 != hash2 { + t.Errorf("Expected equal hashes, got: %s and %s", hash1, hash2) + } + }) + } +} + +func Test_CompositeKey_AddPath_WithExtraFile_Works(t *testing.T) { + tests := []struct { + name string + directories []string + files []string + extraFile string + }{ + { + name: "empty", + directories: []string{}, + files: []string{}, + extraFile: "file", + }, + { + name: "dirs", + directories: []string{"foo", "bar", "foobar", "f/o/o"}, + files: []string{}, + extraFile: "f/o/o/extra", + }, + { + name: "files", + directories: []string{}, + files: []string{"foo", "bar", "foobar"}, + extraFile: "foo.extra", + }, + { + name: "all", + directories: []string{"foo", "bar"}, + files: []string{"foo/bar", "bar/baz", "foobar"}, + extraFile: "bar/extra", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + testDir1, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Error creating tempdir: %s", err) + } + defer os.RemoveAll(testDir1) + err = createFilesystemStructure(testDir1, test.directories, test.files) + if err != nil { + t.Fatalf("Error creating filesytem structure: %s", err) + } + + testDir2, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Error creating tempdir: %s", err) + } + defer os.RemoveAll(testDir2) + err = createFilesystemStructure(testDir2, test.directories, test.files) + if err != nil { + t.Fatalf("Error creating filesytem structure: %s", err) + } + extraPath := path.Join(testDir2, test.extraFile) + err = ioutil.WriteFile(extraPath, []byte(test.extraFile), 0644) + if err != nil { + t.Fatalf("Error creating filesytem structure: %s", err) + } + + hash1, err := hashDirectory(testDir1) + if err != nil { + t.Fatalf("Failed to calculate hash: %s", err) + } + hash2, err := hashDirectory(testDir2) + if err != nil { + t.Fatalf("Failed to calculate hash: %s", err) + } + + if hash1 == hash2 { + t.Errorf("Expected different hashes, got: %s and %s", hash1, hash2) + } + }) + } +} + +func Test_CompositeKey_AddPath_WithExtraDir_Works(t *testing.T) { + tests := []struct { + name string + directories []string + files []string + extraDir string + }{ + { + name: "empty", + directories: []string{}, + files: []string{}, + extraDir: "extra", + }, + { + name: "dirs", + directories: []string{"foo", "bar", "foobar", "f/o/o"}, + files: []string{}, + extraDir: "f/o/o/extra", + }, + { + name: "files", + directories: []string{}, + files: []string{"foo", "bar", "foobar"}, + extraDir: "foo.extra", + }, + { + name: "all", + directories: []string{"foo", "bar"}, + files: []string{"foo/bar", "bar/baz", "foobar"}, + extraDir: "bar/extra", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + testDir1, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Error creating tempdir: %s", err) + } + defer os.RemoveAll(testDir1) + err = createFilesystemStructure(testDir1, test.directories, test.files) + if err != nil { + t.Fatalf("Error creating filesytem structure: %s", err) + } + + testDir2, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Error creating tempdir: %s", err) + } + defer os.RemoveAll(testDir2) + err = createFilesystemStructure(testDir2, test.directories, test.files) + if err != nil { + t.Fatalf("Error creating filesytem structure: %s", err) + } + extraPath := path.Join(testDir2, test.extraDir) + err = os.MkdirAll(extraPath, 0644) + if err != nil { + t.Fatalf("Error creating filesytem structure: %s", err) + } + + hash1, err := hashDirectory(testDir1) + if err != nil { + t.Fatalf("Failed to calculate hash: %s", err) + } + hash2, err := hashDirectory(testDir2) + if err != nil { + t.Fatalf("Failed to calculate hash: %s", err) + } + + if hash1 == hash2 { + t.Errorf("Expected different hashes, got: %s and %s", hash1, hash2) + } + }) + } +} + +func Test_CompositeKey_AddPath_WithExtraFilIgnored_Works(t *testing.T) { + tests := []struct { + name string + directories []string + files []string + extraFile string + }{ + { + name: "empty", + directories: []string{}, + files: []string{}, + extraFile: "extra", + }, + { + name: "dirs", + directories: []string{"foo", "bar", "foobar", "f/o/o"}, + files: []string{}, + extraFile: "f/o/o/extra", + }, + { + name: "files", + directories: []string{}, + files: []string{"foo", "bar", "foobar"}, + extraFile: "extra", + }, + { + name: "all", + directories: []string{"foo", "bar"}, + files: []string{"foo/bar", "bar/baz", "foobar"}, + extraFile: "bar/extra", + }, + } + + err := setIgnoreContext("**/extra") + if err != nil { + t.Fatalf("Error setting exlusion context: %s", err) + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + testDir1, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Error creating tempdir: %s", err) + } + defer os.RemoveAll(testDir1) + err = createFilesystemStructure(testDir1, test.directories, test.files) + if err != nil { + t.Fatalf("Error creating filesytem structure: %s", err) + } + + testDir2, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Error creating tempdir: %s", err) + } + defer os.RemoveAll(testDir2) + err = createFilesystemStructure(testDir2, test.directories, test.files) + if err != nil { + t.Fatalf("Error creating filesytem structure: %s", err) + } + extraPath := path.Join(testDir2, test.extraFile) + err = ioutil.WriteFile(extraPath, []byte(test.extraFile), 0644) + if err != nil { + t.Fatalf("Error creating filesytem structure: %s", err) + } + + hash1, err := hashDirectory(testDir1) + if err != nil { + t.Fatalf("Failed to calculate hash: %s", err) + } + hash2, err := hashDirectory(testDir2) + if err != nil { + t.Fatalf("Failed to calculate hash: %s", err) + } + + if hash1 != hash2 { + t.Errorf("Expected equal hashes, got: %s and %s", hash1, hash2) + } + }) + } +} + +func Test_CompositeKey_AddPath_WithExtraDirIgnored_Works(t *testing.T) { + tests := []struct { + name string + directories []string + files []string + extraDir string + }{ + { + name: "empty", + directories: []string{}, + files: []string{}, + extraDir: "extra", + }, + { + name: "dirs", + directories: []string{"foo", "bar", "foobar", "f/o/o"}, + files: []string{}, + extraDir: "f/o/o/extra", + }, + { + name: "files", + directories: []string{}, + files: []string{"foo", "bar", "foobar"}, + extraDir: "extra", + }, + { + name: "all", + directories: []string{"foo", "bar"}, + files: []string{"foo/bar", "bar/baz", "foobar"}, + extraDir: "bar/extra", + }, + } + + err := setIgnoreContext("**/extra") + if err != nil { + t.Fatalf("Error setting exlusion context: %s", err) + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + testDir1, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Error creating tempdir: %s", err) + } + defer os.RemoveAll(testDir1) + err = createFilesystemStructure(testDir1, test.directories, test.files) + if err != nil { + t.Fatalf("Error creating filesytem structure: %s", err) + } + + testDir2, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Error creating tempdir: %s", err) + } + defer os.RemoveAll(testDir2) + err = createFilesystemStructure(testDir2, test.directories, test.files) + if err != nil { + t.Fatalf("Error creating filesytem structure: %s", err) + } + extraPath := path.Join(testDir2, test.extraDir) + err = os.MkdirAll(extraPath, 0644) + if err != nil { + t.Fatalf("Error creating filesytem structure: %s", err) + } + + hash1, err := hashDirectory(testDir1) + if err != nil { + t.Fatalf("Failed to calculate hash: %s", err) + } + hash2, err := hashDirectory(testDir2) + if err != nil { + t.Fatalf("Failed to calculate hash: %s", err) + } + + if hash1 != hash2 { + t.Errorf("Expected equal hashes, got: %s and %s", hash1, hash2) + } + }) + } +} From 782e4916b6850c3f04e7ae88bdf5ca5f006f7afc Mon Sep 17 00:00:00 2001 From: Cole Wippern Date: Fri, 20 Dec 2019 16:34:54 -0800 Subject: [PATCH 3/4] update build and composite cache tests --- pkg/executor/build_test.go | 18 +++++++++--------- pkg/executor/composite_cache_test.go | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/executor/build_test.go b/pkg/executor/build_test.go index db3f551548..64046bc293 100644 --- a/pkg/executor/build_test.go +++ b/pkg/executor/build_test.go @@ -538,7 +538,7 @@ func Test_stageBuilder_build(t *testing.T) { filePath := filepath.Join(dir, file) ch := NewCompositeCache("", "meow") - ch.AddPath(filePath) + ch.AddPath(filePath, "") hash, err := ch.Hash() if err != nil { t.Errorf("couldn't create hash %v", err) @@ -570,7 +570,7 @@ func Test_stageBuilder_build(t *testing.T) { filePath := filepath.Join(dir, file) ch := NewCompositeCache("", "meow") - ch.AddPath(filePath) + ch.AddPath(filePath, "") hash, err := ch.Hash() if err != nil { t.Errorf("couldn't create hash %v", err) @@ -618,7 +618,7 @@ func Test_stageBuilder_build(t *testing.T) { tarContent := generateTar(t, dir, filename) ch := NewCompositeCache("", "") - ch.AddPath(filepath) + ch.AddPath(filepath, "") hash, err := ch.Hash() if err != nil { @@ -662,7 +662,7 @@ func Test_stageBuilder_build(t *testing.T) { } filePath := filepath.Join(dir, filename) ch := NewCompositeCache("", "") - ch.AddPath(filePath) + ch.AddPath(filePath, "") hash, err := ch.Hash() if err != nil { @@ -713,7 +713,7 @@ func Test_stageBuilder_build(t *testing.T) { } ch.AddKey(fmt.Sprintf("COPY %s bar.txt", filename)) - ch.AddPath(filePath) + ch.AddPath(filePath, "") hash2, err := ch.Hash() if err != nil { @@ -721,7 +721,7 @@ func Test_stageBuilder_build(t *testing.T) { } ch = NewCompositeCache("", fmt.Sprintf("COPY %s foo.txt", filename)) ch.AddKey(fmt.Sprintf("COPY %s bar.txt", filename)) - ch.AddPath(filePath) + ch.AddPath(filePath, "") image := fakeImage{ ImageLayers: []v1.Layer{ @@ -777,14 +777,14 @@ COPY %s bar.txt } filePath := filepath.Join(dir, filename) ch := NewCompositeCache("", fmt.Sprintf("COPY %s foo.txt", filename)) - ch.AddPath(filePath) + ch.AddPath(filePath, "") hash1, err := ch.Hash() if err != nil { t.Errorf("couldn't create hash %v", err) } ch.AddKey(fmt.Sprintf("COPY %s bar.txt", filename)) - ch.AddPath(filePath) + ch.AddPath(filePath, "") hash2, err := ch.Hash() if err != nil { @@ -792,7 +792,7 @@ COPY %s bar.txt } ch = NewCompositeCache("", fmt.Sprintf("COPY %s foo.txt", filename)) ch.AddKey(fmt.Sprintf("COPY %s bar.txt", filename)) - ch.AddPath(filePath) + ch.AddPath(filePath, "") image := fakeImage{ ImageLayers: []v1.Layer{ diff --git a/pkg/executor/composite_cache_test.go b/pkg/executor/composite_cache_test.go index 095c3ae649..764364e93e 100644 --- a/pkg/executor/composite_cache_test.go +++ b/pkg/executor/composite_cache_test.go @@ -80,7 +80,7 @@ func Test_CompositeCache_AddPath_dir(t *testing.T) { fn := func() string { r := NewCompositeCache() - if err := r.AddPath(tmpDir); err != nil { + if err := r.AddPath(tmpDir, ""); err != nil { t.Errorf("expected error to be nil but was %v", err) } @@ -118,7 +118,7 @@ func Test_CompositeCache_AddPath_file(t *testing.T) { p := tmpfile.Name() fn := func() string { r := NewCompositeCache() - if err := r.AddPath(p); err != nil { + if err := r.AddPath(p, ""); err != nil { t.Errorf("expected error to be nil but was %v", err) } From bab1c170e85f5db4dfa2ac9e6e2c5f444568601a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Wanzenb=C3=B6ck?= Date: Fri, 24 Jan 2020 22:27:56 +0100 Subject: [PATCH 4/4] fix skipping ignored directories if they have whitelisted content --- pkg/executor/composite_cache.go | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/pkg/executor/composite_cache.go b/pkg/executor/composite_cache.go index 53118397d4..df1eb5ef85 100644 --- a/pkg/executor/composite_cache.go +++ b/pkg/executor/composite_cache.go @@ -60,16 +60,22 @@ func (s *CompositeCache) AddPath(p, context string) error { if err != nil { return err } - if util.ExcludeFile(p, context) { - return os.ErrNotExist - } if fi.Mode().IsDir() { - k, err := HashDir(p, context) + empty, k, err := hashDir(p, context) if err != nil { return err } - s.keys = append(s.keys, k) + + // Only add the hash of this directory to the key + // if there is any whitelisted content. + if !empty || !util.ExcludeFile(p, context) { + s.keys = append(s.keys, k) + } + return nil + } + + if util.ExcludeFile(p, context) { return nil } fh, err := util.CacheHasher()(p) @@ -85,16 +91,14 @@ func (s *CompositeCache) AddPath(p, context string) error { } // HashDir returns a hash of the directory. -func HashDir(p, context string) (string, error) { +func hashDir(p, context string) (bool, string, error) { sha := sha256.New() + empty := true if err := filepath.Walk(p, func(path string, fi os.FileInfo, err error) error { if err != nil { return err } exclude := util.ExcludeFile(path, context) - if fi.IsDir() && exclude { - return filepath.SkipDir - } if exclude { return nil } @@ -106,10 +110,11 @@ func HashDir(p, context string) (string, error) { if _, err := sha.Write([]byte(fileHash)); err != nil { return err } + empty = false return nil }); err != nil { - return "", err + return false, "", err } - return fmt.Sprintf("%x", sha.Sum(nil)), nil + return empty, fmt.Sprintf("%x", sha.Sum(nil)), nil }