Skip to content

Add thing create command #20

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 6 commits into from
Aug 26, 2021
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,15 @@ 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:
`$ iot-cloud-cli device list`

## Thing commands

Things can be created starting from a template or by cloning another thing. Additionally, a thing name should be specified.

Create a thing from a template:

`$ iot-cloud-cli thing create --name <thingName> --template <template.json>`

Create a thing by cloning another thing:

`$ iot-cloud-cli thing create --name <thingName> --clone-id <thingToCloneID>`
2 changes: 2 additions & 0 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import (

"github.com/arduino/iot-cloud-cli/cli/config"
"github.com/arduino/iot-cloud-cli/cli/device"
"github.com/arduino/iot-cloud-cli/cli/thing"
"github.com/spf13/cobra"
)

func Execute() {
rootCmd := &cobra.Command{}
rootCmd.AddCommand(config.NewCommand())
rootCmd.AddCommand(device.NewCommand())
rootCmd.AddCommand(thing.NewCommand())

if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
Expand Down
49 changes: 49 additions & 0 deletions cli/thing/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package thing

import (
"fmt"

"github.com/arduino/iot-cloud-cli/command/thing"
"github.com/spf13/cobra"
)

var createFlags struct {
name string
deviceID string
template string
cloneID string
}

func initCreateCommand() *cobra.Command {
createCommand := &cobra.Command{
Use: "create",
Short: "Create a thing",
Long: "Create a thing for Arduino IoT Cloud",
RunE: runCreateCommand,
}
createCommand.Flags().StringVarP(&createFlags.name, "name", "n", "", "Thing name")
createCommand.Flags().StringVarP(&createFlags.deviceID, "device-id", "d", "", "ID of Device to bind to the new thing")
createCommand.Flags().StringVarP(&createFlags.cloneID, "clone-id", "c", "", "ID of Thing to be cloned")
createCommand.Flags().StringVarP(&createFlags.template, "template", "t", "", "File containing a thing template")
createCommand.MarkFlagRequired("name")
return createCommand
}

func runCreateCommand(cmd *cobra.Command, args []string) error {
fmt.Printf("Creating thing with name %s\n", createFlags.name)

params := &thing.CreateParams{
Name: createFlags.name,
DeviceID: createFlags.deviceID,
Template: createFlags.template,
CloneID: createFlags.cloneID,
}

thingID, err := thing.Create(params)
if err != nil {
return err
}

fmt.Printf("IoT Cloud thing created with ID: %s\n", thingID)
return nil
}
17 changes: 17 additions & 0 deletions cli/thing/thing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package thing

import (
"github.com/spf13/cobra"
)

func NewCommand() *cobra.Command {
thingCommand := &cobra.Command{
Use: "thing",
Short: "Thing commands.",
Long: "Thing commands.",
}

thingCommand.AddCommand(initCreateCommand())

return thingCommand
}
125 changes: 125 additions & 0 deletions command/thing/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package thing

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"

"errors"

iotclient "github.com/arduino/iot-client-go"
"github.com/arduino/iot-cloud-cli/internal/config"
"github.com/arduino/iot-cloud-cli/internal/iot"
)

// CreateParams contains the parameters needed to create a new thing.
type CreateParams struct {
// Mandatory - contains the name of the thing
Name string
// Optional - contains the ID of the device to be bound to the thing
DeviceID string
// Mandatory if device is empty - contains the path of the template file
Template string
// Mandatory if template is empty- name of things to be cloned
CloneID string
}

// Create allows to create a new thing
func Create(params *CreateParams) (string, error) {
if params.Template == "" && params.CloneID == "" {
return "", fmt.Errorf("%s", "provide either a thing(ID) to clone (--clone) or a thing template file (--template)\n")
}

conf, err := config.Retrieve()
if err != nil {
return "", err
}
iotClient, err := iot.NewClient(conf.Client, conf.Secret)
if err != nil {
return "", err
}

var thing *iotclient.Thing

if params.CloneID != "" {
thing, err = clone(iotClient, params.CloneID)
if err != nil {
return "", err
}

} else if params.Template != "" {
thing, err = loadTemplate(params.Template)
if err != nil {
return "", err
}

} else {
return "", errors.New("provide either a thing(ID) to clone (--clone) or a thing template file (--template)")
}

thing.Name = params.Name
force := true
if params.DeviceID != "" {
thing.DeviceId = params.DeviceID
}
thingID, err := iotClient.AddThing(thing, force)
if err != nil {
return "", err
}

return thingID, nil
}

func clone(client iot.Client, thingID string) (*iotclient.Thing, error) {
clone, err := client.GetThing(thingID)
if err != nil {
return nil, fmt.Errorf("%s: %w", "retrieving the thing to be cloned", err)
}

thing := &iotclient.Thing{}

// Copy device id
if clone.DeviceId != "" {
thing.DeviceId = clone.DeviceId
}

// Copy properties
for _, p := range clone.Properties {
thing.Properties = append(thing.Properties, iotclient.Property{
Name: p.Name,
MinValue: p.MinValue,
MaxValue: p.MaxValue,
Permission: p.Permission,
UpdateParameter: p.UpdateParameter,
UpdateStrategy: p.UpdateStrategy,
Type: p.Type,
VariableName: p.VariableName,
Persist: p.Persist,
Tag: p.Tag,
})
}

return thing, nil
}

