Skip to content

Commit 273b89c

Browse files
committed
Proper gRPC error handling
1 parent 5f4f6e9 commit 273b89c

File tree

10 files changed

+288
-144
lines changed

10 files changed

+288
-144
lines changed

Diff for: cli/compile/compile.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/arduino/arduino-cli/cli/feedback"
2626
"github.com/arduino/arduino-cli/cli/output"
2727
"github.com/arduino/arduino-cli/configuration"
28+
"google.golang.org/grpc/status"
2829

2930
"github.com/arduino/arduino-cli/cli/errorcodes"
3031
"github.com/arduino/arduino-cli/cli/instance"
@@ -193,7 +194,7 @@ func run(cmd *cobra.Command, args []string) {
193194
ImportDir: buildPath,
194195
Programmer: programmer,
195196
}
196-
var err error
197+
var err *status.Status
197198
if output.OutputFormat == "json" {
198199
// TODO: do not print upload output in json mode
199200
uploadOut := new(bytes.Buffer)

Diff for: cli/feedback/feedback.go

+12
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ package feedback
1717

1818
import (
1919
"encoding/json"
20+
"errors"
2021
"fmt"
2122
"io"
2223
"os"
2324

2425
"github.com/sirupsen/logrus"
26+
"google.golang.org/grpc/status"
2527
)
2628

2729
// OutputFormat is used to determine the output format
@@ -102,6 +104,16 @@ func (fb *Feedback) Print(v interface{}) {
102104
// Errorf behaves like fmt.Printf but writes on the error writer and adds a
103105
// newline. It also logs the error.
104106
func (fb *Feedback) Errorf(format string, v ...interface{}) {
107+
// Unbox grpc status errors
108+
for i := range v {
109+
if s, isStatus := v[i].(*status.Status); isStatus {
110+
v[i] = errors.New(s.Message())
111+
} else if err, isErr := v[i].(error); isErr {
112+
if s, isStatus := status.FromError(err); isStatus {
113+
v[i] = errors.New(s.Message())
114+
}
115+
}
116+
}
105117
fb.Error(fmt.Sprintf(format, v...))
106118
}
107119

Diff for: client_example/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
dbg "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/debug/v1"
3333
"github.com/arduino/arduino-cli/rpc/cc/arduino/cli/settings/v1"
3434
"google.golang.org/grpc"
35+
"google.golang.org/grpc/status"
3536
)
3637

3738
var (

Diff for: commands/daemon/daemon.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ func (s *ArduinoCoreServerImpl) Upload(req *rpc.UploadRequest, stream rpc.Arduin
284284
utils.FeedStreamTo(func(data []byte) { stream.Send(&rpc.UploadResponse{ErrStream: data}) }),
285285
)
286286
if err != nil {
287-
return err
287+
return err.Err()
288288
}
289289
return stream.Send(resp)
290290
}
@@ -297,7 +297,7 @@ func (s *ArduinoCoreServerImpl) UploadUsingProgrammer(req *rpc.UploadUsingProgra
297297
utils.FeedStreamTo(func(data []byte) { stream.Send(&rpc.UploadUsingProgrammerResponse{ErrStream: data}) }),
298298
)
299299
if err != nil {
300-
return err
300+
return err.Err()
301301
}
302302
return stream.Send(resp)
303303
}
@@ -310,7 +310,7 @@ func (s *ArduinoCoreServerImpl) BurnBootloader(req *rpc.BurnBootloaderRequest, s
310310
utils.FeedStreamTo(func(data []byte) { stream.Send(&rpc.BurnBootloaderResponse{ErrStream: data}) }),
311311
)
312312
if err != nil {
313-
return err
313+
return err.Err()
314314
}
315315
return stream.Send(resp)
316316
}

Diff for: commands/upload/burnbootloader.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ import (
2222
"github.com/arduino/arduino-cli/commands"
2323
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
2424
"github.com/sirupsen/logrus"
25+
"google.golang.org/grpc/status"
2526
)
2627

