From fc1ba6a47f283f1d68011daf7f0ec9bff45f9fb3 Mon Sep 17 00:00:00 2001 From: Mark Benjamin Date: Thu, 21 Nov 2024 15:34:52 -0500 Subject: [PATCH] Improve SSH errors (#142) * Update errors for ssh * Make errors centered * Make errors more readable --- cmd/root.go | 3 ++- pkg/deploy/repo.go | 4 ++-- pkg/tui/error.go | 33 +++++++++++++++++++++++++++------ pkg/tui/stack.go | 19 ++++++++++++++----- pkg/tui/views/ssh.go | 28 +++++++++++++++++++++++++++- 5 files changed, 72 insertions(+), 15 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 3f5e278..a1843c4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -67,7 +67,8 @@ var rootCmd = &cobra.Command{ println(err.Error()) os.Exit(1) } - if output == command.Interactive && (isPipe() || isCI()) { + // Honor the output flag if it's set + if outputFlag == "" && output == command.Interactive && (isPipe() || isCI()) { output = command.TEXT } ctx = command.SetFormatInContext(ctx, &output) diff --git a/pkg/deploy/repo.go b/pkg/deploy/repo.go index c759ed4..b085006 100644 --- a/pkg/deploy/repo.go +++ b/pkg/deploy/repo.go @@ -14,8 +14,8 @@ func NewRepo(c *client.ClientWithResponses) *Repo { return &Repo{client: c} } -func (d *Repo) ListDeploysForService(ctx context.Context, serviceID string) ([]*client.Deploy, error) { - resp, err := d.client.ListDeploysWithResponse(ctx, serviceID, nil) +func (d *Repo) ListDeploysForService(ctx context.Context, serviceID string, params *client.ListDeploysParams) ([]*client.Deploy, error) { + resp, err := d.client.ListDeploysWithResponse(ctx, serviceID, params) if err != nil { return nil, err } diff --git a/pkg/tui/error.go b/pkg/tui/error.go index 72e81fa..15865d2 100644 --- a/pkg/tui/error.go +++ b/pkg/tui/error.go @@ -1,6 +1,8 @@ package tui import ( + "errors" + tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) @@ -18,16 +20,16 @@ var style = lipgloss.NewStyle(). MarginBottom(1) type ErrorModel struct { - DisplayError string - width int - height int + Err error + width int + height int } func NewErrorModel( - displayError string, + err error, ) *ErrorModel { m := &ErrorModel{ - DisplayError: displayError, + Err: err, } return m @@ -48,5 +50,24 @@ func (m *ErrorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } func (m *ErrorModel) View() string { - return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, style.Render(m.DisplayError)) + var title string + var message string + + userFacingError := &UserFacingError{} + if errors.As(m.Err, userFacingError) { + title = userFacingError.Title + message = userFacingError.Message + } else { + message = m.Err.Error() + } + + var interior string + + if title == "" { + interior = lipgloss.JoinVertical(lipgloss.Center, message) + } else { + interior = lipgloss.JoinVertical(lipgloss.Center, title, message) + } + + return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, style.Render(interior)) } diff --git a/pkg/tui/stack.go b/pkg/tui/stack.go index 383841e..b3d45df 100644 --- a/pkg/tui/stack.go +++ b/pkg/tui/stack.go @@ -13,6 +13,16 @@ import ( renderstyle "github.com/renderinc/cli/pkg/style" ) +type UserFacingError struct { + Title string + Message string + Err error +} + +func (u UserFacingError) Error() string { + return u.Err.Error() +} + var stackHeaderStyle = lipgloss.NewStyle().MarginTop(1).MarginBottom(1) var stackInfoStyle = lipgloss.NewStyle().Foreground(renderstyle.ColorBreadcrumb).Bold(true) @@ -71,7 +81,7 @@ func newSpinner() *spinner.Model { func (m *StackModel) Push(model ModelWithCmd) tea.Cmd { m.stack = append(m.stack, model) - return tea.Batch(model.Model.Init(), func() tea.Msg { return m.StackSizeMsg() }) + return tea.Sequence(model.Model.Init(), tea.WindowSize()) } func (m *StackModel) Pop() *ModelWithCmd { @@ -118,7 +128,7 @@ func (m *StackModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if len(m.stack) > 0 { err := clipboard.WriteAll(m.stack[len(m.stack)-1].Cmd) if err != nil { - m.Push(ModelWithCmd{Model: NewErrorModel("Failed to copy command to clipboard")}) + m.Push(ModelWithCmd{Model: NewErrorModel(fmt.Errorf("Failed to copy command to clipboard"))}) } } } @@ -147,11 +157,10 @@ func (m *StackModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { breadcrumb = previous.Breadcrumb } - m.Push(ModelWithCmd{ - Model: NewErrorModel(msg.Err.Error()), + return m, m.Push(ModelWithCmd{ + Model: NewErrorModel(msg.Err), Breadcrumb: breadcrumb, }) - return m, nil case spinner.TickMsg: if m.loadingSpinner != nil { spin, cmd := m.loadingSpinner.Update(msg) diff --git a/pkg/tui/views/ssh.go b/pkg/tui/views/ssh.go index 488824f..a0813cb 100644 --- a/pkg/tui/views/ssh.go +++ b/pkg/tui/views/ssh.go @@ -8,6 +8,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/renderinc/cli/pkg/client" "github.com/renderinc/cli/pkg/command" + "github.com/renderinc/cli/pkg/deploy" "github.com/renderinc/cli/pkg/resource" "github.com/renderinc/cli/pkg/service" "github.com/renderinc/cli/pkg/tui" @@ -88,7 +89,32 @@ func loadDataSSH(ctx context.Context, in *SSHInput) (*exec.Cmd, error) { } else if details, err := serviceInfo.ServiceDetails.AsBackgroundWorkerDetails(); err == nil { sshAddress = details.SshAddress } else { - return nil, fmt.Errorf("unsupported service type") + return nil, tui.UserFacingError{ + Title: "Failed to SSH", Message: fmt.Sprintf("Cannot SSH into %s service type.", serviceInfo.Type), + } + } + + if serviceInfo.Suspended == client.ServiceSuspendedSuspended { + return nil, tui.UserFacingError{Title: "Failed to SSH", Message: "Cannot SSH into a suspended service."} + } + + deploys, err := deploy.NewRepo(c).ListDeploysForService(ctx, in.ServiceID, &client.ListDeploysParams{}) + if err != nil { + return nil, err + } + + foundLiveDeploy := false + for _, deploy := range deploys { + if deploy.Status != nil && *deploy.Status == client.DeployStatusLive { + foundLiveDeploy = true + break + } + } + + if !foundLiveDeploy { + return nil, tui.UserFacingError{ + Title: "Failed to SSH", Message: "Cannot SSH into a service with no live deploys.", + } } if sshAddress == nil {