github.com/moby/docker@v26.1.3+incompatible/integration/container/copy_test.go (about) 1 package container // import "github.com/docker/docker/integration/container" 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "encoding/json" 7 "io" 8 "os" 9 "path/filepath" 10 "testing" 11 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/errdefs" 14 "github.com/docker/docker/integration/internal/container" 15 "github.com/docker/docker/pkg/archive" 16 "github.com/docker/docker/pkg/jsonmessage" 17 "github.com/docker/docker/testutil/fakecontext" 18 "gotest.tools/v3/assert" 19 is "gotest.tools/v3/assert/cmp" 20 "gotest.tools/v3/skip" 21 ) 22 23 func TestCopyFromContainerPathDoesNotExist(t *testing.T) { 24 ctx := setupTest(t) 25 26 apiClient := testEnv.APIClient() 27 cid := container.Create(ctx, t, apiClient) 28 29 _, _, err := apiClient.CopyFromContainer(ctx, cid, "/dne") 30 assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) 31 assert.Check(t, is.ErrorContains(err, "Could not find the file /dne in container "+cid)) 32 } 33 34 func TestCopyFromContainerPathIsNotDir(t *testing.T) { 35 skip.If(t, testEnv.UsingSnapshotter(), "FIXME: https://github.com/moby/moby/issues/47107") 36 ctx := setupTest(t) 37 38 apiClient := testEnv.APIClient() 39 cid := container.Create(ctx, t, apiClient) 40 41 path := "/etc/passwd/" 42 expected := "not a directory" 43 if testEnv.DaemonInfo.OSType == "windows" { 44 path = "c:/windows/system32/drivers/etc/hosts/" 45 expected = "The filename, directory name, or volume label syntax is incorrect." 46 } 47 _, _, err := apiClient.CopyFromContainer(ctx, cid, path) 48 assert.Assert(t, is.ErrorContains(err, expected)) 49 } 50 51 func TestCopyToContainerPathDoesNotExist(t *testing.T) { 52 ctx := setupTest(t) 53 54 apiClient := testEnv.APIClient() 55 cid := container.Create(ctx, t, apiClient) 56 57 err := apiClient.CopyToContainer(ctx, cid, "/dne", nil, types.CopyToContainerOptions{}) 58 assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) 59 assert.Check(t, is.ErrorContains(err, "Could not find the file /dne in container "+cid)) 60 } 61 62 func TestCopyEmptyFile(t *testing.T) { 63 ctx := setupTest(t) 64 65 apiClient := testEnv.APIClient() 66 cid := container.Create(ctx, t, apiClient) 67 68 // empty content 69 dstDir, _ := makeEmptyArchive(t) 70 err := apiClient.CopyToContainer(ctx, cid, dstDir, bytes.NewReader([]byte("")), types.CopyToContainerOptions{}) 71 assert.NilError(t, err) 72 73 // tar with empty file 74 dstDir, preparedArchive := makeEmptyArchive(t) 75 err = apiClient.CopyToContainer(ctx, cid, dstDir, preparedArchive, types.CopyToContainerOptions{}) 76 assert.NilError(t, err) 77 78 // tar with empty file archive mode 79 dstDir, preparedArchive = makeEmptyArchive(t) 80 err = apiClient.CopyToContainer(ctx, cid, dstDir, preparedArchive, types.CopyToContainerOptions{ 81 CopyUIDGID: true, 82 }) 83 assert.NilError(t, err) 84 85 // copy from empty file 86 rdr, _, err := apiClient.CopyFromContainer(ctx, cid, dstDir) 87 assert.NilError(t, err) 88 defer rdr.Close() 89 } 90 91 func makeEmptyArchive(t *testing.T) (string, io.ReadCloser) { 92 tmpDir := t.TempDir() 93 srcPath := filepath.Join(tmpDir, "empty-file.txt") 94 err := os.WriteFile(srcPath, []byte(""), 0o400) 95 assert.NilError(t, err) 96 97 // TODO(thaJeztah) Add utilities to the client to make steps below less complicated. 98 // Code below is taken from copyToContainer() in docker/cli. 99 srcInfo, err := archive.CopyInfoSourcePath(srcPath, false) 100 assert.NilError(t, err) 101 102 srcArchive, err := archive.TarResource(srcInfo) 103 assert.NilError(t, err) 104 t.Cleanup(func() { 105 srcArchive.Close() 106 }) 107 108 ctrPath := "/empty-file.txt" 109 dstInfo := archive.CopyInfo{Path: ctrPath} 110 dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo) 111 assert.NilError(t, err) 112 t.Cleanup(func() { 113 preparedArchive.Close() 114 }) 115 return dstDir, preparedArchive 116 } 117 118 func TestCopyToContainerPathIsNotDir(t *testing.T) { 119 ctx := setupTest(t) 120 121 apiClient := testEnv.APIClient() 122 cid := container.Create(ctx, t, apiClient) 123 124 path := "/etc/passwd/" 125 if testEnv.DaemonInfo.OSType == "windows" { 126 path = "c:/windows/system32/drivers/etc/hosts/" 127 } 128 err := apiClient.CopyToContainer(ctx, cid, path, nil, types.CopyToContainerOptions{}) 129 assert.Check(t, is.ErrorContains(err, "not a directory")) 130 } 131 132 func TestCopyFromContainer(t *testing.T) { 133 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 134 ctx := setupTest(t) 135 136 apiClient := testEnv.APIClient() 137 138 dir, err := os.MkdirTemp("", t.Name()) 139 assert.NilError(t, err) 140 defer os.RemoveAll(dir) 141 142 buildCtx := fakecontext.New(t, dir, fakecontext.WithFile("foo", "hello"), fakecontext.WithFile("baz", "world"), fakecontext.WithDockerfile(` 143 FROM busybox 144 COPY foo /foo 145 COPY baz /bar/quux/baz 146 RUN ln -s notexist /bar/notarget && ln -s quux/baz /bar/filesymlink && ln -s quux /bar/dirsymlink && ln -s / /bar/root 147 CMD /fake 148 `)) 149 defer buildCtx.Close() 150 151 resp, err := apiClient.ImageBuild(ctx, buildCtx.AsTarReader(t), types.ImageBuildOptions{}) 152 assert.NilError(t, err) 153 defer resp.Body.Close() 154 155 var imageID string 156 err = jsonmessage.DisplayJSONMessagesStream(resp.Body, io.Discard, 0, false, func(msg jsonmessage.JSONMessage) { 157 var r types.BuildResult 158 assert.NilError(t, json.Unmarshal(*msg.Aux, &r)) 159 imageID = r.ID 160 }) 161 assert.NilError(t, err) 162 assert.Assert(t, imageID != "") 163 164 cid := container.Create(ctx, t, apiClient, container.WithImage(imageID)) 165 166 for _, x := range []struct { 167 src string 168 expect map[string]string 169 }{ 170 {"/", map[string]string{"/": "", "/foo": "hello", "/bar/quux/baz": "world", "/bar/filesymlink": "", "/bar/dirsymlink": "", "/bar/notarget": ""}}, 171 {".", map[string]string{"./": "", "./foo": "hello", "./bar/quux/baz": "world", "./bar/filesymlink": "", "./bar/dirsymlink": "", "./bar/notarget": ""}}, 172 {"/.", map[string]string{"./": "", "./foo": "hello", "./bar/quux/baz": "world", "./bar/filesymlink": "", "./bar/dirsymlink": "", "./bar/notarget": ""}}, 173 {"./", map[string]string{"./": "", "./foo": "hello", "./bar/quux/baz": "world", "./bar/filesymlink": "", "./bar/dirsymlink": "", "./bar/notarget": ""}}, 174 {"/./", map[string]string{"./": "", "./foo": "hello", "./bar/quux/baz": "world", "./bar/filesymlink": "", "./bar/dirsymlink": "", "./bar/notarget": ""}}, 175 {"/bar/root", map[string]string{"root": ""}}, 176 {"/bar/root/", map[string]string{"root/": "", "root/foo": "hello", "root/bar/quux/baz": "world", "root/bar/filesymlink": "", "root/bar/dirsymlink": "", "root/bar/notarget": ""}}, 177 {"/bar/root/.", map[string]string{"./": "", "./foo": "hello", "./bar/quux/baz": "world", "./bar/filesymlink": "", "./bar/dirsymlink": "", "./bar/notarget": ""}}, 178 179 {"bar/quux", map[string]string{"quux/": "", "quux/baz": "world"}}, 180 {"bar/quux/", map[string]string{"quux/": "", "quux/baz": "world"}}, 181 {"bar/quux/.", map[string]string{"./": "", "./baz": "world"}}, 182 {"bar/quux/baz", map[string]string{"baz": "world"}}, 183 184 {"bar/filesymlink", map[string]string{"filesymlink": ""}}, 185 {"bar/dirsymlink", map[string]string{"dirsymlink": ""}}, 186 {"bar/dirsymlink/", map[string]string{"dirsymlink/": "", "dirsymlink/baz": "world"}}, 187 {"bar/dirsymlink/.", map[string]string{"./": "", "./baz": "world"}}, 188 {"bar/notarget", map[string]string{"notarget": ""}}, 189 } { 190 t.Run(x.src, func(t *testing.T) { 191 rdr, _, err := apiClient.CopyFromContainer(ctx, cid, x.src) 192 assert.NilError(t, err) 193 defer rdr.Close() 194 195 found := make(map[string]bool, len(x.expect)) 196 var numFound int 197 tr := tar.NewReader(rdr) 198 for numFound < len(x.expect) { 199 h, err := tr.Next() 200 if err == io.EOF { 201 break 202 } 203 assert.NilError(t, err) 204 205 expected, exists := x.expect[h.Name] 206 if !exists { 207 // this archive will have extra stuff in it since we are copying from root 208 // and docker adds a bunch of stuff 209 continue 210 } 211 212 numFound++ 213 found[h.Name] = true 214 215 buf, err := io.ReadAll(tr) 216 if err == nil { 217 assert.Check(t, is.Equal(string(buf), expected)) 218 } 219 } 220 221 for f := range x.expect { 222 assert.Check(t, found[f], f+" not found in archive") 223 } 224 }) 225 } 226 }