diff --git a/internal/commands/config_default_builder.go b/internal/commands/config_default_builder.go index 111eebea7..c35622ec1 100644 --- a/internal/commands/config_default_builder.go +++ b/internal/commands/config_default_builder.go @@ -2,6 +2,10 @@ package commands import ( "fmt" + "os" + "os/signal" + "syscall" + "time" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -48,7 +52,7 @@ func ConfigDefaultBuilder(logger logging.Logger, cfg config.Config, cfgPath stri return nil default: imageName := args[0] - if err := validateBuilderExists(logger, imageName, client); err != nil { + if err := ValidateBuilderExists(logger, imageName, client); err != nil { return errors.Wrapf(err, "validating that builder %s exists", style.Symbol(imageName)) } @@ -68,24 +72,44 @@ func ConfigDefaultBuilder(logger logging.Logger, cfg config.Config, cfgPath stri return cmd } -func validateBuilderExists(logger logging.Logger, imageName string, client PackClient) error { - logger.Debug("Verifying local image...") - info, err := client.InspectBuilder(imageName, true) - if err != nil { - return err - } +func ValidateBuilderExists(logger logging.Logger, imageName string, client PackClient) error { + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) - if info == nil { - logger.Debug("Verifying remote image...") - info, err := client.InspectBuilder(imageName, false) + resultChan := make(chan error, 1) + + go func() { + logger.Debug("Verifying local image...") + info, err := client.InspectBuilder(imageName, true) if err != nil { - return errors.Wrapf(err, "failed to inspect remote image %s", style.Symbol(imageName)) + resultChan <- err + return } if info == nil { - return fmt.Errorf("builder %s not found", style.Symbol(imageName)) + logger.Debug("Verifying remote image...") + info, err = client.InspectBuilder(imageName, false) + if err != nil { + resultChan <- errors.Wrapf(err, "failed to inspect remote image %s", style.Symbol(imageName)) + return + } + + if info == nil { + resultChan <- fmt.Errorf("builder %s not found", style.Symbol(imageName)) + return + } } - } - return nil + resultChan <- nil + }() + + select { + case err := <-resultChan: + return err + case <-sigChan: + return fmt.Errorf("operation aborted") + case <-time.After(30 * time.Second): + return fmt.Errorf("operation timed out") + } } diff --git a/internal/commands/config_default_builder_test.go b/internal/commands/config_default_builder_test.go index e06cde74b..7132b22a6 100644 --- a/internal/commands/config_default_builder_test.go +++ b/internal/commands/config_default_builder_test.go @@ -5,7 +5,9 @@ import ( "fmt" "os" "path/filepath" + "syscall" "testing" + "time" "github.com/golang/mock/gomock" "github.com/heroku/color" @@ -197,6 +199,35 @@ func testConfigDefaultBuilder(t *testing.T, when spec.G, it spec.S) { h.AssertContains(t, outBuf.String(), "builder 'nonexisting/image' not found") }) }) + + when("SIGINT is received during query", func() { + it("aborts the operation", func() { + imageName := "myremotehub.com/buildpacks/builder:latest" + + mockClient.EXPECT().InspectBuilder(imageName, true, gomock.Any()).DoAndReturn(func(string, bool, ...client.BuilderInspectionModifier) (*client.BuilderInfo, error) { + time.Sleep(5 * time.Second) + return nil, nil + }) + + done := make(chan error, 1) + go func() { + cmd.SetArgs([]string{imageName}) + done <- cmd.Execute() + }() + + time.Sleep(2 * time.Second) + + p, err := os.FindProcess(os.Getpid()) + h.AssertNil(t, err) + p.Signal(syscall.SIGINT) + + err = <-done + + h.AssertError(t, err, "operation aborted") + h.AssertContains(t, outBuf.String(), "operation aborted") + }) + }) + }) }) }