/
fileretriever.go
100 lines (83 loc) · 2.45 KB
/
fileretriever.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package dockerutil
import (
"archive/tar"
"context"
"fmt"
"io"
"path"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"go.uber.org/zap"
)
// FileRetriever allows retrieving a single file from a Docker volume.
// In the future it may allow retrieving an entire directory.
type FileRetriever struct {
log *zap.Logger
cli *client.Client
testName string
}
// NewFileRetriever returns a new FileRetriever.
func NewFileRetriever(log *zap.Logger, cli *client.Client, testName string) *FileRetriever {
return &FileRetriever{log: log, cli: cli, testName: testName}
}
// SingleFileContent returns the content of the file named at relPath,
// inside the volume specified by volumeName.
func (r *FileRetriever) SingleFileContent(ctx context.Context, volumeName, relPath string) ([]byte, error) {
const mountPath = "/mnt/dockervolume"
if err := ensureBusybox(ctx, r.cli); err != nil {
return nil, err
}
containerName := fmt.Sprintf("ibctest-getfile-%d-%s", time.Now().UnixNano(), RandLowerCaseLetterString(5))
cc, err := r.cli.ContainerCreate(
ctx,
&container.Config{
Image: busyboxRef,
// Use root user to avoid permission issues when reading files from the volume.
User: GetRootUserString(),
Labels: map[string]string{CleanupLabel: r.testName},
},
&container.HostConfig{
Binds: []string{volumeName + ":" + mountPath},
AutoRemove: true,
},
nil, // No networking necessary.
nil,
containerName,
)
if err != nil {
return nil, fmt.Errorf("creating container: %w", err)
}
defer func() {
if err := r.cli.ContainerRemove(ctx, cc.ID, types.ContainerRemoveOptions{
Force: true,
}); err != nil {
r.log.Warn("Failed to remove file content container", zap.String("container_id", cc.ID), zap.Error(err))
}
}()
rc, _, err := r.cli.CopyFromContainer(ctx, cc.ID, path.Join(mountPath, relPath))
if err != nil {
return nil, fmt.Errorf("copying from container: %w", err)
}
defer func() {
_ = rc.Close()
}()
wantPath := path.Base(relPath)
tr := tar.NewReader(rc)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("reading tar from container: %w", err)
}
if hdr.Name != wantPath {
r.log.Debug("Unexpected path", zap.String("want", relPath), zap.String("got", hdr.Name))
continue
}
return io.ReadAll(tr)
}
return nil, fmt.Errorf("path %q not found in tar from container", relPath)
}