Skip to content

Commit 6ae26b4

Browse files
committed
Added status reporting on mass upload
1 parent 866f993 commit 6ae26b4

File tree

7 files changed

+85
-23
lines changed

7 files changed

+85
-23
lines changed

cli/ota/massupload.go

+30-5
Original file line numberDiff line numberDiff line change
@@ -128,17 +128,42 @@ func (r massUploadResult) String() string {
128128
return "No OTA done."
129129
}
130130
t := table.New()
131-
t.SetHeader("ID", "Result")
131+
hasErrorReason := false
132+
for _, r := range r.res {
133+
if r.OtaStatus.ErrorReason != "" {
134+
hasErrorReason = true
135+
break
136+
}
137+
}
138+
139+
if hasErrorReason {
140+
t.SetHeader("Device ID", "Ota ID", "Result", "Error Reason")
141+
} else {
142+
t.SetHeader("Device ID", "Ota ID", "Result")
143+
}
144+
145+
// Now print the table
132146
for _, r := range r.res {
133147
outcome := "Success"
134148
if r.Err != nil {
135149
outcome = fmt.Sprintf("Fail: %s", r.Err.Error())
136150
}
151+
if r.OtaStatus.Status != ""{
152+
switch r.OtaStatus.Status {
153+
case "in_progress":
154+
outcome = "In progress"
155+
case "failed":
156+
outcome = "Failed"
157+
case "pending":
158+
outcome = "Pending"
159+
}
160+
}
137161

138-
t.AddRow(
139-
r.ID,
140-
outcome,
141-
)
162+
if hasErrorReason {
163+
t.AddRow(r.ID, r.OtaStatus.ID, outcome, r.OtaStatus.ErrorReason)
164+
} else {
165+
t.AddRow(r.ID, r.OtaStatus.ID, outcome)
166+
}
142167
}
143168
return t.Render()
144169
}

command/ota/massupload.go

+20-7
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ import (
2121
"context"
2222
"errors"
2323
"fmt"
24-
"io/ioutil"
2524
"os"
2625
"path/filepath"
2726

2827
"github.com/arduino/arduino-cloud-cli/config"
2928
"github.com/arduino/arduino-cloud-cli/internal/iot"
29+
otaapi "github.com/arduino/arduino-cloud-cli/internal/ota-api"
3030
iotclient "github.com/arduino/iot-client-go"
3131
)
3232

