Skip to content

Commit

Permalink
feat: catalog handling + build service client + state dir
Browse files Browse the repository at this point in the history
  • Loading branch information
szkiba committed Jun 27, 2024
1 parent 74fd3f0 commit 6b8a8fb
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 204 deletions.
5 changes: 1 addition & 4 deletions bin_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,4 @@

package k6exec

const (
k6binary = "k6"
k6temp = "k6-*"
)
const k6binary = "k6"
5 changes: 1 addition & 4 deletions bin_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,4 @@

package k6exec

const (
k6binary = "k6.exe"
k6temp = "k6-*.exe"
)
const k6binary = "k6.exe"
100 changes: 73 additions & 27 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,30 @@ import (
"github.com/grafana/k6deps"
)

const platform = runtime.GOOS + "/" + runtime.GOARCH
const (
platform = runtime.GOOS + "/" + runtime.GOARCH

func depsConvert(deps []*k6deps.Dependency) (string, []k6build.Dependency) {
bdeps := make([]k6build.Dependency, len(deps)-1)
k6module = "k6"
)

func depsConvert(deps k6deps.Dependencies) (string, []k6build.Dependency) {
bdeps := make([]k6build.Dependency, 0, len(deps))
k6constraint := "*"

for _, dep := range deps {
if dep.Name == k6module {
k6constraint = dep.GetConstraints().String()
continue
}

for idx, dep := range deps[1:] {
bdeps[idx] = k6build.Dependency{Name: dep.Name, Constraints: dep.GetConstraints().String()}
bdeps = append(bdeps, k6build.Dependency{Name: dep.Name, Constraints: dep.GetConstraints().String()})
}

return deps[0].GetConstraints().String(), bdeps
return k6constraint, bdeps
}

func build(ctx context.Context, deps []*k6deps.Dependency, _ *Options) (*url.URL, error) {
svc, err := k6build.DefaultLocalBuildService()
func build(ctx context.Context, deps k6deps.Dependencies, opts *Options) (*url.URL, error) {
svc, err := newBuildService(ctx, opts)
if err != nil {
return nil, err
}
Expand All @@ -43,9 +53,56 @@ func build(ctx context.Context, deps []*k6deps.Dependency, _ *Options) (*url.URL
return url.Parse(artifact.URL)
}

func newBuildService(ctx context.Context, opts *Options) (k6build.BuildService, error) {
if opts.BuildServiceURL != nil {
return newBuildServiceClient(opts)
}

return newLocalBuildService(ctx, opts)
}

func newLocalBuildService(ctx context.Context, opts *Options) (k6build.BuildService, error) {
statedir, err := opts.stateSubdir()
if err != nil {
return nil, err
}

catfile := filepath.Join(statedir, "catalog.json")

client, err := opts.client()
if err != nil {
return nil, err
}

if err := download(ctx, opts.catalogURL(), catfile, client); err != nil {
return nil, err
}

cachedir, err := opts.cacheDir()
if err != nil {
return nil, err
}

conf := k6build.LocalBuildServiceConfig{
BuildEnv: map[string]string{"GOWORK": "off"},
Catalog: catfile,
CopyGoEnv: true,
CacheDir: filepath.Join(cachedir, "build"),
Verbose: opts.verbose(),
}

return k6build.NewLocalBuildService(ctx, conf)
}

func newBuildServiceClient(opts *Options) (k6build.BuildService, error) {
return k6build.NewBuildServiceClient(k6build.BuildServiceClientConfig{
URL: opts.BuildServiceURL.String(),
})
}

//nolint:forbidigo
func download(ctx context.Context, from *url.URL, dest string, client *http.Client) error {
tmp, err := os.CreateTemp(filepath.Dir(dest), k6temp)
tmp, err := os.CreateTemp(filepath.Dir(dest), filepath.Base(dest)+"-*")
if err != nil {
return err
}
Expand All @@ -66,6 +123,11 @@ func download(ctx context.Context, from *url.URL, dest string, client *http.Clie
return err
}

err = os.Chmod(tmp.Name(), syscall.S_IRUSR|syscall.S_IXUSR)
if err != nil {
return err
}

return os.Rename(tmp.Name(), dest)
}

Expand All @@ -79,16 +141,8 @@ func fileDownload(from *url.URL, dest *os.File) error {
defer src.Close() //nolint:errcheck

_, err = io.Copy(dest, src)
if err != nil {
return err
}

err = os.Chmod(dest.Name(), syscall.S_IRUSR|syscall.S_IXUSR)
if err != nil {
return err
}

return nil
return err
}

//nolint:forbidigo
Expand All @@ -110,14 +164,6 @@ func httpDownload(ctx context.Context, from *url.URL, dest *os.File, client *htt
defer resp.Body.Close() //nolint:errcheck

_, err = io.Copy(dest, resp.Body)
if err != nil {
return err
}

err = os.Chmod(dest.Name(), syscall.S_IRUSR|syscall.S_IXUSR)
if err != nil {
return err
}

return nil
return err
}
20 changes: 20 additions & 0 deletions cleanup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package k6exec

import (
"fmt"
"os"
)

// CleanupState deletes the state directory belonging to the current process.
func CleanupState(opts *Options) error {
dir, err := opts.stateSubdir()
if err != nil {
return fmt.Errorf("%w: %s", ErrState, err.Error())
}

if err = os.RemoveAll(dir); err != nil { //nolint:forbidigo
return fmt.Errorf("%w: %s", ErrState, err.Error())
}

return nil
}
57 changes: 45 additions & 12 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package cmd
import (
"context"
_ "embed"
"net/url"
"os"

"github.com/grafana/k6deps"
Expand All @@ -16,6 +17,30 @@ var help string

type options struct {
k6exec.Options
buildServiceURL string
catalogURL string
}

func (o *options) postProcess() error {
if len(o.buildServiceURL) > 0 {
val, err := url.Parse(o.buildServiceURL)
if err != nil {
return err
}

o.BuildServiceURL = val
}

if len(o.catalogURL) > 0 {
val, err := url.Parse(o.catalogURL)
if err != nil {
return err
}

o.CatalogURL = val
}

return nil
}

// New creates new cobra command for exec command.
Expand All @@ -31,13 +56,18 @@ func New() *cobra.Command {
DisableAutoGenTag: true,
CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
RunE: func(cmd *cobra.Command, _ []string) error { return cmd.Help() },
PersistentPreRunE: func(_ *cobra.Command, _ []string) error { return opts.postProcess() },
}

root.AddCommand(subcommands(&opts.Options)...)

flags := root.PersistentFlags()

flags.BoolVar(&opts.ForceUpdate, "force-update", false, "force updating the cached k6 executable")
flags.StringVar(&opts.catalogURL, "extension-catalog", "", "URL of the k6 extension catalog to be used")
flags.StringVar(&opts.buildServiceURL, "build-service", "", "URL of the k6 build service to be used")
flags.BoolVarP(&opts.Verbose, "verbose", "v", false, "enable verbose logging")

root.MarkFlagsMutuallyExclusive("extension-catalog", "build-service")

return root
}
Expand All @@ -51,24 +81,25 @@ func usage(cmd *cobra.Command, args []string) {

func exec(sub *cobra.Command, args []string, opts *k6exec.Options) error {
var (
deps k6deps.Dependencies
err error
deps k6deps.Dependencies
err error
dopts k6deps.Options
)

if scriptname, hasScript := scriptArg(sub, args); hasScript {
deps, err = k6deps.Analyze(&k6deps.Options{
Script: k6deps.Source{
Name: scriptname,
},
})
if err != nil {
return err
}
dopts.Script.Name = scriptname
}

dopts.Manifest.Name = "package.json"

deps, err = k6deps.Analyze(&dopts)
if err != nil {
return err
}

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

cmd, err := k6exec.Command(context.TODO(), args, deps, opts)
cmd, err := k6exec.Command(context.Background(), args, deps, opts)
if err != nil {
return err
}
Expand All @@ -77,6 +108,8 @@ func exec(sub *cobra.Command, args []string, opts *k6exec.Options) error {
cmd.Stdout = os.Stdout //nolint:forbidigo
cmd.Stdin = os.Stdin //nolint:forbidigo

defer k6exec.CleanupState(opts) //nolint:errcheck

return cmd.Run()
}

Expand Down
51 changes: 16 additions & 35 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,38 @@ import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"syscall"

"github.com/adrg/xdg"
"github.com/grafana/k6deps"
)

//nolint:forbidigo
func exists(file string) bool {
_, err := os.Stat(file)

return err == nil || !errors.Is(err, os.ErrNotExist)
}

// Command returns the exec.Cmd struct to execute k6 with the given dependencies and arguments.
func Command(ctx context.Context, args []string, deps k6deps.Dependencies, opts *Options) (*exec.Cmd, error) {
cachedir, err := xdg.CacheFile(opts.appname())
dir, err := opts.stateSubdir()
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrCache, err.Error())
return nil, fmt.Errorf("%w: %s", ErrState, err.Error())
}

err = os.MkdirAll(cachedir, syscall.S_IRUSR|syscall.S_IWUSR|syscall.S_IXUSR) //nolint:forbidigo
exe := filepath.Join(dir, k6binary)

loc, err := build(ctx, deps, opts)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrCache, err.Error())
return nil, fmt.Errorf("%w: %s", ErrBuild, err.Error())
}

exe := filepath.Join(cachedir, k6binary)

var mods modules

if !opts.forceUpdate() && exists(exe) {
mods, err = unmarshalVersionOutput(ctx, exe)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrCache, err.Error())
}
client, err := opts.client()
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrDownload, err.Error())
}

