Skip to content

Commit be8fda9

Browse files
committed
fix(update): return better errors
1 parent 5be93c5 commit be8fda9

File tree

7 files changed

+137
-117
lines changed

7 files changed

+137
-117
lines changed

cmd/arduino-app-cli/system/system.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,13 @@ func newUpdateCmd() *cobra.Command {
113113

114114
events := updater.Subscribe()
115115
for event := range events {
116-
feedback.Printf("[%s] %s", event.Type.String(), event.Data)
116+
if event.Type == update.ErrorEvent {
117+
// TODO: add colors to error messages
118+
err := event.GetError()
119+
feedback.Printf("Error: %s [%s]", err.Error(), update.GetUpdateErrorCode(err))
120+
} else {
121+
feedback.Printf("[%s] %s", event.Type.String(), event.GetData())
122+
}
117123

118124
if event.Type == update.DoneEvent {
119125
break

internal/api/handlers/update.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,19 @@ func HandleUpdateEvents(updater *update.Manager) http.HandlerFunc {
128128
return
129129
}
130130
if event.Type == update.ErrorEvent {
131+
err := event.GetError()
132+
code := render.InternalServiceErr
133+
if c := update.GetUpdateErrorCode(err); c != update.UnknownError {
134+
code = render.SSEErrCode(string(c))
135+
}
131136
sseStream.SendError(render.SSEErrorData{
132-
Code: render.InternalServiceErr,
133-
Message: event.Data,
137+
Code: code,
138+
Message: err.Error(),
134139
})
135140
} else {
136141
sseStream.Send(render.SSEEvent{
137142
Type: event.Type.String(),
138-
Data: event.Data,
143+
Data: event.GetData(),
139144
})
140145
}
141146

internal/update/apt/service.go

Lines changed: 17 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
"regexp"
2626
"strings"
2727
"sync"
28-
"time"
2928

3029
"github.com/arduino/go-paths-helper"
3130
"go.bug.st/f"
@@ -84,79 +83,55 @@ func (s *Service) UpgradePackages(ctx context.Context, names []string) (<-chan u
8483
defer s.lock.Unlock()
8584
defer close(eventsCh)
8685

87-
ctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
88-
defer cancel()
89-
90-
eventsCh <- update.Event{Type: update.StartEvent, Data: "Upgrade is starting"}
86+
eventsCh <- update.NewDataEvent(update.StartEvent, "Upgrade is starting")
9187
stream := runUpgradeCommand(ctx, names)
9288
for line, err := range stream {
9389
if err != nil {
94-
eventsCh <- update.Event{
95-
Type: update.ErrorEvent,
96-
Err: err,
97-
Data: "Error running upgrade command",
98-
}
99-
slog.Error("error processing upgrade command output", "error", err)
90+
eventsCh <- update.NewErrorEvent(fmt.Errorf("error running upgrade command: %w", err))
10091
return
10192
}
102-
eventsCh <- update.Event{Type: update.UpgradeLineEvent, Data: line}
93+
eventsCh <- update.NewDataEvent(update.UpgradeLineEvent, line)
10394
}
104-
eventsCh <- update.Event{Type: update.StartEvent, Data: "apt cleaning cache is starting"}
95+
96+
eventsCh <- update.NewDataEvent(update.StartEvent, "apt cleaning cache is starting")
10597
for line, err := range runAptCleanCommand(ctx) {
10698
if err != nil {
107-
eventsCh <- update.Event{
108-
Type: update.ErrorEvent,
109-
Err: err,
110-
Data: "Error running apt clean command",
111-
}
112-
slog.Error("error processing apt clean command output", "error", err)
99+
eventsCh <- update.NewErrorEvent(fmt.Errorf("error running apt clean command: %w", err))
113100
return
114101
}
115-
eventsCh <- update.Event{Type: update.UpgradeLineEvent, Data: line}
102+
eventsCh <- update.NewDataEvent(update.UpgradeLineEvent, line)
116103
}
117-
// TEMPORARY PATCH: stopping and destroying docker containers and images since IDE does not implement it yet.
118-
// TODO: Remove this workaround once IDE implements it.
119-
// Tracking issue: https://github.com/arduino/arduino-app-cli/issues/623
120-
eventsCh <- update.Event{Type: update.UpgradeLineEvent, Data: "Stop and destroy docker containers and images ..."}
104+
105+
eventsCh <- update.NewDataEvent(update.UpgradeLineEvent, "Apt upgrade completed successfully.")
121106
streamCleanup := cleanupDockerContainers(ctx)
122107
for line, err := range streamCleanup {
123108
if err != nil {
124109
// TODO: maybe we should retun an error or a better feedback to the user?
125110
// currently, we just log the error and continue considenring not blocking
126-
slog.Error("Error stopping and destroying docker containers", "error", err)
111+
slog.Warn("Error stopping and destroying docker containers", "error", err)
112+
} else {
113+
eventsCh <- update.NewDataEvent(update.UpgradeLineEvent, line)
127114
}
128-
eventsCh <- update.Event{Type: update.UpgradeLineEvent, Data: line}
129115
}
130116

131-
// TEMPORARY PATCH: Install the latest docker images and show the logs to the users.
132117
// TODO: Remove this workaround once docker image versions are no longer hardcoded in arduino-app-cli.
133118
// Tracking issue: https://github.com/arduino/arduino-app-cli/issues/600
134119
// Currently, we need to launch `arduino-app-cli system init` to pull the latest docker images because
135120
// the version of the docker images are hardcoded in the (new downloaded) version of the arduino-app-cli.
136-
eventsCh <- update.Event{Type: update.UpgradeLineEvent, Data: "Pulling the latest docker images ..."}
121+
eventsCh <- update.NewDataEvent(update.UpgradeLineEvent, "Pulling the latest docker images ...")
137122
streamDocker := pullDockerImages(ctx)
138123
for line, err := range streamDocker {
139124
if err != nil {
140-
eventsCh <- update.Event{
141-
Type: update.ErrorEvent,
142-
Err: err,
143-
Data: "Error upgrading docker images",
144-
}
145-
slog.Error("error upgrading docker images", "error", err)
125+
eventsCh <- update.NewErrorEvent(fmt.Errorf("error pulling docker images: %w", err))
146126
return
147127
}
148-
eventsCh <- update.Event{Type: update.UpgradeLineEvent, Data: line}
128+
eventsCh <- update.NewDataEvent(update.UpgradeLineEvent, line)
149129
}
150-
eventsCh <- update.Event{Type: update.RestartEvent, Data: "Upgrade completed. Restarting ..."}
130+
eventsCh <- update.NewDataEvent(update.RestartEvent, "Upgrade completed. Restarting ...")
151131

152132
err := restartServices(ctx)
153133
if err != nil {
154-
eventsCh <- update.Event{
155-
Type: update.ErrorEvent,
156-
Err: err,
157-
Data: "Error restart services after upgrade",
158-
}
159-
slog.Error("failed to restart services", "error", err)
134+
eventsCh <- update.NewErrorEvent(fmt.Errorf("error restarting services after upgrade: %w", err))
160135
return
161136
}
162137
}()

internal/update/arduino/arduino.go

Lines changed: 14 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ package arduino
1818
import (
1919
"context"
2020
"errors"
21+
"fmt"
2122
"log/slog"
2223
"sync"
23-
"time"
2424

2525
"github.com/arduino/arduino-cli/commands"
2626
"github.com/arduino/arduino-cli/commands/cmderrors"
@@ -134,42 +134,31 @@ func (a *ArduinoPlatformUpdater) UpgradePackages(ctx context.Context, names []st
134134
downloadProgressCB := func(curr *rpc.DownloadProgress) {
135135
data := helpers.ArduinoCLIDownloadProgressToString(curr)
136136
slog.Debug("Download progress", slog.String("download_progress", data))
137-
eventsCh <- update.Event{Type: update.UpgradeLineEvent, Data: data}
137+
eventsCh <- update.NewDataEvent(update.UpgradeLineEvent, data)
138138
}
139139
taskProgressCB := func(msg *rpc.TaskProgress) {
140140
data := helpers.ArduinoCLITaskProgressToString(msg)
141141
slog.Debug("Task progress", slog.String("task_progress", data))
142-
eventsCh <- update.Event{Type: update.UpgradeLineEvent, Data: data}
142+
eventsCh <- update.NewDataEvent(update.UpgradeLineEvent, data)
143143
}
144144

145145
go func() {
146146
defer a.lock.Unlock()
147147
defer close(eventsCh)
148148

149-
ctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
150-
defer cancel()
151-
152-
eventsCh <- update.Event{Type: update.StartEvent, Data: "Upgrade is starting"}
149+
eventsCh <- update.NewDataEvent(update.StartEvent, "Upgrade is starting")
153150

154151
logrus.SetLevel(logrus.ErrorLevel) // Reduce the log level of arduino-cli
155152
srv := commands.NewArduinoCoreServer()
156153

157154
if err := setConfig(ctx, srv); err != nil {
158-
eventsCh <- update.Event{
159-
Type: update.ErrorEvent,
160-
Err: err,
161-
Data: "Error setting additional URLs",
162-
}
155+
eventsCh <- update.NewErrorEvent(fmt.Errorf("error setting config: %w", err))
163156
return
164157
}
165158

166159
var inst *rpc.Instance
167160
if resp, err := srv.Create(ctx, &rpc.CreateRequest{}); err != nil {
168-
eventsCh <- update.Event{
169-
Type: update.ErrorEvent,
170-
Err: err,
171-
Data: "Error creating Arduino instance",
172-
}
161+
eventsCh <- update.NewErrorEvent(fmt.Errorf("error creating arduino-cli instance: %w", err))
173162
return
174163
} else {
175164
inst = resp.GetInstance()
@@ -185,19 +174,11 @@ func (a *ArduinoPlatformUpdater) UpgradePackages(ctx context.Context, names []st
185174
{
186175
stream, _ := commands.UpdateIndexStreamResponseToCallbackFunction(ctx, downloadProgressCB)
187176
if err := srv.UpdateIndex(&rpc.UpdateIndexRequest{Instance: inst}, stream); err != nil {
188-
eventsCh <- update.Event{
189-
Type: update.ErrorEvent,
190-
Err: err,
191-
Data: "Error updating index",
192-
}
177+
eventsCh <- update.NewErrorEvent(fmt.Errorf("error updating index: %w", err))
193178
return
194179
}
195180
if err := srv.Init(&rpc.InitRequest{Instance: inst}, commands.InitStreamResponseToCallbackFunction(ctx, nil)); err != nil {
196-
eventsCh <- update.Event{
197-
Type: update.ErrorEvent,
198-
Err: err,
199-
Data: "Error initializing Arduino instance",
200-
}
181+
eventsCh <- update.NewErrorEvent(fmt.Errorf("error initializing instance: %w", err))
201182
return
202183
}
203184
}
@@ -219,17 +200,13 @@ func (a *ArduinoPlatformUpdater) UpgradePackages(ctx context.Context, names []st
219200
); err != nil {
220201
var alreadyPresent *cmderrors.PlatformAlreadyAtTheLatestVersionError
221202
if errors.As(err, &alreadyPresent) {
222-
eventsCh <- update.Event{Type: update.UpgradeLineEvent, Data: alreadyPresent.Error()}
203+
eventsCh <- update.NewDataEvent(update.UpgradeLineEvent, alreadyPresent.Error())
223204
return
224205
}
225206

226207
var notFound *cmderrors.PlatformNotFoundError
227208
if !errors.As(err, &notFound) {
228-
eventsCh <- update.Event{
229-
Type: update.ErrorEvent,
230-
Err: err,
231-
Data: "Error upgrading platform",
232-
}
209+
eventsCh <- update.NewErrorEvent(fmt.Errorf("error upgrading platform: %w", err))
233210
return
234211
}
235212
// If the platform is not found, we will try to install it
@@ -246,23 +223,16 @@ func (a *ArduinoPlatformUpdater) UpgradePackages(ctx context.Context, names []st
246223
),
247224
)
248225
if err != nil {
249-
eventsCh <- update.Event{
250-
Type: update.ErrorEvent,
251-
Err: err,
252-
Data: "Error installing platform",
253-
}
226+
eventsCh <- update.NewErrorEvent(fmt.Errorf("error installing platform: %w", err))
254227
return
255228
}
256229
} else if respCB().GetPlatform() == nil {
257-
eventsCh <- update.Event{
258-
Type: update.ErrorEvent,
259-
Data: "platform upgrade failed",
260-
}
230+
eventsCh <- update.NewErrorEvent(fmt.Errorf("platform upgrade failed"))
261231
return
262232
}
263233

264234
cbw := orchestrator.NewCallbackWriter(func(line string) {
265-
eventsCh <- update.Event{Type: update.UpgradeLineEvent, Data: line}
235+
eventsCh <- update.NewDataEvent(update.UpgradeLineEvent, line)
266236
})
267237

268238
err := srv.BurnBootloader(
@@ -274,11 +244,7 @@ func (a *ArduinoPlatformUpdater) UpgradePackages(ctx context.Context, names []st
274244
commands.BurnBootloaderToServerStreams(ctx, cbw, cbw),
275245
)
276246
if err != nil {
277-
eventsCh <- update.Event{
278-
Type: update.ErrorEvent,
279-
Err: err,
280-
Data: "Error burning bootloader",
281-
}
247+
eventsCh <- update.NewErrorEvent(fmt.Errorf("error burning bootloader: %w", err))
282248
return
283249
}
284250
}()

internal/update/errors.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package update
2+
3+
import "errors"
4+
5+
type ErrorCode string
6+
7+
const (
8+
NoInternetConnection ErrorCode = "NO_INTERNET_CONNECTION"
9+
OperationInProgress ErrorCode = "OPERATION_IN_PROGRESS"
10+
UnknownError ErrorCode = "UNKNOWN_ERROR"
11+
)
12+
13+
var (
14+
ErrOperationAlreadyInProgress = &UpdateError{
15+
Code: OperationInProgress,
16+
Details: " an operation is already in progress",
17+
}
18+
ErrNoInternetConnection = &UpdateError{
19+
Code: NoInternetConnection,
20+
Details: "no internet connection available",
21+
}
22+
)
23+
24+
type UpdateError struct {
25+
Code ErrorCode `json:"code"`
26+
Details string `json:"details"`
27+
28+
err error
29+
}
30+
31+
func (e *UpdateError) Error() string {
32+
return e.Details
33+
}
34+
35+
func (e *UpdateError) Unwrap() error {
36+
return e.err
37+
}
38+
39+
func NewUnkownError(err error) *UpdateError {
40+
return &UpdateError{
41+
Code: "UNKNOWN_ERROR",
42+
Details: err.Error(),
43+
err: err,
44+
}
45+
}
46+
47+
func GetUpdateErrorCode(err error) ErrorCode {
48+
var updateError *UpdateError
49+
if errors.As(err, &updateError) {
50+
if updateError.Code != "" {
51+
return updateError.Code
52+
}
53+
}
54+
return UnknownError
55+
}

internal/update/event.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package update
1717

18+
import "go.bug.st/f"
19+
1820
// EventType defines the type of upgrade event.
1921
type EventType int
2022

@@ -29,8 +31,9 @@ const (
2931
// Event represents a single event in the upgrade process.
3032
type Event struct {
3133
Type EventType
32-
Data string
33-
Err error // Optional error field for error events
34+
35+
data string
36+
err error // error field for error events
3437
}
3538

3639
func (t EventType) String() string {
@@ -50,6 +53,30 @@ func (t EventType) String() string {
5053
}
5154
}
5255

56+
func NewDataEvent(t EventType, data string) Event {
57+
return Event{
58+
Type: t,
59+
data: data,
60+
}
61+
}
62+
63+
func NewErrorEvent(err error) Event {
64+
return Event{
65+
Type: ErrorEvent,
66+
err: err,
67+
}
68+
}
69+
70+
func (e Event) GetData() string {
71+
f.Assert(e.Type != ErrorEvent, "not a data event")
72+
return e.data
73+
}
74+
75+
func (e Event) GetError() error {
76+
f.Assert(e.Type == ErrorEvent, "not an error event")
77+
return e.err
78+
}
79+
5380
type PackageType string
5481

5582
const (

0 commit comments

Comments
 (0)