From 03acbc07ccb47b00b46d51a5db41c06be33deffa Mon Sep 17 00:00:00 2001 From: Pablo Chacin <pablochacin@gmail.com> Date: Fri, 13 Dec 2024 11:12:34 +0100 Subject: [PATCH] refactor local build service Signed-off-by: Pablo Chacin <pablochacin@gmail.com> --- cmd/local/local.go | 6 +- cmd/server/server.go | 61 ++++++--- pkg/builder/builder.go | 251 ++++++++++++++++++++++++++++++++++++ pkg/local/local.go | 279 +++-------------------------------------- pkg/local/testutils.go | 42 +++---- pkg/store/file/file.go | 5 +- 6 files changed, 338 insertions(+), 306 deletions(-) create mode 100644 pkg/builder/builder.go diff --git a/cmd/local/local.go b/cmd/local/local.go index 7f8dc23..a5dc1b4 100644 --- a/cmd/local/local.go +++ b/cmd/local/local.go @@ -54,7 +54,7 @@ k6build local -k v0.50.0 -e GOPROXY=http://localhost:80 -q // New creates new cobra command for local build command. func New() *cobra.Command { //nolint:funlen var ( - config local.BuildServiceConfig + config local.Config deps []string k6 string output string @@ -127,9 +127,9 @@ func New() *cobra.Command { //nolint:funlen _ = cmd.MarkFlagRequired("platform") cmd.Flags().StringVarP(&config.Catalog, "catalog", "c", k6catalog.DefaultCatalogURL, "dependencies catalog") cmd.Flags().StringVarP(&config.StoreDir, "store-dir", "f", "/tmp/k6build/store", "object store dir") - cmd.Flags().BoolVarP(&config.Verbose, "verbose", "v", false, "print build process output") + cmd.Flags().BoolVarP(&config.Opts.Verbose, "verbose", "v", false, "print build process output") cmd.Flags().BoolVarP(&config.CopyGoEnv, "copy-go-env", "g", true, "copy go environment") - cmd.Flags().StringToStringVarP(&config.BuildEnv, "env", "e", nil, "build environment variables") + cmd.Flags().StringToStringVarP(&config.Opts.Env, "env", "e", nil, "build environment variables") cmd.Flags().StringVarP(&output, "output", "o", "k6", "path to put the binary as an executable.") cmd.Flags().BoolVarP(&quiet, "quiet", "q", false, "don't print artifact's details") cmd.Flags().BoolVar( diff --git a/cmd/server/server.go b/cmd/server/server.go index 68cdf49..d5ff496 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -8,8 +8,9 @@ import ( "os" "github.com/grafana/k6build" - "github.com/grafana/k6build/pkg/local" + "github.com/grafana/k6build/pkg/builder" server "github.com/grafana/k6build/pkg/server" + store "github.com/grafana/k6build/pkg/store/client" "github.com/grafana/k6catalog" "github.com/spf13/cobra" @@ -68,10 +69,15 @@ k6build server -e GOPROXY=http://localhost:80 // New creates new cobra command for the server command. func New() *cobra.Command { //nolint:funlen var ( - config local.BuildServiceConfig - logLevel string - port int - enableCgo bool + logLevel string + port int + enableCgo bool + verbose bool + goEnv map[string]string + copyGoEnv bool + catalogURL string + storeURL string + allowBuildSemvers bool ) cmd := &cobra.Command{ @@ -99,16 +105,41 @@ func New() *cobra.Command { //nolint:funlen ), ) + catalog, err := k6catalog.NewCatalog(cmd.Context(), catalogURL) + if err != nil { + return fmt.Errorf("creating catalog %w", err) + } + + store, err := store.NewStoreClient(store.StoreClientConfig{ + Server: storeURL, + }) + if err != nil { + return fmt.Errorf("creating store %w", err) + } + + // TODO: check this logic if enableCgo { log.Warn("enabling CGO for build service") } else { - if config.BuildEnv == nil { - config.BuildEnv = make(map[string]string) + if goEnv == nil { + goEnv = make(map[string]string) } - config.BuildEnv["CGO_ENABLED"] = "0" + goEnv["CGO_ENABLED"] = "0" } - buildSrv, err := local.NewBuildService(cmd.Context(), config) + config := builder.Config{ + Opts: builder.Opts{ + GoOpts: builder.GoOpts{ + Env: goEnv, + CopyGoEnv: copyGoEnv, + }, + Verbose: verbose, + AllowBuildSemvers: allowBuildSemvers, + }, + Catalog: catalog, + Store: store, + } + buildSrv, err := builder.New(cmd.Context(), config) if err != nil { return fmt.Errorf("creating local build service %w", err) } @@ -135,22 +166,22 @@ func New() *cobra.Command { //nolint:funlen } cmd.Flags().StringVarP( - &config.Catalog, + &catalogURL, "catalog", "c", k6catalog.DefaultCatalogURL, "dependencies catalog. Can be path to a local file or an URL."+ "\n", ) - cmd.Flags().StringVar(&config.StoreURL, "store-url", "http://localhost:9000/store", "store server url") - cmd.Flags().BoolVarP(&config.Verbose, "verbose", "v", false, "print build process output") - cmd.Flags().BoolVarP(&config.CopyGoEnv, "copy-go-env", "g", true, "copy go environment") - cmd.Flags().StringToStringVarP(&config.BuildEnv, "env", "e", nil, "build environment variables") + cmd.Flags().StringVar(&storeURL, "store-url", "http://localhost:9000/store", "store server url") + cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "print build process output") + cmd.Flags().BoolVarP(©GoEnv, "copy-go-env", "g", true, "copy go environment") + cmd.Flags().StringToStringVarP(&goEnv, "env", "e", nil, "build environment variables") cmd.Flags().IntVarP(&port, "port", "p", 8000, "port server will listen") cmd.Flags().StringVarP(&logLevel, "log-level", "l", "INFO", "log level") cmd.Flags().BoolVar(&enableCgo, "enable-cgo", false, "enable CGO for building binaries.") cmd.Flags().BoolVar( - &config.AllowBuildSemvers, + &allowBuildSemvers, "allow-build-semvers", false, "allow building versions with build metadata (e.g v0.0.0+build).", diff --git a/pkg/builder/builder.go b/pkg/builder/builder.go new file mode 100644 index 0000000..c359e27 --- /dev/null +++ b/pkg/builder/builder.go @@ -0,0 +1,251 @@ +// Package builder implements a build service +package builder + +import ( + "bytes" + "context" + "crypto/sha1" //nolint:gosec + "errors" + "fmt" + "os" + "regexp" + "sort" + "sync" + + "github.com/grafana/k6build" + "github.com/grafana/k6build/pkg/store" + "github.com/grafana/k6catalog" + "github.com/grafana/k6foundry" +) + +const ( + k6Dep = "k6" + k6Path = "go.k6.io/k6" + + opRe = `(?<operator>[=|~|>|<|\^|>=|<=|!=]){0,1}(?:\s*)` + verRe = `(?P<version>[v|V](?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*))` + buildRe = `(?:[+|-|])(?P<build>(?:[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))` +) + +var ( + ErrAccessingArtifact = errors.New("accessing artifact") //nolint:revive + ErrBuildingArtifact = errors.New("building artifact") //nolint:revive + ErrInitializingBuilder = errors.New("initializing builder") //nolint:revive + ErrInvalidParameters = errors.New("invalid build parameters") //nolint:revive + ErrBuildSemverNotAllowed = errors.New("semvers with build metadata not allowed") //nolint:revive + + constrainRe = regexp.MustCompile(opRe + verRe + buildRe) +) + +// GoOpts defines the options for the go build environment +type GoOpts = k6foundry.GoOpts + +// Opts defines the options for configuring the builder +type Opts struct { + // Allow semvers with build metadata + AllowBuildSemvers bool + // Generate build output + Verbose bool + // Build environment options + GoOpts +} + +// Config defines the configuration for a Builder +type Config struct { + Opts Opts + Catalog k6catalog.Catalog + Store store.ObjectStore +} + +// Builder implements the BuildService interface +type Builder struct { + allowBuildSemvers bool + catalog k6catalog.Catalog + builder k6foundry.Builder + store store.ObjectStore + mutexes sync.Map +} + +// New returns a new instance of Builder given a BuilderConfig +func New(ctx context.Context, config Config) (*Builder, error) { + if config.Catalog == nil { + return nil, k6build.NewWrappedError(ErrInitializingBuilder, errors.New("catalog cannot be nil")) + } + + if config.Store == nil { + return nil, k6build.NewWrappedError(ErrInitializingBuilder, errors.New("store cannot be nil")) + } + + builderOpts := k6foundry.NativeBuilderOpts{ + GoOpts: k6foundry.GoOpts{ + Env: config.Opts.Env, + CopyGoEnv: config.Opts.CopyGoEnv, + }, + } + if config.Opts.Verbose { + builderOpts.Stdout = os.Stdout + builderOpts.Stderr = os.Stderr + } + + builder, err := k6foundry.NewNativeBuilder(ctx, builderOpts) + if err != nil { + return nil, k6build.NewWrappedError(ErrInitializingBuilder, err) + } + + return &Builder{ + allowBuildSemvers: config.Opts.AllowBuildSemvers, + catalog: config.Catalog, + builder: builder, + store: config.Store, + }, nil +} + +// Build builds a custom k6 binary with dependencies +func (b *Builder) Build( //nolint:funlen + ctx context.Context, + platform string, + k6Constrains string, + deps []k6build.Dependency, +) (k6build.Artifact, error) { + buildPlatform, err := k6foundry.ParsePlatform(platform) + if err != nil { + return k6build.Artifact{}, k6build.NewWrappedError(ErrInvalidParameters, err) + } + + // sort dependencies to ensure idempotence of build + sort.Slice(deps, func(i, j int) bool { return deps[i].Name < deps[j].Name }) + resolved := map[string]string{} + + // check if it is a semver of the form v0.0.0+<build> + // if it is, we don't check with the catalog, but instead we use + // the build metadata as version when building this module + // the build process will return the actual version built in the build info + // and we can check that version with the catalog + var k6Mod k6catalog.Module + buildMetadata, err := hasBuildMetadata(k6Constrains) + if err != nil { + return k6build.Artifact{}, err + } + if buildMetadata != "" { + if !b.allowBuildSemvers { + return k6build.Artifact{}, k6build.NewWrappedError(ErrInvalidParameters, ErrBuildSemverNotAllowed) + } + k6Mod = k6catalog.Module{Path: k6Path, Version: buildMetadata} + } else { + k6Mod, err = b.catalog.Resolve(ctx, k6catalog.Dependency{Name: k6Dep, Constrains: k6Constrains}) + if err != nil { + return k6build.Artifact{}, k6build.NewWrappedError(ErrInvalidParameters, err) + } + } + resolved[k6Dep] = k6Mod.Version + + mods := []k6foundry.Module{} + for _, d := range deps { + m, modErr := b.catalog.Resolve(ctx, k6catalog.Dependency{Name: d.Name, Constrains: d.Constraints}) + if modErr != nil { + return k6build.Artifact{}, k6build.NewWrappedError(ErrInvalidParameters, modErr) + } + mods = append(mods, k6foundry.Module{Path: m.Path, Version: m.Version}) + resolved[d.Name] = m.Version + } + + // generate id form sorted list of dependencies + hashData := bytes.Buffer{} + hashData.WriteString(platform) + hashData.WriteString(fmt.Sprintf(":k6%s", k6Mod.Version)) + for _, d := range deps { + hashData.WriteString(fmt.Sprintf(":%s%s", d, resolved[d.Name])) + } + id := fmt.Sprintf("%x", sha1.Sum(hashData.Bytes())) //nolint:gosec + + unlock := b.lockArtifact(id) + defer unlock() + + artifactObject, err := b.store.Get(ctx, id) + if err == nil { + return k6build.Artifact{ + ID: id, + Checksum: artifactObject.Checksum, + URL: artifactObject.URL, + Dependencies: resolved, + Platform: platform, + }, nil + } + + if !errors.Is(err, store.ErrObjectNotFound) { + return k6build.Artifact{}, k6build.NewWrappedError(ErrAccessingArtifact, err) + } + + artifactBuffer := &bytes.Buffer{} + buildInfo, err := b.builder.Build(ctx, buildPlatform, k6Mod.Version, mods, []string{}, artifactBuffer) + if err != nil { + return k6build.Artifact{}, k6build.NewWrappedError(ErrAccessingArtifact, err) + } + + // if the version has a build metadata, we must use the actual version built + // TODO: check this version is supported + if buildMetadata != "" { + resolved[k6Dep] = buildInfo.ModVersions[k6Mod.Path] + } + + artifactObject, err = b.store.Put(ctx, id, artifactBuffer) + if err != nil { + return k6build.Artifact{}, k6build.NewWrappedError(ErrAccessingArtifact, err) + } + + return k6build.Artifact{ + ID: id, + Checksum: artifactObject.Checksum, + URL: artifactObject.URL, + Dependencies: resolved, + Platform: platform, + }, nil +} + +// lockArtifact obtains a mutex used to prevent concurrent builds of the same artifact and +// returns a function that will unlock the mutex associated to the given id in the object store. +// The lock is also removed from the map. Subsequent calls will get another lock on the same +// id but this is safe as the object should already be in the object strore and no further +// builds are needed. +func (b *Builder) lockArtifact(id string) func() { + value, _ := b.mutexes.LoadOrStore(id, &sync.Mutex{}) + mtx, _ := value.(*sync.Mutex) + mtx.Lock() + + return func() { + b.mutexes.Delete(id) + mtx.Unlock() + } +} + +// hasBuildMetadata checks if the constrain references a version with a build metadata. +// E.g. v0.1.0+build-effa45f +func hasBuildMetadata(constrain string) (string, error) { + opInx := constrainRe.SubexpIndex("operator") + verIdx := constrainRe.SubexpIndex("version") + preIdx := constrainRe.SubexpIndex("build") + matches := constrainRe.FindStringSubmatch(constrain) + + if matches == nil { + return "", nil + } + + op := matches[opInx] + ver := matches[verIdx] + build := matches[preIdx] + + if op != "" && op != "=" { + return "", k6build.NewWrappedError( + ErrInvalidParameters, + fmt.Errorf("only exact match is allowed for versions with build metadata"), + ) + } + + if ver != "v0.0.0" { + return "", k6build.NewWrappedError( + ErrInvalidParameters, + fmt.Errorf("version with build metadata must start with v0.0.0"), + ) + } + return build, nil +} diff --git a/pkg/local/local.go b/pkg/local/local.go index bfdac34..b90b5ef 100644 --- a/pkg/local/local.go +++ b/pkg/local/local.go @@ -2,289 +2,44 @@ package local import ( - "bytes" "context" - "crypto/sha1" //nolint:gosec - "errors" - "fmt" - "os" - "regexp" - "sort" - "sync" "github.com/grafana/k6build" - "github.com/grafana/k6build/pkg/store" - "github.com/grafana/k6build/pkg/store/client" + "github.com/grafana/k6build/pkg/builder" "github.com/grafana/k6build/pkg/store/file" "github.com/grafana/k6catalog" - "github.com/grafana/k6foundry" ) -const ( - k6Dep = "k6" - k6Path = "go.k6.io/k6" +// Opts local builder options +type Opts = builder.Opts - opRe = `(?<operator>[=|~|>|<|\^|>=|<=|!=]){0,1}(?:\s*)` - verRe = `(?P<version>[v|V](?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*))` - buildRe = `(?:[+|-|])(?P<build>(?:[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))` -) - -var ( - ErrAccessingArtifact = errors.New("accessing artifact") //nolint:revive - ErrBuildingArtifact = errors.New("building artifact") //nolint:revive - ErrInitializingBuilder = errors.New("initializing builder") //nolint:revive - ErrInvalidParameters = errors.New("invalid build parameters") //nolint:revive - ErrBuildSemverNotAllowed = errors.New("semvers with build metadata not allowed") //nolint:revive - - constrainRe = regexp.MustCompile(opRe + verRe + buildRe) -) +// GoOpts Go build options +type GoOpts = builder.GoOpts -// BuildServiceConfig defines the configuration for a Local build service -type BuildServiceConfig struct { - // Set build environment variables - // Can be used for setting (or overriding, if CopyGoEnv is true) go environment variables - BuildEnv map[string]string +// Config defines the configuration for a Local build service +type Config struct { + Opts // path to catalog's json file. Can be a file path or a URL Catalog string - // url to remote object store service - StoreURL string // path to object store dir StoreDir string - // Copy go environment. BuildEnv can override the variables copied from go environment. - CopyGoEnv bool - // set verbose build mode - Verbose bool - // Allow semvers with build metadata - AllowBuildSemvers bool -} - -// buildSrv implements the BuildService interface -type localBuildSrv struct { - allowBuildSemvers bool - catalog k6catalog.Catalog - builder k6foundry.Builder - store store.ObjectStore - mutexes sync.Map } // NewBuildService creates a local build service using the given configuration -func NewBuildService(ctx context.Context, config BuildServiceConfig) (k6build.BuildService, error) { +func NewBuildService(ctx context.Context, config Config) (k6build.BuildService, error) { catalog, err := k6catalog.NewCatalog(ctx, config.Catalog) if err != nil { - return nil, k6build.NewWrappedError(ErrInitializingBuilder, err) - } - - builderOpts := k6foundry.NativeBuilderOpts{ - GoOpts: k6foundry.GoOpts{ - Env: config.BuildEnv, - CopyGoEnv: config.CopyGoEnv, - }, - } - if config.Verbose { - builderOpts.Stdout = os.Stdout - builderOpts.Stderr = os.Stderr - } - - builder, err := k6foundry.NewNativeBuilder(ctx, builderOpts) - if err != nil { - return nil, k6build.NewWrappedError(ErrInitializingBuilder, err) - } - - var store store.ObjectStore - - if config.StoreURL != "" { - store, err = client.NewStoreClient( - client.StoreClientConfig{ - Server: config.StoreURL, - }, - ) - if err != nil { - return nil, k6build.NewWrappedError(ErrInitializingBuilder, err) - } - } else { - store, err = file.NewFileStore(config.StoreDir) - if err != nil { - return nil, k6build.NewWrappedError(ErrInitializingBuilder, err) - } - } - - return &localBuildSrv{ - allowBuildSemvers: config.AllowBuildSemvers, - catalog: catalog, - builder: builder, - store: store, - }, nil -} - -// DefaultLocalBuildService creates a local build service with default configuration -func DefaultLocalBuildService() (k6build.BuildService, error) { - catalog, err := k6catalog.DefaultCatalog() - if err != nil { - return nil, k6build.NewWrappedError(ErrInitializingBuilder, err) - } - - builder, err := k6foundry.NewDefaultNativeBuilder() - if err != nil { - return nil, k6build.NewWrappedError(ErrInitializingBuilder, err) - } - - store, err := file.NewTempFileStore() - if err != nil { - return nil, k6build.NewWrappedError(ErrInitializingBuilder, err) + return nil, k6build.NewWrappedError(builder.ErrInitializingBuilder, err) } - return &localBuildSrv{ - catalog: catalog, - builder: builder, - store: store, - }, nil -} - -func (b *localBuildSrv) Build( //nolint:funlen - ctx context.Context, - platform string, - k6Constrains string, - deps []k6build.Dependency, -) (k6build.Artifact, error) { - buildPlatform, err := k6foundry.ParsePlatform(platform) + store, err := file.NewFileStore(config.StoreDir) if err != nil { - return k6build.Artifact{}, k6build.NewWrappedError(ErrInvalidParameters, err) + return nil, k6build.NewWrappedError(builder.ErrInitializingBuilder, err) } - // sort dependencies to ensure idempotence of build - sort.Slice(deps, func(i, j int) bool { return deps[i].Name < deps[j].Name }) - resolved := map[string]string{} - - // check if it is a semver of the form v0.0.0+<build> - // if it is, we don't check with the catalog, but instead we use - // the build metadata as version when building this module - // the build process will return the actual version built in the build info - // and we can check that version with the catalog - var k6Mod k6catalog.Module - buildMetadata, err := hasBuildMetadata(k6Constrains) - if err != nil { - return k6build.Artifact{}, err - } - if buildMetadata != "" { - if !b.allowBuildSemvers { - return k6build.Artifact{}, k6build.NewWrappedError(ErrInvalidParameters, ErrBuildSemverNotAllowed) - } - k6Mod = k6catalog.Module{Path: k6Path, Version: buildMetadata} - } else { - k6Mod, err = b.catalog.Resolve(ctx, k6catalog.Dependency{Name: k6Dep, Constrains: k6Constrains}) - if err != nil { - return k6build.Artifact{}, k6build.NewWrappedError(ErrInvalidParameters, err) - } - } - resolved[k6Dep] = k6Mod.Version - - mods := []k6foundry.Module{} - for _, d := range deps { - m, modErr := b.catalog.Resolve(ctx, k6catalog.Dependency{Name: d.Name, Constrains: d.Constraints}) - if modErr != nil { - return k6build.Artifact{}, k6build.NewWrappedError(ErrInvalidParameters, modErr) - } - mods = append(mods, k6foundry.Module{Path: m.Path, Version: m.Version}) - resolved[d.Name] = m.Version - } - - // generate id form sorted list of dependencies - hashData := bytes.Buffer{} - hashData.WriteString(platform) - hashData.WriteString(fmt.Sprintf(":k6%s", k6Mod.Version)) - for _, d := range deps { - hashData.WriteString(fmt.Sprintf(":%s%s", d, resolved[d.Name])) - } - id := fmt.Sprintf("%x", sha1.Sum(hashData.Bytes())) //nolint:gosec - - unlock := b.lockArtifact(id) - defer unlock() - - artifactObject, err := b.store.Get(ctx, id) - if err == nil { - return k6build.Artifact{ - ID: id, - Checksum: artifactObject.Checksum, - URL: artifactObject.URL, - Dependencies: resolved, - Platform: platform, - }, nil - } - - if !errors.Is(err, store.ErrObjectNotFound) { - return k6build.Artifact{}, k6build.NewWrappedError(ErrAccessingArtifact, err) - } - - artifactBuffer := &bytes.Buffer{} - buildInfo, err := b.builder.Build(ctx, buildPlatform, k6Mod.Version, mods, []string{}, artifactBuffer) - if err != nil { - return k6build.Artifact{}, k6build.NewWrappedError(ErrAccessingArtifact, err) - } - - // if the version has a build metadata, we must use the actual version built - // TODO: check this version is supported - if buildMetadata != "" { - resolved[k6Dep] = buildInfo.ModVersions[k6Mod.Path] - } - - artifactObject, err = b.store.Put(ctx, id, artifactBuffer) - if err != nil { - return k6build.Artifact{}, k6build.NewWrappedError(ErrAccessingArtifact, err) - } - - return k6build.Artifact{ - ID: id, - Checksum: artifactObject.Checksum, - URL: artifactObject.URL, - Dependencies: resolved, - Platform: platform, - }, nil -} - -// lockArtifact obtains a mutex used to prevent concurrent builds of the same artifact and -// returns a function that will unlock the mutex associated to the given id in the object store. -// The lock is also removed from the map. Subsequent calls will get another lock on the same -// id but this is safe as the object should already be in the object strore and no further -// builds are needed. -func (b *localBuildSrv) lockArtifact(id string) func() { - value, _ := b.mutexes.LoadOrStore(id, &sync.Mutex{}) - mtx, _ := value.(*sync.Mutex) - mtx.Lock() - - return func() { - b.mutexes.Delete(id) - mtx.Unlock() - } -} - -// hasBuildMetadata checks if the constrain references a version with a build metadata. -// E.g. v0.1.0+build-effa45f -func hasBuildMetadata(constrain string) (string, error) { - opInx := constrainRe.SubexpIndex("operator") - verIdx := constrainRe.SubexpIndex("version") - preIdx := constrainRe.SubexpIndex("build") - matches := constrainRe.FindStringSubmatch(constrain) - - if matches == nil { - return "", nil - } - - op := matches[opInx] - ver := matches[verIdx] - build := matches[preIdx] - - if op != "" && op != "=" { - return "", k6build.NewWrappedError( - ErrInvalidParameters, - fmt.Errorf("only exact match is allowed for versions with build metadata"), - ) - } - - if ver != "v0.0.0" { - return "", k6build.NewWrappedError( - ErrInvalidParameters, - fmt.Errorf("version with build metadata must start with v0.0.0"), - ) - } - return build, nil + return builder.New(ctx, builder.Config{ + Opts: config.Opts, + Catalog: catalog, + Store: store, + }) } diff --git a/pkg/local/testutils.go b/pkg/local/testutils.go index 88d27ca..2f8a342 100644 --- a/pkg/local/testutils.go +++ b/pkg/local/testutils.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/grafana/k6build" + "github.com/grafana/k6build/pkg/builder" "github.com/grafana/k6build/pkg/store/file" "github.com/grafana/k6catalog" "github.com/grafana/k6foundry" @@ -61,24 +62,6 @@ func SetupTestLocalBuildService(t *testing.T) (k6build.BuildService, error) { goproxySrv := httptest.NewServer(proxy) - opts := k6foundry.NativeBuilderOpts{ - GoOpts: k6foundry.GoOpts{ - CopyGoEnv: true, - Env: map[string]string{ - "GOPROXY": goproxySrv.URL, - "GONOPROXY": "none", - "GOPRIVATE": "go.k6.io", - "GONOSUMDB": "go.k6.io", - }, - TmpCache: true, - }, - } - - builder, err := k6foundry.NewNativeBuilder(context.Background(), opts) - if err != nil { - return nil, fmt.Errorf("setting up test builder %w", err) - } - catalog, err := k6catalog.NewCatalogFromFile("testdata/catalog.json") if err != nil { return nil, fmt.Errorf("setting up test builder %w", err) @@ -89,11 +72,20 @@ func SetupTestLocalBuildService(t *testing.T) (k6build.BuildService, error) { return nil, fmt.Errorf("creating temporary object store %w", err) } - buildsrv := &localBuildSrv{ - builder: builder, - catalog: catalog, - store: store, - } - - return buildsrv, nil + return builder.New(context.Background(), builder.Config{ + Opts: builder.Opts{ + GoOpts: k6foundry.GoOpts{ + CopyGoEnv: true, + Env: map[string]string{ + "GOPROXY": goproxySrv.URL, + "GONOPROXY": "none", + "GOPRIVATE": "go.k6.io", + "GONOSUMDB": "go.k6.io", + }, + TmpCache: true, + }, + }, + Catalog: catalog, + Store: store, + }) } diff --git a/pkg/store/file/file.go b/pkg/store/file/file.go index e49424e..7e5bf69 100644 --- a/pkg/store/file/file.go +++ b/pkg/store/file/file.go @@ -118,7 +118,10 @@ func (f *Store) Get(_ context.Context, id string) (store.Object, error) { return store.Object{}, k6build.NewWrappedError(store.ErrAccessingObject, err) } - objectURL, _ := util.URLFromFilePath(filepath.Join(objectDir, "data")) + objectURL, err := util.URLFromFilePath(filepath.Join(objectDir, "data")) + if err != nil { + return store.Object{}, k6build.NewWrappedError(store.ErrAccessingObject, err) + } return store.Object{ ID: id, Checksum: string(checksum),