if opts.forceUpdate() || !mods.fulfill(deps) {
demands := mods.merge(deps)

loc, err := build(ctx, demands.Sorted(), opts)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrBuild, err.Error())
}

if err = download(ctx, loc, exe, opts.client()); err != nil {
return nil, fmt.Errorf("%w: %s", ErrDownload, err.Error())
}
if err = download(ctx, loc, exe, client); err != nil {
return nil, fmt.Errorf("%w: %s", ErrDownload, err.Error())
}

return exec.Command(exe, args...), nil //nolint:gosec
cmd := exec.CommandContext(ctx, exe, args...) //nolint:gosec

return cmd, nil
}

var (
Expand All @@ -66,4 +45,6 @@ var (
ErrBuild = errors.New("build error")
// ErrCache is returned if an error occurs during cache handling.
ErrCache = errors.New("cache error")
// ErrState is returned if an error occurs during state handling.
ErrState = errors.New("state error")
)
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,25 @@ go 1.22.2
toolchain go1.22.4

require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/adrg/xdg v0.4.0
github.com/evanw/esbuild v0.21.5
github.com/grafana/clireadme v0.1.0
github.com/grafana/k6build v0.2.0
github.com/grafana/k6deps v0.1.2-0.20240617140502-f1b0dfc93f7f
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
github.com/spf13/cobra v1.8.1
golang.org/x/term v0.21.0
)

require (
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/grafana/k6catalog v0.1.0 // indirect
github.com/grafana/k6foundry v0.1.3 // indirect
github.com/grafana/k6pack v0.2.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/mod v0.18.0 // indirect
Expand Down
Loading

0 comments on commit 6b8a8fb

Please sign in to comment.