@@ -19,6 +19,7 @@ package container
1919import (
2020 "fmt"
2121 "os"
22+ "os/exec"
2223 "path/filepath"
2324 "strings"
2425 "syscall"
@@ -29,6 +30,7 @@ import (
2930
3031 "github.com/containerd/nerdctl/v2/pkg/containerutil"
3132 "github.com/containerd/nerdctl/v2/pkg/rootlessutil"
33+ "github.com/containerd/nerdctl/v2/pkg/tarutil"
3234 "github.com/containerd/nerdctl/v2/pkg/testutil"
3335 "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
3436)
@@ -51,7 +53,9 @@ const (
5153 pathIsADirAbsolute = string (os .PathSeparator ) + "is-a-dir" + complexify
5254 pathIsAVolumeMount = string (os .PathSeparator ) + "is-a-volume-mount" + complexify
5355
54- srcFileName = "test-file" + complexify
56+ srcFileName = "test-file" + complexify
57+ tarballName = "test-tar" + complexify
58+ cpFolderName = "nerdctl-cp-test"
5559
5660 // Since nerdctl cp must NOT obey container wd, but instead resolve paths against the root, we set this
5761 // explicitly to ensure we do the right thing wrt that.
@@ -400,6 +404,49 @@ func TestCopyToContainer(t *testing.T) {
400404 },
401405 },
402406 },
407+ {
408+ description : "Copying to container, SRC_PATH is stdin" ,
409+ sourceSpec : "-" ,
410+ sourceIsAFile : true ,
411+ toContainer : true ,
412+ testCases : []testcases {
413+ {
414+ description : "DEST_PATH is a directory, relative" ,
415+ destinationSpec : pathIsADirRelative ,
416+ catFile : filepath .Join (pathIsADirRelative , srcFileName ),
417+ setup : func (base * testutil.Base , container string , destPath string ) {
418+ base .Cmd ("exec" , container , "mkdir" , "-p" , destPath ).AssertOK ()
419+ },
420+ },
421+ {
422+ description : "DEST_PATH is a directory, absolute" ,
423+ destinationSpec : pathIsADirAbsolute ,
424+ catFile : filepath .Join (pathIsADirAbsolute , srcFileName ),
425+ setup : func (base * testutil.Base , container string , destPath string ) {
426+ base .Cmd ("exec" , container , "mkdir" , "-p" , destPath ).AssertOK ()
427+ },
428+ },
429+ {
430+ description : "DEST_PATH is stdout" ,
431+ destinationSpec : "-" ,
432+ expect : icmd.Expected {
433+ ExitCode : 1 ,
434+ Err : "one of src or dest must be a container file specification" ,
435+ },
436+ },
437+ {
438+ description : "DEST_PATH is a file" ,
439+ destinationSpec : pathIsAFileAbsolute ,
440+ setup : func (base * testutil.Base , container string , destPath string ) {
441+ base .Cmd ("exec" , container , "touch" , destPath ).AssertOK ()
442+ },
443+ expect : icmd.Expected {
444+ ExitCode : 1 ,
445+ Err : containerutil .ErrCannotCopyDirToFile .Error (),
446+ },
447+ },
448+ },
449+ },
403450 }
404451
405452 for _ , tg := range testGroups {
@@ -540,6 +587,19 @@ func TestCopyFromContainer(t *testing.T) {
540587 assert .NilError (t , err )
541588 },
542589 },
590+ {
591+ description : "DEST_PATH is stdout" ,
592+ destinationSpec : "-" ,
593+ // Extra dir to account for folder created from extracted tar file
594+ catFile : filepath .Join (pathIsADirAbsolute , filepath .Base (srcDirName ), srcFileName ),
595+ expect : icmd.Expected {
596+ ExitCode : 0 ,
597+ },
598+ setup : func (base * testutil.Base , container string , destPath string ) {
599+ err := os .MkdirAll (destPath , dirPerm )
600+ assert .NilError (t , err )
601+ },
602+ },
543603 },
544604 },
545605 {
@@ -682,6 +742,19 @@ func TestCopyFromContainer(t *testing.T) {
682742 assert .NilError (t , err )
683743 },
684744 },
745+ {
746+ description : "DEST_PATH is stdout" ,
747+ destinationSpec : "-" ,
748+ catFile : filepath .Join (pathIsADirAbsolute , srcDirName , srcFileName ),
749+ expect : icmd.Expected {
750+ ExitCode : 0 ,
751+ },
752+ setup : func (base * testutil.Base , container string , destPath string ) {
753+ // Don't make the topmost dir as this is where the tarball must extract
754+ err := os .MkdirAll (filepath .Dir (destPath ), dirPerm )
755+ assert .NilError (t , err )
756+ },
757+ },
685758 },
686759 },
687760
@@ -713,6 +786,18 @@ func TestCopyFromContainer(t *testing.T) {
713786 assert .NilError (t , err )
714787 },
715788 },
789+ {
790+ description : "DEST_PATH is stdout" ,
791+ destinationSpec : "-" ,
792+ catFile : filepath .Join (pathIsADirAbsolute , srcFileName ),
793+ expect : icmd.Expected {
794+ ExitCode : 0 ,
795+ },
796+ setup : func (base * testutil.Base , container string , destPath string ) {
797+ err := os .MkdirAll (destPath , dirPerm )
798+ assert .NilError (t , err )
799+ },
800+ },
716801 },
717802 },
718803 }
@@ -749,7 +834,12 @@ func cpTestHelper(t *testing.T, tg *testgroup) {
749834 // Get the source path
750835 groupSourceSpec := tg .sourceSpec
751836 groupSourceDir := groupSourceSpec
752- if tg .sourceIsAFile {
837+ fromStdin := false
838+ if tg .sourceSpec == "-" {
839+ groupSourceSpec = filepath .Join (srcDirName , tarballName )
840+ groupSourceDir = srcDirName
841+ fromStdin = true
842+ } else if tg .sourceIsAFile {
753843 groupSourceDir = filepath .Dir (groupSourceSpec )
754844 }
755845
@@ -794,25 +884,37 @@ func cpTestHelper(t *testing.T, tg *testgroup) {
794884
795885 // Prepare the specs and derived variables
796886 sourceSpec := groupSourceSpec
887+ catFile := testCase .catFile
888+
797889 destinationSpec := testCase .destinationSpec
890+ toStdout := false
891+ // tarball destination just sets up the dir to extract to
892+ if destinationSpec == "-" {
893+ toStdout = true
894+ destinationSpec = filepath .Dir (catFile )
895+ }
798896
799897 // If the test case does not specify a catFile, start with the destination spec
800- catFile := testCase .catFile
801898 if catFile == "" {
802899 catFile = destinationSpec
803900 }
804901
805902 sourceFile := filepath .Join (groupSourceDir , srcFileName )
806903 if copyToContainer {
807- // Use an absolute path for evaluation
808904 if ! filepath .IsAbs (catFile ) {
809905 catFile = filepath .Join (string (os .PathSeparator ), catFile )
810906 }
811- // If the sourceFile is still relative, make it absolute to the temp
812- sourceFile = filepath .Join (tempDir , sourceFile )
813- // If the spec path for source on the host was absolute, make sure we put that under tempDir
814- if filepath .IsAbs (sourceSpec ) {
815- sourceSpec = tempDir + sourceSpec
907+
908+ if fromStdin {
909+ sourceFile = filepath .Join (tempDir , groupSourceDir , tarballName )
910+ } else {
911+ // Use an absolute path for evaluation
912+ // If the sourceFile is still relative, make it absolute to the temp
913+ sourceFile = filepath .Join (tempDir , sourceFile )
914+ // If the spec path for source on the host was absolute, make sure we put that under tempDir
915+ if filepath .IsAbs (sourceSpec ) {
916+ sourceSpec = tempDir + sourceSpec
917+ }
816918 }
817919 } else {
818920 // If we are copying to host, we need to make sure we have an absolute path to cat, relative to temp,
@@ -835,11 +937,29 @@ func cpTestHelper(t *testing.T, tg *testgroup) {
835937 }
836938
837939 createFileOnHost := func () {
838- // Create file on the host
839- err := os .MkdirAll (filepath .Dir (sourceFile ), dirPerm )
840- assert .NilError (t , err )
841- err = os .WriteFile (sourceFile , sourceFileContent , filePerm )
842- assert .NilError (t , err )
940+ switch fromStdin {
941+ case true :
942+ d := filepath .Dir (sourceFile )
943+ tarCpFolder := filepath .Join (d , cpFolderName )
944+ tarBinary , _ , err := tarutil .FindTarBinary ()
945+ assert .NilError (t , err )
946+
947+ err = os .MkdirAll (tarCpFolder , dirPerm )
948+ assert .NilError (t , err )
949+ err = os .WriteFile (filepath .Join (tarCpFolder , srcFileName ), sourceFileContent , filePerm )
950+ assert .NilError (t , err )
951+
952+ err = exec .Command (tarBinary , "-cf" , sourceFile , "-C" , tarCpFolder , "." ).Run ()
953+ assert .NilError (t , err )
954+ err = os .RemoveAll (tarCpFolder )
955+ assert .NilError (t , err )
956+ case false :
957+ // Create file on the host
958+ err := os .MkdirAll (filepath .Dir (sourceFile ), dirPerm )
959+ assert .NilError (t , err )
960+ err = os .WriteFile (sourceFile , sourceFileContent , filePerm )
961+ assert .NilError (t , err )
962+ }
843963 }
844964
845965 // Setup: create volume, containers, create the source file
@@ -906,10 +1026,46 @@ func cpTestHelper(t *testing.T, tg *testgroup) {
9061026 // Build the final src and dest specifiers, including `containerXYZ:`
9071027 container := ""
9081028 if copyToContainer {
1029+ if fromStdin {
1030+ if toStdout {
1031+ nerdctlCmd := base .Cmd ("cp" , "-" , "-" )
1032+ nerdctlCmd .Run ()
1033+ nerdctlCmd .Assert (testCase .expect )
1034+ } else {
1035+ sourceSpec = "-"
1036+ f , err := os .Open (sourceFile )
1037+ assert .NilError (t , err )
1038+ nerdctlCmd := base .Cmd ("cp" , sourceSpec , containerRunning + ":" + destinationSpec )
1039+ nerdctlCmd .Stdin = f
1040+
1041+ nerdctlCmd .Run ()
1042+ nerdctlCmd .Assert (testCase .expect )
1043+ f .Close ()
1044+ }
1045+ } else {
1046+ base .Cmd ("cp" , sourceSpec , containerRunning + ":" + destinationSpec ).Assert (testCase .expect )
1047+ }
9091048 container = containerRunning
910- base .Cmd ("cp" , sourceSpec , containerRunning + ":" + destinationSpec ).Assert (testCase .expect )
9111049 } else {
912- base .Cmd ("cp" , containerRunning + ":" + sourceSpec , destinationSpec ).Assert (testCase .expect )
1050+ nerdctlCmd := base .Cmd ("cp" , containerRunning + ":" + sourceSpec , destinationSpec )
1051+ if toStdout {
1052+ out := nerdctlCmd .Out ()
1053+ nerdctlCmd .Assert (testCase .expect )
1054+
1055+ // Since we can't check tar file directly easily, extract to the same destination
1056+ tarDst := filepath .Dir (catFile )
1057+ tarBinary , _ , err := tarutil .FindTarBinary ()
1058+ assert .NilError (t , err )
1059+
1060+ tarCmd := exec .Command (tarBinary , "-C" , tarDst , "-xf" , "-" )
1061+ tarCmd .Stdin = strings .NewReader (out )
1062+ tarCmd .Stdout = os .Stdout
1063+
1064+ tarCmd .Run ()
1065+ assert .NilError (t , tarCmd .Err )
1066+ } else {
1067+ nerdctlCmd .Assert (testCase .expect )
1068+ }
9131069 }
9141070
9151071 // Run the actual test for the running container
@@ -932,19 +1088,32 @@ func cpTestHelper(t *testing.T, tg *testgroup) {
9321088 // ... and for the stopped container
9331089 container = ""
9341090 var cmd * testutil.Cmd
935- if copyToContainer {
1091+ if fromStdin && toStdout {
1092+ cmd = base .Cmd ("cp" , "-" , "-" )
1093+ } else if copyToContainer {
9361094 container = containerStopped
9371095 cmd = base .Cmd ("cp" , sourceSpec , containerStopped + ":" + destinationSpec )
1096+ if fromStdin {
1097+ f , err := os .Open (sourceFile )
1098+ assert .NilError (t , err )
1099+ defer f .Close ()
1100+ cmd .Stdin = f
1101+ }
9381102 } else {
9391103 cmd = base .Cmd ("cp" , containerStopped + ":" + sourceSpec , destinationSpec )
9401104 }
9411105
9421106 if rootlessutil .IsRootless () && ! nerdtest .IsDocker () {
943- cmd .Assert (
944- icmd.Expected {
945- ExitCode : 1 ,
946- Err : containerutil .ErrRootlessCannotCp .Error (),
947- })
1107+ if fromStdin && toStdout {
1108+ // Regular assert test case should work fine if src and dst are invalid
1109+ cmd .Assert (testCase .expect )
1110+ } else {
1111+ cmd .Assert (
1112+ icmd.Expected {
1113+ ExitCode : 1 ,
1114+ Err : containerutil .ErrRootlessCannotCp .Error (),
1115+ })
1116+ }
9481117 return
9491118 }
9501119
0 commit comments