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