Skip to content
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

Improve error output handling #293

Merged
merged 4 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,20 +78,17 @@ func NewOvermindInstance(ctx context.Context, app string) (OvermindInstance, err
log.WithField("instanceDataUrl", instanceDataUrl).Debug("Fetching instance-data")
res, err := otelhttp.DefaultClient.Do(req)
if err != nil {
log.WithContext(ctx).WithError(err).Error("could not fetch instance-data")
return OvermindInstance{}, fmt.Errorf("could not fetch instance-data: %w", err)
}

if res.StatusCode != 200 {
log.WithContext(ctx).WithField("status-code", res.StatusCode).Error("instance-data fetch returned non-200 status")
return OvermindInstance{}, fmt.Errorf("instance-data fetch returned non-200 status: %v", res.StatusCode)
}

defer res.Body.Close()
data := instanceData{}
err = json.NewDecoder(res.Body).Decode(&data)
if err != nil {
log.WithContext(ctx).WithError(err).Error("could not parse instance-data")
return OvermindInstance{}, fmt.Errorf("could not parse instance-data: %w", err)
}

Expand Down
11 changes: 11 additions & 0 deletions cmd/tea_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ type taskModel struct {
spinner spinner.Model
}

type WithTaskModel interface {
TaskModel() taskModel
}

// assert that taskModel implements WithTaskModel
var _ WithTaskModel = (*taskModel)(nil)

func NewTaskModel(title string) taskModel {
return taskModel{
status: taskStatusPending,
Expand All @@ -73,6 +80,10 @@ func (m taskModel) Init() tea.Cmd {
return nil
}

func (m taskModel) TaskModel() taskModel {
return m
}

func (m taskModel) Update(msg tea.Msg) (taskModel, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
Expand Down
4 changes: 4 additions & 0 deletions cmd/tea_ensuretoken.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ func NewEnsureTokenModel(ctx context.Context, app string, apiKey string, require
}
}

func (m ensureTokenModel) TaskModel() taskModel {
return m.taskModel
}

func (m ensureTokenModel) Init() tea.Cmd {
return m.taskModel.Init()
}
Expand Down
4 changes: 4 additions & 0 deletions cmd/tea_initialisesources.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func NewInitialiseSourcesModel() tea.Model {
}
}

func (m initialiseSourcesModel) TaskModel() taskModel {
return m.taskModel
}

func (m initialiseSourcesModel) Init() tea.Cmd {
return m.taskModel.Init()
}
Expand Down
4 changes: 4 additions & 0 deletions cmd/tea_startup.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ func NewInstanceLoaderModel(ctx context.Context, app string) tea.Model {
return result
}

func (m instanceLoaderModel) TaskModel() taskModel {
return m.taskModel
}

func (m instanceLoaderModel) Init() tea.Cmd {
return tea.Batch(
m.taskModel.Init(),
Expand Down
32 changes: 29 additions & 3 deletions cmd/tea_terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ type cmdModel struct {
requiredScopes []string

// UI state
tasks map[string]tea.Model
fatalError string // this will get set if there's a fatalError coming through that doesn't have a task ID set
tasks map[string]tea.Model
terraformHasStarted bool // remember whether terraform already has started. this is important to do the correct workarounds on errors. See also `skipView()`
fatalErrorSeen bool // remember whether a fatalError has been seen to avoid showing pending tasks
fatalError string // this will get set if there's a fatalError coming through that doesn't have a task ID set

// business logic. This model will implement the actual CLI functionality requested.
cmd tea.Model
Expand Down Expand Up @@ -82,10 +84,21 @@ func (m cmdModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

case fatalError:
log.WithError(msg.err).WithField("msg.id", msg.id).Debug("cmdModel: fatalError received")

// skipView based on the previous view. While this is not perfect, it's
// the best we can currently do without taking complete control of
// terraform command i/o
if m.terraformHasStarted {
skipView(m.View())
}

m.fatalErrorSeen = true

// record the fatal error here if it was not from a specific taskModel
if msg.id == 0 {
m.fatalError = msg.err.Error()
}
skipView(m.View())

return m, tea.Sequence(
tea.Batch(batch...),
tea.Quit,
Expand All @@ -101,6 +114,9 @@ func (m cmdModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
batch = append(batch, cmd)
return tm, tea.Batch(batch...)

case triggerTfPlanMsg, runTfApplyMsg:
m.terraformHasStarted = true

case tfPlanFinishedMsg, tfApplyFinishedMsg:
// bump screen after terraform ran
skipView(m.View())
Expand Down Expand Up @@ -172,6 +188,16 @@ func (m cmdModel) View() string {
}
sort.Strings(keys)
for _, k := range keys {
// when we're quitting due to a fatal error, don't show pending tasks as
// they are not relevant anymore
if m.fatalErrorSeen {
t, ok := m.tasks[k].(WithTaskModel)
if ok {
if t.TaskModel().status == taskStatusPending {
continue
}
}
}
tasks = append(tasks, m.tasks[k].View())
}
tasks = append(tasks, m.cmd.View())
Expand Down
8 changes: 5 additions & 3 deletions cmd/terraform_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,11 @@ func (m tfPlanModel) View() string {
bits = append(bits, fmt.Sprintf("\nCheck the blast radius graph and risks at:\n%v\n\n", m.changeUrl))
}

if m.fatalError != "" {
bits = append(bits, deletedLineStyle.Render(fmt.Sprintf("Error: %v", m.fatalError)))
}
// This doesn't do line-wrapping for long errors and is duplicated by the
// fatalError view on cmdModel
// if m.fatalError != "" {
// bits = append(bits, deletedLineStyle.Render(fmt.Sprintf("Error: %v", m.fatalError)))
// }

return strings.Join(bits, "\n") + "\n"
}
Expand Down
Loading