Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 80 additions & 57 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"context"
"errors"
"fmt"
"io"
"net/http"
Expand Down Expand Up @@ -35,80 +36,101 @@ var buildCmd = &cobra.Command{
Docker Export Mode:
By default, Docker packages with 'image' configuration push directly to registries.
Use --docker-export-to-cache to export images to cache instead (enables SLSA L3).

The LEEWAY_DOCKER_EXPORT_TO_CACHE environment variable sets the default for the flag.

Examples:
# Build with Docker export mode enabled (CLI flag)
leeway build --docker-export-to-cache :myapp

# Build with Docker export mode enabled (environment variable)
LEEWAY_DOCKER_EXPORT_TO_CACHE=true leeway build :myapp

# Disable export mode even if env var is set
leeway build --docker-export-to-cache=false :myapp`,
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
_, pkg, _, _ := getTarget(args, false)
if pkg == nil {
log.Fatal("build needs a package")
RunE: func(cmd *cobra.Command, args []string) error {
err := build(cmd, args)
if err != nil {
log.Fatal(err)
}
opts, localCache, shutdown := getBuildOpts(cmd)
defer shutdown()

var (
watch, _ = cmd.Flags().GetBool("watch")
save, _ = cmd.Flags().GetString("save")
serve, _ = cmd.Flags().GetString("serve")
)
if watch {
err := leeway.Build(pkg, opts...)
return nil
},
}

func build(cmd *cobra.Command, args []string) error {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build() is the same logic as what was in the Run function above, except that log.Fatals have been replaced by returning an error.

RunE above still logs any errors with log.Fatal, so the leeway log output remains the same

_, pkg, _, _ := getTarget(args, false)
if pkg == nil {
return errors.New("build needs a package")
}
opts, localCache, shutdown := getBuildOpts(cmd)
defer shutdown()

var (
watch, _ = cmd.Flags().GetBool("watch")
save, _ = cmd.Flags().GetString("save")
serve, _ = cmd.Flags().GetString("serve")
)
if watch {
err := leeway.Build(pkg, opts...)
if err != nil {
return err
}
ctx, cancel := context.WithCancel(context.Background())
if save != "" {
err = saveBuildResult(ctx, save, localCache, pkg)
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
if save != "" {
saveBuildResult(ctx, save, localCache, pkg)
}
if serve != "" {
go serveBuildResult(ctx, serve, localCache, pkg)
cancel()
return err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, not blocking:
It looks like we could invoke cancel() before returning the error, to be tidy, not a functional problem.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

}
}
if serve != "" {
go serveBuildResult(ctx, serve, localCache, pkg)
}

evt, errs := leeway.WatchSources(context.Background(), append(pkg.GetTransitiveDependencies(), pkg), 2*time.Second)
for {
select {
case <-evt:
_, pkg, _, _ := getTarget(args, false)
err := leeway.Build(pkg, opts...)
if err == nil {
cancel()
ctx, cancel = context.WithCancel(context.Background())
if save != "" {
saveBuildResult(ctx, save, localCache, pkg)
}
if serve != "" {
go serveBuildResult(ctx, serve, localCache, pkg)
evt, errs := leeway.WatchSources(context.Background(), append(pkg.GetTransitiveDependencies(), pkg), 2*time.Second)
for {
select {
case <-evt:
_, pkg, _, _ := getTarget(args, false)
err := leeway.Build(pkg, opts...)
if err == nil {
cancel()
ctx, cancel = context.WithCancel(context.Background())
if save != "" {
err = saveBuildResult(ctx, save, localCache, pkg)
if err != nil {
cancel()
return err
}
} else {
log.Error(err)
}
case err = <-errs:
log.Fatal(err)
if serve != "" {
go serveBuildResult(ctx, serve, localCache, pkg)
}
} else {
log.Error(err)
}
case err = <-errs:
cancel()
return err
}
}
}

err := leeway.Build(pkg, opts...)
err := leeway.Build(pkg, opts...)
if err != nil {
return err
}
if save != "" {
err = saveBuildResult(context.Background(), save, localCache, pkg)
if err != nil {
log.Fatal(err)
return err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, not blocking:
We could wrap the error with with return fmt.Errorf("...: %w", err) to help future us catch where this happened.

The same is true with above errors. I wonder if maybe I am missing a convention?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't want to change the output that leeway produces, assumption was that these errors already include enough details and wrapping them would make them more verbose

}
if save != "" {
saveBuildResult(context.Background(), save, localCache, pkg)
}
if serve != "" {
serveBuildResult(context.Background(), serve, localCache, pkg)
}
},
}
if serve != "" {
serveBuildResult(context.Background(), serve, localCache, pkg)
}
return nil
}

func serveBuildResult(ctx context.Context, addr string, localCache cache.LocalCache, pkg *leeway.Package) {
Expand Down Expand Up @@ -148,30 +170,31 @@ func serveBuildResult(ctx context.Context, addr string, localCache cache.LocalCa
}
}

func saveBuildResult(ctx context.Context, loc string, localCache cache.LocalCache, pkg *leeway.Package) {
func saveBuildResult(ctx context.Context, loc string, localCache cache.LocalCache, pkg *leeway.Package) error {
br, exists := localCache.Location(pkg)
if !exists {
log.Fatal("build result is not in local cache despite just being built. Something's wrong with the cache.")
return errors.New("build result is not in local cache despite just being built. Something's wrong with the cache.")
}

fout, err := os.OpenFile(loc, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.WithError(err).Fatal("cannot open result file for writing")
return fmt.Errorf("cannot open result file for writing: %w", err)
}
fin, err := os.OpenFile(br, os.O_RDONLY, 0644)
if err != nil {
fout.Close()
log.WithError(err).Fatal("cannot copy build result")
return fmt.Errorf("cannot copy build result: %w", err)
}

_, err = io.Copy(fout, fin)
fout.Close()
fin.Close()
if err != nil {
log.WithError(err).Fatal("cannot copy build result")
return fmt.Errorf("cannot copy build result: %w", err)
}

fmt.Printf("\n💾 saving build result to %s\n", color.Cyan.Render(loc))
return nil
}

func init() {
Expand Down
2 changes: 1 addition & 1 deletion pkg/leeway/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func newBuildContext(options buildOptions) (ctx *buildContext, err error) {

// Ensure cache directory exists with proper permissions
if err := os.MkdirAll(buildDir, 0755); err != nil {
log.WithError(err).Fatal("failed to create build directory")
return nil, xerrors.Errorf("failed to create build directory: %w", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, not blocking:
func newBuildContext is used by leeway when running scripts, too. (ref)

Build was changed to RunE, but not Run.

We could do similar for running scripts (to be consistent). (ref).

However, if we don't, that's fine because it'll still handle the error and log.fatal.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would still result in a log.Fatal indeed when an error gets returned as part of the run command. There's a ton of log.Fatal left throughout the codebase, it would require a larger cleanup to change all

}

var buildLimit *semaphore.Weighted
Expand Down
Loading