func loadTemplate(file string) (*iotclient.Thing, error) {
templateFile, err := os.Open(file)
if err != nil {
return nil, err
}
defer templateFile.Close()

templateBytes, err := ioutil.ReadAll(templateFile)
if err != nil {
return nil, err
}

thing := &iotclient.Thing{}
err = json.Unmarshal([]byte(templateBytes), thing)
if err != nil {
return nil, fmt.Errorf("%s: %w", "reading template file: template not valid: ", err)
}

return thing, nil
}
9 changes: 6 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ module github.com/arduino/iot-cloud-cli
go 1.15

require (
github.com/antihax/optional v1.0.0 // indirect
github.com/arduino/arduino-cli v0.0.0-20210607095659-16f41352eac3
github.com/arduino/go-paths-helper v1.6.0
github.com/arduino/iot-client-go v1.3.3
github.com/arduino/iot-client-go v1.3.4-0.20210824101852-4a44149473c1
github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v1.1.3
github.com/spf13/viper v1.7.0
github.com/stretchr/testify v1.6.1
go.bug.st/serial v1.3.0
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 // indirect
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210504143626-3b2ad6ccc450 // indirect
google.golang.org/grpc v1.39.0
google.golang.org/protobuf v1.27.1 // indirect
)
18 changes: 10 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ github.com/arduino/go-properties-orderedmap v1.3.0/go.mod h1:DKjD2VXY/NZmlingh4l
github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b/go.mod h1:uwGy5PpN4lqW97FiLnbcx+xx8jly5YuPMJWfVwwjJiQ=
github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b h1:3PjgYG5gVPA7cipp7vIR2lF96KkEJIFBJ+ANnuv6J20=
github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b/go.mod h1:iIPnclBMYm1g32Q5kXoqng4jLhMStReIP7ZxaoUC2y8=
github.com/arduino/iot-client-go v1.3.3 h1:W+92osS+WcdVpePdPmj/BtupM+xV6DOJlI0HGpKrTX4=
github.com/arduino/iot-client-go v1.3.3/go.mod h1:gYvpMt7Qw+OSScTLyIlCnpbvy9y96ey/2zhB4w6FoK0=
github.com/arduino/iot-client-go v1.3.4-0.20210824101852-4a44149473c1 h1:tgVUBPbqkyd3KHTs+gweP5t9KAnkLbAsAMrHvu9jZSg=
github.com/arduino/iot-client-go v1.3.4-0.20210824101852-4a44149473c1/go.mod h1:gYvpMt7Qw+OSScTLyIlCnpbvy9y96ey/2zhB4w6FoK0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
Expand Down Expand Up @@ -494,15 +494,15 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 h1:Ugb8sMTWuWRC3+sz5WeN/4kejDx9BvIwnPUiJBjJE+8=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 h1:3B43BWw0xEBsLZ/NO1VALz6fppU3481pik+2Ksv45z8=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down Expand Up @@ -638,8 +638,9 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
Expand Down Expand Up @@ -701,8 +702,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
28 changes: 28 additions & 0 deletions internal/iot/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package iot

import (
"context"
"encoding/json"
"fmt"

"github.com/antihax/optional"
iotclient "github.com/arduino/iot-client-go"
)

Expand All @@ -13,6 +15,8 @@ type Client interface {
DeleteDevice(id string) error
ListDevices() ([]iotclient.ArduinoDevicev2, error)
AddCertificate(id, csr string) (*iotclient.ArduinoCompressedv2, error)
AddThing(thing *iotclient.Thing, force bool) (string, error)
GetThing(id string) (*iotclient.ArduinoThing, error)
}

type client struct {
Expand Down Expand Up @@ -89,6 +93,30 @@ func (cl *client) AddCertificate(id, csr string) (*iotclient.ArduinoCompressedv2
return &newCert.Compressed, nil
}

// AddThing adds a new thing on Arduino IoT Cloud.
func (cl *client) AddThing(thing *iotclient.Thing, force bool) (string, error) {
opt := &iotclient.ThingsV2CreateOpts{Force: optional.NewBool(force)}
newThing, resp, err := cl.api.ThingsV2Api.ThingsV2Create(cl.ctx, *thing, opt)
if err != nil {
var respObj map[string]interface{}
json.NewDecoder(resp.Body).Decode(&respObj)
resp.Body.Close()
return "", fmt.Errorf("%s: %s: %v", "adding new thing", err, respObj)
}
return newThing.Id, nil
}

// GetThing allows to retrieve a specific thing, given its id,
// from Arduino IoT Cloud.
func (cl *client) GetThing(id string) (*iotclient.ArduinoThing, error) {
thing, _, err := cl.api.ThingsV2Api.ThingsV2Show(cl.ctx, id, nil)
if err != nil {
err = fmt.Errorf("retrieving thing, %w", err)
return nil, err
}
return &thing, 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)
Expand Down