diff --git a/create/application.go b/create/application.go index 971ba01..1b37657 100644 --- a/create/application.go +++ b/create/application.go @@ -12,7 +12,6 @@ import ( "github.com/alecthomas/kong" tea "github.com/charmbracelet/bubbletea" "github.com/grafana/loki/pkg/logproto" - "github.com/mattn/go-isatty" apps "github.com/ninech/apis/apps/v1alpha1" meta "github.com/ninech/apis/meta/v1alpha1" "github.com/ninech/nctl/api" @@ -186,7 +185,7 @@ func (app *applicationCmd) Run(ctx context.Context, client *api.Client) error { if err := c.wait( appWaitCtx, waitForBuildStart(newApp), - waitForBuildFinish(appWaitCtx, cancel, newApp, client.Log), + waitForBuildFinish(appWaitCtx, newApp, client.Log), waitForRelease(newApp), ); err != nil { if buildErr, ok := err.(buildError); ok { @@ -360,19 +359,14 @@ func waitForBuildStart(app *apps.Application) waitStage { } } -func waitForBuildFinish(ctx context.Context, cancel context.CancelFunc, app *apps.Application, logClient *log.Client) waitStage { +func waitForBuildFinish(ctx context.Context, app *apps.Application, logClient *log.Client) waitStage { msg := message{icon: "📦", text: "building application"} - interrupt := make(chan bool, 1) - lb := logbox.New(15, msg.progress(), interrupt) - opts := []tea.ProgramOption{tea.WithoutSignalHandler()} - - // disable input if we are not in a terminal - if !isatty.IsTerminal(os.Stdout.Fd()) { - opts = append(opts, tea.WithInput(nil)) - } - - p := tea.NewProgram(lb, opts...) - + p := tea.NewProgram( + logbox.New(15, msg.progress()), + tea.WithoutSignalHandler(), + tea.WithInput(nil), + tea.WithContext(ctx), + ) return waitStage{ disableSpinner: true, kind: strings.ToLower(apps.BuildKind), @@ -406,18 +400,17 @@ func waitForBuildFinish(ctx context.Context, cancel context.CancelFunc, app *app go func() { if _, err := p.Run(); err != nil { - fmt.Fprintf(os.Stderr, "error running tea program: %s", err) - return - } - p.Wait() - if <-interrupt { - // as the tea program intercepts ctrl+c/d while it's - // running, we need to cancel the context when we get an - // interrupt signal. - cancel() + if !errors.Is(tea.ErrProgramKilled, err) { + fmt.Fprintf(os.Stderr, "error running tea program: %s", err) + } } }() }, + afterWait: func() { + // ensure to cleanly shutdown the tea program + p.Quit() + p.Wait() + }, onResult: func(e watch.Event) (bool, error) { build, ok := e.Object.(*apps.Build) if !ok { @@ -427,14 +420,10 @@ func waitForBuildFinish(ctx context.Context, cancel context.CancelFunc, app *app switch build.Status.AtProvider.BuildStatus { case buildStatusSuccess: p.Send(logbox.Msg{Done: true}) - p.Quit() - p.Wait() return true, nil case buildStatusError: fallthrough case buildStatusUnknown: - p.Quit() - p.Wait() return false, buildError{build: build} } diff --git a/create/create.go b/create/create.go index 59df0e8..a121892 100644 --- a/create/create.go +++ b/create/create.go @@ -60,6 +60,8 @@ type waitStage struct { disableSpinner bool // beforeWait is a hook that is called just before the wait is being run. beforeWait func() + // afterWait is a hook that is called after the wait to clean up. + afterWait func() } type message struct { @@ -106,6 +108,10 @@ func (c *creator) createResource(ctx context.Context) error { func (c *creator) wait(ctx context.Context, stages ...waitStage) error { for _, stage := range stages { + if stage.afterWait != nil { + defer stage.afterWait() + } + stage.setDefaults(c) spinner, err := format.NewSpinner( @@ -174,7 +180,6 @@ func isWatchError(err error) bool { } func (w *waitStage) wait(ctx context.Context, client *api.Client) error { - if !w.disableSpinner { _ = w.spinner.Start() } @@ -213,11 +218,17 @@ func (w *waitStage) watch(ctx context.Context, client *api.Client) error { return nil } case <-ctx.Done(): - msg := "timeout waiting for %s" - w.spinner.StopFailMessage(format.ProgressMessagef("", msg, w.kind)) - _ = w.spinner.StopFail() + switch ctx.Err() { + case context.DeadlineExceeded: + msg := "timeout waiting for %s" + w.spinner.StopFailMessage(format.ProgressMessagef("", msg, w.kind)) + _ = w.spinner.StopFail() - return fmt.Errorf(msg, w.kind) + return fmt.Errorf(msg, w.kind) + case context.Canceled: + _ = w.spinner.StopFail() + return nil + } } } } diff --git a/delete/delete.go b/delete/delete.go index 7d819f1..f397c1b 100644 --- a/delete/delete.go +++ b/delete/delete.go @@ -147,11 +147,17 @@ func (d *deleter) waitForDeletion(ctx context.Context, client *api.Client) error return fmt.Errorf("unable to get %s %q: %w", d.kind, d.mg.GetName(), err) } case <-ctx.Done(): - msg := "timeout waiting for %s" - spinner.StopFailMessage(format.ProgressMessagef("", msg, d.kind)) - _ = spinner.StopFail() + switch ctx.Err() { + case context.DeadlineExceeded: + msg := "timeout waiting for %s" + spinner.StopFailMessage(format.ProgressMessagef("", msg, d.kind)) + _ = spinner.StopFail() - return fmt.Errorf("timeout waiting for %s", d.kind) + return fmt.Errorf(msg, d.kind) + case context.Canceled: + _ = spinner.StopFail() + return nil + } } } } diff --git a/internal/logbox/logbox.go b/internal/logbox/logbox.go index 334cfe8..fded650 100644 --- a/internal/logbox/logbox.go +++ b/internal/logbox/logbox.go @@ -34,19 +34,16 @@ type LogBox struct { results []Msg quitting bool spinner spinner.Model - interrupt chan bool } -// New initializes a LogBox. The interrupt channel will be written to on -// ctrl+c/ctrl+d since it intercepts these. -func New(height int, waitMessage string, interrupt chan bool) LogBox { +// New initializes a LogBox. +func New(height int, waitMessage string) LogBox { s := spinner.New(spinner.WithSpinner(spinner.MiniDot), spinner.WithStyle(lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{}))) return LogBox{ height: height, waitMessage: waitMessage, spinner: s, results: []Msg{}, - interrupt: interrupt, } } @@ -56,14 +53,6 @@ func (lb LogBox) Init() tea.Cmd { func (lb LogBox) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.String() { - case "ctrl+c", "ctrl+d": - lb.interrupt <- true - return lb, tea.Quit - default: - return lb, nil - } case Msg: if msg.Done { lb.quitting = true diff --git a/main.go b/main.go index 11a7ae7..ceafec0 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "os" "os/signal" "strings" + "syscall" "github.com/alecthomas/kong" @@ -145,7 +146,7 @@ func main() { func setupSignalHandler(ctx context.Context, cancel context.CancelFunc) { c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) + signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) go func() { defer func() { signal.Stop(c)