Skip to content

Commit 272e8c1

Browse files
Suppor for OTA status retrieval (#150)
* Added support for ota v2 * Fixed go compatibility * Fixed output for ota status * refactor: fix typos * Tagging improvements * Tag in case of v1.x lib update * Fixed compile * Removed not used param * Increased timeout --------- Co-authored-by: Roberto Gazia <[email protected]>
1 parent 4db92cc commit 272e8c1

File tree

16 files changed

+849
-47
lines changed

16 files changed

+849
-47
lines changed

cli/device/list.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ import (
3333
)
3434

3535
type listFlags struct {
36-
tags map[string]string
36+
tags map[string]string
37+
deviceIds string
3738
}
3839

3940
func initListCommand() *cobra.Command {
@@ -56,6 +57,7 @@ func initListCommand() *cobra.Command {
5657
"Comma-separated list of tags with format <key>=<value>.\n"+
5758
"List only devices that match the provided tags.",
5859
)
60+
listCommand.Flags().StringVarP(&flags.deviceIds, "device-ids", "d", "", "Comma separated list of Device IDs")
5961
return listCommand
6062
}
6163

@@ -67,7 +69,7 @@ func runListCommand(flags *listFlags) error {
6769
return fmt.Errorf("retrieving credentials: %w", err)
6870
}
6971

70-
params := &device.ListParams{Tags: flags.tags}
72+
params := &device.ListParams{Tags: flags.tags, DeviceIds: flags.deviceIds}
7173
devs, err := device.List(context.TODO(), params, cred)
7274
if err != nil {
7375
return err

cli/device/tag/create.go

+28-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"context"
2222
"fmt"
2323
"os"
24+
"strings"
2425

2526
"github.com/arduino/arduino-cli/cli/errorcodes"
2627
"github.com/arduino/arduino-cli/cli/feedback"
@@ -32,6 +33,7 @@ import (
3233

3334
type createTagsFlags struct {
3435
id string
36+
ids string
3537
tags map[string]string
3638
}
3739

@@ -49,23 +51,45 @@ func InitCreateTagsCommand() *cobra.Command {
4951
},
5052
}
5153
createTagsCommand.Flags().StringVarP(&flags.id, "id", "i", "", "Device ID")
54+
createTagsCommand.Flags().StringVarP(&flags.ids, "ids", "", "", "Comma-separated list of Device IDs")
5255
createTagsCommand.Flags().StringToStringVar(
5356
&flags.tags,
5457
"tags",
5558
nil,
5659
"Comma-separated list of tags with format <key>=<value>.",
5760
)
58-
createTagsCommand.MarkFlagRequired("id")
5961
createTagsCommand.MarkFlagRequired("tags")
6062
return createTagsCommand
6163
}
6264

6365
func runCreateTagsCommand(flags *createTagsFlags) error {
64-
logrus.Infof("Creating tags on device %s", flags.id)
66+
if flags.id == "" && flags.ids == "" {
67+
return fmt.Errorf("missing required flag(s) \"id\" or \"ids\"")
68+
}
69+
70+
if flags.id != "" {
71+
if err := creteTag(flags.id, flags.tags); err != nil {
72+
return err
73+
}
74+
}
75+
if flags.ids != "" {
76+
idsArray := strings.Split(flags.ids, ",")
77+
for _, id := range idsArray {
78+
id = strings.TrimSpace(id)
79+
if err := creteTag(id, flags.tags); err != nil {
80+
return err
81+
}
82+
}
83+
}
84+
return nil
85+
}
86+
87+
func creteTag(id string, tags map[string]string) error {
88+
logrus.Infof("Creating tags on device %s", id)
6589

6690
params := &tag.CreateTagsParams{
67-
ID: flags.id,
68-
Tags: flags.tags,
91+
ID: id,
92+
Tags: tags,
6993
Resource: tag.Device,
7094
}
7195

cli/device/tag/delete.go

+31-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"context"
2222
"fmt"
2323
"os"
24+
"strings"
2425

2526
"github.com/arduino/arduino-cli/cli/errorcodes"
2627
"github.com/arduino/arduino-cli/cli/feedback"
@@ -32,6 +33,7 @@ import (
3233

3334
type deleteTagsFlags struct {
3435
id string
36+
ids string
3537
keys []string
3638
}
3739

@@ -49,23 +51,48 @@ func InitDeleteTagsCommand() *cobra.Command {
4951
},
5052
}
5153
deleteTagsCommand.Flags().StringVarP(&flags.id, "id", "i", "", "Device ID")
54+
deleteTagsCommand.Flags().StringVarP(&flags.id, "ids", "", "", "Comma-separated list of Device IDs")
5255
deleteTagsCommand.Flags().StringSliceVarP(&flags.keys, "keys", "k", nil, "Comma-separated list of keys of tags to delete")
53-
deleteTagsCommand.MarkFlagRequired("id")
5456
deleteTagsCommand.MarkFlagRequired("keys")
5557
return deleteTagsCommand
5658
}
5759

5860
func runDeleteTagsCommand(flags *deleteTagsFlags) error {
59-
logrus.Infof("Deleting tags with keys %s", flags.keys)
61+
if flags.id == "" && flags.ids == "" {
62+
return fmt.Errorf("missing required flag(s) \"id\" or \"ids\"")
63+
}
64+
65+
if flags.id != "" {
66+
err := deleteTags(flags.id, flags.keys)
67+
if err != nil {
68+
return err
69+
}
70+
}
71+
if flags.ids != "" {
72+
ids := strings.Split(flags.ids, ",")
73+
for _, id := range ids {
74+
id = strings.TrimSpace(id)
75+
err := deleteTags(id, flags.keys)
76+
if err != nil {
77+
return err
78+
}
79+
}
80+
}
81+
82+
return nil
83+
}
84+
85+
func deleteTags(id string, keys []string) error {
86+
logrus.Infof("Deleting tags with keys %s", keys)
6087

6188
cred, err := config.RetrieveCredentials()
6289
if err != nil {
6390
return fmt.Errorf("retrieving credentials: %w", err)
6491
}
6592

6693
params := &tag.DeleteTagsParams{
67-
ID: flags.id,
68-
Keys: flags.keys,
94+
ID: id,
95+
Keys: keys,
6996
Resource: tag.Device,
7097
}
7198

cli/ota/massupload.go

+23-22
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"fmt"
2323
"os"
2424
"sort"
25-
"strings"
2625

2726
"github.com/arduino/arduino-cli/cli/errorcodes"
2827
"github.com/arduino/arduino-cli/cli/feedback"
@@ -99,22 +98,6 @@ func runMassUploadCommand(flags *massUploadFlags) error {
9998
})
10099

101100
feedback.PrintResult(massUploadResult{resp})
102-
103-
var failed []string
104-
for _, r := range resp {
105-
if r.Err != nil {
106-
failed = append(failed, r.ID)
107-
}
108-
}
109-
if len(failed) == 0 {
110-
return nil
111-
}
112-
failDevs := strings.Join(failed, ",")
113-
feedback.Printf(
114-
"You can try to perform the OTA again on the failed devices using the following command:\n"+
115-
"$ arduino-cloud-cli ota mass-upload --file %s --fqbn %s -d %s",
116-
params.File, params.FQBN, failDevs,
117-
)
118101
return nil
119102
}
120103

