github.com/containerd/nerdctl@v1.7.7/cmd/nerdctl/container_cp_linux_test.go (about)

     1  /*
     2     Copyright The containerd Authors.
     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 main
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  	"syscall"
    25  	"testing"
    26  
    27  	"github.com/containerd/nerdctl/pkg/rootlessutil"
    28  	"github.com/containerd/nerdctl/pkg/testutil"
    29  	"gotest.tools/v3/assert"
    30  )
    31  
    32  func TestCopyToContainer(t *testing.T) {
    33  	t.Parallel()
    34  	base := testutil.NewBase(t)
    35  	testContainer := testutil.Identifier(t)
    36  	testStoppedContainer := "stopped-container-" + testutil.Identifier(t)
    37  
    38  	base.Cmd("run", "-d", "--name", testContainer, testutil.CommonImage, "sleep", "1h").AssertOK()
    39  	defer base.Cmd("rm", "-f", testContainer).Run()
    40  
    41  	base.Cmd("run", "-d", "--name", testStoppedContainer, testutil.CommonImage, "sleep", "1h").AssertOK()
    42  	defer base.Cmd("rm", "-f", testStoppedContainer).Run()
    43  	// Stop container immediately after starting for testing copying into stopped container
    44  	base.Cmd("stop", testStoppedContainer).AssertOK()
    45  	srcUID := os.Geteuid()
    46  	srcDir := t.TempDir()
    47  	srcFile := filepath.Join(srcDir, "test-file")
    48  	srcFileContent := []byte("test-file-content")
    49  	err := os.WriteFile(srcFile, srcFileContent, 0o644)
    50  	assert.NilError(t, err)
    51  
    52  	assertCat := func(catPath string, testContainer string, stopped bool) {
    53  		if stopped {
    54  			base.Cmd("start", testContainer).AssertOK()
    55  			defer base.Cmd("stop", testContainer).AssertOK()
    56  		}
    57  		t.Logf("catPath=%q", catPath)
    58  		base.Cmd("exec", testContainer, "cat", catPath).AssertOutExactly(string(srcFileContent))
    59  		base.Cmd("exec", testContainer, "stat", "-c", "%u", catPath).AssertOutExactly(fmt.Sprintf("%d\n", srcUID))
    60  	}
    61  
    62  	// For the test matrix, see https://docs.docker.com/engine/reference/commandline/cp/
    63  	t.Run("SRC_PATH specifies a file", func(t *testing.T) {
    64  		srcPath := srcFile
    65  		t.Run("DEST_PATH does not exist", func(t *testing.T) {
    66  			destPath := "/dest-no-exist-no-slash"
    67  			base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK()
    68  			catPath := destPath
    69  			assertCat(catPath, testContainer, false)
    70  			if rootlessutil.IsRootless() {
    71  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
    72  			}
    73  			base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK()
    74  			assertCat(catPath, testStoppedContainer, true)
    75  		})
    76  		t.Run("DEST_PATH does not exist and ends with /", func(t *testing.T) {
    77  			destPath := "/dest-no-exist-with-slash/"
    78  			base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertFail()
    79  			if rootlessutil.IsRootless() {
    80  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
    81  			}
    82  			base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertFail()
    83  		})
    84  		t.Run("DEST_PATH exists and is a file", func(t *testing.T) {
    85  			destPath := "/dest-file-exists"
    86  			base.Cmd("exec", testContainer, "touch", destPath).AssertOK()
    87  			base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK()
    88  			catPath := destPath
    89  			assertCat(catPath, testContainer, false)
    90  			if rootlessutil.IsRootless() {
    91  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
    92  			}
    93  			base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK()
    94  			assertCat(catPath, testStoppedContainer, true)
    95  		})
    96  		t.Run("DEST_PATH exists and is a directory", func(t *testing.T) {
    97  			destPath := "/dest-dir-exists"
    98  			base.Cmd("exec", testContainer, "mkdir", "-p", destPath).AssertOK()
    99  			base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK()
   100  			catPath := filepath.Join(destPath, filepath.Base(srcFile))
   101  			assertCat(catPath, testContainer, false)
   102  			if rootlessutil.IsRootless() {
   103  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
   104  			}
   105  			base.Cmd("start", testStoppedContainer).AssertOK()
   106  			base.Cmd("exec", testStoppedContainer, "mkdir", "-p", destPath).AssertOK()
   107  			base.Cmd("stop", testStoppedContainer).AssertOK()
   108  			base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK()
   109  			assertCat(catPath, testStoppedContainer, true)
   110  		})
   111  		t.Run("DEST_PATH is in a volume", func(t *testing.T) {
   112  			// Create a volume
   113  			vol := "somevol"
   114  			base.Cmd("volume", "create", vol).AssertOK()
   115  			defer base.Cmd("volume", "rm", vol).Run()
   116  			con := fmt.Sprintf("%s-with-volume", testContainer)
   117  			mountDir := "/some_dir"
   118  			base.Cmd("run", "-d", "--name", con, "-v", fmt.Sprintf("%s:%s", vol, mountDir), testutil.CommonImage, "sleep", "1h").AssertOK()
   119  			defer base.Cmd("rm", "-f", con).Run()
   120  			catPath := filepath.Join(mountDir, filepath.Base(srcFile))
   121  			// Running container test
   122  			base.Cmd("cp", srcPath, con+":"+mountDir).AssertOK()
   123  			assertCat(catPath, con, false)
   124  
   125  			// Skip for rootless
   126  			if rootlessutil.IsRootless() {
   127  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
   128  			}
   129  			// Stopped container test
   130  			// Delete previously copied file
   131  			base.Cmd("exec", con, "rm", catPath).AssertOK()
   132  			base.Cmd("stop", con).AssertOK()
   133  			base.Cmd("cp", srcPath, con+":"+mountDir).AssertOK()
   134  			assertCat(catPath, con, true)
   135  		})
   136  		t.Run("Destination path is a read-only", func(t *testing.T) {
   137  			vol := "somevol"
   138  			base.Cmd("volume", "create", vol).AssertOK()
   139  			defer base.Cmd("volume", "rm", vol).Run()
   140  			con := fmt.Sprintf("%s-with-read-only-volume", testContainer)
   141  			mountDir := "/some_dir"
   142  			// Create container with read-only volume mounted
   143  			base.Cmd("run", "-d", "--name", con, "-v", fmt.Sprintf("%s:%s:ro", vol, mountDir), testutil.CommonImage, "sleep", "1h").AssertOK()
   144  			defer base.Cmd("rm", "-f", con).Run()
   145  			base.Cmd("cp", srcPath, con+":"+mountDir).AssertFail()
   146  
   147  			// Skip for rootless
   148  			if rootlessutil.IsRootless() {
   149  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
   150  			}
   151  
   152  			// Stopped container test
   153  			// Delete previously copied file
   154  			base.Cmd("stop", con).AssertOK()
   155  			base.Cmd("cp", srcPath, con+":"+mountDir).AssertFail()
   156  		})
   157  		t.Run("Destination path is a read-only and default tmpfs mount point", func(t *testing.T) {
   158  			vol := "somevol"
   159  			base.Cmd("volume", "create", vol).AssertOK()
   160  			defer base.Cmd("volume", "rm", vol).Run()
   161  			con := fmt.Sprintf("%s-with-read-only-volume", testContainer)
   162  
   163  			// /tmp is from rootfs of alpine
   164  			mountDir := "/tmp"
   165  			// Create container with read-only mounted volume mounted at /tmp
   166  			base.Cmd("run", "-d", "--name", con, "-v", fmt.Sprintf("%s:%s:ro", vol, mountDir), testutil.CommonImage, "sleep", "1h").AssertOK()
   167  			defer base.Cmd("rm", "-f", con).Run()
   168  			base.Cmd("cp", srcPath, con+":"+mountDir).AssertFail()
   169  
   170  			// Skip for rootless
   171  			if rootlessutil.IsRootless() {
   172  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
   173  			}
   174  
   175  			// Stopped container test
   176  			// Delete previously copied file
   177  			base.Cmd("stop", con).AssertOK()
   178  			base.Cmd("cp", srcPath, con+":"+mountDir).AssertFail()
   179  		})
   180  	})
   181  	t.Run("SRC_PATH specifies a directory", func(t *testing.T) {
   182  		srcPath := srcDir
   183  		t.Run("DEST_PATH does not exist", func(t *testing.T) {
   184  			destPath := "/dest2-no-exist"
   185  			base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK()
   186  			catPath := filepath.Join(destPath, filepath.Base(srcFile))
   187  			assertCat(catPath, testContainer, false)
   188  			if rootlessutil.IsRootless() {
   189  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
   190  			}
   191  			base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK()
   192  			assertCat(catPath, testStoppedContainer, true)
   193  		})
   194  		t.Run("DEST_PATH exists and is a file", func(t *testing.T) {
   195  			destPath := "/dest2-file-exists"
   196  			base.Cmd("exec", testContainer, "touch", destPath).AssertOK()
   197  			base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertFail()
   198  			if rootlessutil.IsRootless() {
   199  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
   200  			}
   201  			base.Cmd("start", testStoppedContainer).AssertOK()
   202  			base.Cmd("exec", testStoppedContainer, "touch", destPath).AssertOK()
   203  			base.Cmd("stop", testStoppedContainer).AssertOK()
   204  			base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertFail()
   205  		})
   206  		t.Run("DEST_PATH exists and is a directory", func(t *testing.T) {
   207  			t.Run("SRC_PATH does not end with `/.`", func(t *testing.T) {
   208  				destPath := "/dest2-dir-exists"
   209  				base.Cmd("exec", testContainer, "mkdir", "-p", destPath).AssertOK()
   210  				base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK()
   211  				catPath := filepath.Join(destPath, strings.TrimPrefix(srcFile, filepath.Dir(srcDir)+"/"))
   212  				assertCat(catPath, testContainer, false)
   213  				if rootlessutil.IsRootless() {
   214  					t.Skip("Test skipped in rootless mode for testStoppedContainer")
   215  				}
   216  				base.Cmd("start", testStoppedContainer).AssertOK()
   217  				base.Cmd("exec", testStoppedContainer, "mkdir", "-p", destPath).AssertOK()
   218  				base.Cmd("stop", testStoppedContainer).AssertOK()
   219  				base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK()
   220  				assertCat(catPath, testStoppedContainer, true)
   221  			})
   222  			t.Run("SRC_PATH does end with `/.`", func(t *testing.T) {
   223  				srcPath += "/."
   224  				destPath := "/dest2-dir2-exists"
   225  				base.Cmd("exec", testContainer, "mkdir", "-p", destPath).AssertOK()
   226  				base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK()
   227  				catPath := filepath.Join(destPath, filepath.Base(srcFile))
   228  				t.Logf("catPath=%q", catPath)
   229  				assertCat(catPath, testContainer, false)
   230  				if rootlessutil.IsRootless() {
   231  					t.Skip("Test skipped in rootless mode for testStoppedContainer")
   232  				}
   233  				base.Cmd("start", testStoppedContainer).AssertOK()
   234  				base.Cmd("exec", testStoppedContainer, "mkdir", "-p", destPath).AssertOK()
   235  				base.Cmd("stop", testStoppedContainer).AssertOK()
   236  				base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK()
   237  				assertCat(catPath, testStoppedContainer, true)
   238  			})
   239  		})
   240  	})
   241  }
   242  
   243  func TestCopyFromContainer(t *testing.T) {
   244  	t.Parallel()
   245  	base := testutil.NewBase(t)
   246  	testContainer := testutil.Identifier(t)
   247  	testStoppedContainer := "stopped-container-" + testutil.Identifier(t)
   248  	base.Cmd("run", "-d", "--name", testContainer, testutil.CommonImage, "sleep", "1h").AssertOK()
   249  	defer base.Cmd("rm", "-f", testContainer).Run()
   250  
   251  	base.Cmd("run", "-d", "--name", testStoppedContainer, testutil.CommonImage, "sleep", "1h").AssertOK()
   252  	defer base.Cmd("rm", "-f", testStoppedContainer).Run()
   253  
   254  	euid := os.Geteuid()
   255  	srcUID := 42
   256  	srcDir := "/test-dir"
   257  	srcFile := filepath.Join(srcDir, "test-file")
   258  	srcFileContent := []byte("test-file-content")
   259  	mkSrcScript := fmt.Sprintf("mkdir -p %q && echo -n %q >%q && chown %d %q", srcDir, srcFileContent, srcFile, srcUID, srcFile)
   260  	base.Cmd("exec", testContainer, "sh", "-euc", mkSrcScript).AssertOK()
   261  	base.Cmd("exec", testStoppedContainer, "sh", "-euc", mkSrcScript).AssertOK()
   262  	// Stop container for testing copying out of stopped container
   263  	base.Cmd("stop", testStoppedContainer)
   264  
   265  	assertCat := func(catPath string) {
   266  		t.Logf("catPath=%q", catPath)
   267  		got, err := os.ReadFile(catPath)
   268  		assert.NilError(t, err)
   269  		assert.DeepEqual(t, srcFileContent, got)
   270  		st, err := os.Stat(catPath)
   271  		assert.NilError(t, err)
   272  		stSys := st.Sys().(*syscall.Stat_t)
   273  		// stSys.Uid matches euid, not srcUID
   274  		assert.DeepEqual(t, uint32(euid), stSys.Uid)
   275  	}
   276  
   277  	td := t.TempDir()
   278  	// For the test matrix, see https://docs.docker.com/engine/reference/commandline/cp/
   279  	t.Run("SRC_PATH specifies a file", func(t *testing.T) {
   280  		srcPath := srcFile
   281  		t.Run("DEST_PATH does not exist", func(t *testing.T) {
   282  			destPath := filepath.Join(td, "dest-no-exist-no-slash")
   283  			base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertOK()
   284  			catPath := destPath
   285  			assertCat(catPath)
   286  			if rootlessutil.IsRootless() {
   287  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
   288  			}
   289  			base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertOK()
   290  			assertCat(catPath)
   291  		})
   292  		t.Run("DEST_PATH does not exist and ends with /", func(t *testing.T) {
   293  			destPath := td + "/dest-no-exist-with-slash/" // Avoid filepath.Join, to forcibly append "/"
   294  			base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertFail()
   295  			if rootlessutil.IsRootless() {
   296  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
   297  			}
   298  			base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertFail()
   299  		})
   300  		t.Run("DEST_PATH exists and is a file", func(t *testing.T) {
   301  			destPath := filepath.Join(td, "dest-file-exists")
   302  			err := os.WriteFile(destPath, []byte(""), 0o644)
   303  			assert.NilError(t, err)
   304  			base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertOK()
   305  			catPath := destPath
   306  			assertCat(catPath)
   307  			if rootlessutil.IsRootless() {
   308  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
   309  			}
   310  			base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertOK()
   311  			assertCat(catPath)
   312  		})
   313  		t.Run("DEST_PATH exists and is a directory", func(t *testing.T) {
   314  			destPath := filepath.Join(td, "dest-dir-exists")
   315  			err := os.Mkdir(destPath, 0o755)
   316  			assert.NilError(t, err)
   317  			base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertOK()
   318  			catPath := filepath.Join(destPath, filepath.Base(srcFile))
   319  			assertCat(catPath)
   320  			if rootlessutil.IsRootless() {
   321  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
   322  			}
   323  			base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertOK()
   324  			assertCat(catPath)
   325  		})
   326  		t.Run("SRC_PATH is in a volume", func(t *testing.T) {
   327  			// Setup
   328  			// Create a volume
   329  			vol := "somevol"
   330  			base.Cmd("volume", "create", vol).AssertOK()
   331  			defer base.Cmd("volume", "rm", "-f", vol).Run()
   332  
   333  			// Create container for test
   334  			con := fmt.Sprintf("%s-with-volume", testContainer)
   335  
   336  			mountDir := "/some_dir"
   337  			base.Cmd("run", "-d", "--name", con, "-v", fmt.Sprintf("%s:%s", vol, mountDir), testutil.CommonImage, "sleep", "1h").AssertOK()
   338  			defer base.Cmd("rm", "-f", con).Run()
   339  
   340  			// Create a file to mounted volume
   341  			mountedVolFile := filepath.Join(mountDir, "test-file")
   342  			mkSrcScript = fmt.Sprintf("echo -n %q >%q && chown %d %q", srcFileContent, mountedVolFile, srcUID, mountedVolFile)
   343  			base.Cmd("exec", con, "sh", "-euc", mkSrcScript).AssertOK()
   344  
   345  			// Create destination directory on host for copy
   346  			destPath := filepath.Join(td, "dest-dir")
   347  			err := os.Mkdir(destPath, 0o700)
   348  			assert.NilError(t, err)
   349  
   350  			catPath := filepath.Join(destPath, filepath.Base(mountedVolFile))
   351  
   352  			// Running container test
   353  			base.Cmd("cp", con+":"+mountedVolFile, destPath).AssertOK()
   354  			assertCat(catPath)
   355  
   356  			// Skip for rootless
   357  			if rootlessutil.IsRootless() {
   358  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
   359  			}
   360  			// Stopped container test
   361  			base.Cmd("stop", con).AssertOK()
   362  			base.Cmd("cp", con+":"+mountedVolFile, destPath).AssertOK()
   363  			assertCat(catPath)
   364  		})
   365  	})
   366  	t.Run("SRC_PATH specifies a directory", func(t *testing.T) {
   367  		srcPath := srcDir
   368  		t.Run("DEST_PATH does not exist", func(t *testing.T) {
   369  			destPath := filepath.Join(td, "dest2-no-exist")
   370  			base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertOK()
   371  			catPath := filepath.Join(destPath, filepath.Base(srcFile))
   372  			assertCat(catPath)
   373  			if rootlessutil.IsRootless() {
   374  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
   375  			}
   376  			base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertOK()
   377  			assertCat(catPath)
   378  		})
   379  		t.Run("DEST_PATH exists and is a file", func(t *testing.T) {
   380  			destPath := filepath.Join(td, "dest2-file-exists")
   381  			err := os.WriteFile(destPath, []byte(""), 0o644)
   382  			assert.NilError(t, err)
   383  			base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertFail()
   384  			if rootlessutil.IsRootless() {
   385  				t.Skip("Test skipped in rootless mode for testStoppedContainer")
   386  			}
   387  			base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertFail()
   388  		})
   389  		t.Run("DEST_PATH exists and is a directory", func(t *testing.T) {
   390  			t.Run("SRC_PATH does not end with `/.`", func(t *testing.T) {
   391  				destPath := filepath.Join(td, "dest2-dir-exists")
   392  				err := os.Mkdir(destPath, 0o755)
   393  				assert.NilError(t, err)
   394  				base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertOK()
   395  				catPath := filepath.Join(destPath, strings.TrimPrefix(srcFile, filepath.Dir(srcDir)+"/"))
   396  				assertCat(catPath)
   397  				if rootlessutil.IsRootless() {
   398  					t.Skip("Test skipped in rootless mode for testStoppedContainer")
   399  				}
   400  				base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertOK()
   401  				assertCat(catPath)
   402  			})
   403  			t.Run("SRC_PATH does end with `/.`", func(t *testing.T) {
   404  				srcPath += "/."
   405  				destPath := filepath.Join(td, "dest2-dir2-exists")
   406  				err := os.Mkdir(destPath, 0o755)
   407  				assert.NilError(t, err)
   408  				base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertOK()
   409  				catPath := filepath.Join(destPath, filepath.Base(srcFile))
   410  				assertCat(catPath)
   411  				if rootlessutil.IsRootless() {
   412  					t.Skip("Test skipped in rootless mode for testStoppedContainer")
   413  				}
   414  				base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertOK()
   415  				assertCat(catPath)
   416  			})
   417  		})
   418  	})
   419  }