diff --git a/README.md b/README.md index a412f297..f40e446f 100644 --- a/README.md +++ b/README.md @@ -52,3 +52,18 @@ Create a thing from a template: Create a thing by cloning another thing: `$ iot-cloud-cli thing create --name --clone-id ` + + +Things can be printed thanks to a list command. + +Print a list of available things and their properties by using this command: + +`$ iot-cloud-cli thing list --properties` + +Print a *filtered* list of available things, print only things belonging to the ids list: + +`$ iot-cloud-cli thing list --ids ,` + +Print only the thing associated to the passed device: + +`$ iot-cloud-cli thing list --device-id ` \ No newline at end of file diff --git a/cli/thing/list.go b/cli/thing/list.go new file mode 100644 index 00000000..5212af2c --- /dev/null +++ b/cli/thing/list.go @@ -0,0 +1,82 @@ +package thing + +import ( + "fmt" + "strings" + + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cli/table" + "github.com/arduino/iot-cloud-cli/command/thing" + "github.com/spf13/cobra" +) + +var listFlags struct { + ids []string + deviceID string + properties bool +} + +func initListCommand() *cobra.Command { + listCommand := &cobra.Command{ + Use: "list", + Short: "List things", + Long: "List things on Arduino IoT Cloud", + RunE: runListCommand, + } + // list only the things corresponding to the passed ids + listCommand.Flags().StringSliceVarP(&listFlags.ids, "ids", "i", []string{}, "List of thing IDs to be retrieved") + // list only the thing associated to the passed device id + listCommand.Flags().StringVarP(&listFlags.deviceID, "device-id", "d", "", "ID of Device associated to the thing to be retrieved") + listCommand.Flags().BoolVarP(&listFlags.properties, "properties", "p", false, "Show thing properties") + return listCommand +} + +func runListCommand(cmd *cobra.Command, args []string) error { + fmt.Println("Listing things") + + params := &thing.ListParams{ + IDs: listFlags.ids, + Properties: listFlags.properties, + } + if listFlags.deviceID != "" { + params.DeviceID = &listFlags.deviceID + } + + things, err := thing.List(params) + if err != nil { + return err + } + + feedback.PrintResult(result{things}) + return nil +} + +type result struct { + things []thing.ThingInfo +} + +func (r result) Data() interface{} { + return r.things +} + +func (r result) String() string { + if len(r.things) == 0 { + return "No things found." + } + t := table.New() + + h := []interface{}{"Name", "ID", "Device"} + if listFlags.properties { + h = append(h, "Properties") + } + t.SetHeader(h...) + + for _, thing := range r.things { + r := []interface{}{thing.Name, thing.ID, thing.DeviceID} + if listFlags.properties { + r = append(r, strings.Join(thing.Properties, ", ")) + } + t.AddRow(r...) + } + return t.Render() +} diff --git a/cli/thing/thing.go b/cli/thing/thing.go index a1ccce9e..a0ab4259 100644 --- a/cli/thing/thing.go +++ b/cli/thing/thing.go @@ -12,6 +12,7 @@ func NewCommand() *cobra.Command { } thingCommand.AddCommand(initCreateCommand()) + thingCommand.AddCommand(initListCommand()) return thingCommand } diff --git a/command/thing/list.go b/command/thing/list.go new file mode 100644 index 00000000..caa21286 --- /dev/null +++ b/command/thing/list.go @@ -0,0 +1,61 @@ +package thing + +import ( + "github.com/arduino/iot-cloud-cli/internal/config" + "github.com/arduino/iot-cloud-cli/internal/iot" +) + +// ThingInfo contains the main parameters of +// an Arduino IoT Cloud thing. +type ThingInfo struct { + Name string + ID string + DeviceID string + Properties []string +} + +// ListParams contains the optional parameters needed +// to filter the things to be listed. +// If IDs is valid, only things belonging to that list are listed. +// If DeviceID is provided, only things associated to that device are listed. +// If Properties is true, properties names are retrieved. +type ListParams struct { + IDs []string + DeviceID *string + Properties bool +} + +// List command is used to list +// the things of Arduino IoT Cloud. +func List(params *ListParams) ([]ThingInfo, error) { + conf, err := config.Retrieve() + if err != nil { + return nil, err + } + iotClient, err := iot.NewClient(conf.Client, conf.Secret) + if err != nil { + return nil, err + } + + foundThings, err := iotClient.ListThings(params.IDs, params.DeviceID, params.Properties) + if err != nil { + return nil, err + } + + var things []ThingInfo + for _, foundThing := range foundThings { + var props []string + for _, p := range foundThing.Properties { + props = append(props, p.Name) + } + th := ThingInfo{ + Name: foundThing.Name, + ID: foundThing.Id, + DeviceID: foundThing.DeviceId, + Properties: props, + } + things = append(things, th) + } + + return things, nil +} diff --git a/internal/iot/client.go b/internal/iot/client.go index c8ee311c..f4b7d463 100644 --- a/internal/iot/client.go +++ b/internal/iot/client.go @@ -17,6 +17,7 @@ type Client interface { AddCertificate(id, csr string) (*iotclient.ArduinoCompressedv2, error) AddThing(thing *iotclient.Thing, force bool) (string, error) GetThing(id string) (*iotclient.ArduinoThing, error) + ListThings(ids []string, device *string, props bool) ([]iotclient.ArduinoThing, error) } type client struct { @@ -117,6 +118,27 @@ func (cl *client) GetThing(id string) (*iotclient.ArduinoThing, error) { return &thing, nil } +// ListThings returns a list of things on Arduino IoT Cloud. +func (cl *client) ListThings(ids []string, device *string, props bool) ([]iotclient.ArduinoThing, error) { + opts := &iotclient.ThingsV2ListOpts{} + opts.ShowProperties = optional.NewBool(props) + + if ids != nil { + opts.Ids = optional.NewInterface(ids) + } + + if device != nil { + opts.DeviceId = optional.NewString(*device) + } + + things, _, err := cl.api.ThingsV2Api.ThingsV2List(cl.ctx, opts) + if err != nil { + err = fmt.Errorf("retrieving things, %w", err) + return nil, err + } + return things, nil +} + func (cl *client) setup(client, secret string) error { // Get the access token in exchange of client_id and client_secret tok, err := token(client, secret)