-
-
Notifications
You must be signed in to change notification settings - Fork 464
/
file.go
141 lines (118 loc) · 3.25 KB
/
file.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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package testcontainers
import (
"archive/tar"
"bytes"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
func isDir(path string) (bool, error) {
file, err := os.Open(path)
if err != nil {
return false, err
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
return false, err
}
if fileInfo.IsDir() {
return true, nil
}
return false, nil
}
// 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{}
Logger.Printf(">> creating TAR file from directory: %s\n", src)
// tar > gzip > buffer
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 {
if errFn != nil {
return fmt.Errorf("error traversing the file system: %w", errFn)
}
// if a symlink, skip file
if fi.Mode().Type() == os.ModeSymlink {
Logger.Printf(">> skipping symlink: %s\n", file)
return nil
}
// generate tar header
header, err := tar.FileInfoHeader(fi, file)
if err != nil {
return fmt.Errorf("error getting file info header: %w", err)
}
// 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
if err := tw.WriteHeader(header); err != nil {
return fmt.Errorf("error writing header: %w", err)
}
// if not a dir, write file content
if !fi.IsDir() {
data, err := os.Open(file)
if err != nil {
return fmt.Errorf("error opening file: %w", err)
}
defer data.Close()
if _, err := io.Copy(tw, data); err != nil {
return fmt.Errorf("error compressing file: %w", err)
}
}
return nil
})
if err != nil {
return buffer, err
}
// produce tar
if err := tw.Close(); err != nil {
return buffer, fmt.Errorf("error closing tar file: %w", err)
}
// produce gzip
if err := zr.Close(); err != nil {
return buffer, fmt.Errorf("error closing gzip file: %w", err)
}
return buffer, nil
}
// tarFile compress a single file using tar + gzip algorithms
func tarFile(basePath string, fileContent func(tw io.Writer) error, fileContentSize int64, fileMode int64) (*bytes.Buffer, error) {
buffer := &bytes.Buffer{}
zr := gzip.NewWriter(buffer)
tw := tar.NewWriter(zr)
hdr := &tar.Header{
Name: basePath,
Mode: fileMode,
Size: fileContentSize,
}
if err := tw.WriteHeader(hdr); err != nil {
return buffer, err
}
if err := fileContent(tw); err != nil {
return buffer, err
}
// produce tar
if err := tw.Close(); err != nil {
return buffer, fmt.Errorf("error closing tar file: %w", err)
}
// produce gzip
if err := zr.Close(); err != nil {
return buffer, fmt.Errorf("error closing gzip file: %w", err)
}
return buffer, nil
}