Skip to content

Commit

Permalink
Merge pull request #133 from ninech/logbox-interrupt
Browse files Browse the repository at this point in the history
fix: improve logbox interrupt handling
  • Loading branch information
ctrox authored Jul 24, 2024
2 parents fb91425 + 823062b commit f7895fe
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 50 deletions.
43 changes: 16 additions & 27 deletions create/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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 {
Expand All @@ -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}
}

Expand Down
21 changes: 16 additions & 5 deletions create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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
}
}
}
}
Expand Down
14 changes: 10 additions & 4 deletions delete/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
}
15 changes: 2 additions & 13 deletions internal/logbox/logbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"os/signal"
"strings"
"syscall"

"github.com/alecthomas/kong"

Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit f7895fe

Please sign in to comment.