From 699a4bee326ee3cd73b5ac4670e93c713715fb52 Mon Sep 17 00:00:00 2001 From: Christopher Hlubek Date: Thu, 16 Jul 2020 10:56:59 +0200 Subject: [PATCH] Bugfix: Reproducible layers with whiteout When deleting files of previous layers, the whiteout files were not added to the tar file in a consistent order. This change adds a stable sorting to the whiteout files and adds unit tests to check for stable sorting. --- pkg/snapshot/snapshot.go | 6 ++ pkg/snapshot/snapshot_test.go | 154 ++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) diff --git a/pkg/snapshot/snapshot.go b/pkg/snapshot/snapshot.go index e8523d4e3..c0b0a3131 100644 --- a/pkg/snapshot/snapshot.go +++ b/pkg/snapshot/snapshot.go @@ -108,6 +108,9 @@ func (s *Snapshotter) TakeSnapshot(files []string, shdCheckDelete bool) (string, } } } + + sort.Strings(filesToWhiteout) + t := util.NewTar(f) defer t.Close() if err := writeToTar(t, filesToAdd, filesToWhiteout); err != nil { @@ -178,7 +181,10 @@ func (s *Snapshotter) scanFullFilesystem() ([]string, []string, error) { } } timing.DefaultRun.Stop(timer) + sort.Strings(filesToAdd) + sort.Strings(filesToWhiteOut) + // 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 7150ae0c4..37bcf23a4 100644 --- a/pkg/snapshot/snapshot_test.go +++ b/pkg/snapshot/snapshot_test.go @@ -391,6 +391,160 @@ func TestSnasphotPreservesFileOrder(t *testing.T) { } } +func TestSnasphotPreservesWhiteoutOrder(t *testing.T) { + newFiles := map[string]string{ + "foo": "newbaz1", + "bar/bat": "baz", + "bar/qux": "quuz", + "qux": "quuz", + "corge": "grault", + "garply": "waldo", + "fred": "plugh", + "xyzzy": "thud", + } + + newFileNames := []string{} + + for fileName := range newFiles { + newFileNames = append(newFileNames, fileName) + } + + filesInTars := [][]string{} + + for i := 0; i <= 2; i++ { + testDir, snapshotter, cleanup, err := setUpTest() + testDirWithoutLeadingSlash := strings.TrimLeft(testDir, "/") + defer cleanup() + + if err != nil { + t.Fatal(err) + } + // Make some changes to the filesystem + if err := testutil.SetupFiles(testDir, newFiles); err != nil { + t.Fatalf("Error setting up fs: %s", err) + } + + filesToSnapshot := []string{} + for _, file := range newFileNames { + filesToSnapshot = append(filesToSnapshot, filepath.Join(testDir, file)) + } + + // Take a snapshot + _, err = snapshotter.TakeSnapshot(filesToSnapshot, false) + if err != nil { + t.Fatalf("Error taking snapshot of fs: %s", err) + } + + // Delete all files + for p := range newFiles { + err := os.Remove(filepath.Join(testDir, p)) + if err != nil { + t.Fatalf("Error deleting file: %s", err) + } + } + + // Take a snapshot again + tarPath, err := snapshotter.TakeSnapshot(filesToSnapshot, true) + if err != nil { + t.Fatalf("Error taking snapshot of fs: %s", err) + } + + f, err := os.Open(tarPath) + if err != nil { + t.Fatal(err) + } + tr := tar.NewReader(f) + filesInTars = append(filesInTars, []string{}) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + t.Fatal(err) + } + filesInTars[i] = append(filesInTars[i], strings.TrimPrefix(hdr.Name, testDirWithoutLeadingSlash)) + } + } + + // Check contents of all snapshots, make sure files appear in consistent order + for i := 1; i < len(filesInTars); i++ { + testutil.CheckErrorAndDeepEqual(t, false, nil, filesInTars[0], filesInTars[i]) + } +} + + +func TestSnasphotFSPreservesWhiteoutOrder(t *testing.T) { + newFiles := map[string]string{ + "foo": "newbaz1", + "bar/bat": "baz", + "bar/qux": "quuz", + "qux": "quuz", + "corge": "grault", + "garply": "waldo", + "fred": "plugh", + "xyzzy": "thud", + } + + filesInTars := [][]string{} + + for i := 0; i <= 2; i++ { + testDir, snapshotter, cleanup, err := setUpTest() + testDirWithoutLeadingSlash := strings.TrimLeft(testDir, "/") + defer cleanup() + + if err != nil { + t.Fatal(err) + } + // Make some changes to the filesystem + if err := testutil.SetupFiles(testDir, newFiles); err != nil { + t.Fatalf("Error setting up fs: %s", err) + } + + // Take a snapshot + _, err = snapshotter.TakeSnapshotFS() + if err != nil { + t.Fatalf("Error taking snapshot of fs: %s", err) + } + + // Delete all files + for p := range newFiles { + err := os.Remove(filepath.Join(testDir, p)) + if err != nil { + t.Fatalf("Error deleting file: %s", err) + } + } + + // Take a snapshot again + 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) + } + tr := tar.NewReader(f) + filesInTars = append(filesInTars, []string{}) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + t.Fatal(err) + } + filesInTars[i] = append(filesInTars[i], strings.TrimPrefix(hdr.Name, testDirWithoutLeadingSlash)) + } + } + + // Check contents of all snapshots, make sure files appear in consistent order + for i := 1; i < len(filesInTars); i++ { + testutil.CheckErrorAndDeepEqual(t, false, nil, filesInTars[0], filesInTars[i]) + } +} + func TestSnapshotOmitsUnameGname(t *testing.T) { _, snapshotter, cleanup, err := setUpTest()