github.com/GoogleContainerTools/kaniko@v1.23.0/pkg/util/fs_util_test.go (about) 1 /* 2 Copyright 2018 Google LLC 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package util 18 19 import ( 20 "archive/tar" 21 "bytes" 22 "fmt" 23 "io" 24 "io/fs" 25 "os" 26 "path/filepath" 27 "reflect" 28 "sort" 29 "strings" 30 "testing" 31 "time" 32 33 "github.com/GoogleContainerTools/kaniko/pkg/config" 34 "github.com/GoogleContainerTools/kaniko/pkg/constants" 35 "github.com/GoogleContainerTools/kaniko/pkg/mocks/go-containerregistry/mockv1" 36 "github.com/GoogleContainerTools/kaniko/testutil" 37 "github.com/golang/mock/gomock" 38 v1 "github.com/google/go-containerregistry/pkg/v1" 39 "github.com/google/go-containerregistry/pkg/v1/types" 40 ) 41 42 func Test_DetectFilesystemSkiplist(t *testing.T) { 43 testDir := t.TempDir() 44 fileContents := ` 45 228 122 0:90 / / rw,relatime - aufs none rw,si=f8e2406af90782bc,dio,dirperm1 46 229 228 0:98 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw 47 230 228 0:99 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 48 231 230 0:100 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 49 232 228 0:101 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro` 50 51 path := filepath.Join(testDir, "mountinfo") 52 if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil { 53 t.Fatalf("Error creating tempdir: %s", err) 54 } 55 if err := os.WriteFile(path, []byte(fileContents), 0o644); err != nil { 56 t.Fatalf("Error writing file contents to %s: %s", path, err) 57 } 58 59 err := DetectFilesystemIgnoreList(path) 60 expectedSkiplist := []IgnoreListEntry{ 61 {"/kaniko", false}, 62 {"/proc", false}, 63 {"/dev", false}, 64 {"/dev/pts", false}, 65 {"/sys", false}, 66 {"/etc/mtab", false}, 67 {"/tmp/apt-key-gpghome", true}, 68 } 69 actualSkiplist := ignorelist 70 sort.Slice(actualSkiplist, func(i, j int) bool { 71 return actualSkiplist[i].Path < actualSkiplist[j].Path 72 }) 73 sort.Slice(expectedSkiplist, func(i, j int) bool { 74 return expectedSkiplist[i].Path < expectedSkiplist[j].Path 75 }) 76 testutil.CheckErrorAndDeepEqual(t, false, err, expectedSkiplist, actualSkiplist) 77 } 78 79 func Test_AddToIgnoreList(t *testing.T) { 80 t.Cleanup(func() { 81 ignorelist = append([]IgnoreListEntry{}, defaultIgnoreList...) 82 }) 83 84 AddToIgnoreList(IgnoreListEntry{ 85 Path: "/tmp", 86 PrefixMatchOnly: false, 87 }) 88 89 if !CheckIgnoreList("/tmp") { 90 t.Errorf("CheckIgnoreList() = %v, want %v", false, true) 91 } 92 } 93 94 var tests = []struct { 95 files map[string]string 96 directory string 97 expectedFiles []string 98 }{ 99 { 100 files: map[string]string{ 101 "/workspace/foo/a": "baz1", 102 "/workspace/foo/b": "baz2", 103 "/kaniko/file": "file", 104 }, 105 directory: "/workspace/foo/", 106 expectedFiles: []string{ 107 "workspace/foo/a", 108 "workspace/foo/b", 109 "workspace/foo", 110 }, 111 }, 112 { 113 files: map[string]string{ 114 "/workspace/foo/a": "baz1", 115 }, 116 directory: "/workspace/foo/a", 117 expectedFiles: []string{ 118 "workspace/foo/a", 119 }, 120 }, 121 { 122 files: map[string]string{ 123 "/workspace/foo/a": "baz1", 124 "/workspace/foo/b": "baz2", 125 "/workspace/baz": "hey", 126 "/kaniko/file": "file", 127 }, 128 directory: "/workspace", 129 expectedFiles: []string{ 130 "workspace/foo/a", 131 "workspace/foo/b", 132 "workspace/baz", 133 "workspace", 134 "workspace/foo", 135 }, 136 }, 137 { 138 files: map[string]string{ 139 "/workspace/foo/a": "baz1", 140 "/workspace/foo/b": "baz2", 141 }, 142 directory: "", 143 expectedFiles: []string{ 144 "workspace/foo/a", 145 "workspace/foo/b", 146 "workspace", 147 "workspace/foo", 148 ".", 149 }, 150 }, 151 } 152 153 func Test_RelativeFiles(t *testing.T) { 154 for _, test := range tests { 155 testDir := t.TempDir() 156 if err := testutil.SetupFiles(testDir, test.files); err != nil { 157 t.Fatalf("err setting up files: %v", err) 158 } 159 actualFiles, err := RelativeFiles(test.directory, testDir) 160 sort.Strings(actualFiles) 161 sort.Strings(test.expectedFiles) 162 testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFiles, actualFiles) 163 } 164 } 165 166 func Test_ParentDirectories(t *testing.T) { 167 tests := []struct { 168 name string 169 path string 170 rootDir string 171 expected []string 172 }{ 173 { 174 name: "regular path", 175 path: "/path/to/dir", 176 rootDir: "/", 177 expected: []string{ 178 "/", 179 "/path", 180 "/path/to", 181 }, 182 }, 183 { 184 name: "current directory", 185 path: ".", 186 rootDir: "/", 187 expected: []string{ 188 "/", 189 }, 190 }, 191 { 192 name: "non / root directory", 193 path: "/tmp/kaniko/test/another/dir", 194 rootDir: "/tmp/kaniko/", 195 expected: []string{ 196 "/tmp/kaniko", 197 "/tmp/kaniko/test", 198 "/tmp/kaniko/test/another", 199 }, 200 }, 201 { 202 name: "non / root director same path", 203 path: "/tmp/123", 204 rootDir: "/tmp/123", 205 expected: []string{ 206 "/tmp/123", 207 }, 208 }, 209 { 210 name: "non / root directory path", 211 path: "/tmp/120162240/kaniko", 212 rootDir: "/tmp/120162240", 213 expected: []string{ 214 "/tmp/120162240", 215 }, 216 }, 217 } 218 219 for _, tt := range tests { 220 t.Run(tt.name, func(t *testing.T) { 221 original := config.RootDir 222 defer func() { config.RootDir = original }() 223 config.RootDir = tt.rootDir 224 actual := ParentDirectories(tt.path) 225 226 testutil.CheckErrorAndDeepEqual(t, false, nil, tt.expected, actual) 227 }) 228 } 229 } 230 231 func Test_ParentDirectoriesWithoutLeadingSlash(t *testing.T) { 232 tests := []struct { 233 name string 234 path string 235 expected []string 236 }{ 237 { 238 name: "regular path", 239 path: "/path/to/dir", 240 expected: []string{ 241 "/", 242 "path", 243 "path/to", 244 }, 245 }, 246 { 247 name: "current directory", 248 path: ".", 249 expected: []string{ 250 "/", 251 }, 252 }, 253 } 254 255 for _, tt := range tests { 256 t.Run(tt.name, func(t *testing.T) { 257 actual := ParentDirectoriesWithoutLeadingSlash(tt.path) 258 testutil.CheckErrorAndDeepEqual(t, false, nil, tt.expected, actual) 259 }) 260 } 261 } 262 263 func Test_CheckIgnoreList(t *testing.T) { 264 type args struct { 265 path string 266 ignorelist []IgnoreListEntry 267 } 268 tests := []struct { 269 name string 270 args args 271 want bool 272 }{ 273 { 274 name: "file ignored", 275 args: args{ 276 path: "/foo", 277 ignorelist: []IgnoreListEntry{{"/foo", false}}, 278 }, 279 want: true, 280 }, 281 { 282 name: "directory ignored", 283 args: args{ 284 path: "/foo/bar", 285 ignorelist: []IgnoreListEntry{{"/foo", false}}, 286 }, 287 want: true, 288 }, 289 { 290 name: "grandparent ignored", 291 args: args{ 292 path: "/foo/bar/baz", 293 ignorelist: []IgnoreListEntry{{"/foo", false}}, 294 }, 295 want: true, 296 }, 297 { 298 name: "sibling ignored", 299 args: args{ 300 path: "/foo/bar/baz", 301 ignorelist: []IgnoreListEntry{{"/foo/bat", false}}, 302 }, 303 want: false, 304 }, 305 { 306 name: "prefix match only ", 307 args: args{ 308 path: "/tmp/apt-key-gpghome.xft/gpg.key", 309 ignorelist: []IgnoreListEntry{{"/tmp/apt-key-gpghome.*", true}}, 310 }, 311 want: true, 312 }, 313 } 314 for _, tt := range tests { 315 t.Run(tt.name, func(t *testing.T) { 316 original := ignorelist 317 defer func() { 318 ignorelist = original 319 }() 320 ignorelist = tt.args.ignorelist 321 got := CheckIgnoreList(tt.args.path) 322 if got != tt.want { 323 t.Errorf("CheckIgnoreList() = %v, want %v", got, tt.want) 324 } 325 }) 326 } 327 } 328 329 func TestHasFilepathPrefix(t *testing.T) { 330 type args struct { 331 path string 332 prefix string 333 prefixMatchOnly bool 334 } 335 tests := []struct { 336 name string 337 args args 338 want bool 339 }{ 340 { 341 name: "parent", 342 args: args{ 343 path: "/foo/bar", 344 prefix: "/foo", 345 prefixMatchOnly: false, 346 }, 347 want: true, 348 }, 349 { 350 name: "nested parent", 351 args: args{ 352 path: "/foo/bar/baz", 353 prefix: "/foo/bar", 354 prefixMatchOnly: false, 355 }, 356 want: true, 357 }, 358 { 359 name: "sibling", 360 args: args{ 361 path: "/foo/bar", 362 prefix: "/bar", 363 prefixMatchOnly: false, 364 }, 365 want: false, 366 }, 367 { 368 name: "nested sibling", 369 args: args{ 370 path: "/foo/bar/baz", 371 prefix: "/foo/bar", 372 prefixMatchOnly: false, 373 }, 374 want: true, 375 }, 376 { 377 name: "name prefix", 378 args: args{ 379 path: "/foo2/bar", 380 prefix: "/foo", 381 prefixMatchOnly: false, 382 }, 383 want: false, 384 }, 385 { 386 name: "prefix match only (volume)", 387 args: args{ 388 path: "/foo", 389 prefix: "/foo", 390 prefixMatchOnly: true, 391 }, 392 want: false, 393 }, 394 } 395 for _, tt := range tests { 396 t.Run(tt.name, func(t *testing.T) { 397 if got := HasFilepathPrefix(tt.args.path, tt.args.prefix, tt.args.prefixMatchOnly); got != tt.want { 398 t.Errorf("HasFilepathPrefix() = %v, want %v", got, tt.want) 399 } 400 }) 401 } 402 } 403 404 func BenchmarkHasFilepathPrefix(b *testing.B) { 405 tests := []struct { 406 path string 407 prefix string 408 prefixMatchOnly bool 409 }{ 410 { 411 path: "/foo/bar", 412 prefix: "/foo", 413 prefixMatchOnly: true, 414 }, 415 { 416 path: "/foo/bar/baz", 417 prefix: "/foo", 418 prefixMatchOnly: true, 419 }, 420 { 421 path: "/foo/bar/baz/foo", 422 prefix: "/foo", 423 prefixMatchOnly: true, 424 }, 425 { 426 path: "/foo/bar/baz/foo/foobar", 427 prefix: "/foo", 428 prefixMatchOnly: true, 429 }, 430 { 431 path: "/foo/bar", 432 prefix: "/foo/bar", 433 prefixMatchOnly: true, 434 }, 435 { 436 path: "/foo/bar/baz", 437 prefix: "/foo/bar", 438 prefixMatchOnly: true, 439 }, 440 { 441 path: "/foo/bar/baz/foo", 442 prefix: "/foo/bar", 443 prefixMatchOnly: true, 444 }, 445 { 446 path: "/foo/bar/baz/foo/foobar", 447 prefix: "/foo/bar", 448 prefixMatchOnly: true, 449 }, 450 { 451 path: "/foo/bar", 452 prefix: "/foo/bar/baz", 453 prefixMatchOnly: true, 454 }, 455 { 456 path: "/foo/bar/baz", 457 prefix: "/foo/bar/baz", 458 prefixMatchOnly: true, 459 }, 460 { 461 path: "/foo/bar/baz/foo", 462 prefix: "/foo/bar/baz", 463 prefixMatchOnly: true, 464 }, 465 { 466 path: "/foo/bar/baz/foo/foobar", 467 prefix: "/foo/bar/baz", 468 prefixMatchOnly: true, 469 }, 470 } 471 for _, ts := range tests { 472 name := fmt.Sprint("PathDepth=", strings.Count(ts.path, "/"), ",PrefixDepth=", strings.Count(ts.prefix, "/")) 473 b.Run(name, func(b *testing.B) { 474 b.ReportAllocs() 475 for i := 0; i < b.N; i++ { 476 HasFilepathPrefix(ts.path, ts.prefix, ts.prefixMatchOnly) 477 } 478 }) 479 } 480 } 481 482 type checker func(root string, t *testing.T) 483 484 func fileExists(p string) checker { 485 return func(root string, t *testing.T) { 486 _, err := os.Stat(filepath.Join(root, p)) 487 if err != nil { 488 t.Fatalf("File %s does not exist", filepath.Join(root, p)) 489 } 490 } 491 } 492 493 func fileMatches(p string, c []byte) checker { 494 return func(root string, t *testing.T) { 495 actual, err := os.ReadFile(filepath.Join(root, p)) 496 if err != nil { 497 t.Fatalf("error reading file: %s", p) 498 } 499 if !reflect.DeepEqual(actual, c) { 500 t.Errorf("file contents do not match. %v!=%v", actual, c) 501 } 502 } 503 } 504 505 func timesMatch(p string, fTime time.Time) checker { 506 return func(root string, t *testing.T) { 507 fi, err := os.Stat(filepath.Join(root, p)) 508 if err != nil { 509 t.Fatalf("error statting file %s", p) 510 } 511 512 if fi.ModTime().UTC() != fTime.UTC() { 513 t.Errorf("Expected modtime to equal %v but was %v", fTime, fi.ModTime()) 514 } 515 } 516 } 517 518 func permissionsMatch(p string, perms os.FileMode) checker { 519 return func(root string, t *testing.T) { 520 fi, err := os.Stat(filepath.Join(root, p)) 521 if err != nil { 522 t.Fatalf("error statting file %s", p) 523 } 524 if fi.Mode() != perms { 525 t.Errorf("Permissions do not match. %s != %s", fi.Mode(), perms) 526 } 527 } 528 } 529 530 func linkPointsTo(src, dst string) checker { 531 return func(root string, t *testing.T) { 532 link := filepath.Join(root, src) 533 got, err := os.Readlink(link) 534 if err != nil { 535 t.Fatalf("error reading link %s: %s", link, err) 536 } 537 if got != dst { 538 t.Errorf("link destination does not match: %s != %s", got, dst) 539 } 540 } 541 } 542 543 func filesAreHardlinks(first, second string) checker { 544 return func(root string, t *testing.T) { 545 fi1, err := os.Stat(filepath.Join(root, first)) 546 if err != nil { 547 t.Fatalf("error getting file %s", first) 548 } 549 fi2, err := os.Stat(filepath.Join(root, second)) 550 if err != nil { 551 t.Fatalf("error getting file %s", second) 552 } 553 stat1 := getSyscallStatT(fi1) 554 stat2 := getSyscallStatT(fi2) 555 if stat1.Ino != stat2.Ino { 556 t.Errorf("%s and %s aren't hardlinks as they dont' have the same inode", first, second) 557 } 558 } 559 } 560 561 func fileHeader(name string, contents string, mode int64, fTime time.Time) *tar.Header { 562 return &tar.Header{ 563 Name: name, 564 Size: int64(len(contents)), 565 Mode: mode, 566 Typeflag: tar.TypeReg, 567 Uid: os.Getuid(), 568 Gid: os.Getgid(), 569 AccessTime: fTime, 570 ModTime: fTime, 571 } 572 } 573 574 func linkHeader(name, linkname string) *tar.Header { 575 return &tar.Header{ 576 Name: name, 577 Size: 0, 578 Typeflag: tar.TypeSymlink, 579 Linkname: linkname, 580 } 581 } 582 583 func hardlinkHeader(name, linkname string) *tar.Header { 584 return &tar.Header{ 585 Name: name, 586 Size: 0, 587 Typeflag: tar.TypeLink, 588 Linkname: linkname, 589 } 590 } 591 592 func dirHeader(name string, mode int64) *tar.Header { 593 return &tar.Header{ 594 Name: name, 595 Size: 0, 596 Typeflag: tar.TypeDir, 597 Mode: mode, 598 Uid: os.Getuid(), 599 Gid: os.Getgid(), 600 } 601 } 602 603 func createUncompressedTar(fileContents map[string]string, tarFileName, testDir string) error { 604 if err := testutil.SetupFiles(testDir, fileContents); err != nil { 605 return err 606 } 607 tarFile, err := os.Create(filepath.Join(testDir, tarFileName)) 608 if err != nil { 609 return err 610 } 611 t := NewTar(tarFile) 612 defer t.Close() 613 for file := range fileContents { 614 filePath := filepath.Join(testDir, file) 615 if err := t.AddFileToTar(filePath); err != nil { 616 return err 617 } 618 } 619 return nil 620 } 621 622 func Test_UnTar(t *testing.T) { 623 tcs := []struct { 624 name string 625 setupTarContents map[string]string 626 tarFileName string 627 destination string 628 expectedFileList []string 629 errorExpected bool 630 }{ 631 { 632 name: "multfile tar", 633 setupTarContents: map[string]string{ 634 "foo/file1": "hello World", 635 "bar/file1": "hello World", 636 "bar/file2": "hello World", 637 "file1": "hello World", 638 }, 639 tarFileName: "test.tar", 640 destination: "/", 641 expectedFileList: []string{"foo/file1", "bar/file1", "bar/file2", "file1"}, 642 errorExpected: false, 643 }, 644 { 645 name: "empty tar", 646 setupTarContents: map[string]string{}, 647 tarFileName: "test.tar", 648 destination: "/", 649 expectedFileList: nil, 650 errorExpected: false, 651 }, 652 } 653 for _, tc := range tcs { 654 t.Run(tc.name, func(t *testing.T) { 655 testDir := t.TempDir() 656 if err := createUncompressedTar(tc.setupTarContents, tc.tarFileName, testDir); err != nil { 657 t.Fatal(err) 658 } 659 file, err := os.Open(filepath.Join(testDir, tc.tarFileName)) 660 if err != nil { 661 t.Fatal(err) 662 } 663 fileList, err := UnTar(file, tc.destination) 664 if err != nil { 665 t.Fatal(err) 666 } 667 // update expectedFileList to take into factor temp directory 668 for i, file := range tc.expectedFileList { 669 tc.expectedFileList[i] = filepath.Join(testDir, file) 670 } 671 // sort both slices to ensure objects are in the same order for deep equals 672 sort.Strings(tc.expectedFileList) 673 sort.Strings(fileList) 674 testutil.CheckErrorAndDeepEqual(t, tc.errorExpected, err, tc.expectedFileList, fileList) 675 }) 676 } 677 } 678 679 func TestExtractFile(t *testing.T) { 680 type tc struct { 681 name string 682 hdrs []*tar.Header 683 tmpdir string 684 contents []byte 685 checkers []checker 686 } 687 688 defaultTestTime, err := time.Parse(time.RFC3339, "1912-06-23T00:00:00Z") 689 if err != nil { 690 t.Fatal(err) 691 } 692 693 tcs := []tc{ 694 { 695 name: "normal file", 696 contents: []byte("helloworld"), 697 hdrs: []*tar.Header{fileHeader("./bar", "helloworld", 0o644, defaultTestTime)}, 698 checkers: []checker{ 699 fileExists("/bar"), 700 fileMatches("/bar", []byte("helloworld")), 701 permissionsMatch("/bar", 0o644), 702 timesMatch("/bar", defaultTestTime), 703 }, 704 }, 705 { 706 name: "normal file, directory does not exist", 707 contents: []byte("helloworld"), 708 hdrs: []*tar.Header{fileHeader("./foo/bar", "helloworld", 0o644, defaultTestTime)}, 709 checkers: []checker{ 710 fileExists("/foo/bar"), 711 fileMatches("/foo/bar", []byte("helloworld")), 712 permissionsMatch("/foo/bar", 0o644), 713 permissionsMatch("/foo", 0o755|os.ModeDir), 714 }, 715 }, 716 { 717 name: "normal file, directory is created after", 718 contents: []byte("helloworld"), 719 hdrs: []*tar.Header{ 720 fileHeader("./foo/bar", "helloworld", 0o644, defaultTestTime), 721 dirHeader("./foo", 0o722), 722 }, 723 checkers: []checker{ 724 fileExists("/foo/bar"), 725 fileMatches("/foo/bar", []byte("helloworld")), 726 permissionsMatch("/foo/bar", 0o644), 727 permissionsMatch("/foo", 0o722|os.ModeDir), 728 }, 729 }, 730 { 731 name: "symlink", 732 hdrs: []*tar.Header{linkHeader("./bar", "bar/bat")}, 733 checkers: []checker{ 734 linkPointsTo("/bar", "bar/bat"), 735 }, 736 }, 737 { 738 name: "symlink relative path", 739 hdrs: []*tar.Header{linkHeader("./bar", "./foo/bar/baz")}, 740 checkers: []checker{ 741 linkPointsTo("/bar", "./foo/bar/baz"), 742 }, 743 }, 744 { 745 name: "symlink parent does not exist", 746 hdrs: []*tar.Header{linkHeader("./foo/bar/baz", "../../bat")}, 747 checkers: []checker{ 748 linkPointsTo("/foo/bar/baz", "../../bat"), 749 }, 750 }, 751 { 752 name: "symlink parent does not exist 2", 753 hdrs: []*tar.Header{linkHeader("./foo/bar/baz", "../../bat")}, 754 checkers: []checker{ 755 linkPointsTo("/foo/bar/baz", "../../bat"), 756 permissionsMatch("/foo", 0o755|os.ModeDir), 757 permissionsMatch("/foo/bar", 0o755|os.ModeDir), 758 }, 759 }, 760 { 761 name: "hardlink", 762 tmpdir: "/tmp/hardlink", 763 hdrs: []*tar.Header{ 764 fileHeader("/bin/gzip", "gzip-binary", 0o751, defaultTestTime), 765 hardlinkHeader("/bin/uncompress", "/bin/gzip"), 766 }, 767 checkers: []checker{ 768 fileExists("/bin/gzip"), 769 filesAreHardlinks("/bin/uncompress", "/bin/gzip"), 770 }, 771 }, 772 { 773 name: "file with setuid bit", 774 contents: []byte("helloworld"), 775 hdrs: []*tar.Header{fileHeader("./bar", "helloworld", 0o4644, defaultTestTime)}, 776 checkers: []checker{ 777 fileExists("/bar"), 778 fileMatches("/bar", []byte("helloworld")), 779 permissionsMatch("/bar", 0o644|os.ModeSetuid), 780 }, 781 }, 782 { 783 name: "dir with sticky bit", 784 contents: []byte("helloworld"), 785 hdrs: []*tar.Header{ 786 dirHeader("./foo", 0o1755), 787 fileHeader("./foo/bar", "helloworld", 0o644, defaultTestTime), 788 }, 789 checkers: []checker{ 790 fileExists("/foo/bar"), 791 fileMatches("/foo/bar", []byte("helloworld")), 792 permissionsMatch("/foo/bar", 0o644), 793 permissionsMatch("/foo", 0o755|os.ModeDir|os.ModeSticky), 794 }, 795 }, 796 } 797 798 for _, tc := range tcs { 799 t.Run(tc.name, func(t *testing.T) { 800 tc := tc 801 t.Parallel() 802 r := "" 803 804 if tc.tmpdir != "" { 805 r = tc.tmpdir 806 } else { 807 r = t.TempDir() 808 } 809 defer os.RemoveAll(r) 810 811 for _, hdr := range tc.hdrs { 812 if err := ExtractFile(r, hdr, filepath.Clean(hdr.Name), bytes.NewReader(tc.contents)); err != nil { 813 t.Fatal(err) 814 } 815 } 816 for _, checker := range tc.checkers { 817 checker(r, t) 818 } 819 }) 820 } 821 } 822 823 func TestCopySymlink(t *testing.T) { 824 type tc struct { 825 name string 826 linkTarget string 827 dest string 828 beforeLink func(r string) error 829 } 830 831 tcs := []tc{{ 832 name: "absolute symlink", 833 linkTarget: "/abs/dest", 834 }, { 835 name: "relative symlink", 836 linkTarget: "rel", 837 }, { 838 name: "symlink copy overwrites existing file", 839 linkTarget: "/abs/dest", 840 dest: "overwrite_me", 841 beforeLink: func(r string) error { 842 return os.WriteFile(filepath.Join(r, "overwrite_me"), nil, 0o644) 843 }, 844 }} 845 846 for _, tc := range tcs { 847 t.Run(tc.name, func(t *testing.T) { 848 tc := tc 849 t.Parallel() 850 r := t.TempDir() 851 os.MkdirAll(filepath.Join(r, filepath.Dir(tc.linkTarget)), 0o777) 852 tc.linkTarget = filepath.Join(r, tc.linkTarget) 853 os.WriteFile(tc.linkTarget, nil, 0o644) 854 855 if tc.beforeLink != nil { 856 if err := tc.beforeLink(r); err != nil { 857 t.Fatal(err) 858 } 859 } 860 link := filepath.Join(r, "link") 861 dest := filepath.Join(r, "copy") 862 if tc.dest != "" { 863 dest = filepath.Join(r, tc.dest) 864 } 865 if err := os.Symlink(tc.linkTarget, link); err != nil { 866 t.Fatal(err) 867 } 868 if _, err := CopySymlink(link, dest, FileContext{}); err != nil { 869 t.Fatal(err) 870 } 871 if _, err := os.Lstat(dest); err != nil { 872 t.Fatalf("error reading link %s: %s", link, err) 873 } 874 }) 875 } 876 } 877 878 func Test_childDirInSkiplist(t *testing.T) { 879 type args struct { 880 path string 881 ignorelist []IgnoreListEntry 882 } 883 tests := []struct { 884 name string 885 args args 886 want bool 887 }{ 888 { 889 name: "not in ignorelist", 890 args: args{ 891 path: "/foo", 892 }, 893 want: false, 894 }, 895 { 896 name: "child in ignorelist", 897 args: args{ 898 path: "/foo", 899 ignorelist: []IgnoreListEntry{ 900 { 901 Path: "/foo/bar", 902 }, 903 }, 904 }, 905 want: true, 906 }, 907 } 908 oldIgnoreList := ignorelist 909 defer func() { 910 ignorelist = oldIgnoreList 911 }() 912 913 for _, tt := range tests { 914 t.Run(tt.name, func(t *testing.T) { 915 ignorelist = tt.args.ignorelist 916 if got := childDirInIgnoreList(tt.args.path); got != tt.want { 917 t.Errorf("childDirInIgnoreList() = %v, want %v", got, tt.want) 918 } 919 }) 920 } 921 } 922 923 func Test_correctDockerignoreFileIsUsed(t *testing.T) { 924 type args struct { 925 dockerfilepath string 926 buildcontext string 927 excluded []string 928 included []string 929 } 930 tests := []struct { 931 name string 932 args args 933 }{ 934 { 935 name: "relative dockerfile used", 936 args: args{ 937 dockerfilepath: "../../integration/dockerfiles/Dockerfile_dockerignore_relative", 938 buildcontext: "../../integration/", 939 excluded: []string{"ignore_relative/bar"}, 940 included: []string{"ignore_relative/foo", "ignore/bar"}, 941 }, 942 }, 943 { 944 name: "context dockerfile is used", 945 args: args{ 946 dockerfilepath: "../../integration/dockerfiles/Dockerfile_test_dockerignore", 947 buildcontext: "../../integration/", 948 excluded: []string{"ignore/bar"}, 949 included: []string{"ignore/foo", "ignore_relative/bar"}, 950 }, 951 }, 952 } 953 for _, tt := range tests { 954 fileContext, err := NewFileContextFromDockerfile(tt.args.dockerfilepath, tt.args.buildcontext) 955 if err != nil { 956 t.Fatal(err) 957 } 958 for _, excl := range tt.args.excluded { 959 t.Run(tt.name+" to exclude "+excl, func(t *testing.T) { 960 if !fileContext.ExcludesFile(excl) { 961 t.Errorf("'%v' not excluded", excl) 962 } 963 }) 964 } 965 for _, incl := range tt.args.included { 966 t.Run(tt.name+" to include "+incl, func(t *testing.T) { 967 if fileContext.ExcludesFile(incl) { 968 t.Errorf("'%v' not included", incl) 969 } 970 }) 971 } 972 } 973 } 974 975 func Test_CopyFile_skips_self(t *testing.T) { 976 t.Parallel() 977 tempDir := t.TempDir() 978 979 tempFile := filepath.Join(tempDir, "foo") 980 expected := "bar" 981 982 if err := os.WriteFile( 983 tempFile, 984 []byte(expected), 985 0o755, 986 ); err != nil { 987 t.Fatal(err) 988 } 989 990 ignored, err := CopyFile(tempFile, tempFile, FileContext{}, DoNotChangeUID, DoNotChangeGID, fs.FileMode(0o600), true) 991 if err != nil { 992 t.Fatal(err) 993 } 994 995 if ignored { 996 t.Fatal("expected file to NOT be ignored") 997 } 998 999 // Ensure file has expected contents 1000 actualData, err := os.ReadFile(tempFile) 1001 if err != nil { 1002 t.Fatal(err) 1003 } 1004 1005 if actual := string(actualData); actual != expected { 1006 t.Fatalf("expected file contents to be %q, but got %q", expected, actual) 1007 } 1008 } 1009 1010 func fakeExtract(_ string, _ *tar.Header, _ string, _ io.Reader) error { 1011 return nil 1012 } 1013 1014 func Test_GetFSFromLayers_with_whiteouts_include_whiteout_enabled(t *testing.T) { 1015 resetMountInfoFile := provideEmptyMountinfoFile() 1016 defer resetMountInfoFile() 1017 1018 ctrl := gomock.NewController(t) 1019 1020 root := t.TempDir() 1021 // Write a whiteout path 1022 d1 := []byte("Hello World\n") 1023 if err := os.WriteFile(filepath.Join(root, "foobar"), d1, 0o644); err != nil { 1024 t.Fatal(err) 1025 } 1026 1027 opts := []FSOpt{ 1028 // I'd rather use the real func (util.ExtractFile) 1029 // but you have to be root to chown 1030 ExtractFunc(fakeExtract), 1031 IncludeWhiteout(), 1032 } 1033 1034 expectErr := false 1035 1036 f := func(expectedFiles []string, tw *tar.Writer) { 1037 for _, f := range expectedFiles { 1038 f := strings.TrimPrefix(strings.TrimPrefix(f, root), "/") 1039 1040 hdr := &tar.Header{ 1041 Name: f, 1042 Mode: 0o644, 1043 Size: int64(len("Hello World\n")), 1044 } 1045 1046 if err := tw.WriteHeader(hdr); err != nil { 1047 t.Fatal(err) 1048 } 1049 1050 if _, err := tw.Write([]byte("Hello World\n")); err != nil { 1051 t.Fatal(err) 1052 } 1053 } 1054 1055 if err := tw.Close(); err != nil { 1056 t.Fatal(err) 1057 } 1058 } 1059 1060 expectedFiles := []string{ 1061 filepath.Join(root, "foobar"), 1062 } 1063 1064 buf := new(bytes.Buffer) 1065 tw := tar.NewWriter(buf) 1066 1067 f(expectedFiles, tw) 1068 1069 mockLayer := mockv1.NewMockLayer(ctrl) 1070 mockLayer.EXPECT().MediaType().Return(types.OCILayer, nil) 1071 1072 rc := io.NopCloser(buf) 1073 mockLayer.EXPECT().Uncompressed().Return(rc, nil) 1074 1075 secondLayerFiles := []string{ 1076 filepath.Join(root, ".wh.foobar"), 1077 } 1078 1079 buf = new(bytes.Buffer) 1080 tw = tar.NewWriter(buf) 1081 1082 f(secondLayerFiles, tw) 1083 1084 mockLayer2 := mockv1.NewMockLayer(ctrl) 1085 mockLayer2.EXPECT().MediaType().Return(types.OCILayer, nil) 1086 1087 rc = io.NopCloser(buf) 1088 mockLayer2.EXPECT().Uncompressed().Return(rc, nil) 1089 1090 layers := []v1.Layer{ 1091 mockLayer, 1092 mockLayer2, 1093 } 1094 1095 expectedFiles = append(expectedFiles, secondLayerFiles...) 1096 1097 actualFiles, err := GetFSFromLayers(root, layers, opts...) 1098 1099 assertGetFSFromLayers( 1100 t, 1101 actualFiles, 1102 expectedFiles, 1103 err, 1104 expectErr, 1105 ) 1106 // Make sure whiteout files are removed form the root. 1107 _, err = os.Lstat(filepath.Join(root, "foobar")) 1108 if err == nil || !os.IsNotExist(err) { 1109 t.Errorf("expected whiteout foobar file to be deleted. However found it.") 1110 } 1111 } 1112 1113 func provideEmptyMountinfoFile() func() { 1114 // Provide empty mountinfo file to prevent /tmp from ending up in ignore list on 1115 // distributions with /tmp mountpoint. Otherwise, tests expecting operations in /tmp 1116 // can fail. 1117 config.MountInfoPath = "/dev/null" 1118 return func() { 1119 config.MountInfoPath = constants.MountInfoPath 1120 } 1121 } 1122 1123 func Test_GetFSFromLayers_with_whiteouts_include_whiteout_disabled(t *testing.T) { 1124 resetMountInfoFile := provideEmptyMountinfoFile() 1125 defer resetMountInfoFile() 1126 1127 ctrl := gomock.NewController(t) 1128 1129 root := t.TempDir() 1130 // Write a whiteout path 1131 d1 := []byte("Hello World\n") 1132 if err := os.WriteFile(filepath.Join(root, "foobar"), d1, 0o644); err != nil { 1133 t.Fatal(err) 1134 } 1135 1136 opts := []FSOpt{ 1137 // I'd rather use the real func (util.ExtractFile) 1138 // but you have to be root to chown 1139 ExtractFunc(fakeExtract), 1140 } 1141 1142 expectErr := false 1143 1144 f := func(expectedFiles []string, tw *tar.Writer) { 1145 for _, f := range expectedFiles { 1146 f := strings.TrimPrefix(strings.TrimPrefix(f, root), "/") 1147 1148 hdr := &tar.Header{ 1149 Name: f, 1150 Mode: 0o644, 1151 Size: int64(len("Hello world\n")), 1152 } 1153 1154 if err := tw.WriteHeader(hdr); err != nil { 1155 t.Fatal(err) 1156 } 1157 1158 if _, err := tw.Write([]byte("Hello world\n")); err != nil { 1159 t.Fatal(err) 1160 } 1161 } 1162 1163 if err := tw.Close(); err != nil { 1164 t.Fatal(err) 1165 } 1166 } 1167 1168 expectedFiles := []string{ 1169 filepath.Join(root, "foobar"), 1170 } 1171 1172 buf := new(bytes.Buffer) 1173 tw := tar.NewWriter(buf) 1174 1175 f(expectedFiles, tw) 1176 1177 mockLayer := mockv1.NewMockLayer(ctrl) 1178 mockLayer.EXPECT().MediaType().Return(types.OCILayer, nil) 1179 layerFiles := []string{ 1180 filepath.Join(root, "foobar"), 1181 } 1182 buf = new(bytes.Buffer) 1183 tw = tar.NewWriter(buf) 1184 1185 f(layerFiles, tw) 1186 1187 rc := io.NopCloser(buf) 1188 mockLayer.EXPECT().Uncompressed().Return(rc, nil) 1189 1190 secondLayerFiles := []string{ 1191 filepath.Join(root, ".wh.foobar"), 1192 } 1193 1194 buf = new(bytes.Buffer) 1195 tw = tar.NewWriter(buf) 1196 1197 f(secondLayerFiles, tw) 1198 1199 mockLayer2 := mockv1.NewMockLayer(ctrl) 1200 mockLayer2.EXPECT().MediaType().Return(types.OCILayer, nil) 1201 1202 rc = io.NopCloser(buf) 1203 mockLayer2.EXPECT().Uncompressed().Return(rc, nil) 1204 1205 layers := []v1.Layer{ 1206 mockLayer, 1207 mockLayer2, 1208 } 1209 1210 actualFiles, err := GetFSFromLayers(root, layers, opts...) 1211 1212 assertGetFSFromLayers( 1213 t, 1214 actualFiles, 1215 expectedFiles, 1216 err, 1217 expectErr, 1218 ) 1219 // Make sure whiteout files are removed form the root. 1220 _, err = os.Lstat(filepath.Join(root, "foobar")) 1221 if err == nil || !os.IsNotExist(err) { 1222 t.Errorf("expected whiteout foobar file to be deleted. However found it.") 1223 } 1224 } 1225 1226 func Test_GetFSFromLayers_ignorelist(t *testing.T) { 1227 resetMountInfoFile := provideEmptyMountinfoFile() 1228 defer resetMountInfoFile() 1229 1230 ctrl := gomock.NewController(t) 1231 1232 root := t.TempDir() 1233 // Write a whiteout path 1234 fileContents := []byte("Hello World\n") 1235 if err := os.Mkdir(filepath.Join(root, "testdir"), 0o775); err != nil { 1236 t.Fatal(err) 1237 } 1238 1239 opts := []FSOpt{ 1240 // I'd rather use the real func (util.ExtractFile) 1241 // but you have to be root to chown 1242 ExtractFunc(fakeExtract), 1243 IncludeWhiteout(), 1244 } 1245 1246 f := func(expectedFiles []string, tw *tar.Writer) { 1247 for _, f := range expectedFiles { 1248 f := strings.TrimPrefix(strings.TrimPrefix(f, root), "/") 1249 1250 hdr := &tar.Header{ 1251 Name: f, 1252 Mode: 0o644, 1253 Size: int64(len(string(fileContents))), 1254 } 1255 1256 if err := tw.WriteHeader(hdr); err != nil { 1257 t.Fatal(err) 1258 } 1259 1260 if _, err := tw.Write(fileContents); err != nil { 1261 t.Fatal(err) 1262 } 1263 } 1264 1265 if err := tw.Close(); err != nil { 1266 t.Fatal(err) 1267 } 1268 } 1269 1270 // first, testdir is not in ignorelist, so it should be deleted 1271 expectedFiles := []string{ 1272 filepath.Join(root, ".wh.testdir"), 1273 filepath.Join(root, "testdir", "file"), 1274 filepath.Join(root, "other-file"), 1275 } 1276 1277 buf := new(bytes.Buffer) 1278 tw := tar.NewWriter(buf) 1279 1280 f(expectedFiles, tw) 1281 1282 mockLayer := mockv1.NewMockLayer(ctrl) 1283 mockLayer.EXPECT().MediaType().Return(types.OCILayer, nil) 1284 layerFiles := []string{ 1285 filepath.Join(root, ".wh.testdir"), 1286 filepath.Join(root, "testdir", "file"), 1287 filepath.Join(root, "other-file"), 1288 } 1289 buf = new(bytes.Buffer) 1290 tw = tar.NewWriter(buf) 1291 1292 f(layerFiles, tw) 1293 1294 rc := io.NopCloser(buf) 1295 mockLayer.EXPECT().Uncompressed().Return(rc, nil) 1296 1297 layers := []v1.Layer{ 1298 mockLayer, 1299 } 1300 1301 actualFiles, err := GetFSFromLayers(root, layers, opts...) 1302 assertGetFSFromLayers( 1303 t, 1304 actualFiles, 1305 expectedFiles, 1306 err, 1307 false, 1308 ) 1309 1310 // Make sure whiteout files are removed form the root. 1311 _, err = os.Lstat(filepath.Join(root, "testdir")) 1312 if err == nil || !os.IsNotExist(err) { 1313 t.Errorf("expected testdir to be deleted. However found it.") 1314 } 1315 1316 // second, testdir is in ignorelist, so it should not be deleted 1317 original := append([]IgnoreListEntry{}, defaultIgnoreList...) 1318 defer func() { 1319 defaultIgnoreList = original 1320 }() 1321 defaultIgnoreList = append(defaultIgnoreList, IgnoreListEntry{ 1322 Path: filepath.Join(root, "testdir"), 1323 }) 1324 if err := os.Mkdir(filepath.Join(root, "testdir"), 0o775); err != nil { 1325 t.Fatal(err) 1326 } 1327 1328 expectedFiles = []string{ 1329 filepath.Join(root, "other-file"), 1330 } 1331 1332 buf = new(bytes.Buffer) 1333 tw = tar.NewWriter(buf) 1334 1335 f(expectedFiles, tw) 1336 1337 mockLayer = mockv1.NewMockLayer(ctrl) 1338 mockLayer.EXPECT().MediaType().Return(types.OCILayer, nil) 1339 layerFiles = []string{ 1340 filepath.Join(root, ".wh.testdir"), 1341 filepath.Join(root, "other-file"), 1342 } 1343 buf = new(bytes.Buffer) 1344 tw = tar.NewWriter(buf) 1345 1346 f(layerFiles, tw) 1347 1348 rc = io.NopCloser(buf) 1349 mockLayer.EXPECT().Uncompressed().Return(rc, nil) 1350 1351 layers = []v1.Layer{ 1352 mockLayer, 1353 } 1354 1355 actualFiles, err = GetFSFromLayers(root, layers, opts...) 1356 assertGetFSFromLayers( 1357 t, 1358 actualFiles, 1359 expectedFiles, 1360 err, 1361 false, 1362 ) 1363 1364 // Make sure testdir still exists. 1365 _, err = os.Lstat(filepath.Join(root, "testdir")) 1366 if err != nil { 1367 t.Errorf("expected testdir to exist, but could not Lstat it: %v", err) 1368 } 1369 } 1370 1371 func Test_GetFSFromLayers(t *testing.T) { 1372 ctrl := gomock.NewController(t) 1373 1374 root := t.TempDir() 1375 1376 opts := []FSOpt{ 1377 // I'd rather use the real func (util.ExtractFile) 1378 // but you have to be root to chown 1379 ExtractFunc(fakeExtract), 1380 } 1381 1382 expectErr := false 1383 expectedFiles := []string{ 1384 filepath.Join(root, "foobar"), 1385 } 1386 1387 buf := new(bytes.Buffer) 1388 tw := tar.NewWriter(buf) 1389 1390 for _, f := range expectedFiles { 1391 f := strings.TrimPrefix(strings.TrimPrefix(f, root), "/") 1392 1393 hdr := &tar.Header{ 1394 Name: f, 1395 Mode: 0o644, 1396 Size: int64(len("Hello world\n")), 1397 } 1398 1399 if err := tw.WriteHeader(hdr); err != nil { 1400 t.Fatal(err) 1401 } 1402 1403 if _, err := tw.Write([]byte("Hello world\n")); err != nil { 1404 t.Fatal(err) 1405 } 1406 } 1407 1408 if err := tw.Close(); err != nil { 1409 t.Fatal(err) 1410 } 1411 1412 mockLayer := mockv1.NewMockLayer(ctrl) 1413 mockLayer.EXPECT().MediaType().Return(types.OCILayer, nil) 1414 1415 rc := io.NopCloser(buf) 1416 mockLayer.EXPECT().Uncompressed().Return(rc, nil) 1417 1418 layers := []v1.Layer{ 1419 mockLayer, 1420 } 1421 1422 actualFiles, err := GetFSFromLayers(root, layers, opts...) 1423 1424 assertGetFSFromLayers( 1425 t, 1426 actualFiles, 1427 expectedFiles, 1428 err, 1429 expectErr, 1430 ) 1431 } 1432 1433 func assertGetFSFromLayers( 1434 t *testing.T, 1435 actualFiles []string, 1436 expectedFiles []string, 1437 err error, 1438 expectErr bool, //nolint:unparam 1439 ) { 1440 t.Helper() 1441 if !expectErr && err != nil { 1442 t.Error(err) 1443 t.FailNow() 1444 } else if expectErr && err == nil { 1445 t.Error("expected err to not be nil") 1446 t.FailNow() 1447 } 1448 1449 if len(actualFiles) != len(expectedFiles) { 1450 t.Errorf("expected %s to equal %s", actualFiles, expectedFiles) 1451 t.FailNow() 1452 } 1453 1454 for i := range expectedFiles { 1455 if actualFiles[i] != expectedFiles[i] { 1456 t.Errorf("expected %s to equal %s", actualFiles[i], expectedFiles[i]) 1457 } 1458 } 1459 } 1460 1461 func TestInitIgnoreList(t *testing.T) { 1462 mountInfo := `36 35 98:0 /kaniko /test/kaniko rw,noatime master:1 - ext3 /dev/root rw,errors=continue 1463 36 35 98:0 /proc /test/proc rw,noatime master:1 - ext3 /dev/root rw,errors=continue 1464 ` 1465 mFile, err := os.CreateTemp("", "mountinfo") 1466 if err != nil { 1467 t.Fatal(err) 1468 } 1469 defer mFile.Close() 1470 if _, err := mFile.WriteString(mountInfo); err != nil { 1471 t.Fatal(err) 1472 } 1473 config.MountInfoPath = mFile.Name() 1474 defer func() { 1475 config.MountInfoPath = constants.MountInfoPath 1476 }() 1477 1478 expected := []IgnoreListEntry{ 1479 { 1480 Path: "/kaniko", 1481 PrefixMatchOnly: false, 1482 }, 1483 { 1484 Path: "/test/kaniko", 1485 PrefixMatchOnly: false, 1486 }, 1487 { 1488 Path: "/test/proc", 1489 PrefixMatchOnly: false, 1490 }, 1491 { 1492 Path: "/etc/mtab", 1493 PrefixMatchOnly: false, 1494 }, 1495 { 1496 Path: "/tmp/apt-key-gpghome", 1497 PrefixMatchOnly: true, 1498 }, 1499 } 1500 1501 original := append([]IgnoreListEntry{}, ignorelist...) 1502 defer func() { ignorelist = original }() 1503 1504 err = InitIgnoreList() 1505 if err != nil { 1506 t.Fatal(err) 1507 } 1508 sort.Slice(expected, func(i, j int) bool { 1509 return expected[i].Path < expected[j].Path 1510 }) 1511 sort.Slice(ignorelist, func(i, j int) bool { 1512 return ignorelist[i].Path < ignorelist[j].Path 1513 }) 1514 testutil.CheckDeepEqual(t, expected, ignorelist) 1515 } 1516 1517 func Test_setFileTimes(t *testing.T) { 1518 testDir := t.TempDir() 1519 1520 p := filepath.Join(testDir, "foo.txt") 1521 1522 if err := os.WriteFile(p, []byte("meow"), 0o777); err != nil { 1523 t.Fatal(err) 1524 } 1525 1526 type testcase struct { 1527 desc string 1528 path string 1529 aTime time.Time 1530 mTime time.Time 1531 } 1532 1533 testCases := []testcase{ 1534 { 1535 desc: "zero for mod and access", 1536 path: p, 1537 }, 1538 { 1539 desc: "zero for mod", 1540 path: p, 1541 aTime: time.Now(), 1542 }, 1543 { 1544 desc: "zero for access", 1545 path: p, 1546 mTime: time.Now(), 1547 }, 1548 { 1549 desc: "both non-zero", 1550 path: p, 1551 mTime: time.Now(), 1552 aTime: time.Now(), 1553 }, 1554 } 1555 1556 for _, tc := range testCases { 1557 t.Run(tc.desc, func(t *testing.T) { 1558 err := setFileTimes(tc.path, tc.aTime, tc.mTime) 1559 if err != nil { 1560 t.Errorf("expected err to be nil not %s", err) 1561 } 1562 }) 1563 } 1564 }