@@ -46,8 +46,9 @@ type MassUploadParams struct {
4646

4747
// Result of an ota upload on a device.
4848
type Result struct {
49-
ID string
50-
Err error
49+
ID string
50+
Err error
51+
OtaStatus otaapi.Ota
5152
}
5253

5354
// MassUpload command is used to mass upload a firmware OTA,
@@ -60,7 +61,7 @@ func MassUpload(ctx context.Context, params *MassUploadParams, cred *config.Cred
6061
}
6162

6263
// Generate .ota file
63-
otaDir, err := ioutil.TempDir("", "")
64+
otaDir, err := os.MkdirTemp("", "")
6465
if err != nil {
6566
return nil, fmt.Errorf("%s: %w", "cannot create temporary folder", err)
6667
}
@@ -76,6 +77,7 @@ func MassUpload(ctx context.Context, params *MassUploadParams, cred *config.Cred
7677
if err != nil {
7778
return nil, err
7879
}
80+
otapi := otaapi.NewClient(cred)
7981

8082
// Prepare the list of device-ids to update
8183
d, err := idsGivenTags(ctx, iotClient, params.Tags)
@@ -96,7 +98,7 @@ func MassUpload(ctx context.Context, params *MassUploadParams, cred *config.Cred
9698
expiration = otaDeferredExpirationMins
9799
}
98100

99-
res := run(ctx, iotClient, valid, otaFile, expiration)
101+
res := run(ctx, iotClient, otapi, valid, otaFile, expiration)
100102
res = append(res, invalid...)
101103
return res, nil
102104
}
@@ -155,7 +157,11 @@ type otaUploader interface {
155157
DeviceOTA(ctx context.Context, id string, file *os.File, expireMins int) error
156158
}
157159

158-
func run(ctx context.Context, uploader otaUploader, ids []string, otaFile string, expiration int) []Result {
160+
type otaStatusGetter interface {
161+
GetOtaLastStatusByDeviceID(deviceID string) (*otaapi.OtaStatusByDeviceResponse, error)
162+
}
163+
164+
func run(ctx context.Context, uploader otaUploader, otapi otaStatusGetter, ids []string, otaFile string, expiration int) []Result {
159165
type job struct {
160166
id string
161167
file *os.File
@@ -181,7 +187,14 @@ func run(ctx context.Context, uploader otaUploader, ids []string, otaFile string
181187
go func() {
182188
for job := range jobs {
183189
err := uploader.DeviceOTA(ctx, job.id, job.file, expiration)
184-
resCh <- Result{ID: job.id, Err: err}
190+
otaResult := Result{ID: job.id, Err: err}
191+
192+
otaID, otaapierr := otapi.GetOtaLastStatusByDeviceID(job.id)
193+
if otaapierr == nil && otaID != nil && len(otaID.Ota) > 0 {
194+
otaResult.OtaStatus = otaID.Ota[0]
195+
}
196+
197+
resCh <- otaResult
185198
}
186199
}()
187200
}

command/ota/massupload_test.go

+18-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import (
77
"strings"
88
"testing"
99

10+
otaapi "github.com/arduino/arduino-cloud-cli/internal/ota-api"
1011
iotclient "github.com/arduino/iot-client-go"
12+
"github.com/gofrs/uuid"
1113
)
1214

1315
const testFilename = "testdata/empty.bin"
@@ -20,6 +22,20 @@ func (d *deviceUploaderTest) DeviceOTA(ctx context.Context, id string, file *os.
2022
return d.deviceOTA(ctx, id, file, expireMins)
2123
}
2224

25+
type otaStatusGetterTest struct{}
26+
27+
func (s *otaStatusGetterTest) GetOtaLastStatusByDeviceID(deviceID string) (*otaapi.OtaStatusByDeviceResponse, error) {
28+
ota := otaapi.Ota{
29+
ID: uuid.Must(uuid.NewV4()).String(),
30+
Status: "in_progress",
31+
StartedAt: "2021-09-01T12:00:00Z",
32+
}
33+
response := &otaapi.OtaStatusByDeviceResponse{
34+
Ota: []otaapi.Ota{ota},
35+
}
36+
return response, nil
37+
}
38+
2339
func TestRun(t *testing.T) {
2440
var (
2541
failPrefix = "00000000"
@@ -38,9 +54,10 @@ func TestRun(t *testing.T) {
3854
return nil
3955
},
4056
}
57+
mockStatusClient := &otaStatusGetterTest{}
4158

4259
devs := []string{okID1, failID1, okID2, failID2, okID3}
43-
res := run(context.TODO(), mockClient, devs, testFilename, 0)
60+
res := run(context.TODO(), mockClient, mockStatusClient, devs, testFilename, 0)
4461
if len(res) != len(devs) {
4562
t.Errorf("expected %d results, got %d", len(devs), len(res))
4663
}

command/ota/status.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"gopkg.in/yaml.v3"
1111
)
1212

13-
func formatOtaByIdOutput( st *otaapi.OtaStatusPrint) {
13+
func formatOtaByIdOutput(st *otaapi.OtaStatusPrint) {
1414
printOut(st)
1515
}
1616

@@ -65,7 +65,7 @@ func PrintOtaStatus(otaid, device string, cred *config.Credentials, limit int, o
6565
res, err := otapi.GetOtaStatusByOtaID(otaid, limit, order)
6666
if err == nil && res != nil {
6767
formatOtaByIdOutput(&otaapi.OtaStatusPrint{
68-
Ota: res.Ota,
68+
Ota: res.Ota,
6969
Details: res.States,
7070
})
7171
} else if err != nil {

command/ota/upload.go

+1-4
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,6 @@ func Upload(ctx context.Context, params *UploadParams, cred *config.Credentials)
5151
return err
5252
}
5353
otapi := otaapi.NewClient(cred)
54-
if err != nil {
55-
return err
56-
}
5754

5855
dev, err := iotClient.DeviceShow(ctx, params.DeviceID)
5956
if err != nil {
@@ -88,7 +85,7 @@ func Upload(ctx context.Context, params *UploadParams, cred *config.Credentials)
8885
return err
8986
}
9087
// Try to get ota-id from API
91-
otaID, err := otapi.GetOtaStatusByDeviceID(params.DeviceID, 1, otaapi.OrderDesc)
88+
otaID, err := otapi.GetOtaLastStatusByDeviceID(params.DeviceID)
9289
if err != nil {
9390
return err
9491
}

internal/iot/client.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,8 @@ func (cl *Client) DeviceOTA(ctx context.Context, id string, file *os.File, expir
195195
ExpireInMins: optional.NewInt32(int32(expireMins)),
196196
Async: optional.NewBool(true),
197197
}
198-
_, err = cl.api.DevicesV2OtaApi.DevicesV2OtaUpload(ctx, id, file, opt)
199-
if err != nil {
198+
resp, err := cl.api.DevicesV2OtaApi.DevicesV2OtaUpload(ctx, id, file, opt)
199+
if err != nil && resp.StatusCode != 409 { // 409 (Conflict) is the status code for an already existing OTA for the same SHA/device, so ignoring it.
200200
err = fmt.Errorf("uploading device ota: %w", errorDetail(err))
201201
return err
202202
}

internal/ota-api/client.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ import (
3232
"golang.org/x/oauth2"
3333
)
3434

35-
const OrderDesc = "desc"
36-
const OrderAsc = "asc"
35+
const (
36+
OrderDesc = "desc"
37+
OrderAsc = "asc"
38+
)
39+
40+
var ErrAlreadyInProgress = fmt.Errorf("already in progress")
3741

3842
type OtaApiClient struct {
3943
client *http.Client
@@ -130,6 +134,10 @@ func (c *OtaApiClient) GetOtaStatusByOtaID(otaid string, limit int, order string
130134
return nil, err
131135
}
132136

137+
func (c *OtaApiClient) GetOtaLastStatusByDeviceID(deviceID string) (*OtaStatusByDeviceResponse, error) {
138+
return c.GetOtaStatusByDeviceID(deviceID, 1, OrderDesc)
139+
}
140+
133141
func (c *OtaApiClient) GetOtaStatusByDeviceID(deviceID string, limit int, order string) (*OtaStatusByDeviceResponse, error) {
134142

135143
if deviceID == "" {
@@ -169,6 +177,8 @@ func (c *OtaApiClient) GetOtaStatusByDeviceID(deviceID string, limit int, order
169177
return &otaResponse, nil
170178
} else if res.StatusCode == 404 || res.StatusCode == 400 {
171179
return nil, fmt.Errorf("device-id %s not found", deviceID)
180+
} else if res.StatusCode == 409 {
181+
return nil, ErrAlreadyInProgress
172182
}
173183

174184
return nil, err

0 commit comments

Comments
 (0)