Skip to content

Suppor for OTA status retrieval #150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading