Skip to content

Commit 2050984

Browse files
authoredMar 12, 2025··
Merge pull request #75 from grafana/process-archive-in-memory
process archive in mememory
2 parents 99bd2a0 + 13ecacc commit 2050984

File tree

5 files changed

+80
-184
lines changed

5 files changed

+80
-184
lines changed
 

‎analyze.go

+30-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
package k6deps
22

3-
import "encoding/json"
3+
import (
4+
"encoding/json"
5+
"os"
6+
)
47

58
// Analyze searches, loads and analyzes the specified sources,
69
// extracting the k6 extensions and their version constraints.
710
// Note: if archive is specified, the other three sources will not be taken into account,
811
// since the archive may contain them.
912
func Analyze(opts *Options) (Dependencies, error) {
13+
if !opts.Archive.IsEmpty() {
14+
return archiveAnalizer(opts.Archive)()
15+
}
16+
1017
if err := loadSources(opts); err != nil {
1118
return nil, err
1219
}
@@ -86,6 +93,28 @@ func envAnalyzer(src Source) analyzer {
8693
}
8794
}
8895

96+
func archiveAnalizer(src Source) analyzer {
97+
return func() (Dependencies, error) {
98+
input := src.Reader
99+
if input == nil {
100+
tar, err := os.Open(src.Name) //nolint:forbidigo
101+
if err != nil {
102+
return nil, err
103+
}
104+
defer tar.Close() //nolint:errcheck
105+
106+
input = tar
107+
}
108+
109+
analyzer, err := processArchive(input)
110+
if err != nil {
111+
return nil, err
112+
}
113+
114+
return analyzer()
115+
}
116+
}
117+
89118
func mergeAnalyzers(sources ...analyzer) analyzer {
90119
return func() (Dependencies, error) {
91120
deps := make(Dependencies)

‎archive.go

+44-113
Original file line numberDiff line numberDiff line change
@@ -2,157 +2,88 @@ package k6deps
22

33
import (
44
"archive/tar"
5+
"bytes"
56
"encoding/json"
67
"errors"
78
"io"
8-
"os"
99
"path/filepath"
10-
"strings"
11-
12-
"github.com/grafana/k6pack"
10+
"slices"
1311
)
1412

15-
//nolint:forbidigo
16-
func loadMetadata(dir string, opts *Options) error {
17-
var meta archiveMetadata
18-
19-
data, err := os.ReadFile(filepath.Join(filepath.Clean(dir), "metadata.json"))
20-
if err != nil {
21-
return err
22-
}
23-
24-
if err = json.Unmarshal(data, &meta); err != nil {
25-
return err
26-
}
27-
28-
opts.Manifest.Ignore = true // no manifest (yet) in archive
29-
30-
opts.Script.Name = filepath.Join(
31-
dir,
32-
"file",
33-
filepath.FromSlash(strings.TrimPrefix(meta.Filename, "file:///")),
34-
)
35-
36-
if value, found := meta.Env[EnvDependencies]; found {
37-
opts.Env.Name = EnvDependencies
38-
opts.Env.Contents = []byte(value)
39-
} else {
40-
opts.Env.Ignore = true
41-
}
42-
43-
contents, err := os.ReadFile(opts.Script.Name)
44-
if err != nil {
45-
return err
46-
}
47-
48-
script, _, err := k6pack.Pack(string(contents), &k6pack.Options{Filename: opts.Script.Name})
49-
if err != nil {
50-
return err
51-
}
52-
53-
opts.Script.Contents = script
54-
55-
return nil
56-
}
57-
5813
type archiveMetadata struct {
5914
Filename string `json:"filename"`
6015
Env map[string]string `json:"env"`
6116
}
6217

6318
const maxFileSize = 1024 * 1024 * 10 // 10M
6419

65-
//nolint:forbidigo
66-
func extractArchive(dir string, input io.Reader) error {
20+
func processArchive(input io.Reader) (analyzer, error) {
6721
reader := tar.NewReader(input)
6822

23+
analyzers := make([]analyzer, 0)
24+
6925
for {
7026
header, err := reader.Next()
7127

7228
switch {
73-
case err == io.EOF:
74-
return nil
29+
case errors.Is(err, io.EOF):
30+
return mergeAnalyzers(analyzers...), nil
7531
case err != nil:
76-
return err
32+
return nil, err
7733
case header == nil:
7834
continue
7935
}
8036

81-
target := filepath.Join(dir, filepath.Clean(filepath.FromSlash(header.Name)))
82-
83-
switch header.Typeflag {
84-
case tar.TypeDir:
85-
if err := os.MkdirAll(target, 0o750); err != nil {
86-
return err
87-
}
37+
if header.Typeflag != tar.TypeReg || !shouldProcess(header.Name) {
38+
continue
39+
}
8840

89-
case tar.TypeReg:
90-
if shouldSkip(target) {
91-
continue
92-
}
41+
content := &bytes.Buffer{}
42+
if _, err := io.CopyN(content, reader, maxFileSize); err != nil && !errors.Is(err, io.EOF) {
43+
return nil, err
44+
}
9345

94-
file, err := os.OpenFile(filepath.Clean(target), os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) //nolint:gosec
46+
// if the file is metadata.json, we extract the dependencies from the env
47+
if header.Name == "metadata.json" {
48+
analyzer, err := analizeMetadata(content.Bytes())
9549
if err != nil {
96-
return err
97-
}
98-
99-
if _, err := io.CopyN(file, reader, maxFileSize); err != nil && !errors.Is(err, io.EOF) {
100-
return err
101-
}
102-
103-
if err = file.Close(); err != nil {
104-
return err
105-
}
106-
107-
// if it is a link or symlink, we copy the content of the linked file to the target
108-
// we assume the linked file was already processed and exists in the directory.
109-
case tar.TypeLink, tar.TypeSymlink:
110-
if shouldSkip(target) {
111-
continue
50+
return nil, err
11251
}
52+
analyzers = append(analyzers, analyzer)
53+
continue
54+
}
11355

114-
linkedFile := filepath.Join(dir, filepath.Clean(filepath.FromSlash(header.Linkname)))
115-
if err := followLink(linkedFile, target); err != nil {
116-
return err
117-
}
56+
// analize the file content as an script
57+
target := filepath.Clean(filepath.FromSlash(header.Name))
58+
src := Source{
59+
Name: target,
60+
Contents: content.Bytes(),
11861
}
62+
63+
analyzers = append(analyzers, scriptAnalyzer(src))
11964
}
12065
}
12166

122-
// indicates if the file should be skipped during extraction
123-
// we skip csv files and .json except metadata.json
124-
func shouldSkip(target string) bool {
67+
// indicates if the file should be processed during extraction
68+
func shouldProcess(target string) bool {
12569
ext := filepath.Ext(target)
126-
return ext == ".csv" || (ext == ".json" && filepath.Base(target) != "metadata.json")
70+
return slices.Contains([]string{".js", ".ts"}, ext) || slices.Contains([]string{"metadata.json", "data"}, target)
12771
}
12872

129-
//nolint:forbidigo
130-
func followLink(linkedFile string, target string) error {
131-
source, err := os.Open(filepath.Clean(linkedFile))
132-
if err != nil {
133-
return err
134-
}
135-
defer source.Close() //nolint:errcheck
136-
137-
// we need to get the lined file info to create the target file with the same permissions
138-
info, err := source.Stat()
139-
if err != nil {
140-
return err
141-
}
142-
143-
file, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY, info.Mode()) //nolint:gosec
144-
if err != nil {
145-
return err
73+
// analizeMetadata extracts the dependencies from the metadata.json file
74+
func analizeMetadata(content []byte) (analyzer, error) {
75+
metadata := archiveMetadata{}
76+
if err := json.Unmarshal(content, &metadata); err != nil {
77+
return nil, err
14678
}
14779

148-
_, err = io.Copy(file, source)
149-
if err != nil {
150-
return err
80+
if value, found := metadata.Env[EnvDependencies]; found {
81+
src := Source{
82+
Name: EnvDependencies,
83+
Contents: []byte(value),
84+
}
85+
return envAnalyzer(src), nil
15186
}
15287

153-
err = file.Close()
154-
if err != nil {
155-
return err
156-
}
157-
return nil
88+
return empty, nil
15889
}

‎archive_test.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,10 @@ func Test_analyzeArchive_Reader(t *testing.T) {
4343
Archive: Source{Reader: file},
4444
}
4545

46-
actual, err := Analyze(opts)
47-
require.NoError(t, err)
46+
expected := &Dependencies{}
47+
_ = expected.UnmarshalText([]byte(`k6>0.54;k6/x/faker>0.4.0;k6/x/sql>=1.0.1;k6/x/sql/driver/ramsql*`))
4848

49-
expected, err := Analyze(opts)
49+
actual, err := Analyze(opts)
5050
require.NoError(t, err)
51-
5251
require.Equal(t, expected.String(), actual.String())
5352
}

‎cmd/cmd.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,15 @@ func deps(opts *options, args []string) error {
9292
}
9393

9494
if opts.input != "" && !ignoreStdin {
95-
buffer := &bytes.Buffer{}
96-
buffer.ReadFrom(os.Stdin) //nolint:errcheck,forbidigo,gosec
97-
9895
switch opts.input {
9996
case "js":
97+
buffer := &bytes.Buffer{}
98+
buffer.ReadFrom(os.Stdin) //nolint:errcheck,forbidigo,gosec
10099
opts.Script.Name = "stdin"
101100
opts.Script.Contents = buffer.Bytes()
102101
case "tar":
103102
opts.Archive.Name = "stdin"
104-
opts.Archive.Contents = buffer.Bytes()
103+
opts.Archive.Reader = os.Stdin //nolint:forbidigo
105104
default:
106105
return fmt.Errorf("unsupported input format: %s", opts.input)
107106
}

‎options.go

-62
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package k6deps
22

33
import (
4-
"bytes"
54
"errors"
65
"io"
76
"os"
@@ -31,28 +30,6 @@ func (s *Source) IsEmpty() bool {
3130
return len(s.Contents) == 0 && s.Reader == nil && len(s.Name) == 0
3231
}
3332

34-
func (s *Source) getReader() (io.Reader, func() error, error) {
35-
if s.Reader != nil {
36-
return s.Reader, nil, nil
37-
}
38-
39-
if len(s.Contents) > 0 {
40-
return bytes.NewReader(s.Contents), nil, nil
41-
}
42-
43-
fileName, err := filepath.Abs(s.Name)
44-
if err != nil {
45-
return nil, nil, err
46-
}
47-
48-
file, err := os.Open(filepath.Clean(fileName)) //nolint:forbidigo
49-
if err != nil {
50-
return nil, nil, err
51-
}
52-
53-
return file, file.Close, nil
54-
}
55-
5633
// Options contains the parameters of the dependency analysis.
5734
type Options struct {
5835
// Script contains the properties of the k6 test script to be analyzed.
@@ -99,10 +76,6 @@ func (opts *Options) lookupEnv(key string) (string, bool) {
9976
}
10077

10178
func loadSources(opts *Options) error {
102-
if !opts.Archive.Ignore && !opts.Archive.IsEmpty() {
103-
return loadArchive(opts)
104-
}
105-
10679
if err := loadManifest(opts); err != nil {
10780
return err
10881
}
@@ -214,38 +187,3 @@ func findManifest(filename string) ([]byte, string, bool, error) {
214187

215188
return nil, "", false, nil
216189
}
217-
218-
//nolint:forbidigo
219-
func loadArchive(opts *Options) error {
220-
if opts.Archive.Ignore || opts.Archive.IsEmpty() {
221-
return nil
222-
}
223-
224-
reader, closer, err := opts.Archive.getReader()
225-
if err != nil {
226-
return err
227-
}
228-
if closer != nil {
229-
defer closer() //nolint:errcheck
230-
}
231-
232-
dir, err := os.MkdirTemp("", "k6deps-*")
233-
if err != nil {
234-
return err
235-
}
236-
237-
defer os.RemoveAll(dir) //nolint:errcheck
238-
239-
err = extractArchive(dir, reader)
240-
if err != nil {
241-
return err
242-
}
243-
244-
// archive should be self contained
245-
opts.Script.Ignore = true
246-
opts.Archive.Ignore = true
247-
opts.Env.Ignore = true
248-
opts.Manifest.Ignore = true
249-
250-
return loadMetadata(dir, opts)
251-
}

0 commit comments

Comments
 (0)
Please sign in to comment.