From d610eb661703589ef3903cef8a8ce7db4b9107d9 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Thu, 4 Nov 2021 12:42:56 +0100 Subject: [PATCH 1/8] Add device create-tags command --- cli/device/device.go | 2 ++ cli/device/tag/create.go | 69 ++++++++++++++++++++++++++++++++++++ command/device/tag/create.go | 47 ++++++++++++++++++++++++ internal/iot/client.go | 14 ++++++++ 4 files changed, 132 insertions(+) create mode 100644 cli/device/tag/create.go create mode 100644 command/device/tag/create.go diff --git a/cli/device/device.go b/cli/device/device.go index 7163c6ce..a1348585 100644 --- a/cli/device/device.go +++ b/cli/device/device.go @@ -18,6 +18,7 @@ package device import ( + "github.com/arduino/arduino-cloud-cli/cli/device/tag" "github.com/spf13/cobra" ) @@ -31,6 +32,7 @@ func NewCommand() *cobra.Command { deviceCommand.AddCommand(initCreateCommand()) deviceCommand.AddCommand(initListCommand()) deviceCommand.AddCommand(initDeleteCommand()) + deviceCommand.AddCommand(tag.InitCreateTagsCommand()) return deviceCommand } diff --git a/cli/device/tag/create.go b/cli/device/tag/create.go new file mode 100644 index 00000000..ee0eadba --- /dev/null +++ b/cli/device/tag/create.go @@ -0,0 +1,69 @@ +// 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 . + +package tag + +import ( + "os" + + "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cloud-cli/command/device/tag" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var createTagsFlags struct { + id string + tags map[string]string +} + +func InitCreateTagsCommand() *cobra.Command { + createTagsCommand := &cobra.Command{ + Use: "create-tags", + Short: "Create or overwrite tags on a device", + Long: "Create or overwrite tags on a device of Arduino IoT Cloud", + Run: runCreateTagsCommand, + } + createTagsCommand.Flags().StringVarP(&createTagsFlags.id, "id", "i", "", "Device ID") + createTagsCommand.Flags().StringToStringVar( + &createTagsFlags.tags, + "tags", + nil, + "List of comma-separated tags. A tag has this format: =", + ) + createTagsCommand.MarkFlagRequired("id") + createTagsCommand.MarkFlagRequired("tags") + return createTagsCommand +} + +func runCreateTagsCommand(cmd *cobra.Command, args []string) { + logrus.Infof("Creating tags on device %s\n", createTagsFlags.id) + + params := &tag.CreateTagsParams{ + ID: createTagsFlags.id, + Tags: createTagsFlags.tags, + } + + err := tag.CreateTags(params) + if err != nil { + feedback.Errorf("Error during device create-tags: %v", err) + os.Exit(errorcodes.ErrGeneric) + } + + logrus.Info("Tags successfully created") +} diff --git a/command/device/tag/create.go b/command/device/tag/create.go new file mode 100644 index 00000000..695c2ec7 --- /dev/null +++ b/command/device/tag/create.go @@ -0,0 +1,47 @@ +// 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 . + +package tag + +import ( + "github.com/arduino/arduino-cloud-cli/internal/config" + "github.com/arduino/arduino-cloud-cli/internal/iot" +) + +// CreateTagsParams contains the parameters needed to create or overwrite tags on a device. +type CreateTagsParams struct { + ID string // Device ID + Tags map[string]string // Map of tags to create +} + +// CreateTags allows to create or overwrite tags on a device +func CreateTags(params *CreateTagsParams) error { + conf, err := config.Retrieve() + if err != nil { + return err + } + iotClient, err := iot.NewClient(conf.Client, conf.Secret) + if err != nil { + return err + } + + err = iotClient.DeviceTagsCreate(params.ID, params.Tags) + if err != nil { + return err + } + return nil +} diff --git a/internal/iot/client.go b/internal/iot/client.go index 0b42326a..fa01c353 100644 --- a/internal/iot/client.go +++ b/internal/iot/client.go @@ -33,6 +33,7 @@ type Client interface { DeviceList() ([]iotclient.ArduinoDevicev2, error) DeviceShow(id string) (*iotclient.ArduinoDevicev2, error) DeviceOTA(id string, file *os.File, expireMins int) error + DeviceTagsCreate(id string, tags map[string]string) error CertificateCreate(id, csr string) (*iotclient.ArduinoCompressedv2, error) ThingCreate(thing *iotclient.Thing, force bool) (*iotclient.ArduinoThing, error) ThingUpdate(id string, thing *iotclient.Thing, force bool) error @@ -126,6 +127,19 @@ func (cl *client) DeviceOTA(id string, file *os.File, expireMins int) error { return nil } +// DeviceTagsCreate allows to create or overwrite tags on a device of Arduino IoT Cloud. +func (cl *client) DeviceTagsCreate(id string, tags map[string]string) error { + for key, val := range tags { + t := iotclient.Tag{Key: key, Value: val} + _, err := cl.api.DevicesV2TagsApi.DevicesV2TagsUpsert(cl.ctx, id, t) + if err != nil { + err = fmt.Errorf("cannot create tag %s: %w", key, errorDetail(err)) + return err + } + } + return nil +} + // CertificateCreate allows to upload a certificate on Arduino IoT Cloud. // It returns the certificate parameters populated by the cloud. func (cl *client) CertificateCreate(id, csr string) (*iotclient.ArduinoCompressedv2, error) { From 2bee75c394405d2c4036b57fd3afd65259d52af2 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Thu, 4 Nov 2021 14:14:22 +0100 Subject: [PATCH 2/8] Add device delete-tags command --- cli/device/device.go | 1 + cli/device/tag/delete.go | 66 ++++++++++++++++++++++++++++++++++++ command/device/tag/delete.go | 45 ++++++++++++++++++++++++ internal/iot/client.go | 14 ++++++++ 4 files changed, 126 insertions(+) create mode 100644 cli/device/tag/delete.go create mode 100644 command/device/tag/delete.go diff --git a/cli/device/device.go b/cli/device/device.go index a1348585..51a894ec 100644 --- a/cli/device/device.go +++ b/cli/device/device.go @@ -33,6 +33,7 @@ func NewCommand() *cobra.Command { deviceCommand.AddCommand(initListCommand()) deviceCommand.AddCommand(initDeleteCommand()) deviceCommand.AddCommand(tag.InitCreateTagsCommand()) + deviceCommand.AddCommand(tag.InitDeleteTagsCommand()) return deviceCommand } diff --git a/cli/device/tag/delete.go b/cli/device/tag/delete.go new file mode 100644 index 00000000..19dfdc1d --- /dev/null +++ b/cli/device/tag/delete.go @@ -0,0 +1,66 @@ +// 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 . + +package tag + +import ( + "os" + + "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cloud-cli/command/device/tag" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var deleteTagsFlags struct { + id string + keys []string +} + +func InitDeleteTagsCommand() *cobra.Command { + deleteTagsCommand := &cobra.Command{ + Use: "delete-tags", + Short: "Delete tags of a device", + Long: "Delete tags of a device of Arduino IoT Cloud", + Run: runDeleteTagsCommand, + } + + deleteTagsCommand.Flags().StringVarP(&deleteTagsFlags.id, "id", "i", "", "Device ID") + deleteTagsCommand.Flags().StringSliceVarP(&deleteTagsFlags.keys, "keys", "k", nil, "List of comma-separated tag keys to delete") + + deleteTagsCommand.MarkFlagRequired("id") + deleteTagsCommand.MarkFlagRequired("keys") + return deleteTagsCommand +} + +func runDeleteTagsCommand(cmd *cobra.Command, args []string) { + logrus.Infof("Deleting tags %s\n", deleteTagsFlags.keys) + + params := &tag.DeleteTagsParams{ + ID: deleteTagsFlags.id, + Keys: deleteTagsFlags.keys, + } + + err := tag.DeleteTags(params) + if err != nil { + feedback.Errorf("Error during device delete-tags: %v", err) + os.Exit(errorcodes.ErrGeneric) + } + + logrus.Info("Tags successfully deleted") +} diff --git a/command/device/tag/delete.go b/command/device/tag/delete.go new file mode 100644 index 00000000..7e9513cf --- /dev/null +++ b/command/device/tag/delete.go @@ -0,0 +1,45 @@ +// 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 . + +package tag + +import ( + "github.com/arduino/arduino-cloud-cli/internal/config" + "github.com/arduino/arduino-cloud-cli/internal/iot" +) + +// DeleteTagsParams contains the parameters needed to +// delete tags of a device from Arduino IoT Cloud. +type DeleteTagsParams struct { + ID string + Keys []string // Keys of tags to delete +} + +// DeleteTags command is used to delete tags of a device +// from Arduino IoT Cloud. +func DeleteTags(params *DeleteTagsParams) error { + conf, err := config.Retrieve() + if err != nil { + return err + } + iotClient, err := iot.NewClient(conf.Client, conf.Secret) + if err != nil { + return err + } + + return iotClient.DeviceTagsDelete(params.ID, params.Keys) +} diff --git a/internal/iot/client.go b/internal/iot/client.go index fa01c353..e12ddac1 100644 --- a/internal/iot/client.go +++ b/internal/iot/client.go @@ -34,6 +34,7 @@ type Client interface { DeviceShow(id string) (*iotclient.ArduinoDevicev2, error) DeviceOTA(id string, file *os.File, expireMins int) error DeviceTagsCreate(id string, tags map[string]string) error + DeviceTagsDelete(id string, keys []string) error CertificateCreate(id, csr string) (*iotclient.ArduinoCompressedv2, error) ThingCreate(thing *iotclient.Thing, force bool) (*iotclient.ArduinoThing, error) ThingUpdate(id string, thing *iotclient.Thing, force bool) error @@ -140,6 +141,19 @@ func (cl *client) DeviceTagsCreate(id string, tags map[string]string) error { return nil } +// DeviceTagsDelete deletes the tags of a device of Arduino IoT Cloud, +// given the device id and the keys of the tags. +func (cl *client) DeviceTagsDelete(id string, keys []string) error { + for _, key := range keys { + _, err := cl.api.DevicesV2TagsApi.DevicesV2TagsDelete(cl.ctx, id, key) + if err != nil { + err = fmt.Errorf("cannot delete tag %s: %w", key, errorDetail(err)) + return err + } + } + return nil +} + // CertificateCreate allows to upload a certificate on Arduino IoT Cloud. // It returns the certificate parameters populated by the cloud. func (cl *client) CertificateCreate(id, csr string) (*iotclient.ArduinoCompressedv2, error) { From 1e724ae429b80e41e05fbf9d2740fc8ec8a7b68c Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Thu, 4 Nov 2021 15:33:33 +0100 Subject: [PATCH 3/8] Move tag commands in tag package --- cli/device/tag/create.go | 7 ++++--- cli/device/tag/delete.go | 7 ++++--- command/{device => }/tag/create.go | 25 +++++++++++++++++-------- command/{device => }/tag/delete.go | 17 ++++++++++++++--- command/tag/resource.go | 13 +++++++++++++ 5 files changed, 52 insertions(+), 17 deletions(-) rename command/{device => }/tag/create.go (69%) rename command/{device => }/tag/delete.go (79%) create mode 100644 command/tag/resource.go diff --git a/cli/device/tag/create.go b/cli/device/tag/create.go index ee0eadba..39008a6c 100644 --- a/cli/device/tag/create.go +++ b/cli/device/tag/create.go @@ -22,7 +22,7 @@ import ( "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cloud-cli/command/device/tag" + "github.com/arduino/arduino-cloud-cli/command/tag" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -55,8 +55,9 @@ func runCreateTagsCommand(cmd *cobra.Command, args []string) { logrus.Infof("Creating tags on device %s\n", createTagsFlags.id) params := &tag.CreateTagsParams{ - ID: createTagsFlags.id, - Tags: createTagsFlags.tags, + ID: createTagsFlags.id, + Tags: createTagsFlags.tags, + Resource: tag.Device, } err := tag.CreateTags(params) diff --git a/cli/device/tag/delete.go b/cli/device/tag/delete.go index 19dfdc1d..f8a73d0b 100644 --- a/cli/device/tag/delete.go +++ b/cli/device/tag/delete.go @@ -22,7 +22,7 @@ import ( "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cloud-cli/command/device/tag" + "github.com/arduino/arduino-cloud-cli/command/tag" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -52,8 +52,9 @@ func runDeleteTagsCommand(cmd *cobra.Command, args []string) { logrus.Infof("Deleting tags %s\n", deleteTagsFlags.keys) params := &tag.DeleteTagsParams{ - ID: deleteTagsFlags.id, - Keys: deleteTagsFlags.keys, + ID: deleteTagsFlags.id, + Keys: deleteTagsFlags.keys, + Resource: tag.Device, } err := tag.DeleteTags(params) diff --git a/command/device/tag/create.go b/command/tag/create.go similarity index 69% rename from command/device/tag/create.go rename to command/tag/create.go index 695c2ec7..8da2f296 100644 --- a/command/device/tag/create.go +++ b/command/tag/create.go @@ -18,17 +18,22 @@ package tag import ( + "errors" + "github.com/arduino/arduino-cloud-cli/internal/config" "github.com/arduino/arduino-cloud-cli/internal/iot" ) -// CreateTagsParams contains the parameters needed to create or overwrite tags on a device. +// CreateTagsParams contains the parameters needed to create or overwrite +// tags on a resource of Arduino IoT Cloud. type CreateTagsParams struct { - ID string // Device ID - Tags map[string]string // Map of tags to create + ID string // Resource ID + Tags map[string]string // Map of tags to create + Resource ResourceType } -// CreateTags allows to create or overwrite tags on a device +// CreateTags allows to create or overwrite tags +// on a resource of Arduino IoT Cloud func CreateTags(params *CreateTagsParams) error { conf, err := config.Retrieve() if err != nil { @@ -39,9 +44,13 @@ func CreateTags(params *CreateTagsParams) error { return err } - err = iotClient.DeviceTagsCreate(params.ID, params.Tags) - if err != nil { - return err + switch params.Resource { + case Thing: + // err = iotClient.ThingTagsCreate(params.ID, params.Tags) + case Device: + err = iotClient.DeviceTagsCreate(params.ID, params.Tags) + default: + err = errors.New("passed Resource parameter is not valid") } - return nil + return err } diff --git a/command/device/tag/delete.go b/command/tag/delete.go similarity index 79% rename from command/device/tag/delete.go rename to command/tag/delete.go index 7e9513cf..28596a1a 100644 --- a/command/device/tag/delete.go +++ b/command/tag/delete.go @@ -18,6 +18,8 @@ package tag import ( + "errors" + "github.com/arduino/arduino-cloud-cli/internal/config" "github.com/arduino/arduino-cloud-cli/internal/iot" ) @@ -25,8 +27,9 @@ import ( // DeleteTagsParams contains the parameters needed to // delete tags of a device from Arduino IoT Cloud. type DeleteTagsParams struct { - ID string - Keys []string // Keys of tags to delete + ID string + Keys []string // Keys of tags to delete + Resource ResourceType } // DeleteTags command is used to delete tags of a device @@ -41,5 +44,13 @@ func DeleteTags(params *DeleteTagsParams) error { return err } - return iotClient.DeviceTagsDelete(params.ID, params.Keys) + switch params.Resource { + case Thing: + // err = iotClient.ThingTagsDelete(params.ID, params.Keys) + case Device: + err = iotClient.DeviceTagsDelete(params.ID, params.Keys) + default: + err = errors.New("passed Resource parameter is not valid") + } + return err } diff --git a/command/tag/resource.go b/command/tag/resource.go new file mode 100644 index 00000000..3912e120 --- /dev/null +++ b/command/tag/resource.go @@ -0,0 +1,13 @@ +package tag + +// ResourceType specifies which resource the +// tag command refers to. +// Valid resources are the entities of the +// cloud that have a 'tags' field. +type ResourceType int + +const ( + None ResourceType = iota + Device + Thing +) From 33cafe201880bfd5950198684934555b955fd008 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Thu, 4 Nov 2021 15:48:04 +0100 Subject: [PATCH 4/8] Add thing create-tags command --- cli/thing/tag/create.go | 70 +++++++++++++++++++++++++++++++++++++++++ cli/thing/thing.go | 2 ++ command/tag/create.go | 2 +- internal/iot/client.go | 14 +++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 cli/thing/tag/create.go diff --git a/cli/thing/tag/create.go b/cli/thing/tag/create.go new file mode 100644 index 00000000..60b26f2b --- /dev/null +++ b/cli/thing/tag/create.go @@ -0,0 +1,70 @@ +// 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 . + +package tag + +import ( + "os" + + "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cloud-cli/command/tag" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var createTagsFlags struct { + id string + tags map[string]string +} + +func InitCreateTagsCommand() *cobra.Command { + createTagsCommand := &cobra.Command{ + Use: "create-tags", + Short: "Create or overwrite tags on a thing", + Long: "Create or overwrite tags on a thing of Arduino IoT Cloud", + Run: runCreateTagsCommand, + } + createTagsCommand.Flags().StringVarP(&createTagsFlags.id, "id", "i", "", "Thing ID") + createTagsCommand.Flags().StringToStringVar( + &createTagsFlags.tags, + "tags", + nil, + "List of comma-separated tags. A tag has this format: =", + ) + createTagsCommand.MarkFlagRequired("id") + createTagsCommand.MarkFlagRequired("tags") + return createTagsCommand +} + +func runCreateTagsCommand(cmd *cobra.Command, args []string) { + logrus.Infof("Creating tags on thing %s\n", createTagsFlags.id) + + params := &tag.CreateTagsParams{ + ID: createTagsFlags.id, + Tags: createTagsFlags.tags, + Resource: tag.Thing, + } + + err := tag.CreateTags(params) + if err != nil { + feedback.Errorf("Error during thing create-tags: %v", err) + os.Exit(errorcodes.ErrGeneric) + } + + logrus.Info("Tags successfully created") +} diff --git a/cli/thing/thing.go b/cli/thing/thing.go index f33bffa9..f21d515a 100644 --- a/cli/thing/thing.go +++ b/cli/thing/thing.go @@ -18,6 +18,7 @@ package thing import ( + "github.com/arduino/arduino-cloud-cli/cli/thing/tag" "github.com/spf13/cobra" ) @@ -34,6 +35,7 @@ func NewCommand() *cobra.Command { thingCommand.AddCommand(initDeleteCommand()) thingCommand.AddCommand(initExtractCommand()) thingCommand.AddCommand(initBindCommand()) + thingCommand.AddCommand(tag.InitCreateTagsCommand()) return thingCommand } diff --git a/command/tag/create.go b/command/tag/create.go index 8da2f296..a0970906 100644 --- a/command/tag/create.go +++ b/command/tag/create.go @@ -46,7 +46,7 @@ func CreateTags(params *CreateTagsParams) error { switch params.Resource { case Thing: - // err = iotClient.ThingTagsCreate(params.ID, params.Tags) + err = iotClient.ThingTagsCreate(params.ID, params.Tags) case Device: err = iotClient.DeviceTagsCreate(params.ID, params.Tags) default: diff --git a/internal/iot/client.go b/internal/iot/client.go index e12ddac1..204698d2 100644 --- a/internal/iot/client.go +++ b/internal/iot/client.go @@ -41,6 +41,7 @@ type Client interface { ThingDelete(id string) error ThingShow(id string) (*iotclient.ArduinoThing, error) ThingList(ids []string, device *string, props bool) ([]iotclient.ArduinoThing, error) + ThingTagsCreate(id string, tags map[string]string) error DashboardCreate(dashboard *iotclient.Dashboardv2) (*iotclient.ArduinoDashboardv2, error) DashboardShow(id string) (*iotclient.ArduinoDashboardv2, error) DashboardDelete(id string) error @@ -234,6 +235,19 @@ func (cl *client) ThingList(ids []string, device *string, props bool) ([]iotclie return things, nil } +// ThingTagsCreate allows to create or overwrite tags on a thing of Arduino IoT Cloud. +func (cl *client) ThingTagsCreate(id string, tags map[string]string) error { + for key, val := range tags { + t := iotclient.Tag{Key: key, Value: val} + _, err := cl.api.ThingsV2TagsApi.ThingsV2TagsUpsert(cl.ctx, id, t) + if err != nil { + err = fmt.Errorf("cannot create tag %s: %w", key, errorDetail(err)) + return err + } + } + return nil +} + // DashboardCreate adds a new dashboard on Arduino IoT Cloud. func (cl *client) DashboardCreate(dashboard *iotclient.Dashboardv2) (*iotclient.ArduinoDashboardv2, error) { newDashboard, _, err := cl.api.DashboardsV2Api.DashboardsV2Create(cl.ctx, *dashboard) From 10beb800f00398ba0b23bc23c21f0ab69f5ba6c4 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Thu, 4 Nov 2021 16:06:30 +0100 Subject: [PATCH 5/8] Add thing delete-tags command --- cli/thing/tag/delete.go | 67 +++++++++++++++++++++++++++++++++++++++++ cli/thing/thing.go | 1 + command/tag/delete.go | 2 +- internal/iot/client.go | 14 +++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 cli/thing/tag/delete.go diff --git a/cli/thing/tag/delete.go b/cli/thing/tag/delete.go new file mode 100644 index 00000000..d2385d99 --- /dev/null +++ b/cli/thing/tag/delete.go @@ -0,0 +1,67 @@ +// 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 . + +package tag + +import ( + "os" + + "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cloud-cli/command/tag" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var deleteTagsFlags struct { + id string + keys []string +} + +func InitDeleteTagsCommand() *cobra.Command { + deleteTagsCommand := &cobra.Command{ + Use: "delete-tags", + Short: "Delete tags of a thing", + Long: "Delete tags of a thing of Arduino IoT Cloud", + Run: runDeleteTagsCommand, + } + + deleteTagsCommand.Flags().StringVarP(&deleteTagsFlags.id, "id", "i", "", "Thing ID") + deleteTagsCommand.Flags().StringSliceVarP(&deleteTagsFlags.keys, "keys", "k", nil, "List of comma-separated tag keys to delete") + + deleteTagsCommand.MarkFlagRequired("id") + deleteTagsCommand.MarkFlagRequired("keys") + return deleteTagsCommand +} + +func runDeleteTagsCommand(cmd *cobra.Command, args []string) { + logrus.Infof("Deleting tags %s\n", deleteTagsFlags.keys) + + params := &tag.DeleteTagsParams{ + ID: deleteTagsFlags.id, + Keys: deleteTagsFlags.keys, + Resource: tag.Thing, + } + + err := tag.DeleteTags(params) + if err != nil { + feedback.Errorf("Error during thing delete-tags: %v", err) + os.Exit(errorcodes.ErrGeneric) + } + + logrus.Info("Tags successfully deleted") +} diff --git a/cli/thing/thing.go b/cli/thing/thing.go index f21d515a..cf5b3d7f 100644 --- a/cli/thing/thing.go +++ b/cli/thing/thing.go @@ -36,6 +36,7 @@ func NewCommand() *cobra.Command { thingCommand.AddCommand(initExtractCommand()) thingCommand.AddCommand(initBindCommand()) thingCommand.AddCommand(tag.InitCreateTagsCommand()) + thingCommand.AddCommand(tag.InitDeleteTagsCommand()) return thingCommand } diff --git a/command/tag/delete.go b/command/tag/delete.go index 28596a1a..cb8fce63 100644 --- a/command/tag/delete.go +++ b/command/tag/delete.go @@ -46,7 +46,7 @@ func DeleteTags(params *DeleteTagsParams) error { switch params.Resource { case Thing: - // err = iotClient.ThingTagsDelete(params.ID, params.Keys) + err = iotClient.ThingTagsDelete(params.ID, params.Keys) case Device: err = iotClient.DeviceTagsDelete(params.ID, params.Keys) default: diff --git a/internal/iot/client.go b/internal/iot/client.go index 204698d2..335bac36 100644 --- a/internal/iot/client.go +++ b/internal/iot/client.go @@ -42,6 +42,7 @@ type Client interface { ThingShow(id string) (*iotclient.ArduinoThing, error) ThingList(ids []string, device *string, props bool) ([]iotclient.ArduinoThing, error) ThingTagsCreate(id string, tags map[string]string) error + ThingTagsDelete(id string, keys []string) error DashboardCreate(dashboard *iotclient.Dashboardv2) (*iotclient.ArduinoDashboardv2, error) DashboardShow(id string) (*iotclient.ArduinoDashboardv2, error) DashboardDelete(id string) error @@ -248,6 +249,19 @@ func (cl *client) ThingTagsCreate(id string, tags map[string]string) error { return nil } +// ThingTagsDelete deletes the tags of a thing of Arduino IoT Cloud, +// given the thing id and the keys of the tags. +func (cl *client) ThingTagsDelete(id string, keys []string) error { + for _, key := range keys { + _, err := cl.api.ThingsV2TagsApi.ThingsV2TagsDelete(cl.ctx, id, key) + if err != nil { + err = fmt.Errorf("cannot delete tag %s: %w", key, errorDetail(err)) + return err + } + } + return nil +} + // DashboardCreate adds a new dashboard on Arduino IoT Cloud. func (cl *client) DashboardCreate(dashboard *iotclient.Dashboardv2) (*iotclient.ArduinoDashboardv2, error) { newDashboard, _, err := cl.api.DashboardsV2Api.DashboardsV2Create(cl.ctx, *dashboard) From 2dd7395dce31db285c852e5adfaa677954a43cc2 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Thu, 4 Nov 2021 17:19:00 +0100 Subject: [PATCH 6/8] Update readme --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 25367cb6..00f7bd87 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,14 @@ Once a device has been created thorugh the provisioning procedure, it can be del Devices currently present on Arduino IoT Cloud can be retrieved by using this command: `$ arduino-cloud-cli device list` +Add tags to a device. Tags should be passed as a comma-separated list of `=` items: + +`$ arduino-cloud-cli device create-tags --id --tags =,=` + +Delete specific tags of a device. The keys of the tags to delete should be passed in a comma-separated list of string: + +`$ arduino-cloud-cli device delete-tags --id --keys ,` + ## Thing commands Things can be created starting from a template or by cloning another thing. @@ -110,6 +118,15 @@ Bind a thing to an existing device: `$ arduino-cloud-cli thing bind --id --device-id ` +Add tags to a thing. Tags should be passed as a comma-separated list of `=` items: + +`$ arduino-cloud-cli thing create-tags --id --tags =,=` + +Delete specific tags of a thing. The keys of the tags to delete should be passed in a comma-separated list of string: + +`$ arduino-cloud-cli thing delete-tags --id --keys ,` + + ## Ota commands Perform an OTA firmware update. Note that the binary file (`.bin`) should be compiled using an arduino core that supports the specified device. From 53b72417853f48c91517e1f2a8b200b729f2199b Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Thu, 4 Nov 2021 17:21:56 +0100 Subject: [PATCH 7/8] Update mocks --- internal/iot/mocks/Client.go | 56 ++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/internal/iot/mocks/Client.go b/internal/iot/mocks/Client.go index 09baa3a9..96920346 100644 --- a/internal/iot/mocks/Client.go +++ b/internal/iot/mocks/Client.go @@ -217,6 +217,34 @@ func (_m *Client) DeviceShow(id string) (*iot.ArduinoDevicev2, error) { return r0, r1 } +// DeviceTagsCreate provides a mock function with given fields: id, tags +func (_m *Client) DeviceTagsCreate(id string, tags map[string]string) error { + ret := _m.Called(id, tags) + + var r0 error + if rf, ok := ret.Get(0).(func(string, map[string]string) error); ok { + r0 = rf(id, tags) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DeviceTagsDelete provides a mock function with given fields: id, keys +func (_m *Client) DeviceTagsDelete(id string, keys []string) error { + ret := _m.Called(id, keys) + + var r0 error + if rf, ok := ret.Get(0).(func(string, []string) error); ok { + r0 = rf(id, keys) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // ThingCreate provides a mock function with given fields: thing, force func (_m *Client) ThingCreate(thing *iot.Thing, force bool) (*iot.ArduinoThing, error) { ret := _m.Called(thing, force) @@ -300,6 +328,34 @@ func (_m *Client) ThingShow(id string) (*iot.ArduinoThing, error) { return r0, r1 } +// ThingTagsCreate provides a mock function with given fields: id, tags +func (_m *Client) ThingTagsCreate(id string, tags map[string]string) error { + ret := _m.Called(id, tags) + + var r0 error + if rf, ok := ret.Get(0).(func(string, map[string]string) error); ok { + r0 = rf(id, tags) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ThingTagsDelete provides a mock function with given fields: id, keys +func (_m *Client) ThingTagsDelete(id string, keys []string) error { + ret := _m.Called(id, keys) + + var r0 error + if rf, ok := ret.Get(0).(func(string, []string) error); ok { + r0 = rf(id, keys) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // ThingUpdate provides a mock function with given fields: id, thing, force func (_m *Client) ThingUpdate(id string, thing *iot.Thing, force bool) error { ret := _m.Called(id, thing, force) From 9b9c10f639f9b5d366a955f1737df6d2760efded Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Mon, 8 Nov 2021 15:04:11 +0100 Subject: [PATCH 8/8] Apply suggestions from code review Co-authored-by: Giuseppe Lumia --- README.md | 4 ++-- cli/device/tag/create.go | 2 +- cli/device/tag/delete.go | 4 ++-- cli/thing/tag/create.go | 2 +- cli/thing/tag/delete.go | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 00f7bd87..994e0bae 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ Add tags to a device. Tags should be passed as a comma-separated list of `= `$ arduino-cloud-cli device create-tags --id --tags =,=` -Delete specific tags of a device. The keys of the tags to delete should be passed in a comma-separated list of string: +Delete specific tags of a device. The keys of the tags to delete should be passed in a comma-separated list of strings: `$ arduino-cloud-cli device delete-tags --id --keys ,` @@ -122,7 +122,7 @@ Add tags to a thing. Tags should be passed as a comma-separated list of `=< `$ arduino-cloud-cli thing create-tags --id --tags =,=` -Delete specific tags of a thing. The keys of the tags to delete should be passed in a comma-separated list of string: +Delete specific tags of a thing. The keys of the tags to delete should be passed in a comma-separated list of strings: `$ arduino-cloud-cli thing delete-tags --id --keys ,` diff --git a/cli/device/tag/create.go b/cli/device/tag/create.go index 39008a6c..e43345e4 100644 --- a/cli/device/tag/create.go +++ b/cli/device/tag/create.go @@ -44,7 +44,7 @@ func InitCreateTagsCommand() *cobra.Command { &createTagsFlags.tags, "tags", nil, - "List of comma-separated tags. A tag has this format: =", + "Comma-separated list of tags with format =.", ) createTagsCommand.MarkFlagRequired("id") createTagsCommand.MarkFlagRequired("tags") diff --git a/cli/device/tag/delete.go b/cli/device/tag/delete.go index f8a73d0b..e5b38dd3 100644 --- a/cli/device/tag/delete.go +++ b/cli/device/tag/delete.go @@ -41,7 +41,7 @@ func InitDeleteTagsCommand() *cobra.Command { } deleteTagsCommand.Flags().StringVarP(&deleteTagsFlags.id, "id", "i", "", "Device ID") - deleteTagsCommand.Flags().StringSliceVarP(&deleteTagsFlags.keys, "keys", "k", nil, "List of comma-separated tag keys to delete") + deleteTagsCommand.Flags().StringSliceVarP(&deleteTagsFlags.keys, "keys", "k", nil, "Comma-separated list of keys of tags to delete") deleteTagsCommand.MarkFlagRequired("id") deleteTagsCommand.MarkFlagRequired("keys") @@ -49,7 +49,7 @@ func InitDeleteTagsCommand() *cobra.Command { } func runDeleteTagsCommand(cmd *cobra.Command, args []string) { - logrus.Infof("Deleting tags %s\n", deleteTagsFlags.keys) + logrus.Infof("Deleting tags with keys %s\n", deleteTagsFlags.keys) params := &tag.DeleteTagsParams{ ID: deleteTagsFlags.id, diff --git a/cli/thing/tag/create.go b/cli/thing/tag/create.go index 60b26f2b..18b7e5a1 100644 --- a/cli/thing/tag/create.go +++ b/cli/thing/tag/create.go @@ -44,7 +44,7 @@ func InitCreateTagsCommand() *cobra.Command { &createTagsFlags.tags, "tags", nil, - "List of comma-separated tags. A tag has this format: =", + "Comma-separated list of tags with format =.", ) createTagsCommand.MarkFlagRequired("id") createTagsCommand.MarkFlagRequired("tags") diff --git a/cli/thing/tag/delete.go b/cli/thing/tag/delete.go index d2385d99..fd5ae9bb 100644 --- a/cli/thing/tag/delete.go +++ b/cli/thing/tag/delete.go @@ -41,7 +41,7 @@ func InitDeleteTagsCommand() *cobra.Command { } deleteTagsCommand.Flags().StringVarP(&deleteTagsFlags.id, "id", "i", "", "Thing ID") - deleteTagsCommand.Flags().StringSliceVarP(&deleteTagsFlags.keys, "keys", "k", nil, "List of comma-separated tag keys to delete") + deleteTagsCommand.Flags().StringSliceVarP(&deleteTagsFlags.keys, "keys", "k", nil, "Comma-separated list of keys of tags to delete") deleteTagsCommand.MarkFlagRequired("id") deleteTagsCommand.MarkFlagRequired("keys") @@ -49,7 +49,7 @@ func InitDeleteTagsCommand() *cobra.Command { } func runDeleteTagsCommand(cmd *cobra.Command, args []string) { - logrus.Infof("Deleting tags %s\n", deleteTagsFlags.keys) + logrus.Infof("Deleting tags with keys %s\n", deleteTagsFlags.keys) params := &tag.DeleteTagsParams{ ID: deleteTagsFlags.id,