-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy patharchive.go
158 lines (127 loc) · 3.29 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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package k6deps
import (
"archive/tar"
"encoding/json"
"errors"
"io"
"os"
"path/filepath"
"strings"
"github.com/grafana/k6pack"
)
//nolint:forbidigo
func loadMetadata(dir string, opts *Options) error {
var meta archiveMetadata
data, err := os.ReadFile(filepath.Join(filepath.Clean(dir), "metadata.json"))
if err != nil {
return err
}
if err = json.Unmarshal(data, &meta); err != nil {
return err
}
opts.Manifest.Ignore = true // no manifest (yet) in archive
opts.Script.Name = filepath.Join(
dir,
"file",
filepath.FromSlash(strings.TrimPrefix(meta.Filename, "file:///")),
)
if value, found := meta.Env[EnvDependencies]; found {
opts.Env.Name = EnvDependencies
opts.Env.Contents = []byte(value)
} else {
opts.Env.Ignore = true
}
contents, err := os.ReadFile(opts.Script.Name)
if err != nil {
return err
}
script, _, err := k6pack.Pack(string(contents), &k6pack.Options{Filename: opts.Script.Name})
if err != nil {
return err
}
opts.Script.Contents = script
return nil
}
type archiveMetadata struct {
Filename string `json:"filename"`
Env map[string]string `json:"env"`
}
const maxFileSize = 1024 * 1024 * 10 // 10M
//nolint:forbidigo
func extractArchive(dir string, input io.Reader) error {
reader := tar.NewReader(input)
for {
header, err := reader.Next()
switch {
case err == io.EOF:
return nil
case err != nil:
return err
case header == nil:
continue
}
target := filepath.Join(dir, filepath.Clean(filepath.FromSlash(header.Name)))
switch header.Typeflag {
case tar.TypeDir:
if err := os.MkdirAll(target, 0o750); err != nil {
return err
}
case tar.TypeReg:
if shouldSkip(target) {
continue
}
file, err := os.OpenFile(filepath.Clean(target), os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) //nolint:gosec
if err != nil {
return err
}
if _, err := io.CopyN(file, reader, maxFileSize); err != nil && !errors.Is(err, io.EOF) {
return err
}
if err = file.Close(); err != nil {
return err
}
// if it is a link or symlink, we copy the content of the linked file to the target
// we assume the linked file was already processed and exists in the directory.
case tar.TypeLink, tar.TypeSymlink:
if shouldSkip(target) {
continue
}
linkedFile := filepath.Join(dir, filepath.Clean(filepath.FromSlash(header.Linkname)))
if err := followLink(linkedFile, target); err != nil {
return err
}
}
}
}
// indicates if the file should be skipped during extraction
// we skip csv files and .json except metadata.json
func shouldSkip(target string) bool {
ext := filepath.Ext(target)
return ext == ".csv" || (ext == ".json" && filepath.Base(target) != "metadata.json")
}
//nolint:forbidigo
func followLink(linkedFile string, target string) error {
source, err := os.Open(filepath.Clean(linkedFile))
if err != nil {
return err
}
defer source.Close() //nolint:errcheck
// we need to get the lined file info to create the target file with the same permissions
info, err := source.Stat()
if err != nil {
return err
}
file, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY, info.Mode()) //nolint:gosec
if err != nil {
return err
}
_, err = io.Copy(file, source)
if err != nil {
return err
}
err = file.Close()
if err != nil {
return err
}
return nil
}