Skip to content

Commit

Permalink
Suppor for OTA status retrieval (#150)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
rjtokenring and robgee86 authored May 2, 2024
1 parent 4db92cc commit 272e8c1
Show file tree
Hide file tree
Showing 16 changed files with 849 additions and 47 deletions.
6 changes: 4 additions & 2 deletions cli/device/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ import (
)

type listFlags struct {
tags map[string]string
tags map[string]string
deviceIds string
}

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

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

params := &device.ListParams{Tags: flags.tags}
params := &device.ListParams{Tags: flags.tags, DeviceIds: flags.deviceIds}
devs, err := device.List(context.TODO(), params, cred)
if err != nil {
return err
Expand Down
32 changes: 28 additions & 4 deletions cli/device/tag/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"context"
"fmt"
"os"
"strings"

"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
Expand All @@ -32,6 +33,7 @@ import (

type createTagsFlags struct {
id string
ids string
tags map[string]string
}

Expand All @@ -49,23 +51,45 @@ func InitCreateTagsCommand() *cobra.Command {
},
}
createTagsCommand.Flags().StringVarP(&flags.id, "id", "i", "", "Device ID")
createTagsCommand.Flags().StringVarP(&flags.ids, "ids", "", "", "Comma-separated list of Device IDs")
createTagsCommand.Flags().StringToStringVar(
&flags.tags,
"tags",
nil,
"Comma-separated list of tags with format <key>=<value>.",
)
createTagsCommand.MarkFlagRequired("id")
createTagsCommand.MarkFlagRequired("tags")
return createTagsCommand
}

func runCreateTagsCommand(flags *createTagsFlags) error {
logrus.Infof("Creating tags on device %s", flags.id)
if flags.id == "" && flags.ids == "" {
return fmt.Errorf("missing required flag(s) \"id\" or \"ids\"")
}

if flags.id != "" {
if err := creteTag(flags.id, flags.tags); err != nil {
return err
}
}
if flags.ids != "" {
idsArray := strings.Split(flags.ids, ",")
for _, id := range idsArray {
id = strings.TrimSpace(id)
if err := creteTag(id, flags.tags); err != nil {
return err
}
}
}
return nil
}

func creteTag(id string, tags map[string]string) error {
logrus.Infof("Creating tags on device %s", id)

params := &tag.CreateTagsParams{
ID: flags.id,
Tags: flags.tags,
ID: id,
Tags: tags,
Resource: tag.Device,
}

Expand Down
35 changes: 31 additions & 4 deletions cli/device/tag/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"context"
"fmt"
"os"
"strings"

"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
Expand All @@ -32,6 +33,7 @@ import (

type deleteTagsFlags struct {
id string
ids string
keys []string
}

Expand All @@ -49,23 +51,48 @@ func InitDeleteTagsCommand() *cobra.Command {
},
}
deleteTagsCommand.Flags().StringVarP(&flags.id, "id", "i", "", "Device ID")
deleteTagsCommand.Flags().StringVarP(&flags.id, "ids", "", "", "Comma-separated list of Device IDs")
deleteTagsCommand.Flags().StringSliceVarP(&flags.keys, "keys", "k", nil, "Comma-separated list of keys of tags to delete")
deleteTagsCommand.MarkFlagRequired("id")
deleteTagsCommand.MarkFlagRequired("keys")
return deleteTagsCommand
}

func runDeleteTagsCommand(flags *deleteTagsFlags) error {
logrus.Infof("Deleting tags with keys %s", flags.keys)
if flags.id == "" && flags.ids == "" {
return fmt.Errorf("missing required flag(s) \"id\" or \"ids\"")
}

if flags.id != "" {
err := deleteTags(flags.id, flags.keys)
if err != nil {
return err
}
}
if flags.ids != "" {
ids := strings.Split(flags.ids, ",")
for _, id := range ids {
id = strings.TrimSpace(id)
err := deleteTags(id, flags.keys)
if err != nil {
return err
}
}
}

return nil
}

func deleteTags(id string, keys []string) error {
logrus.Infof("Deleting tags with keys %s", keys)

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

params := &tag.DeleteTagsParams{
ID: flags.id,
Keys: flags.keys,
ID: id,
Keys: keys,
Resource: tag.Device,
}

Expand Down
45 changes: 23 additions & 22 deletions cli/ota/massupload.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"fmt"
"os"
"sort"
"strings"

"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
Expand Down Expand Up @@ -99,22 +98,6 @@ func runMassUploadCommand(flags *massUploadFlags) error {
})

feedback.PrintResult(massUploadResult{resp})

var failed []string
for _, r := range resp {
if r.Err != nil {
failed = append(failed, r.ID)
}
}
if len(failed) == 0 {
return nil
}
failDevs := strings.Join(failed, ",")
feedback.Printf(
"You can try to perform the OTA again on the failed devices using the following command:\n"+
"$ arduino-cloud-cli ota mass-upload --file %s --fqbn %s -d %s",
params.File, params.FQBN, failDevs,
)
return nil
}

