Skip to content

Commit

Permalink
feat: optimizes file copies to and from containers (#2450)
Browse files Browse the repository at this point in the history
* feat: optimizes file copies to and from containers

Signed-off-by: Adrian Cole <[email protected]>

* drift

Signed-off-by: Adrian Cole <[email protected]>

* go 1.21 defense

Signed-off-by: Adrian Cole <[email protected]>

---------

Signed-off-by: Adrian Cole <[email protected]>
  • Loading branch information
codefromthecrypt authored Apr 3, 2024
1 parent 88622f0 commit 697c264
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 10 deletions.
36 changes: 30 additions & 6 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"net/url"
"os"
"path/filepath"
Expand Down Expand Up @@ -602,19 +603,41 @@ func (c *DockerContainer) CopyFileToContainer(ctx context.Context, hostFilePath
return c.CopyDirToContainer(ctx, hostFilePath, containerFilePath, fileMode)
}

fileContent, err := os.ReadFile(hostFilePath)
f, err := os.Open(hostFilePath)
if err != nil {
return err
}
return c.CopyToContainer(ctx, fileContent, containerFilePath, fileMode)
defer f.Close()

info, err := f.Stat()
if err != nil {
return err
}

// In Go 1.22 os.File is always an io.WriterTo. However, testcontainers
// currently allows Go 1.21, so we need to trick the compiler a little.
var file fs.File = f
return c.copyToContainer(ctx, func(tw io.Writer) error {
// Attempt optimized writeTo, implemented in linux
if wt, ok := file.(io.WriterTo); ok {
_, err := wt.WriteTo(tw)
return err
}
_, err := io.Copy(tw, f)
return err
}, info.Size(), containerFilePath, fileMode)
}

// CopyToContainer copies fileContent data to a file in container
func (c *DockerContainer) CopyToContainer(ctx context.Context, fileContent []byte, containerFilePath string, fileMode int64) error {
buffer, err := tarFile(fileContent, containerFilePath, fileMode)
if err != nil {
return c.copyToContainer(ctx, func(tw io.Writer) error {
_, err := tw.Write(fileContent)
return err
}
}, int64(len(fileContent)), containerFilePath, fileMode)
}

func (c *DockerContainer) copyToContainer(ctx context.Context, fileContent func(tw io.Writer) error, fileContentSize int64, containerFilePath string, fileMode int64) error {
buffer, err := tarFile(containerFilePath, fileContent, fileContentSize, fileMode)

err = c.provider.client.CopyToContainer(ctx, c.ID, "/", buffer, types.CopyToContainerOptions{})
if err != nil {
Expand Down Expand Up @@ -1574,7 +1597,8 @@ func (p *DockerProvider) SaveImages(ctx context.Context, output string, images .
_ = imageReader.Close()
}()

_, err = io.Copy(outputFile, imageReader)
// Attempt optimized readFrom, implemented in linux
_, err = outputFile.ReadFrom(imageReader)
if err != nil {
return fmt.Errorf("writing images to output %w", err)
}
Expand Down
6 changes: 3 additions & 3 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func tarDir(src string, fileMode int64) (*bytes.Buffer, error) {
}

// tarFile compress a single file using tar + gzip algorithms
func tarFile(fileContent []byte, basePath string, fileMode int64) (*bytes.Buffer, error) {
func tarFile(basePath string, fileContent func(tw io.Writer) error, fileContentSize int64, fileMode int64) (*bytes.Buffer, error) {
buffer := &bytes.Buffer{}

zr := gzip.NewWriter(buffer)
Expand All @@ -119,12 +119,12 @@ func tarFile(fileContent []byte, basePath string, fileMode int64) (*bytes.Buffer
hdr := &tar.Header{
Name: basePath,
Mode: fileMode,
Size: int64(len(fileContent)),
Size: fileContentSize,
}
if err := tw.WriteHeader(hdr); err != nil {
return buffer, err
}
if _, err := tw.Write(fileContent); err != nil {
if err := fileContent(tw); err != nil {
return buffer, err
}

Expand Down
5 changes: 4 additions & 1 deletion file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@ func Test_TarFile(t *testing.T) {
t.Fatal(err)
}

buff, err := tarFile(b, "Docker.file", 0o755)
buff, err := tarFile("Docker.file", func(tw io.Writer) error {
_, err := tw.Write(b)
return err
}, int64(len(b)), 0o755)
if err != nil {
t.Fatal(err)
}
Expand Down

0 comments on commit 697c264

Please sign in to comment.