@@ -131,17 +114,35 @@ func (r massUploadResult) String() string {
131114
return "No OTA done."
132115
}
133116
t := table.New()
134-
t.SetHeader("ID", "Result")
117+
hasErrorReason := false
118+
for _, r := range r.res {
119+
if r.OtaStatus.ErrorReason != "" {
120+
hasErrorReason = true
121+
break
122+
}
123+
}
124+
125+
if hasErrorReason {
126+
t.SetHeader("Device ID", "Ota ID", "Result", "Error Reason")
127+
} else {
128+
t.SetHeader("Device ID", "Ota ID", "Result")
129+
}
130+
131+
// Now print the table
135132
for _, r := range r.res {
136133
outcome := "Success"
137134
if r.Err != nil {
138135
outcome = fmt.Sprintf("Fail: %s", r.Err.Error())
139136
}
137+
if r.OtaStatus.Status != "" {
138+
outcome = r.OtaStatus.MapStatus()
139+
}
140140

141-
t.AddRow(
142-
r.ID,
143-
outcome,
144-
)
141+
line := []interface{}{r.ID, r.OtaStatus.ID, outcome}
142+
if hasErrorReason {
143+
line = append(line, r.OtaStatus.ErrorReason)
144+
}
145+
t.AddRow(line...)
145146
}
146147
return t.Render()
147148
}