2728
// BurnBootloader FIXMEDOC
28-
func BurnBootloader(ctx context.Context, req *rpc.BurnBootloaderRequest, outStream io.Writer, errStream io.Writer) (*rpc.BurnBootloaderResponse, error) {
29+
func BurnBootloader(ctx context.Context, req *rpc.BurnBootloaderRequest, outStream io.Writer, errStream io.Writer) (*rpc.BurnBootloaderResponse, *status.Status) {
2930
logrus.
3031
WithField("fqbn", req.GetFqbn()).
3132
WithField("port", req.GetPort()).

Diff for: commands/upload/upload.go

+30-29
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,25 @@ import (
3737
"github.com/pkg/errors"
3838
"github.com/sirupsen/logrus"
3939
"go.bug.st/serial"
40+
"google.golang.org/grpc/codes"
41+
"google.golang.org/grpc/status"
4042
)
4143

4244
// Upload FIXMEDOC
43-
func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadResponse, error) {
45+
func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadResponse, *status.Status) {
4446
logrus.Tracef("Upload %s on %s started", req.GetSketchPath(), req.GetFqbn())
4547

4648
// TODO: make a generic function to extract sketch from request
4749
// and remove duplication in commands/compile.go
4850
sketchPath := paths.New(req.GetSketchPath())
4951
sketch, err := sketches.NewSketchFromPath(sketchPath)
5052
if err != nil && req.GetImportDir() == "" && req.GetImportFile() == "" {
51-
return nil, fmt.Errorf("opening sketch: %s", err)
53+
return nil, status.Newf(codes.InvalidArgument, "Sketch not found: %s", err)
5254
}
5355

5456
pm := commands.GetPackageManager(req.GetInstance().GetId())
5557

56-
err = runProgramAction(
58+
return &rpc.UploadResponse{}, runProgramAction(
5759
pm,
5860
sketch,
5961
req.GetImportFile(),
@@ -67,18 +69,14 @@ func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, er
6769
outStream,
6870
errStream,
6971
)
70-
if err != nil {
71-
return nil, err
72-
}
73-
return &rpc.UploadResponse{}, nil
7472
}
7573

7674
// UsingProgrammer FIXMEDOC
77-
func UsingProgrammer(ctx context.Context, req *rpc.UploadUsingProgrammerRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadUsingProgrammerResponse, error) {
75+
func UsingProgrammer(ctx context.Context, req *rpc.UploadUsingProgrammerRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadUsingProgrammerResponse, *status.Status) {
7876
logrus.Tracef("Upload using programmer %s on %s started", req.GetSketchPath(), req.GetFqbn())
7977

8078
if req.GetProgrammer() == "" {
81-
return nil, errors.New("programmer not specified")
79+
return nil, status.New(codes.InvalidArgument, "Programmer not specified")
8280
}
8381
_, err := Upload(ctx, &rpc.UploadRequest{
8482
Instance: req.GetInstance(),
@@ -99,17 +97,17 @@ func runProgramAction(pm *packagemanager.PackageManager,
9997
importFile, importDir, fqbnIn, port string,
10098
programmerID string,
10199
verbose, verify, burnBootloader bool,
102-
outStream, errStream io.Writer) error {
100+
outStream, errStream io.Writer) *status.Status {
103101

104102
if burnBootloader && programmerID == "" {
105-
return fmt.Errorf("no programmer specified for burning bootloader")
103+
return status.New(codes.InvalidArgument, "Programmer not specified")
106104
}
107105

108106
// FIXME: make a specification on how a port is specified via command line
109107
if port == "" && sketch != nil && sketch.Metadata != nil {
110108
deviceURI, err := url.Parse(sketch.Metadata.CPU.Port)
111109
if err != nil {
112-
return fmt.Errorf("invalid Device URL format: %s", err)
110+
return status.Newf(codes.InvalidArgument, "Invalid Device URL format: %s", err)
113111
}
114112
if deviceURI.Scheme == "serial" {
115113
port = deviceURI.Host + deviceURI.Path
@@ -121,18 +119,18 @@ func runProgramAction(pm *packagemanager.PackageManager,
121119
fqbnIn = sketch.Metadata.CPU.Fqbn
122120
}
123121
if fqbnIn == "" {
124-
return fmt.Errorf("no Fully Qualified Board Name provided")
122+
return status.New(codes.InvalidArgument, "No FQBN (Fully Qualified Board Name) provided")
125123
}
126124
fqbn, err := cores.ParseFQBN(fqbnIn)
127125
if err != nil {
128-
return fmt.Errorf("incorrect FQBN: %s", err)
126+
return status.Newf(codes.InvalidArgument, fmt.Sprintf("Invalid FQBN: %s", err))
129127
}
130128
logrus.WithField("fqbn", fqbn).Tracef("Detected FQBN")
131129

132130
// Find target board and board properties
133131
_, boardPlatform, board, boardProperties, buildPlatform, err := pm.ResolveFQBN(fqbn)
134132
if err != nil {
135-
return fmt.Errorf("incorrect FQBN: %s", err)
133+
return status.Newf(codes.InvalidArgument, "Could not resolve FQBN: %s", err)
136134
}
137135
logrus.
138136
WithField("boardPlatform", boardPlatform).
@@ -149,7 +147,7 @@ func runProgramAction(pm *packagemanager.PackageManager,
149147
programmer = buildPlatform.Programmers[programmerID]
150148
}
151149
if programmer == nil {
152-
return fmt.Errorf("programmer '%s' not available", programmerID)
150+
return status.Newf(codes.InvalidArgument, "Programmer '%s' not available", programmerID)
153151
}
154152
}
155153

@@ -174,7 +172,7 @@ func runProgramAction(pm *packagemanager.PackageManager,
174172
if t, ok := props.GetOk(toolProperty); ok {
175173
uploadToolID = t
176174
} else {
177-
return fmt.Errorf("cannot get programmer tool: undefined '%s' property", toolProperty)
175+
return status.Newf(codes.FailedPrecondition, "Cannot get programmer tool: undefined '%s' property", toolProperty)
178176
}
179177
}
180178

@@ -190,7 +188,7 @@ func runProgramAction(pm *packagemanager.PackageManager,
190188
Trace("Upload tool")
191189

192190
if split := strings.Split(uploadToolID, ":"); len(split) > 2 {
193-
return fmt.Errorf("invalid 'upload.tool' property: %s", uploadToolID)
191+
return status.Newf(codes.FailedPrecondition, "Invalid 'upload.tool' property: %s", uploadToolID)
194192
} else if len(split) == 2 {
195193
uploadToolID = split[1]
196194
uploadToolPlatform = pm.GetInstalledPlatformRelease(
@@ -229,7 +227,10 @@ func runProgramAction(pm *packagemanager.PackageManager,
229227
}
230228

231229
if !uploadProperties.ContainsKey("upload.protocol") && programmer == nil {
232-
return fmt.Errorf("a programmer is required to upload for this board")
230+
err, _ := status.
231+
Newf(codes.InvalidArgument, "A programmer is required to upload on this board").
232+
WithDetails(&rpc.UploadError{Code: rpc.UploadError_CODE_PROGRAMMER_REQUIRED_FOR_UPLOAD})
233+
return err
233234
}
234235

235236
// Set properties for verbose upload
@@ -277,13 +278,13 @@ func runProgramAction(pm *packagemanager.PackageManager,
277278
if !burnBootloader {
278279
importPath, sketchName, err := determineBuildPathAndSketchName(importFile, importDir, sketch, fqbn)
279280
if err != nil {
280-
return errors.Errorf("retrieving build artifacts: %s", err)
281+
return status.Newf(codes.Internal, "Error finding build artifacts: %s", err)
281282
}
282283
if !importPath.Exist() {
283-
return fmt.Errorf("compiled sketch not found in %s", importPath)
284+
return status.Newf(codes.Internal, "Compiled sketch not found in %s", importPath)
284285
}
285286
if !importPath.IsDir() {
286-
return fmt.Errorf("expected compiled sketch in directory %s, but is a file instead", importPath)
287+
return status.Newf(codes.Internal, "Expected compiled sketch in directory %s, but is a file instead", importPath)
287288
}
288289
uploadProperties.SetPath("build.path", importPath)
289290
uploadProperties.Set("build.project_name", sketchName)
@@ -296,12 +297,12 @@ func runProgramAction(pm *packagemanager.PackageManager,
296297
// Perform reset via 1200bps touch if requested
297298
if uploadProperties.GetBoolean("upload.use_1200bps_touch") {
298299
if port == "" {
299-
return fmt.Errorf("no upload port provided")
300+
return status.New(codes.InvalidArgument, "No upload port specified")
300301
}
301302

302303
ports, err := serial.GetPortsList()
303304
if err != nil {
304-
return fmt.Errorf("cannot get serial port list: %s", err)
305+
return status.Newf(codes.Internal, "Cannot get serial port list: %s", err)
305306
}
306307
for _, p := range ports {
307308
if p == port {
@@ -327,7 +328,7 @@ func runProgramAction(pm *packagemanager.PackageManager,
327328

328329
actualPort, err = serialutils.WaitForNewSerialPortOrDefaultTo(actualPort)
329330
if err != nil {
330-
return errors.WithMessage(err, "detecting serial port")
331+
return status.Newf(codes.Internal, "Failed detecting serial port: %s", err)
331332
}
332333
}
333334
}
@@ -345,18 +346,18 @@ func runProgramAction(pm *packagemanager.PackageManager,
345346
// Run recipes for upload
346347
if burnBootloader {
347348
if err := runTool("erase.pattern", uploadProperties, outStream, errStream, verbose); err != nil {
348-
return fmt.Errorf("chip erase error: %s", err)
349+
return status.Newf(codes.Internal, "Failed chip erase: %s", err)
349350
}
350351
if err := runTool("bootloader.pattern", uploadProperties, outStream, errStream, verbose); err != nil {
351-
return fmt.Errorf("burn bootloader error: %s", err)
352+
return status.Newf(codes.Internal, "Failed to burn bootloader: %s", err)
352353
}
353354
} else if programmer != nil {
354355
if err := runTool("program.pattern", uploadProperties, outStream, errStream, verbose); err != nil {
355-
return fmt.Errorf("programming error: %s", err)
356+
return status.Newf(codes.Internal, "Failed programming: %s", err)
356357
}
357358
} else {
358359
if err := runTool("upload.pattern", uploadProperties, outStream, errStream, verbose); err != nil {
359-
return fmt.Errorf("uploading error: %s", err)
360+
return status.Newf(codes.Internal, "Failed uploading: %s", err)
360361
}
361362
}
362363

Diff for: commands/upload/upload_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ func TestUploadPropertiesComposition(t *testing.T) {
178178
testRunner := func(t *testing.T, test test, verboseVerify bool) {
179179
outStream := &bytes.Buffer{}
180180
errStream := &bytes.Buffer{}
181-
err := runProgramAction(
181+
status := runProgramAction(
182182
pm,
183183
nil, // sketch
184184
"", // importFile
@@ -197,9 +197,9 @@ func TestUploadPropertiesComposition(t *testing.T) {
197197
verboseVerifyOutput = "quiet noverify"
198198
}
199199
if test.expectedOutput == "FAIL" {
200-
require.Error(t, err)
200+
require.NotNil(t, status)
201201
} else {
202-
require.NoError(t, err)
202+
require.Nil(t, status)
203203
outFiltered := strings.ReplaceAll(outStream.String(), "\r", "")
204204
outFiltered = strings.ReplaceAll(outFiltered, "\\", "/")
205205
require.Contains(t, outFiltered, strings.ReplaceAll(test.expectedOutput, "$$VERBOSE-VERIFY$$", verboseVerifyOutput))

Diff for: go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ require (
4646
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71
4747
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
4848
golang.org/x/text v0.3.2
49+
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
4950
google.golang.org/grpc v1.27.0
5051
google.golang.org/protobuf v1.25.0
5152
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect

0 commit comments

Comments
 (0)