Expand All @@ -131,17 +114,35 @@ func (r massUploadResult) String() string {
return "No OTA done."
}
t := table.New()
t.SetHeader("ID", "Result")
hasErrorReason := false
for _, r := range r.res {
if r.OtaStatus.ErrorReason != "" {
hasErrorReason = true
break
}
}

if hasErrorReason {
t.SetHeader("Device ID", "Ota ID", "Result", "Error Reason")
} else {
t.SetHeader("Device ID", "Ota ID", "Result")
}

// Now print the table
for _, r := range r.res {
outcome := "Success"
if r.Err != nil {
outcome = fmt.Sprintf("Fail: %s", r.Err.Error())
}
if r.OtaStatus.Status != "" {
outcome = r.OtaStatus.MapStatus()
}

t.AddRow(
r.ID,
outcome,
)
line := []interface{}{r.ID, r.OtaStatus.ID, outcome}
if hasErrorReason {
line = append(line, r.OtaStatus.ErrorReason)
}
t.AddRow(line...)
}
return t.Render()
}
1 change: 1 addition & 0 deletions cli/ota/ota.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func NewCommand() *cobra.Command {

otaCommand.AddCommand(initUploadCommand())
otaCommand.AddCommand(initMassUploadCommand())
otaCommand.AddCommand(initOtaStatusCommand())
otaCommand.AddCommand(initEncodeBinaryCommand())
otaCommand.AddCommand(initDecodeHeaderCommand())

Expand Down
72 changes: 72 additions & 0 deletions cli/ota/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// This file is part of arduino-cloud-cli.
//
// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

package ota

import (
"fmt"
"os"

"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cloud-cli/command/ota"
"github.com/arduino/arduino-cloud-cli/config"
"github.com/spf13/cobra"
)

type statusFlags struct {
otaID string
otaIDs string
deviceId string
limit int16
sort string
}

func initOtaStatusCommand() *cobra.Command {
flags := &statusFlags{}
uploadCommand := &cobra.Command{
Use: "status",
Short: "OTA status",
Long: "Get OTA status by OTA or device ID",
Run: func(cmd *cobra.Command, args []string) {
if err := runOtaStatusCommand(flags); err != nil {
feedback.Errorf("Error during ota get status: %v", err)
os.Exit(errorcodes.ErrGeneric)
}
},
}
uploadCommand.Flags().StringVarP(&flags.otaID, "ota-id", "o", "", "OTA ID")
uploadCommand.Flags().StringVarP(&flags.otaIDs, "ota-ids", "", "", "OTA IDs (comma separated)")
uploadCommand.Flags().StringVarP(&flags.deviceId, "device-id", "d", "", "Device ID")
uploadCommand.Flags().Int16VarP(&flags.limit, "limit", "l", 10, "Output limit (default: 10)")
uploadCommand.Flags().StringVarP(&flags.sort, "sort", "s", "desc", "Sorting (default: desc)")

return uploadCommand
}

func runOtaStatusCommand(flags *statusFlags) error {
if flags.otaID == "" && flags.deviceId == "" && flags.otaIDs == "" {
return fmt.Errorf("required flag(s) \"ota-id\" or \"device-id\" or \"ota-ids\" not set")
}

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

return ota.PrintOtaStatus(flags.otaID, flags.otaIDs, flags.deviceId, cred, int(flags.limit), flags.sort)
}
23 changes: 22 additions & 1 deletion command/device/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package device
import (
"context"
"fmt"
"strings"

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

// List command is used to list
Expand All @@ -43,9 +45,19 @@ func List(ctx context.Context, params *ListParams, cred *config.Credentials) ([]
if err != nil {
return nil, err
}
var deviceIdFilter []string
if params.DeviceIds != "" {
deviceIdFilter = strings.Split(params.DeviceIds, ",")
for i := range deviceIdFilter {
deviceIdFilter[i] = strings.TrimSpace(deviceIdFilter[i])
}
}

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

return devices, nil
}

func sliceContains(s []string, v string) bool {
for i := range s {
if v == s[i] {
return true
}
}
return false
}
Loading

0 comments on commit 272e8c1

Please sign in to comment.