cli/ota/ota.go

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func NewCommand() *cobra.Command {
3030

3131
otaCommand.AddCommand(initUploadCommand())
3232
otaCommand.AddCommand(initMassUploadCommand())
33+
otaCommand.AddCommand(initOtaStatusCommand())
3334
otaCommand.AddCommand(initEncodeBinaryCommand())
3435
otaCommand.AddCommand(initDecodeHeaderCommand())
3536

cli/ota/status.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// This file is part of arduino-cloud-cli.
2+
//
3+
// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as published
7+
// by the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
package ota
19+
20+
import (
21+
"fmt"
22+
"os"
23+
24+
"github.com/arduino/arduino-cli/cli/errorcodes"
25+
"github.com/arduino/arduino-cli/cli/feedback"
26+
"github.com/arduino/arduino-cloud-cli/command/ota"
27+
"github.com/arduino/arduino-cloud-cli/config"
28+
"github.com/spf13/cobra"
29+
)
30+
31+
type statusFlags struct {
32+
otaID string
33+
otaIDs string
34+
deviceId string
35+
limit int16
36+
sort string
37+
}
38+
39+
func initOtaStatusCommand() *cobra.Command {
40+
flags := &statusFlags{}
41+
uploadCommand := &cobra.Command{
42+
Use: "status",
43+
Short: "OTA status",
44+
Long: "Get OTA status by OTA or device ID",
45+
Run: func(cmd *cobra.Command, args []string) {
46+
if err := runOtaStatusCommand(flags); err != nil {
47+
feedback.Errorf("Error during ota get status: %v", err)
48+
os.Exit(errorcodes.ErrGeneric)
49+
}
50+
},
51+
}
52+
uploadCommand.Flags().StringVarP(&flags.otaID, "ota-id", "o", "", "OTA ID")
53+
uploadCommand.Flags().StringVarP(&flags.otaIDs, "ota-ids", "", "", "OTA IDs (comma separated)")
54+
uploadCommand.Flags().StringVarP(&flags.deviceId, "device-id", "d", "", "Device ID")
55+
uploadCommand.Flags().Int16VarP(&flags.limit, "limit", "l", 10, "Output limit (default: 10)")
56+
uploadCommand.Flags().StringVarP(&flags.sort, "sort", "s", "desc", "Sorting (default: desc)")
57+
58+
return uploadCommand
59+
}
60+
61+
func runOtaStatusCommand(flags *statusFlags) error {
62+
if flags.otaID == "" && flags.deviceId == "" && flags.otaIDs == "" {
63+
return fmt.Errorf("required flag(s) \"ota-id\" or \"device-id\" or \"ota-ids\" not set")
64+
}
65+
66+
cred, err := config.RetrieveCredentials()
67+
if err != nil {
68+
return fmt.Errorf("retrieving credentials: %w", err)
69+
}
70+
71+
return ota.PrintOtaStatus(flags.otaID, flags.otaIDs, flags.deviceId, cred, int(flags.limit), flags.sort)
72+
}

command/device/list.go

+22-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package device
2020
import (
2121
"context"
2222
"fmt"
23+
"strings"
2324

2425
"github.com/arduino/arduino-cloud-cli/config"
2526
"github.com/arduino/arduino-cloud-cli/internal/iot"
@@ -28,7 +29,8 @@ import (
2829
// ListParams contains the optional parameters needed
2930
// to filter the devices to be listed.
3031
type ListParams struct {
31-
Tags map[string]string // If tags are provided, only devices that have all these tags are listed.
32+
Tags map[string]string // If tags are provided, only devices that have all these tags are listed.
33+
DeviceIds string // If ids are provided, only devices with these ids are listed.
3234
}
3335

3436
// List command is used to list
@@ -43,9 +45,19 @@ func List(ctx context.Context, params *ListParams, cred *config.Credentials) ([]
4345
if err != nil {
4446
return nil, err
4547
}
48+
var deviceIdFilter []string
49+
if params.DeviceIds != "" {
50+
deviceIdFilter = strings.Split(params.DeviceIds, ",")
51+
for i := range deviceIdFilter {
52+
deviceIdFilter[i] = strings.TrimSpace(deviceIdFilter[i])
53+
}
54+
}
4655

4756
var devices []DeviceInfo
4857
for _, foundDev := range foundDevices {
58+
if len(deviceIdFilter) > 0 && !sliceContains(deviceIdFilter, foundDev.Id) {
59+
continue
60+
}
4961
dev, err := getDeviceInfo(&foundDev)
5062
if err != nil {
5163
return nil, fmt.Errorf("parsing device %s from cloud: %w", foundDev.Id, err)
@@ -55,3 +67,12 @@ func List(ctx context.Context, params *ListParams, cred *config.Credentials) ([]
5567

5668
return devices, nil
5769
}
70+
71+
func sliceContains(s []string, v string) bool {
72+
for i := range s {
73+
if v == s[i] {
74+
return true
75+
}
76+
}
77+
return false
78+
}

0 commit comments

Comments
 (0)