-
Notifications
You must be signed in to change notification settings - Fork 20
Graceful otel shutdown on build failure #320
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ package cmd | |
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
| "io" | ||
| "net/http" | ||
|
|
@@ -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 { | ||
| _, 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit, not blocking:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added
WVerlaek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit, not blocking: The same is true with above errors. I wonder if maybe I am missing a convention?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
|
@@ -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() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
||
There was a problem hiding this comment.
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 theRunfunction above, except thatlog.Fatals have been replaced by returning an error.RunEabove still logs any errors withlog.Fatal, so the leeway log output remains the same