Skip to content

Commit 74fd3f0

Browse files
committed
feat: added --force-update for forcing k6 binary update
1 parent ffc4640 commit 74fd3f0

File tree

7 files changed

+152
-49
lines changed

7 files changed

+152
-49
lines changed

build.go

+54-21
Original file line numberDiff line numberDiff line change
@@ -2,55 +2,76 @@ package k6exec
22

33
import (
44
"context"
5-
"errors"
65
"fmt"
76
"io"
87
"net/http"
98
"net/url"
109
"os"
11-
"os/exec"
1210
"path/filepath"
11+
"runtime"
1312
"syscall"
1413

14+
"github.com/grafana/k6build"
1515
"github.com/grafana/k6deps"
1616
)
1717

18-
func build(_ []*k6deps.Dependency) (*url.URL, error) {
19-
loc, err := exec.LookPath(k6binary)
18+
const platform = runtime.GOOS + "/" + runtime.GOARCH
19+
20+
func depsConvert(deps []*k6deps.Dependency) (string, []k6build.Dependency) {
21+
bdeps := make([]k6build.Dependency, len(deps)-1)
22+
23+
for idx, dep := range deps[1:] {
24+
bdeps[idx] = k6build.Dependency{Name: dep.Name, Constraints: dep.GetConstraints().String()}
25+
}
26+
27+
return deps[0].GetConstraints().String(), bdeps
28+
}
29+
30+
func build(ctx context.Context, deps []*k6deps.Dependency, _ *Options) (*url.URL, error) {
31+
svc, err := k6build.DefaultLocalBuildService()
2032
if err != nil {
2133
return nil, err
2234
}
2335

24-
return url.Parse("file://" + filepath.ToSlash(loc))
36+
k6constraints, bdeps := depsConvert(deps)
37+
38+
artifact, err := svc.Build(ctx, platform, k6constraints, bdeps)
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
return url.Parse(artifact.URL)
2544
}
2645

27-
func download(ctx context.Context, from *url.URL, dir string, client *http.Client) error {
28-
dest, err := os.CreateTemp(dir, k6temp) //nolint:forbidigo
46+
//nolint:forbidigo
47+
func download(ctx context.Context, from *url.URL, dest string, client *http.Client) error {
48+
tmp, err := os.CreateTemp(filepath.Dir(dest), k6temp)
2949
if err != nil {
3050
return err
3151
}
3252

3353
if from.Scheme == "file" {
34-
err = fileDownload(from, dest)
54+
err = fileDownload(from, tmp)
3555
} else {
36-
err = httpDownload(ctx, from, dest, client)
56+
err = httpDownload(ctx, from, tmp, client)
3757
}
3858

3959
if err != nil {
40-
_ = os.Remove(dest.Name()) //nolint:forbidigo
60+
_ = os.Remove(tmp.Name())
4161

4262
return err
4363
}
4464

45-
if err = dest.Close(); err != nil {
65+
if err = tmp.Close(); err != nil {
4666
return err
4767
}
4868

49-
return os.Rename(dest.Name(), filepath.Join(dir, k6binary)) //nolint:forbidigo
69+
return os.Rename(tmp.Name(), dest)
5070
}
5171

52-
func fileDownload(from *url.URL, dest *os.File) error { //nolint:forbidigo
53-
src, err := os.Open(from.Path) //nolint:forbidigo
72+
//nolint:forbidigo
73+
func fileDownload(from *url.URL, dest *os.File) error {
74+
src, err := os.Open(from.Path)
5475
if err != nil {
5576
return err
5677
}
@@ -62,29 +83,41 @@ func fileDownload(from *url.URL, dest *os.File) error { //nolint:forbidigo
6283
return err
6384
}
6485

65-
return os.Chmod(dest.Name(), syscall.S_IRUSR|syscall.S_IXUSR) //nolint:forbidigo
86+
err = os.Chmod(dest.Name(), syscall.S_IRUSR|syscall.S_IXUSR)
87+
if err != nil {
88+
return err
89+
}
90+
91+
return nil
6692
}
6793

68-
func httpDownload(ctx context.Context, from *url.URL, dest *os.File, client *http.Client) error { //nolint:forbidigo
94+
//nolint:forbidigo
95+
func httpDownload(ctx context.Context, from *url.URL, dest *os.File, client *http.Client) error {
6996
req, err := http.NewRequestWithContext(ctx, http.MethodGet, from.String(), nil)
7097
if err != nil {
7198
return err
7299
}
73100

74101
resp, err := client.Do(req)
75102
if err != nil {
76-
return fmt.Errorf("%w: %s", errDownload, err.Error())
103+
return err
77104
}
78105

79106
if resp.StatusCode != http.StatusOK {
80-
return fmt.Errorf("%w: %s", errDownload, resp.Status)
107+
return fmt.Errorf("%w: %s", os.ErrNotExist, resp.Status)
81108
}
82109

83110
defer resp.Body.Close() //nolint:errcheck
84111

85112
_, err = io.Copy(dest, resp.Body)
113+
if err != nil {
114+
return err
115+
}
86116

87-
return err
88-
}
117+
err = os.Chmod(dest.Name(), syscall.S_IRUSR|syscall.S_IXUSR)
118+
if err != nil {
119+
return err
120+
}
89121

90-
var errDownload = errors.New("download error")
122+
return nil
123+
}

cmd/cmd.go

+16-6
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,14 @@ import (
1414
//go:embed help.md
1515
var help string
1616

17+
type options struct {
18+
k6exec.Options
19+
}
20+
1721
// New creates new cobra command for exec command.
1822
func New() *cobra.Command {
23+
opts := new(options)
24+
1925
root := &cobra.Command{
2026
Use: "exec [flags] [command]",
2127
Short: "Lanch k6 with extensions",
@@ -27,19 +33,23 @@ func New() *cobra.Command {
2733
RunE: func(cmd *cobra.Command, _ []string) error { return cmd.Help() },
2834
}
2935

30-
root.AddCommand(subcommands()...)
36+
root.AddCommand(subcommands(&opts.Options)...)
37+
38+
flags := root.PersistentFlags()
39+
40+
flags.BoolVar(&opts.ForceUpdate, "force-update", false, "force updating the cached k6 executable")
3141

3242
return root
3343
}
3444

3545
func usage(cmd *cobra.Command, args []string) {
36-
err := exec(cmd, append(args, "-h"))
46+
err := exec(cmd, append(args, "-h"), nil)
3747
if err != nil {
3848
cmd.PrintErr(err)
3949
}
4050
}
4151

42-
func exec(sub *cobra.Command, args []string) error {
52+
func exec(sub *cobra.Command, args []string, opts *k6exec.Options) error {
4353
var (
4454
deps k6deps.Dependencies
4555
err error
@@ -58,7 +68,7 @@ func exec(sub *cobra.Command, args []string) error {
5868

5969
args = append([]string{sub.Name()}, args...)
6070

61-
cmd, err := k6exec.Command(context.TODO(), args, deps, nil)
71+
cmd, err := k6exec.Command(context.TODO(), args, deps, opts)
6272
if err != nil {
6373
return err
6474
}
@@ -91,15 +101,15 @@ func scriptArg(cmd *cobra.Command, args []string) (string, bool) {
91101
return last, true
92102
}
93103

94-
func subcommands() []*cobra.Command {
104+
func subcommands(opts *k6exec.Options) []*cobra.Command {
95105
annext := map[string]string{useExtensions: "true"}
96106

97107
all := make([]*cobra.Command, 0, len(commands))
98108

99109
for _, name := range commands {
100110
cmd := &cobra.Command{
101111
Use: name,
102-
RunE: exec,
112+
RunE: func(cmd *cobra.Command, args []string) error { return exec(cmd, args, opts) },
103113
SilenceErrors: true,
104114
SilenceUsage: true,
105115
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},

cmd/help.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@ Launch k6 with a seamless extension user experience.
22

33
The launcher will always run the k6 test script with the appropriate k6 binary, which contains the extensions used by the script.
44

5-
The launcher can be used to execute any k6 subcommand. Use the `help` subcommand to list the available subcommands.
6-
5+
Any k6 command can be used. Use the `help` command to list the available k6 commands.

command.go

+38-9
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,68 @@ package k6exec
22

33
import (
44
"context"
5+
"errors"
6+
"fmt"
7+
"os"
58
"os/exec"
69
"path/filepath"
10+
"syscall"
711

812
"github.com/adrg/xdg"
913
"github.com/grafana/k6deps"
1014
)
1115

16+
//nolint:forbidigo
17+
func exists(file string) bool {
18+
_, err := os.Stat(file)
19+
20+
return err == nil || !errors.Is(err, os.ErrNotExist)
21+
}
22+
1223
// Command returns the exec.Cmd struct to execute k6 with the given dependencies and arguments.
1324
func Command(ctx context.Context, args []string, deps k6deps.Dependencies, opts *Options) (*exec.Cmd, error) {
1425
cachedir, err := xdg.CacheFile(opts.appname())
1526
if err != nil {
16-
return nil, err
27+
return nil, fmt.Errorf("%w: %s", ErrCache, err.Error())
28+
}
29+
30+
err = os.MkdirAll(cachedir, syscall.S_IRUSR|syscall.S_IWUSR|syscall.S_IXUSR) //nolint:forbidigo
31+
if err != nil {
32+
return nil, fmt.Errorf("%w: %s", ErrCache, err.Error())
1733
}
1834

1935
exe := filepath.Join(cachedir, k6binary)
2036

21-
mods, err := unmarshalVersionOutput(ctx, exe)
22-
if err != nil {
23-
return nil, err
37+
var mods modules
38+
39+
if !opts.forceUpdate() && exists(exe) {
40+
mods, err = unmarshalVersionOutput(ctx, exe)
41+
if err != nil {
42+
return nil, fmt.Errorf("%w: %s", ErrCache, err.Error())
43+
}
2444
}
2545

26-
if !mods.fulfill(deps) {
46+
if opts.forceUpdate() || !mods.fulfill(deps) {
2747
demands := mods.merge(deps)
2848

29-
loc, err := build(demands.Sorted())
49+
loc, err := build(ctx, demands.Sorted(), opts)
3050
if err != nil {
31-
return nil, err
51+
return nil, fmt.Errorf("%w: %s", ErrBuild, err.Error())
3252
}
3353

34-
if err = download(ctx, loc, cachedir, opts.client()); err != nil {
35-
return nil, err
54+
if err = download(ctx, loc, exe, opts.client()); err != nil {
55+
return nil, fmt.Errorf("%w: %s", ErrDownload, err.Error())
3656
}
3757
}
3858

3959
return exec.Command(exe, args...), nil //nolint:gosec
4060
}
61+
62+
var (
63+
// ErrDownload is returned if an error occurs during download.
64+
ErrDownload = errors.New("download error")
65+
// ErrBuild is returned if an error occurs during build.
66+
ErrBuild = errors.New("build error")
67+
// ErrCache is returned if an error occurs during cache handling.
68+
ErrCache = errors.New("cache error")
69+
)

go.mod

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
module github.com/grafana/k6exec
22

3-
go 1.21
3+
go 1.22.2
4+
5+
toolchain go1.22.4
46

57
require (
8+
github.com/Masterminds/semver/v3 v3.2.1
69
github.com/adrg/xdg v0.4.0
710
github.com/evanw/esbuild v0.21.5
811
github.com/grafana/clireadme v0.1.0
9-
github.com/grafana/k6deps v0.1.2-0.20240617062847-966ac73b7402
10-
github.com/spf13/cobra v1.8.0
12+
github.com/grafana/k6build v0.2.0
13+
github.com/grafana/k6deps v0.1.2-0.20240617140502-f1b0dfc93f7f
14+
github.com/spf13/cobra v1.8.1
1115
golang.org/x/term v0.21.0
1216
)
1317

1418
require (
15-
github.com/Masterminds/semver/v3 v3.2.1 // indirect
16-
github.com/grafana/k6pack v0.2.0 // indirect
19+
github.com/Masterminds/semver v1.5.0 // indirect
20+
github.com/grafana/k6catalog v0.1.0 // indirect
21+
github.com/grafana/k6foundry v0.1.3 // indirect
22+
github.com/grafana/k6pack v0.2.1 // indirect
1723
github.com/inconshreveable/mousetrap v1.1.0 // indirect
24+
github.com/sirupsen/logrus v1.9.3 // indirect
1825
github.com/spf13/pflag v1.0.5 // indirect
26+
golang.org/x/mod v0.18.0 // indirect
1927
golang.org/x/sys v0.21.0 // indirect
2028
)

go.sum

+23-4
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,53 @@
1+
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
2+
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
13
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
24
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
35
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
46
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
57
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
8+
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
69
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
710
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
811
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
912
github.com/evanw/esbuild v0.21.5 h1:oShm8TT5QUhf6vM7teg0nmd14eHu64dPmVluC2f4DMg=
1013
github.com/evanw/esbuild v0.21.5/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
14+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
15+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1116
github.com/grafana/clireadme v0.1.0 h1:KYEYSnYdSzmHf3bufaK6fQZ5j4dzvM/T+G6Ba+qNnAM=
1217
github.com/grafana/clireadme v0.1.0/go.mod h1:Wy4KIG2ZBGMYAYyF9l7qAy+yoJVasqk/txsRgoRI3gc=
13-
github.com/grafana/k6deps v0.1.1 h1:ei24xajAvBagLVu0O7UaSz0tJ1GhHxqnmJ+inCfQ7Z8=
14-
github.com/grafana/k6deps v0.1.1/go.mod h1:j8UOs5mZhn5+hpJqDtl5zjYRjMpBKYf+FwaBmHHcfao=
15-
github.com/grafana/k6deps v0.1.2-0.20240617062847-966ac73b7402 h1:l5tA8297dPeW9j+7EiowQzv8TuxLO/nzIW3Y3q7WgcA=
16-
github.com/grafana/k6deps v0.1.2-0.20240617062847-966ac73b7402/go.mod h1:j8UOs5mZhn5+hpJqDtl5zjYRjMpBKYf+FwaBmHHcfao=
18+
github.com/grafana/k6build v0.1.5 h1:uhMSujUhLNB3jSs7vfHP6x1yBOq/1fP0TVekibkmgiM=
19+
github.com/grafana/k6build v0.1.5/go.mod h1:DXItIZzDI1gnMOC0+oSE2OsjNJtR4ahLHYC8EQ643T8=
20+
github.com/grafana/k6build v0.2.0 h1:4IRinD5iuPW7+XR5590UduPwm1hBAwH2bpdkMADifP8=
21+
github.com/grafana/k6build v0.2.0/go.mod h1:DXItIZzDI1gnMOC0+oSE2OsjNJtR4ahLHYC8EQ643T8=
22+
github.com/grafana/k6catalog v0.1.0 h1:jLmbmB3EUJ+zyQG3hWy6dWbtMjvTkvJNx1d4LX8it6I=
23+
github.com/grafana/k6catalog v0.1.0/go.mod h1:8R9eXAh2nb69+drkj0rZ4aemso0jcwCbPP6Q3E5LqCw=
24+
github.com/grafana/k6deps v0.1.2-0.20240617140502-f1b0dfc93f7f h1:TQE8m+ScEDi4s+vlWpvMoc2U2fiWtEt85PuRflStCiw=
25+
github.com/grafana/k6deps v0.1.2-0.20240617140502-f1b0dfc93f7f/go.mod h1:j8UOs5mZhn5+hpJqDtl5zjYRjMpBKYf+FwaBmHHcfao=
26+
github.com/grafana/k6foundry v0.1.3 h1:05sRM5ik+MsZr1tdJR/rTjI8trLpWFbG+vzmnpmsC5g=
27+
github.com/grafana/k6foundry v0.1.3/go.mod h1:b6n4InFgXl+3yPobmlyJfcJmLozU9CI9IIUuq8YqEiM=
1728
github.com/grafana/k6pack v0.2.0 h1:Y8udypzuzFdcFMRHnP5VkdjYXWLb/ouKzm4ct3OqPmg=
1829
github.com/grafana/k6pack v0.2.0/go.mod h1:cltO5vmQOObJDtKbRkNVGymk3J+MRhriJyQg1JJzeEc=
30+
github.com/grafana/k6pack v0.2.1 h1:S9EkeFuRMnfwP/lHrKnlgctlNDiUKgKU1bEKbIfOUro=
31+
github.com/grafana/k6pack v0.2.1/go.mod h1:BEy4y0GE+gXbdp8EldJGXd1g1Py3wBBxDE2AwzHsMxI=
1932
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
2033
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
2134
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2235
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
2336
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
37+
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
38+
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
2439
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
2540
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
41+
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
42+
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
2643
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
2744
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
2845
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
2946
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
3047
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
3148
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
49+
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
50+
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
3251
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
3352
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
3453
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=

0 commit comments

Comments
 (0)