github.com/GoogleContainerTools/kaniko@v1.23.0/pkg/commands/copy_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 commands 18 19 import ( 20 "archive/tar" 21 "fmt" 22 "io" 23 "io/fs" 24 "os" 25 "path/filepath" 26 "strings" 27 "syscall" 28 "testing" 29 30 "github.com/GoogleContainerTools/kaniko/pkg/dockerfile" 31 "github.com/GoogleContainerTools/kaniko/pkg/util" 32 "github.com/GoogleContainerTools/kaniko/testutil" 33 v1 "github.com/google/go-containerregistry/pkg/v1" 34 "github.com/moby/buildkit/frontend/dockerfile/instructions" 35 "github.com/pkg/errors" 36 "github.com/sirupsen/logrus" 37 ) 38 39 var copyTests = []struct { 40 name string 41 sourcesAndDest []string 42 expectedDest []string 43 }{ 44 { 45 name: "copy foo into tempCopyExecuteTest/", 46 sourcesAndDest: []string{"foo", "tempCopyExecuteTest/"}, 47 expectedDest: []string{"foo"}, 48 }, 49 { 50 name: "copy foo into tempCopyExecuteTest", 51 sourcesAndDest: []string{"foo", "tempCopyExecuteTest"}, 52 expectedDest: []string{"tempCopyExecuteTest"}, 53 }, 54 { 55 name: "copy f* into tempCopyExecuteTest", 56 sourcesAndDest: []string{"foo*", "tempCopyExecuteTest"}, 57 expectedDest: []string{"tempCopyExecuteTest"}, 58 }, 59 { 60 name: "copy fo? into tempCopyExecuteTest", 61 sourcesAndDest: []string{"fo?", "tempCopyExecuteTest"}, 62 expectedDest: []string{"tempCopyExecuteTest"}, 63 }, 64 { 65 name: "copy f[o][osp] into tempCopyExecuteTest", 66 sourcesAndDest: []string{"f[o][osp]", "tempCopyExecuteTest"}, 67 expectedDest: []string{"tempCopyExecuteTest"}, 68 }, 69 { 70 name: "Copy into several to-be-created directories", 71 sourcesAndDest: []string{"f[o][osp]", "tempCopyExecuteTest/foo/bar"}, 72 expectedDest: []string{"bar"}, 73 }, 74 } 75 76 func setupTestTemp(t *testing.T) string { 77 tempDir := t.TempDir() 78 logrus.Debugf("Tempdir: %s", tempDir) 79 80 srcPath, err := filepath.Abs("../../integration/context") 81 if err != nil { 82 logrus.Fatalf("Error getting abs path %s", srcPath) 83 } 84 cperr := filepath.Walk(srcPath, 85 func(path string, info os.FileInfo, err error) error { 86 if err != nil { 87 return err 88 } 89 if path != srcPath { 90 if err != nil { 91 return err 92 } 93 tempPath := strings.TrimPrefix(path, srcPath) 94 fileInfo, err := os.Stat(path) 95 if err != nil { 96 return err 97 } 98 if fileInfo.IsDir() { 99 os.MkdirAll(tempDir+"/"+tempPath, 0777) 100 } else { 101 out, err := os.Create(tempDir + "/" + tempPath) 102 if err != nil { 103 return err 104 } 105 defer out.Close() 106 107 in, err := os.Open(path) 108 if err != nil { 109 return err 110 } 111 defer in.Close() 112 113 _, err = io.Copy(out, in) 114 if err != nil { 115 return err 116 } 117 } 118 } 119 return nil 120 }) 121 if cperr != nil { 122 logrus.Fatalf("Error populating temp dir %s", cperr) 123 } 124 125 return tempDir 126 } 127 128 func readDirectory(dirName string) ([]fs.FileInfo, error) { 129 entries, err := os.ReadDir(dirName) 130 if err != nil { 131 return nil, err 132 } 133 134 testDir := make([]fs.FileInfo, 0, len(entries)) 135 136 for _, entry := range entries { 137 info, err := entry.Info() 138 if err != nil { 139 return nil, err 140 } 141 testDir = append(testDir, info) 142 } 143 return testDir, err 144 } 145 146 func Test_CachingCopyCommand_ExecuteCommand(t *testing.T) { 147 tempDir := setupTestTemp(t) 148 149 tarContent, err := prepareTarFixture(t, []string{"foo.txt"}) 150 if err != nil { 151 t.Errorf("couldn't prepare tar fixture %v", err) 152 } 153 154 config := &v1.Config{} 155 buildArgs := &dockerfile.BuildArgs{} 156 157 type testCase struct { 158 description string 159 expectLayer bool 160 expectErr bool 161 count *int 162 expectedCount int 163 command *CachingCopyCommand 164 extractedFiles []string 165 contextFiles []string 166 } 167 testCases := []testCase{ 168 func() testCase { 169 err = os.WriteFile(filepath.Join(tempDir, "foo.txt"), []byte("meow"), 0644) 170 if err != nil { 171 t.Errorf("couldn't write tempfile %v", err) 172 t.FailNow() 173 } 174 175 c := &CachingCopyCommand{ 176 img: fakeImage{ 177 ImageLayers: []v1.Layer{ 178 fakeLayer{TarContent: tarContent}, 179 }, 180 }, 181 fileContext: util.FileContext{Root: tempDir}, 182 cmd: &instructions.CopyCommand{ 183 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{"foo.txt"}, DestPath: ""}}, 184 } 185 count := 0 186 tc := testCase{ 187 description: "with valid image and valid layer", 188 count: &count, 189 expectedCount: 1, 190 expectLayer: true, 191 extractedFiles: []string{"/foo.txt"}, 192 contextFiles: []string{"foo.txt"}, 193 } 194 c.extractFn = func(_ string, _ *tar.Header, _ string, _ io.Reader) error { 195 *tc.count++ 196 return nil 197 } 198 tc.command = c 199 return tc 200 }(), 201 func() testCase { 202 c := &CachingCopyCommand{} 203 tc := testCase{ 204 description: "with no image", 205 expectErr: true, 206 } 207 c.extractFn = func(_ string, _ *tar.Header, _ string, _ io.Reader) error { 208 return nil 209 } 210 tc.command = c 211 return tc 212 }(), 213 func() testCase { 214 c := &CachingCopyCommand{ 215 img: fakeImage{}, 216 } 217 c.extractFn = func(_ string, _ *tar.Header, _ string, _ io.Reader) error { 218 return nil 219 } 220 return testCase{ 221 description: "with image containing no layers", 222 expectErr: true, 223 command: c, 224 } 225 }(), 226 func() testCase { 227 c := &CachingCopyCommand{ 228 img: fakeImage{ 229 ImageLayers: []v1.Layer{ 230 fakeLayer{}, 231 }, 232 }, 233 } 234 c.extractFn = func(_ string, _ *tar.Header, _ string, _ io.Reader) error { 235 return nil 236 } 237 tc := testCase{ 238 description: "with image one layer which has no tar content", 239 expectErr: false, // this one probably should fail but doesn't because of how ExecuteCommand and util.GetFSFromLayers are implemented - cvgw- 2019-11-25 240 expectLayer: true, 241 } 242 tc.command = c 243 return tc 244 }(), 245 } 246 247 for _, tc := range testCases { 248 t.Run(tc.description, func(t *testing.T) { 249 c := tc.command 250 err := c.ExecuteCommand(config, buildArgs) 251 if !tc.expectErr && err != nil { 252 t.Errorf("Expected err to be nil but was %v", err) 253 } else if tc.expectErr && err == nil { 254 t.Error("Expected err but was nil") 255 } 256 257 if tc.count != nil { 258 if *tc.count != tc.expectedCount { 259 t.Errorf("Expected extractFn to be called %v times but was called %v times", tc.expectedCount, *tc.count) 260 } 261 for _, file := range tc.extractedFiles { 262 match := false 263 cFiles := c.FilesToSnapshot() 264 for _, cFile := range cFiles { 265 if file == cFile { 266 match = true 267 break 268 } 269 } 270 if !match { 271 t.Errorf("Expected extracted files to include %v but did not %v", file, cFiles) 272 } 273 } 274 275 cmdFiles, err := c.FilesUsedFromContext( 276 config, buildArgs, 277 ) 278 if err != nil { 279 t.Errorf("failed to get files used from context from command %v", err) 280 } 281 282 if len(cmdFiles) != len(tc.contextFiles) { 283 t.Errorf("expected files used from context to equal %v but was %v", tc.contextFiles, cmdFiles) 284 } 285 } 286 287 if c.layer == nil && tc.expectLayer { 288 t.Error("expected the command to have a layer set but instead was nil") 289 } else if c.layer != nil && !tc.expectLayer { 290 t.Error("expected the command to have no layer set but instead found a layer") 291 } 292 }) 293 } 294 } 295 296 func TestCopyExecuteCmd(t *testing.T) { 297 tempDir := setupTestTemp(t) 298 299 cfg := &v1.Config{ 300 Cmd: nil, 301 Env: []string{}, 302 WorkingDir: tempDir, 303 } 304 fileContext := util.FileContext{Root: tempDir} 305 306 for _, test := range copyTests { 307 t.Run(test.name, func(t *testing.T) { 308 dirList := []string{} 309 310 cmd := CopyCommand{ 311 cmd: &instructions.CopyCommand{ 312 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: test.sourcesAndDest[0 : len(test.sourcesAndDest)-1], 313 DestPath: test.sourcesAndDest[len(test.sourcesAndDest)-1]}, 314 }, 315 fileContext: fileContext, 316 } 317 318 buildArgs := copySetUpBuildArgs() 319 dest := cfg.WorkingDir + "/" + test.sourcesAndDest[len(test.sourcesAndDest)-1] 320 321 err := cmd.ExecuteCommand(cfg, buildArgs) 322 if err != nil { 323 t.Error() 324 } 325 326 fi, err := os.Open(dest) 327 if err != nil { 328 t.Error() 329 } 330 defer fi.Close() 331 fstat, err := fi.Stat() 332 if err != nil { 333 t.Error() 334 } 335 if fstat == nil { 336 t.Error() 337 return // Unrecoverable, will segfault in the next line 338 } 339 if fstat.IsDir() { 340 files, err := readDirectory(dest) 341 if err != nil { 342 t.Error() 343 } 344 for _, file := range files { 345 logrus.Debugf("File: %v", file.Name()) 346 dirList = append(dirList, file.Name()) 347 } 348 } else { 349 dirList = append(dirList, filepath.Base(dest)) 350 } 351 352 testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedDest, dirList) 353 os.RemoveAll(dest) 354 }) 355 } 356 } 357 358 func copySetUpBuildArgs() *dockerfile.BuildArgs { 359 buildArgs := dockerfile.NewBuildArgs([]string{ 360 "buildArg1=foo", 361 "buildArg2=foo2", 362 }) 363 buildArgs.AddArg("buildArg1", nil) 364 d := "default" 365 buildArgs.AddArg("buildArg2", &d) 366 return buildArgs 367 } 368 369 func Test_resolveIfSymlink(t *testing.T) { 370 type testCase struct { 371 destPath string 372 expectedPath string 373 err error 374 } 375 376 tmpDir := t.TempDir() 377 378 baseDir, err := os.MkdirTemp(tmpDir, "not-linked") 379 if err != nil { 380 t.Error(err) 381 } 382 383 path, err := os.CreateTemp(baseDir, "foo.txt") 384 if err != nil { 385 t.Error(err) 386 } 387 388 thepath, err := filepath.Abs(filepath.Dir(path.Name())) 389 if err != nil { 390 t.Error(err) 391 } 392 cases := []testCase{ 393 {destPath: thepath, expectedPath: thepath, err: nil}, 394 {destPath: "/", expectedPath: "/", err: nil}, 395 } 396 baseDir = tmpDir 397 symLink := filepath.Join(baseDir, "symlink") 398 if err := os.Symlink(filepath.Base(thepath), symLink); err != nil { 399 t.Error(err) 400 } 401 cases = append(cases, 402 testCase{filepath.Join(symLink, "foo.txt"), filepath.Join(thepath, "foo.txt"), nil}, 403 testCase{filepath.Join(symLink, "inner", "foo.txt"), filepath.Join(thepath, "inner", "foo.txt"), nil}, 404 ) 405 406 absSymlink := filepath.Join(tmpDir, "abs-symlink") 407 if err := os.Symlink(thepath, absSymlink); err != nil { 408 t.Error(err) 409 } 410 cases = append(cases, 411 testCase{filepath.Join(absSymlink, "foo.txt"), filepath.Join(thepath, "foo.txt"), nil}, 412 testCase{filepath.Join(absSymlink, "inner", "foo.txt"), filepath.Join(thepath, "inner", "foo.txt"), nil}, 413 ) 414 415 for i, c := range cases { 416 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 417 res, e := resolveIfSymlink(c.destPath) 418 if !errors.Is(e, c.err) { 419 t.Errorf("%s: expected %v but got %v", c.destPath, c.err, e) 420 } 421 422 if res != c.expectedPath { 423 t.Errorf("%s: expected %v but got %v", c.destPath, c.expectedPath, res) 424 } 425 }) 426 } 427 } 428 429 func Test_CopyEnvAndWildcards(t *testing.T) { 430 setupDirs := func(t *testing.T) (string, string) { 431 testDir := t.TempDir() 432 433 dir := filepath.Join(testDir, "bar") 434 435 if err := os.MkdirAll(dir, 0777); err != nil { 436 t.Fatal(err) 437 } 438 file := filepath.Join(dir, "bam.txt") 439 440 if err := os.WriteFile(file, []byte("meow"), 0777); err != nil { 441 t.Fatal(err) 442 } 443 targetPath := filepath.Join(dir, "dam.txt") 444 if err := os.WriteFile(targetPath, []byte("woof"), 0777); err != nil { 445 t.Fatal(err) 446 } 447 if err := os.Symlink("dam.txt", filepath.Join(dir, "sym.link")); err != nil { 448 t.Fatal(err) 449 } 450 451 return testDir, filepath.Base(dir) 452 } 453 454 t.Run("copy sources into a dir defined in env variable", func(t *testing.T) { 455 testDir, srcDir := setupDirs(t) 456 defer os.RemoveAll(testDir) 457 expected, err := readDirectory(filepath.Join(testDir, srcDir)) 458 if err != nil { 459 t.Fatal(err) 460 } 461 462 targetPath := filepath.Join(testDir, "target") + "/" 463 464 cmd := CopyCommand{ 465 cmd: &instructions.CopyCommand{ 466 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{srcDir + "/*"}, DestPath: "$TARGET_PATH"}, 467 }, 468 fileContext: util.FileContext{Root: testDir}, 469 } 470 471 cfg := &v1.Config{ 472 Cmd: nil, 473 Env: []string{"TARGET_PATH=" + targetPath}, 474 WorkingDir: testDir, 475 } 476 477 err = cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 478 if err != nil { 479 t.Fatal(err) 480 } 481 testutil.CheckNoError(t, err) 482 483 actual, err := readDirectory(targetPath) 484 if err != nil { 485 t.Fatal(err) 486 } 487 for i, f := range actual { 488 testutil.CheckDeepEqual(t, expected[i].Name(), f.Name()) 489 testutil.CheckDeepEqual(t, expected[i].Mode(), f.Mode()) 490 } 491 492 }) 493 494 t.Run("copy sources into a dir defined in env variable with no file found", func(t *testing.T) { 495 testDir, srcDir := setupDirs(t) 496 defer os.RemoveAll(testDir) 497 498 targetPath := filepath.Join(testDir, "target") + "/" 499 500 cmd := CopyCommand{ 501 cmd: &instructions.CopyCommand{ 502 //should only dam and bam be copied 503 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{srcDir + "/tam[s]"}, DestPath: "$TARGET_PATH"}, 504 }, 505 fileContext: util.FileContext{Root: testDir}, 506 } 507 508 cfg := &v1.Config{ 509 Cmd: nil, 510 Env: []string{"TARGET_PATH=" + targetPath}, 511 WorkingDir: testDir, 512 } 513 514 err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 515 if err != nil { 516 t.Fatal(err) 517 } 518 testutil.CheckNoError(t, err) 519 520 actual, err := readDirectory(targetPath) 521 522 //check it should error out since no files are copied and targetPath is not created 523 if err == nil { 524 t.Fatal("expected error no dirrectory but got nil") 525 } 526 527 //actual should empty since no files are copied 528 testutil.CheckDeepEqual(t, 0, len(actual)) 529 }) 530 } 531 532 func TestCopyCommand_ExecuteCommand_Extended(t *testing.T) { 533 setupDirs := func(t *testing.T) (string, string) { 534 testDir := t.TempDir() 535 536 dir := filepath.Join(testDir, "bar") 537 538 if err := os.MkdirAll(dir, 0777); err != nil { 539 t.Fatal(err) 540 } 541 file := filepath.Join(dir, "bam.txt") 542 543 if err := os.WriteFile(file, []byte("meow"), 0777); err != nil { 544 t.Fatal(err) 545 } 546 targetPath := filepath.Join(dir, "dam.txt") 547 if err := os.WriteFile(targetPath, []byte("woof"), 0777); err != nil { 548 t.Fatal(err) 549 } 550 if err := os.Symlink("dam.txt", filepath.Join(dir, "sym.link")); err != nil { 551 t.Fatal(err) 552 } 553 554 return testDir, filepath.Base(dir) 555 } 556 557 t.Run("copy dir to another dir", func(t *testing.T) { 558 testDir, srcDir := setupDirs(t) 559 defer os.RemoveAll(testDir) 560 expected, err := readDirectory(filepath.Join(testDir, srcDir)) 561 if err != nil { 562 t.Fatal(err) 563 } 564 565 cmd := CopyCommand{ 566 cmd: &instructions.CopyCommand{ 567 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{srcDir}, DestPath: "dest"}, 568 }, 569 fileContext: util.FileContext{Root: testDir}, 570 } 571 572 cfg := &v1.Config{ 573 Cmd: nil, 574 Env: []string{}, 575 WorkingDir: testDir, 576 } 577 578 err = cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 579 if err != nil { 580 t.Fatal(err) 581 } 582 testutil.CheckNoError(t, err) 583 // Check if "dest" dir exists with contents of srcDir 584 actual, err := readDirectory(filepath.Join(testDir, "dest")) 585 if err != nil { 586 t.Fatal(err) 587 } 588 for i, f := range actual { 589 testutil.CheckDeepEqual(t, expected[i].Name(), f.Name()) 590 testutil.CheckDeepEqual(t, expected[i].Mode(), f.Mode()) 591 } 592 }) 593 594 t.Run("copy dir to another dir - with ignored files", func(t *testing.T) { 595 testDir, srcDir := setupDirs(t) 596 defer os.RemoveAll(testDir) 597 ignoredFile := "bam.txt" 598 srcFiles, err := readDirectory(filepath.Join(testDir, srcDir)) 599 if err != nil { 600 t.Fatal(err) 601 } 602 expected := map[string]fs.FileInfo{} 603 for _, sf := range srcFiles { 604 if sf.Name() == ignoredFile { 605 continue 606 } 607 expected[sf.Name()] = sf 608 } 609 610 cmd := CopyCommand{ 611 cmd: &instructions.CopyCommand{ 612 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{srcDir}, DestPath: "dest"}, 613 }, 614 fileContext: util.FileContext{ 615 Root: testDir, 616 ExcludedFiles: []string{filepath.Join(srcDir, ignoredFile)}}, 617 } 618 619 cfg := &v1.Config{ 620 Cmd: nil, 621 Env: []string{}, 622 WorkingDir: testDir, 623 } 624 625 err = cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 626 if err != nil { 627 t.Fatal(err) 628 } 629 testutil.CheckNoError(t, err) 630 // Check if "dest" dir exists with contents of srcDir 631 actual, err := readDirectory(filepath.Join(testDir, "dest")) 632 if err != nil { 633 t.Fatal(err) 634 } 635 636 if len(actual) != len(expected) { 637 t.Errorf("%v files are expected to be copied, but got %v", len(expected), len(actual)) 638 } 639 for _, f := range actual { 640 if f.Name() == ignoredFile { 641 t.Errorf("file %v is expected to be ignored, but copied", f.Name()) 642 } 643 testutil.CheckDeepEqual(t, expected[f.Name()].Name(), f.Name()) 644 testutil.CheckDeepEqual(t, expected[f.Name()].Mode(), f.Mode()) 645 } 646 }) 647 648 t.Run("copy file to a dir", func(t *testing.T) { 649 testDir, srcDir := setupDirs(t) 650 defer os.RemoveAll(testDir) 651 cmd := CopyCommand{ 652 cmd: &instructions.CopyCommand{ 653 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{filepath.Join(srcDir, "bam.txt")}, DestPath: "dest/"}, 654 }, 655 fileContext: util.FileContext{Root: testDir}, 656 } 657 658 cfg := &v1.Config{ 659 Cmd: nil, 660 Env: []string{}, 661 WorkingDir: testDir, 662 } 663 664 err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 665 testutil.CheckNoError(t, err) 666 // Check if "dest" dir exists with file bam.txt 667 files, err := readDirectory(filepath.Join(testDir, "dest")) 668 if err != nil { 669 t.Fatal(err) 670 } 671 testutil.CheckDeepEqual(t, 1, len(files)) 672 testutil.CheckDeepEqual(t, files[0].Name(), "bam.txt") 673 }) 674 675 t.Run("copy file to a filepath", func(t *testing.T) { 676 testDir, srcDir := setupDirs(t) 677 defer os.RemoveAll(testDir) 678 cmd := CopyCommand{ 679 cmd: &instructions.CopyCommand{ 680 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{filepath.Join(srcDir, "bam.txt")}, DestPath: "dest"}, 681 }, 682 fileContext: util.FileContext{Root: testDir}, 683 } 684 685 cfg := &v1.Config{ 686 Cmd: nil, 687 Env: []string{}, 688 WorkingDir: testDir, 689 } 690 691 err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 692 testutil.CheckNoError(t, err) 693 // Check if bam.txt is copied to dest file 694 if _, err := os.Lstat(filepath.Join(testDir, "dest")); err != nil { 695 t.Fatal(err) 696 } 697 }) 698 t.Run("copy file to a dir without trailing /", func(t *testing.T) { 699 testDir, srcDir := setupDirs(t) 700 defer os.RemoveAll(testDir) 701 702 destDir := filepath.Join(testDir, "dest") 703 if err := os.MkdirAll(destDir, 0777); err != nil { 704 t.Fatal(err) 705 } 706 707 cmd := CopyCommand{ 708 cmd: &instructions.CopyCommand{ 709 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{filepath.Join(srcDir, "bam.txt")}, DestPath: "dest"}, 710 }, 711 fileContext: util.FileContext{Root: testDir}, 712 } 713 714 cfg := &v1.Config{ 715 Cmd: nil, 716 Env: []string{}, 717 WorkingDir: testDir, 718 } 719 720 err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 721 testutil.CheckNoError(t, err) 722 // Check if "dest" dir exists with file bam.txt 723 files, err := readDirectory(filepath.Join(testDir, "dest")) 724 if err != nil { 725 t.Fatal(err) 726 } 727 testutil.CheckDeepEqual(t, 1, len(files)) 728 testutil.CheckDeepEqual(t, files[0].Name(), "bam.txt") 729 730 }) 731 732 t.Run("copy symlink file to a dir", func(t *testing.T) { 733 testDir, srcDir := setupDirs(t) 734 defer os.RemoveAll(testDir) 735 736 cmd := CopyCommand{ 737 cmd: &instructions.CopyCommand{ 738 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{filepath.Join(srcDir, "sym.link")}, DestPath: "dest/"}, 739 }, 740 fileContext: util.FileContext{Root: testDir}, 741 } 742 743 cfg := &v1.Config{ 744 Cmd: nil, 745 Env: []string{}, 746 WorkingDir: testDir, 747 } 748 749 err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 750 testutil.CheckNoError(t, err) 751 // Check if "dest" dir exists with link sym.link 752 files, err := readDirectory(filepath.Join(testDir, "dest")) 753 if err != nil { 754 t.Fatal(err) 755 } 756 // bam.txt and sym.link should be present 757 testutil.CheckDeepEqual(t, 1, len(files)) 758 testutil.CheckDeepEqual(t, files[0].Name(), "sym.link") 759 testutil.CheckDeepEqual(t, true, files[0].Mode()&os.ModeSymlink != 0) 760 linkName, err := os.Readlink(filepath.Join(testDir, "dest", "sym.link")) 761 if err != nil { 762 t.Fatal(err) 763 } 764 testutil.CheckDeepEqual(t, linkName, "dam.txt") 765 }) 766 767 t.Run("copy deadlink symlink file to a dir", func(t *testing.T) { 768 testDir, srcDir := setupDirs(t) 769 defer os.RemoveAll(testDir) 770 doesNotExists := filepath.Join(testDir, "dead.txt") 771 if err := os.WriteFile(doesNotExists, []byte("remove me"), 0777); err != nil { 772 t.Fatal(err) 773 } 774 if err := os.Symlink("../dead.txt", filepath.Join(testDir, srcDir, "dead.link")); err != nil { 775 t.Fatal(err) 776 } 777 if err := os.Remove(doesNotExists); err != nil { 778 t.Fatal(err) 779 } 780 781 cmd := CopyCommand{ 782 cmd: &instructions.CopyCommand{ 783 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{filepath.Join(srcDir, "dead.link")}, DestPath: "dest/"}, 784 }, 785 fileContext: util.FileContext{Root: testDir}, 786 } 787 788 cfg := &v1.Config{ 789 Cmd: nil, 790 Env: []string{}, 791 WorkingDir: testDir, 792 } 793 794 err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 795 testutil.CheckNoError(t, err) 796 // Check if "dest" dir exists with link dead.link 797 files, err := readDirectory(filepath.Join(testDir, "dest")) 798 if err != nil { 799 t.Fatal(err) 800 } 801 testutil.CheckDeepEqual(t, 1, len(files)) 802 testutil.CheckDeepEqual(t, files[0].Name(), "dead.link") 803 testutil.CheckDeepEqual(t, true, files[0].Mode()&os.ModeSymlink != 0) 804 linkName, err := os.Readlink(filepath.Join(testDir, "dest", "dead.link")) 805 if err != nil { 806 t.Fatal(err) 807 } 808 testutil.CheckDeepEqual(t, linkName, "../dead.txt") 809 }) 810 811 t.Run("copy src symlink dir to a dir", func(t *testing.T) { 812 testDir, srcDir := setupDirs(t) 813 defer os.RemoveAll(testDir) 814 expected, err := os.ReadDir(filepath.Join(testDir, srcDir)) 815 if err != nil { 816 t.Fatal(err) 817 } 818 819 another := filepath.Join(testDir, "another") 820 os.Symlink(filepath.Join(testDir, srcDir), another) 821 822 cmd := CopyCommand{ 823 cmd: &instructions.CopyCommand{ 824 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{"another"}, DestPath: "dest"}, 825 }, 826 fileContext: util.FileContext{Root: testDir}, 827 } 828 829 cfg := &v1.Config{ 830 Cmd: nil, 831 Env: []string{}, 832 WorkingDir: testDir, 833 } 834 835 err = cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 836 testutil.CheckNoError(t, err) 837 // Check if "dest" dir exists with contents of srcDir 838 actual, err := os.ReadDir(filepath.Join(testDir, "dest")) 839 if err != nil { 840 t.Fatal(err) 841 } 842 for i, f := range actual { 843 testutil.CheckDeepEqual(t, expected[i].Name(), f.Name()) 844 testutil.CheckDeepEqual(t, expected[i].Type(), f.Type()) 845 } 846 }) 847 848 t.Run("copy dir with a symlink to a file outside of current src dir", func(t *testing.T) { 849 testDir, srcDir := setupDirs(t) 850 defer os.RemoveAll(testDir) 851 expected, err := readDirectory(filepath.Join(testDir, srcDir)) 852 if err != nil { 853 t.Fatal(err) 854 } 855 856 anotherSrc := filepath.Join(testDir, "anotherSrc") 857 if err := os.MkdirAll(anotherSrc, 0777); err != nil { 858 t.Fatal(err) 859 } 860 targetPath := filepath.Join(anotherSrc, "target.txt") 861 if err := os.WriteFile(targetPath, []byte("woof"), 0777); err != nil { 862 t.Fatal(err) 863 } 864 if err := os.Symlink(targetPath, filepath.Join(testDir, srcDir, "zSym.link")); err != nil { 865 t.Fatal(err) 866 } 867 868 cmd := CopyCommand{ 869 cmd: &instructions.CopyCommand{ 870 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{srcDir}, DestPath: "dest"}, 871 }, 872 fileContext: util.FileContext{Root: testDir}, 873 } 874 875 cfg := &v1.Config{ 876 Cmd: nil, 877 Env: []string{}, 878 WorkingDir: testDir, 879 } 880 881 err = cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 882 testutil.CheckNoError(t, err) 883 // Check if "dest" dir exists contents of srcDir and an extra zSym.link created 884 // in this test 885 actual, err := readDirectory(filepath.Join(testDir, "dest")) 886 if err != nil { 887 t.Fatal(err) 888 } 889 testutil.CheckDeepEqual(t, 4, len(actual)) 890 for i, f := range expected { 891 testutil.CheckDeepEqual(t, f.Name(), actual[i].Name()) 892 testutil.CheckDeepEqual(t, f.Mode(), actual[i].Mode()) 893 } 894 linkName, err := os.Readlink(filepath.Join(testDir, "dest", "zSym.link")) 895 if err != nil { 896 t.Fatal(err) 897 } 898 testutil.CheckDeepEqual(t, linkName, targetPath) 899 }) 900 901 t.Run("copy src symlink dir to a dir", func(t *testing.T) { 902 testDir, srcDir := setupDirs(t) 903 defer os.RemoveAll(testDir) 904 expected, err := os.ReadDir(filepath.Join(testDir, srcDir)) 905 if err != nil { 906 t.Fatal(err) 907 } 908 909 another := filepath.Join(testDir, "another") 910 os.Symlink(filepath.Join(testDir, srcDir), another) 911 912 cmd := CopyCommand{ 913 cmd: &instructions.CopyCommand{ 914 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{"another"}, DestPath: "dest"}, 915 }, 916 fileContext: util.FileContext{Root: testDir}, 917 } 918 919 cfg := &v1.Config{ 920 Cmd: nil, 921 Env: []string{}, 922 WorkingDir: testDir, 923 } 924 925 err = cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 926 testutil.CheckNoError(t, err) 927 // Check if "dest" dir exists with bam.txt and "dest" dir is a symlink 928 actual, err := os.ReadDir(filepath.Join(testDir, "dest")) 929 if err != nil { 930 t.Fatal(err) 931 } 932 for i, f := range actual { 933 testutil.CheckDeepEqual(t, expected[i].Name(), f.Name()) 934 testutil.CheckDeepEqual(t, expected[i].Type(), f.Type()) 935 } 936 }) 937 938 t.Run("copy src dir to a dest dir which is a symlink", func(t *testing.T) { 939 testDir, srcDir := setupDirs(t) 940 defer os.RemoveAll(testDir) 941 expected, err := readDirectory(filepath.Join(testDir, srcDir)) 942 if err != nil { 943 t.Fatal(err) 944 } 945 946 dest := filepath.Join(testDir, "dest") 947 if err := os.MkdirAll(dest, 0777); err != nil { 948 t.Fatal(err) 949 } 950 linkedDest := filepath.Join(testDir, "linkDest") 951 if err := os.Symlink(dest, linkedDest); err != nil { 952 t.Fatal(err) 953 } 954 955 cmd := CopyCommand{ 956 cmd: &instructions.CopyCommand{ 957 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{srcDir}, DestPath: linkedDest}, 958 }, 959 fileContext: util.FileContext{Root: testDir}, 960 } 961 962 cfg := &v1.Config{ 963 Cmd: nil, 964 Env: []string{}, 965 WorkingDir: testDir, 966 } 967 968 err = cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 969 testutil.CheckNoError(t, err) 970 // Check if "linkdest" dir exists with contents of srcDir 971 actual, err := readDirectory(filepath.Join(testDir, "linkDest")) 972 if err != nil { 973 t.Fatal(err) 974 } 975 for i, f := range expected { 976 testutil.CheckDeepEqual(t, f.Name(), actual[i].Name()) 977 testutil.CheckDeepEqual(t, f.Mode(), actual[i].Mode()) 978 } 979 // Check if linkDest -> dest 980 linkName, err := os.Readlink(filepath.Join(testDir, "linkDest")) 981 if err != nil { 982 t.Fatal(err) 983 } 984 testutil.CheckDeepEqual(t, linkName, dest) 985 }) 986 987 t.Run("copy src file to a dest dir which is a symlink", func(t *testing.T) { 988 testDir, srcDir := setupDirs(t) 989 defer os.RemoveAll(testDir) 990 991 dest := filepath.Join(testDir, "dest") 992 if err := os.MkdirAll(dest, 0777); err != nil { 993 t.Fatal(err) 994 } 995 linkedDest := filepath.Join(testDir, "linkDest") 996 if err := os.Symlink(dest, linkedDest); err != nil { 997 t.Fatal(err) 998 } 999 1000 cmd := CopyCommand{ 1001 cmd: &instructions.CopyCommand{ 1002 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{fmt.Sprintf("%s/bam.txt", srcDir)}, DestPath: linkedDest}, 1003 }, 1004 fileContext: util.FileContext{Root: testDir}, 1005 } 1006 1007 cfg := &v1.Config{ 1008 Cmd: nil, 1009 Env: []string{}, 1010 WorkingDir: testDir, 1011 } 1012 1013 err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 1014 testutil.CheckNoError(t, err) 1015 // Check if "linkDest" link is same. 1016 actual, err := readDirectory(filepath.Join(testDir, "dest")) 1017 if err != nil { 1018 t.Fatal(err) 1019 } 1020 testutil.CheckDeepEqual(t, "bam.txt", actual[0].Name()) 1021 c, err := os.ReadFile(filepath.Join(testDir, "dest", "bam.txt")) 1022 if err != nil { 1023 t.Fatal(err) 1024 } 1025 testutil.CheckDeepEqual(t, "meow", string(c)) 1026 // Check if linkDest -> dest 1027 linkName, err := os.Readlink(filepath.Join(testDir, "linkDest")) 1028 if err != nil { 1029 t.Fatal(err) 1030 } 1031 testutil.CheckDeepEqual(t, linkName, dest) 1032 }) 1033 1034 t.Run("copy src file to a dest dir with chown", func(t *testing.T) { 1035 testDir, srcDir := setupDirs(t) 1036 defer os.RemoveAll(testDir) 1037 1038 original := getUserGroup 1039 defer func() { getUserGroup = original }() 1040 1041 uid := os.Getuid() 1042 gid := os.Getgid() 1043 1044 getUserGroup = func(userStr string, _ []string) (int64, int64, error) { 1045 return int64(uid), int64(gid), nil 1046 } 1047 1048 cmd := CopyCommand{ 1049 cmd: &instructions.CopyCommand{ 1050 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{fmt.Sprintf("%s/bam.txt", srcDir)}, DestPath: testDir}, 1051 Chown: "alice:group", 1052 }, 1053 fileContext: util.FileContext{Root: testDir}, 1054 } 1055 1056 cfg := &v1.Config{ 1057 Cmd: nil, 1058 Env: []string{}, 1059 WorkingDir: testDir, 1060 } 1061 1062 err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 1063 testutil.CheckNoError(t, err) 1064 1065 actual, err := readDirectory(filepath.Join(testDir)) 1066 if err != nil { 1067 t.Fatal(err) 1068 } 1069 1070 testutil.CheckDeepEqual(t, "bam.txt", actual[0].Name()) 1071 1072 if stat, ok := actual[0].Sys().(*syscall.Stat_t); ok { 1073 if int(stat.Uid) != uid { 1074 t.Errorf("uid don't match, got %d, expected %d", stat.Uid, uid) 1075 } 1076 if int(stat.Gid) != gid { 1077 t.Errorf("gid don't match, got %d, expected %d", stat.Gid, gid) 1078 } 1079 } 1080 }) 1081 1082 t.Run("copy src file to a dest dir with chown and random user", func(t *testing.T) { 1083 testDir, srcDir := setupDirs(t) 1084 defer os.RemoveAll(testDir) 1085 1086 original := getUserGroup 1087 defer func() { getUserGroup = original }() 1088 1089 getUserGroup = func(userStr string, _ []string) (int64, int64, error) { 1090 return 12345, 12345, nil 1091 } 1092 1093 cmd := CopyCommand{ 1094 cmd: &instructions.CopyCommand{ 1095 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{fmt.Sprintf("%s/bam.txt", srcDir)}, DestPath: testDir}, 1096 Chown: "missing:missing", 1097 }, 1098 fileContext: util.FileContext{Root: testDir}, 1099 } 1100 1101 cfg := &v1.Config{ 1102 Cmd: nil, 1103 Env: []string{}, 1104 WorkingDir: testDir, 1105 } 1106 1107 err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 1108 if !errors.Is(err, os.ErrPermission) { 1109 testutil.CheckNoError(t, err) 1110 } 1111 }) 1112 1113 t.Run("copy src dir with relative symlinks in a dir", func(t *testing.T) { 1114 testDir, srcDir := setupDirs(t) 1115 defer os.RemoveAll(testDir) 1116 1117 // Make another dir inside bar with a relative symlink 1118 dir := filepath.Join(testDir, srcDir, "another") 1119 if err := os.MkdirAll(dir, 0777); err != nil { 1120 t.Fatal(err) 1121 } 1122 os.Symlink("../bam.txt", filepath.Join(dir, "bam_relative.txt")) 1123 1124 dest := filepath.Join(testDir, "copy") 1125 cmd := CopyCommand{ 1126 cmd: &instructions.CopyCommand{ 1127 SourcesAndDest: instructions.SourcesAndDest{SourcePaths: []string{srcDir}, DestPath: dest}, 1128 }, 1129 fileContext: util.FileContext{Root: testDir}, 1130 } 1131 1132 cfg := &v1.Config{ 1133 Cmd: nil, 1134 Env: []string{}, 1135 WorkingDir: testDir, 1136 } 1137 err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{})) 1138 testutil.CheckNoError(t, err) 1139 actual, err := os.ReadDir(filepath.Join(dest, "another")) 1140 if err != nil { 1141 t.Fatal(err) 1142 } 1143 testutil.CheckDeepEqual(t, "bam_relative.txt", actual[0].Name()) 1144 linkName, err := os.Readlink(filepath.Join(dest, "another", "bam_relative.txt")) 1145 if err != nil { 1146 t.Fatal(err) 1147 } 1148 testutil.CheckDeepEqual(t, "../bam.txt", linkName) 1149 }) 1150 }