From 619fc5e59bda3428b2c573ad15166edc358832af Mon Sep 17 00:00:00 2001 From: Matthew Dawson Date: Thu, 11 Jul 2019 08:42:44 -0400 Subject: [PATCH] Make container layers captured using FS snapshots reproducible When a Dockerfile command requires using the TakeSnapshotFS function, the resulting layer has a random ordering of files. This causes the layer to have a non-deterministic hash defeating the reproducible flag. Issue #710 appears to document this issue as well. To fix, always sort the list of files to be added in scanFullFilesystem. This avoids trying to sort the file list during execution, and takes almost no time to complete. --- pkg/snapshot/snapshot.go | 3 +++ pkg/snapshot/snapshot_test.go | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/pkg/snapshot/snapshot.go b/pkg/snapshot/snapshot.go index 90638168d..2eb68ea2c 100644 --- a/pkg/snapshot/snapshot.go +++ b/pkg/snapshot/snapshot.go @@ -20,6 +20,7 @@ import ( "fmt" "io/ioutil" "path/filepath" + "sort" "syscall" "github.com/GoogleContainerTools/kaniko/pkg/timing" @@ -186,6 +187,8 @@ func (s *Snapshotter) scanFullFilesystem() ([]string, []string, error) { // Also add parent directories to keep the permission of them correctly. filesToAdd = filesWithParentDirs(filesToAdd) + sort.Strings(filesToAdd) + // Add files to the layered map for _, file := range filesToAdd { if err := s.l.Add(file); err != nil { diff --git a/pkg/snapshot/snapshot_test.go b/pkg/snapshot/snapshot_test.go index ea6f4bceb..bfa445f58 100644 --- a/pkg/snapshot/snapshot_test.go +++ b/pkg/snapshot/snapshot_test.go @@ -87,6 +87,45 @@ func TestSnapshotFSFileChange(t *testing.T) { } } +func TestSnapshotFSIsReproducible(t *testing.T) { + testDir, snapshotter, cleanup, err := setUpTestDir() + defer cleanup() + if err != nil { + t.Fatal(err) + } + // Make some changes to the filesystem + newFiles := map[string]string{ + "foo": "newbaz1", + "bar/bat": "baz", + } + if err := testutil.SetupFiles(testDir, newFiles); err != nil { + t.Fatalf("Error setting up fs: %s", err) + } + // Take another snapshot + tarPath, err := snapshotter.TakeSnapshotFS() + if err != nil { + t.Fatalf("Error taking snapshot of fs: %s", err) + } + + f, err := os.Open(tarPath) + if err != nil { + t.Fatal(err) + } + // Check contents of the snapshot, make sure contents are sorted by name + tr := tar.NewReader(f) + var filesInTar []string + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + filesInTar = append(filesInTar, hdr.Name) + } + if !sort.StringsAreSorted(filesInTar) { + t.Fatalf("Expected the file in the tar archive were sorted, actual list was not sorted: %v", filesInTar) + } +} + func TestSnapshotFSChangePermissions(t *testing.T) { testDir, snapshotter, cleanup, err := setUpTestDir() defer cleanup()