Skip to content

Commit 59b290f

Browse files
committed
Add thing create command (#20)
things can be created in two ways: - starting from a template. templates can be extracted from existing things - cloning another thing, already available on arduino iot cloud The parameters to create a new thing are: - thing name - mandatory - device (id) to bind the thing to - optional - thing template - mandatory if no thing to clone is passed - thing to clone (id) - mandatory if no template commits history: * Add thing create command * Check clone params before copying * Thing create uses updated iot-client-go Given that iot-client-go has been updated and now supports the creation of things with a slice of properties, it's been here employed to create a thing. Issue: response's error details, coming from arduino iot cloud, are masked by iot-client-go * Add optional * fix thing create - improve flags * Update readme
1 parent 1312750 commit 59b290f

File tree

8 files changed

+253
-204
lines changed

8 files changed

+253
-204
lines changed

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,15 @@ Once a device has been created thorugh the provisioning procedure, it can be del
4040

4141
Devices currently present on Arduino IoT Cloud can be retrieved by using this command:
4242
`$ iot-cloud-cli device list`
43+
44+
## Thing commands
45+
46+
Things can be created starting from a template or by cloning another thing. Additionally, a thing name should be specified.
47+
48+
Create a thing from a template:
49+
50+
`$ iot-cloud-cli thing create --name <thingName> --template <template.json>`
51+
52+
Create a thing by cloning another thing:
53+
54+
`$ iot-cloud-cli thing create --name <thingName> --clone-id <thingToCloneID>`

cli/root.go

+2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ import (
66

77
"github.com/arduino/iot-cloud-cli/cli/config"
88
"github.com/arduino/iot-cloud-cli/cli/device"
9+
"github.com/arduino/iot-cloud-cli/cli/thing"
910
"github.com/spf13/cobra"
1011
)
1112

1213
func Execute() {
1314
rootCmd := &cobra.Command{}
1415
rootCmd.AddCommand(config.NewCommand())
1516
rootCmd.AddCommand(device.NewCommand())
17+
rootCmd.AddCommand(thing.NewCommand())
1618

1719
if err := rootCmd.Execute(); err != nil {
1820
fmt.Fprintln(os.Stderr, err)

cli/thing/create.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package thing
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/arduino/iot-cloud-cli/command/thing"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
var createFlags struct {
11+
name string
12+
deviceID string
13+
template string
14+
cloneID string
15+
}
16+
17+
func initCreateCommand() *cobra.Command {
18+
createCommand := &cobra.Command{
19+
Use: "create",
20+
Short: "Create a thing",
21+
Long: "Create a thing for Arduino IoT Cloud",
22+
RunE: runCreateCommand,
23+
}
24+
createCommand.Flags().StringVarP(&createFlags.name, "name", "n", "", "Thing name")
25+
createCommand.Flags().StringVarP(&createFlags.deviceID, "device-id", "d", "", "ID of Device to bind to the new thing")
26+
createCommand.Flags().StringVarP(&createFlags.cloneID, "clone-id", "c", "", "ID of Thing to be cloned")
27+
createCommand.Flags().StringVarP(&createFlags.template, "template", "t", "", "File containing a thing template")
28+
createCommand.MarkFlagRequired("name")
29+
return createCommand
30+
}
31+
32+
func runCreateCommand(cmd *cobra.Command, args []string) error {
33+
fmt.Printf("Creating thing with name %s\n", createFlags.name)
34+
35+
params := &thing.CreateParams{
36+
Name: createFlags.name,
37+
DeviceID: createFlags.deviceID,
38+
Template: createFlags.template,
39+
CloneID: createFlags.cloneID,
40+
}
41+
42+
thingID, err := thing.Create(params)
43+
if err != nil {
44+
return err
45+
}
46+
47+
fmt.Printf("IoT Cloud thing created with ID: %s\n", thingID)
48+
return nil
49+
}

cli/thing/thing.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package thing
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
)
6+
7+
func NewCommand() *cobra.Command {
8+
thingCommand := &cobra.Command{
9+
Use: "thing",
10+
Short: "Thing commands.",
11+
Long: "Thing commands.",
12+
}
13+
14+
thingCommand.AddCommand(initCreateCommand())
15+
16+
return thingCommand
17+
}

command/thing/create.go

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package thing
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io/ioutil"
7+
"os"
8+
9+
"errors"
10+
11+
iotclient "github.com/arduino/iot-client-go"
12+
"github.com/arduino/iot-cloud-cli/internal/config"
13+
"github.com/arduino/iot-cloud-cli/internal/iot"
14+
)
15+
16+
// CreateParams contains the parameters needed to create a new thing.
17+
type CreateParams struct {
18+
// Mandatory - contains the name of the thing
19+
Name string
20+
// Optional - contains the ID of the device to be bound to the thing
21+
DeviceID string
22+
// Mandatory if device is empty - contains the path of the template file
23+
Template string
24+
// Mandatory if template is empty- name of things to be cloned
25+
CloneID string
26+
}
27+
28+
// Create allows to create a new thing
29+
func Create(params *CreateParams) (string, error) {
30+
if params.Template == "" && params.CloneID == "" {
31+
return "", fmt.Errorf("%s", "provide either a thing(ID) to clone (--clone) or a thing template file (--template)\n")
32+
}
33+
34+
conf, err := config.Retrieve()
35+
if err != nil {
36+
return "", err
37+
}
38+
iotClient, err := iot.NewClient(conf.Client, conf.Secret)
39+
if err != nil {
40+
return "", err
41+
}
42+
43+
var thing *iotclient.Thing
44+
45+
if params.CloneID != "" {
46+
thing, err = clone(iotClient, params.CloneID)
47+
if err != nil {
48+
return "", err
49+
}
50+
51+
} else if params.Template != "" {
52+
thing, err = loadTemplate(params.Template)
53+
if err != nil {
54+
return "", err
55+
}
56+
57+
} else {
58+
return "", errors.New("provide either a thing(ID) to clone (--clone) or a thing template file (--template)")
59+
}
60+
61+
thing.Name = params.Name
62+
force := true
63+
if params.DeviceID != "" {
64+
thing.DeviceId = params.DeviceID
65+
}
66+
thingID, err := iotClient.AddThing(thing, force)
67+
if err != nil {
68+
return "", err
69+
}
70+
71+
return thingID, nil
72+
}
73+
74+
func clone(client iot.Client, thingID string) (*iotclient.Thing, error) {
75+
clone, err := client.GetThing(thingID)
76+
if err != nil {
77+
return nil, fmt.Errorf("%s: %w", "retrieving the thing to be cloned", err)
78+
}
79+
80+
thing := &iotclient.Thing{}
81+
82+
// Copy device id
83+
if clone.DeviceId != "" {
84+
thing.DeviceId = clone.DeviceId
85+
}
86+
87+
// Copy properties
88+
for _, p := range clone.Properties {
89+
thing.Properties = append(thing.Properties, iotclient.Property{
90+
Name: p.Name,
91+
MinValue: p.MinValue,
92+
MaxValue: p.MaxValue,
93+
Permission: p.Permission,
94+
UpdateParameter: p.UpdateParameter,
95+
UpdateStrategy: p.UpdateStrategy,
96+
Type: p.Type,
97+
VariableName: p.VariableName,
98+
Persist: p.Persist,
99+
Tag: p.Tag,
100+
})
101+
}
102+
103+
return thing, nil
104+
}
105+
106+
func loadTemplate(file string) (*iotclient.Thing, error) {
107+
templateFile, err := os.Open(file)
108+
if err != nil {
109+
return nil, err
110+
}
111+
defer templateFile.Close()
112+
113+
templateBytes, err := ioutil.ReadAll(templateFile)
114+
if err != nil {
115+
return nil, err
116+
}
117+
118+
thing := &iotclient.Thing{}
119+
err = json.Unmarshal([]byte(templateBytes), thing)
120+
if err != nil {
121+
return nil, fmt.Errorf("%s: %w", "reading template file: template not valid: ", err)
122+
}
123+
124+
return thing, nil
125+
}

go.mod

+7-6
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,22 @@ module github.com/arduino/iot-cloud-cli
33
go 1.15
44

55
require (
6+
github.com/antihax/optional v1.0.0
67
github.com/arduino/arduino-cli v0.0.0-20210607095659-16f41352eac3
78
github.com/arduino/go-paths-helper v1.6.0
8-
github.com/arduino/iot-client-go v1.3.3
9-
github.com/bcmi-labs/oniudra-cli v0.15.8
10-
github.com/eclipse/paho.mqtt.golang v1.3.2
9+
github.com/arduino/iot-client-go v1.3.4-0.20210824101852-4a44149473c1
1110
github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6
12-
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f
11+
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f // indirect
1312
github.com/sirupsen/logrus v1.8.1
1413
github.com/spf13/cobra v1.1.3
1514
github.com/spf13/viper v1.7.0
1615
github.com/stretchr/testify v1.6.1
1716
go.bug.st/serial v1.3.0
18-
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 // indirect
19-
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
17+
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
18+
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
2019
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6 // indirect
20+
google.golang.org/appengine v1.6.7 // indirect
2121
google.golang.org/genproto v0.0.0-20210504143626-3b2ad6ccc450 // indirect
2222
google.golang.org/grpc v1.39.0
23+
google.golang.org/protobuf v1.27.1 // indirect
2324
)

0 commit comments

Comments
 (0)