Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: always absolute paths when tarring files to a Docker container #814

Merged
merged 20 commits into from
Feb 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5c82fdb
chore: extract to variable to avoid double calculation
mdelapenya Feb 10, 2023
adb8b74
chore: add tests including absolute paths
mdelapenya Feb 10, 2023
b6fd5e2
chore: merge tests using a test table
mdelapenya Feb 10, 2023
1f2ff58
fix: keep parent directory as the root of the TAR file
mdelapenya Feb 10, 2023
c6262a5
docs: document the change
mdelapenya Feb 10, 2023
fca2d7b
chore: make the path OS-agnostic
mdelapenya Feb 10, 2023
9029449
chore: simplify
mdelapenya Feb 10, 2023
032a827
fix: use filepath to be OS-independent
mdelapenya Feb 10, 2023
6c4817f
chore(deps): bump github.com/jackc/pgx/v4 in /examples/cockroachdb (#…
dependabot[bot] Feb 13, 2023
b0bede5
chore(deps): bump golang.org/x/sys from 0.4.0 to 0.5.0 (#816)
dependabot[bot] Feb 13, 2023
8686f05
chore(deps): bump golang.org/x/text from 0.6.0 to 0.7.0 (#818)
dependabot[bot] Feb 14, 2023
6456914
chore(deps): bump github.com/containerd/containerd from 1.6.16 to 1.6…
dependabot[bot] Feb 14, 2023
7cc6efb
chore(deps): bump google.golang.org/grpc from 1.52.3 to 1.53.0 in /ex…
mdelapenya Feb 14, 2023
998f6b9
feat: add localstack module (#800)
mdelapenya Feb 14, 2023
7f085c7
chore(deps): bump go.mongodb.org/mongo-driver in /examples/mongodb (#…
dependabot[bot] Feb 14, 2023
002ed2b
chore(deps): bump google.golang.org/api from 0.109.0 to 0.110.0 in /e…
mdelapenya Feb 14, 2023
eef3fa9
feat: support generating Go modules or example modules (#826)
mdelapenya Feb 14, 2023
8466f5c
fix: always pass the absolute path to the tar code
mdelapenya Feb 15, 2023
bdb96cb
Merge branch 'main' into full-path-copy-to-container
mdelapenya Feb 15, 2023
9d53673
Merge branch 'main' into full-path-copy-to-container
mdelapenya Feb 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"path/filepath"
"time"

"github.com/docker/docker/api/types"
Expand Down Expand Up @@ -234,6 +235,13 @@ func (c *ContainerRequest) GetContext() (io.Reader, error) {
return c.ContextArchive, nil
}

// always pass context as absolute path
abs, err := filepath.Abs(c.Context)
if err != nil {
return nil, fmt.Errorf("error getting absolute path: %w", err)
}
c.Context = abs

buildContext, err := archive.TarWithOptions(c.Context, &archive.TarOptions{})
if err != nil {
return nil, err
Expand Down
27 changes: 21 additions & 6 deletions docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2022,18 +2022,20 @@ func TestDockerContainerCopyDirToContainer(t *testing.T) {
Started: true,
})

p := filepath.Join(".", "testresources", "Dokerfile")
require.NoError(t, err)
terminateContainerOnEnd(t, ctx, nginxC)

err = nginxC.CopyDirToContainer(ctx, "./testresources/Dockerfile", "/tmp/testresources/Dockerfile", 700)
err = nginxC.CopyDirToContainer(ctx, p, "/tmp/testresources/Dockerfile", 700)
require.Error(t, err) // copying a file using the directory method will raise an error

err = nginxC.CopyDirToContainer(ctx, "./testresources", "/tmp/testresources", 700)
p = filepath.Join(".", "testresources")
err = nginxC.CopyDirToContainer(ctx, p, "/tmp/testresources", 700)
if err != nil {
t.Fatal(err)
}

assertExtractedFiles(t, ctx, nginxC, "./testresources", "/tmp/testresources/")
assertExtractedFiles(t, ctx, nginxC, p, "/tmp/testresources/")
}

func TestDockerCreateContainerWithFiles(t *testing.T) {
Expand Down Expand Up @@ -2109,15 +2111,27 @@ func TestDockerCreateContainerWithDirs(t *testing.T) {
ctx := context.Background()
hostDirName := "testresources"

abs, err := filepath.Abs(filepath.Join(".", hostDirName))
assert.Nil(t, err)

tests := []struct {
name string
dir ContainerFile
hasError bool
}{
{
name: "success copy directory with full path",
dir: ContainerFile{
HostFilePath: abs,
ContainerFilePath: "/tmp/" + hostDirName, // the parent dir must exist
FileMode: 700,
},
hasError: false,
},
{
name: "success copy directory",
dir: ContainerFile{
HostFilePath: "./" + hostDirName,
HostFilePath: filepath.Join("./", hostDirName),
ContainerFilePath: "/tmp/" + hostDirName, // the parent dir must exist
FileMode: 700,
},
Expand Down Expand Up @@ -2561,10 +2575,11 @@ func assertExtractedFiles(t *testing.T, ctx context.Context, container Container
require.NoError(t, err)
}

fp := filepath.Join(containerFilePath, srcFile.Name())
// copy file by file, as there is a limitation in the Docker client to copy an entiry directory from the container
// paths for the container files are using Linux path separators
fd, err := container.CopyFileFromContainer(ctx, containerFilePath+"/"+srcFile.Name())
require.NoError(t, err, "Path not found in container: %s", containerFilePath+"/"+srcFile.Name())
fd, err := container.CopyFileFromContainer(ctx, fp)
require.NoError(t, err, "Path not found in container: %s", fp)
defer fd.Close()

targetPath := filepath.Join(tmpDir, srcFile.Name())
Expand Down
21 changes: 17 additions & 4 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io"
"os"
"path/filepath"
"strings"
)

func isDir(path string) (bool, error) {
Expand All @@ -31,6 +32,13 @@ func isDir(path string) (bool, error) {

// tarDir compress a directory using tar + gzip algorithms
func tarDir(src string, fileMode int64) (*bytes.Buffer, error) {
// always pass src as absolute path
abs, err := filepath.Abs(src)
if err != nil {
return &bytes.Buffer{}, fmt.Errorf("error getting absolute path: %w", err)
}
src = abs

buffer := &bytes.Buffer{}

fmt.Printf(">> creating TAR file from directory: %s\n", src)
Expand All @@ -39,8 +47,12 @@ func tarDir(src string, fileMode int64) (*bytes.Buffer, error) {
zr := gzip.NewWriter(buffer)
tw := tar.NewWriter(zr)

_, baseDir := filepath.Split(src)
// keep the path relative to the parent directory
index := strings.LastIndex(src, baseDir)

// walk through every file in the folder
err := filepath.Walk(src, func(file string, fi os.FileInfo, errFn error) error {
err = filepath.Walk(src, func(file string, fi os.FileInfo, errFn error) error {
if errFn != nil {
return fmt.Errorf("error traversing the file system: %w", errFn)
}
Expand All @@ -57,9 +69,10 @@ func tarDir(src string, fileMode int64) (*bytes.Buffer, error) {
return fmt.Errorf("error getting file info header: %w", err)
}

// must provide real name
// (see https://golang.org/src/archive/tar/common.go?#L626)
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
header.Name = filepath.ToSlash(file)
// see https://pkg.go.dev/archive/tar#FileInfoHeader:
// Since fs.FileInfo's Name method only returns the base name of the file it describes,
// it may be necessary to modify Header.Name to provide the full path name of the file.
header.Name = filepath.ToSlash(file[index:])
header.Mode = fileMode

// write header
Expand Down
77 changes: 50 additions & 27 deletions file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_IsDir(t *testing.T) {
Expand Down Expand Up @@ -53,38 +54,60 @@ func Test_IsDir(t *testing.T) {
}

func Test_TarDir(t *testing.T) {
src := filepath.Join(".", "testresources")

buff, err := tarDir(src, 0755)
if err != nil {
t.Fatal(err)
originalSrc := filepath.Join(".", "testresources")
tests := []struct {
abs bool
}{
{
abs: false,
},
{
abs: true,
},
}

tmpDir := filepath.Join(t.TempDir(), "subfolder")
err = untar(tmpDir, bytes.NewReader(buff.Bytes()))
if err != nil {
t.Fatal(err)
}
for _, test := range tests {
t.Run(fmt.Sprintf("TarDir with abs=%t", test.abs), func(t *testing.T) {
src := originalSrc
if test.abs {
absSrc, err := filepath.Abs(src)
require.Nil(t, err)

srcFiles, err := os.ReadDir(src)
if err != nil {
log.Fatal(err)
}
src = absSrc
}

for _, srcFile := range srcFiles {
if srcFile.IsDir() {
continue
}
srcBytes, err := os.ReadFile(filepath.Join(src, srcFile.Name()))
if err != nil {
t.Fatal(err)
}
buff, err := tarDir(src, 0755)
if err != nil {
t.Fatal(err)
}

untarBytes, err := os.ReadFile(filepath.Join(tmpDir, "testresources", srcFile.Name()))
if err != nil {
t.Fatal(err)
}
assert.Equal(t, srcBytes, untarBytes)
tmpDir := filepath.Join(t.TempDir(), "subfolder")
err = untar(tmpDir, bytes.NewReader(buff.Bytes()))
if err != nil {
t.Fatal(err)
}

srcFiles, err := os.ReadDir(src)
if err != nil {
log.Fatal(err)
}

for _, srcFile := range srcFiles {
if srcFile.IsDir() {
continue
}
srcBytes, err := os.ReadFile(filepath.Join(src, srcFile.Name()))
if err != nil {
t.Fatal(err)
}

untarBytes, err := os.ReadFile(filepath.Join(tmpDir, "testresources", srcFile.Name()))
if err != nil {
t.Fatal(err)
}
assert.Equal(t, srcBytes, untarBytes)
}
})
}
}

Expand Down