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  }