diff --git a/.gitignore b/.gitignore index 53bde68083..053636ef51 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ site/ .direnv/ src/mkdocs-codeinclude-plugin src/pip-delete-this-directory.txt +.idea/ diff --git a/container.go b/container.go index 044dbfa3ee..7c945ed94f 100644 --- a/container.go +++ b/container.go @@ -49,6 +49,7 @@ type Container interface { NetworkAliases(context.Context) (map[string][]string, error) // get container network aliases for a network Exec(ctx context.Context, cmd []string) (int, error) ContainerIP(context.Context) (string, error) // get container ip + CopyFileToContainer(ctx context.Context, hostFilePath string, containerFilePath string, fileMode int64) error } // ImageBuildInfo defines what is needed to build an image diff --git a/docker.go b/docker.go index ba8fd741cd..f537b50cd8 100644 --- a/docker.go +++ b/docker.go @@ -1,6 +1,7 @@ package testcontainers import ( + "archive/tar" "bytes" "context" "encoding/binary" @@ -11,6 +12,7 @@ import ( "net/url" "os" "os/exec" + "path/filepath" "strings" "time" @@ -306,6 +308,32 @@ func (c *DockerContainer) Exec(ctx context.Context, cmd []string) (int, error) { return exitCode, nil } +func (c *DockerContainer) CopyFileToContainer(ctx context.Context, hostFilePath string, containerFilePath string, fileMode int64) error { + fileContent, err := ioutil.ReadFile(hostFilePath) + if err != nil { + return err + } + + buffer := &bytes.Buffer{} + + tw := tar.NewWriter(buffer) + defer tw.Close() + + hdr := &tar.Header{ + Name: filepath.Base(containerFilePath), + Mode: fileMode, + Size: int64(len(fileContent)), + } + if err := tw.WriteHeader(hdr); err != nil { + return err + } + if _, err := tw.Write(fileContent); err != nil { + return err + } + + return c.provider.client.CopyToContainer(ctx, c.ID, filepath.Dir(containerFilePath), buffer, types.CopyToContainerOptions{}) +} + // StartLogProducer will start a concurrent process that will continuously read logs // from the container and will send them to each added LogConsumer func (c *DockerContainer) StartLogProducer(ctx context.Context) error { diff --git a/docker_test.go b/docker_test.go index 5a6c27db8d..ccfc7c471a 100644 --- a/docker_test.go +++ b/docker_test.go @@ -1267,3 +1267,27 @@ func TestContainerNonExistentImage(t *testing.T) { }) } + +func TestDockerContainerCopyFileToContainer(t *testing.T) { + ctx := context.Background() + + nginxC, err := GenericContainer(ctx, GenericContainerRequest{ + ContainerRequest: ContainerRequest{ + Image: "nginx:1.17.6", + ExposedPorts: []string{"80/tcp"}, + WaitingFor: wait.ForListeningPort("80/tcp"), + }, + Started: true, + }) + defer nginxC.Terminate(ctx) + + copiedFileName := "hello_copy.sh" + nginxC.CopyFileToContainer(ctx, "./testresources/hello.sh", "/"+copiedFileName, 700) + c, err := nginxC.Exec(ctx, []string{"bash", copiedFileName}) + if err != nil { + t.Fatal(err) + } + if c != 0 { + t.Fatalf("File %s should exist, expected return code 0, got %v", copiedFileName, c) + } +} diff --git a/docs/features/copy_file.md b/docs/features/copy_file.md new file mode 100644 index 0000000000..b275a84d26 --- /dev/null +++ b/docs/features/copy_file.md @@ -0,0 +1,19 @@ +# Copy Files To Container + +If you would like to copy a file to a container, you can do it using the `CopyFileToContainer` method... + +```go +ctx := context.Background() + +nginxC, err := GenericContainer(ctx, GenericContainerRequest{ + ContainerRequest: ContainerRequest{ + Image: "nginx:1.17.6", + ExposedPorts: []string{"80/tcp"}, + WaitingFor: wait.ForListeningPort("80/tcp"), + }, + Started: true, + }) + +nginxC.CopyFileToContainer(ctx, "./testresources/hello.sh", "/hello_copy.sh", fileContent, 700) +``` +