-
Notifications
You must be signed in to change notification settings - Fork 109
/
archive.go
124 lines (111 loc) · 3.19 KB
/
archive.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
package cli
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"io/fs"
"math"
"os"
"path/filepath"
"strings"
"go.viam.com/utils"
"golang.org/x/exp/maps"
)
// getArchiveFilePaths traverses the provided rootpaths recursively,
// collecting the file paths of all regular files and symlinks.
// This list of paths should be passed to createArchive.
func getArchiveFilePaths(rootpaths []string) ([]string, error) {
files := map[string]bool{}
for _, pathRoot := range rootpaths {
err := filepath.WalkDir(filepath.Clean(pathRoot), func(path string, info fs.DirEntry, err error) error {
if err != nil {
return err
}
// If the file is regular (no mode type set) or is a symlink, add it to the files
// The only files we are excluding are special files:
// ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular
if info.Type()&fs.ModeType&^fs.ModeSymlink == 0 {
files[path] = true
}
return nil
})
if err != nil {
return nil, err
}
}
return maps.Keys(files), nil
}
// createArchive compresses and archives the provided file paths into a tar.gz format,
// writing the resulting binary data to the supplied "buf" writer.
// If "stdout" is provided, the function outputs compression progress information.
func createArchive(files []string, buf, stdout io.Writer) error {
// Create new Writers for gzip and tar
// These writers are chained. Writing to the tar writer will
// write to the gzip writer which in turn will write to
// the "buf" writer
gw := gzip.NewWriter(buf)
//nolint:errcheck
defer gw.Close()
tw := tar.NewWriter(gw)
//nolint:errcheck
defer tw.Close()
// Close the line with the progress reading
defer func() {
if stdout != nil {
printf(stdout, "")
}
}()
if stdout != nil {
fmt.Fprintf(stdout, "\rCompressing... %d%% (%d/%d files)", 0, 1, len(files)) // no newline
}
// Iterate over files and add them to the tar archive
for i, file := range files {
err := addToArchive(tw, file)
if err != nil {
return err
}
if stdout != nil {
compressPercent := int(math.Ceil(100 * float64(i+1) / float64(len(files))))
fmt.Fprintf(stdout, "\rCompressing... %d%% (%d/%d files)", compressPercent, i+1, len(files)) // no newline
}
}
return nil
}
func addToArchive(tw *tar.Writer, filename string) error {
// Open the file which will be written into the archive
//nolint:gosec
file, err := os.Open(filename)
if err != nil {
return err
}
defer utils.UncheckedErrorFunc(file.Close)
info, err := file.Stat()
if err != nil {
return err
}
// Create a tar Header from the FileInfo data
header, err := tar.FileInfoHeader(info, info.Name())
if err != nil {
return err
}
// See 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 = filename
err = tw.WriteHeader(header)
if err != nil {
return err
}
// Copy file content to tar archive
_, err = io.Copy(tw, file)
if err != nil {
return err
}
return nil
}
func isTarball(path string) bool {
return strings.HasSuffix(strings.ToLower(path), ".tar.gz") ||
strings.HasSuffix(strings.ToLower(path), ".tgz")
}