Skip to content

Commit 5ca90df

Browse files
authored
[WIRE-422] Custom templates support (#157)
1 parent a17c934 commit 5ca90df

File tree

26 files changed

+1769
-18
lines changed

26 files changed

+1769
-18
lines changed

Diff for: .licenses/go/github.com/arduino/iot-client-go/v2.dep.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: github.com/arduino/iot-client-go/v2
3-
version: v2.0.2
3+
version: v2.0.3
44
type: go
55
summary:
66
homepage: https://pkg.go.dev/github.com/arduino/iot-client-go/v2

Diff for: README.md

+47-2
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ Here are the FQBNs of the Arduino boards that can be provisioned with this comma
114114
* `arduino:samd:mkrnb1500`
115115
* `arduino:mbed_opta:opta`
116116
* `arduino:mbed_giga:giga`
117+
* `arduino:esp32:nano_nora`
118+
* `arduino:renesas_uno:unor4wifi`
117119

118120
If the device supports more than one connectivity type (Eg: WiFi and Ethernet) the --connection flag can be used to set the desired connectivity
119121

@@ -327,10 +329,17 @@ Note that the binary file (`.bin`) should be compiled using an arduino core that
327329
arduino-cloud-cli ota upload --device-id <deviceID> --file <sketch-file.ino.bin>
328330
```
329331

330-
The default OTA upload should complete in 10 minutes. Use `--deferred` flag to extend this time to one week (see an example sketch [here](https://github.com/arduino-libraries/ArduinoIoTCloud/blob/ab0af75a5666f875929029ac6df59e04789269c5/examples/ArduinoIoTCloud-DeferredOTA/ArduinoIoTCloud-DeferredOTA.ino)):
332+
This schedule a new OTA. Its ID is printed as output.
333+
It is possible to check status for scheduled/executed OTAs using status command.
331334

332335
```bash
333-
arduino-cloud-cli ota upload --device-id <deviceID> --file <sketch-file.ino.bin> --deferred
336+
arduino-cloud-cli ota status --ota-id <otaID>
337+
```
338+
339+
or by device
340+
341+
```bash
342+
arduino-cloud-cli ota status --device-id <deviceID>
334343
```
335344

336345
### Mass upload
@@ -383,3 +392,39 @@ Create a dashboard: dashboards can be created only starting from a template. Sup
383392
```bash
384393
arduino-cloud-cli dashboard create --name <dashboardName> --template <template.(json|yaml)> --override <thing-0>=<actualThingID>,<thing-1>=<otherActualThingID>
385394
```
395+
396+
## Custom templates
397+
398+
### List custom templates
399+
400+
Use following command to list available custom templates
401+
402+
```bash
403+
arduino-cloud-cli template list
404+
```
405+
406+
### Export custom template
407+
408+
Given list command, it is possible to get custom template ID. Given its ID, use following command to export it as '.tino' archive:
409+
410+
```bash
411+
arduino-cloud-cli template export -t <templateID>
412+
```
413+
414+
it is possible to specify output directory with '-d' flag
415+
416+
### Import custom template
417+
418+
To import a custom template, use command:
419+
420+
```bash
421+
arduino-cloud-cli template import -f <template .tino archive>
422+
```
423+
424+
### Template apply
425+
426+
It is possible to apply a given template to a device. Apply will generate required resources. Configure device connectivity using '-n' option (see --help for further details).
427+
428+
```bash
429+
arduino-cloud-cli template apply -d <deviceID> -t <templateID> -p "<name prefix>" -n SECRET_SSID=<ssid>,SECRET_OPTIONAL_PASS=<pwd>
430+
```

Diff for: cli/cli.go

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/arduino/arduino-cloud-cli/cli/dashboard"
3030
"github.com/arduino/arduino-cloud-cli/cli/device"
3131
"github.com/arduino/arduino-cloud-cli/cli/ota"
32+
"github.com/arduino/arduino-cloud-cli/cli/template"
3233
"github.com/arduino/arduino-cloud-cli/cli/thing"
3334
"github.com/arduino/arduino-cloud-cli/cli/version"
3435
"github.com/sirupsen/logrus"
@@ -65,6 +66,7 @@ func Execute() {
6566
cli.AddCommand(thing.NewCommand())
6667
cli.AddCommand(dashboard.NewCommand())
6768
cli.AddCommand(ota.NewCommand())
69+
cli.AddCommand(template.NewCommand())
6870

6971
if err := cli.Execute(); err != nil {
7072
fmt.Fprintln(os.Stderr, err)

Diff for: cli/device/device.go

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ func NewCommand() *cobra.Command {
3131

3232
deviceCommand.AddCommand(initCreateCommand())
3333
deviceCommand.AddCommand(initListCommand())
34+
deviceCommand.AddCommand(initShowCommand())
3435
deviceCommand.AddCommand(initDeleteCommand())
3536
deviceCommand.AddCommand(tag.InitCreateTagsCommand())
3637
deviceCommand.AddCommand(tag.InitDeleteTagsCommand())

Diff for: cli/device/show.go

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// This file is part of arduino-cloud-cli.
2+
//
3+
// Copyright (C) 2024 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as published
7+
// by the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
package device
19+
20+
import (
21+
"context"
22+
"fmt"
23+
"os"
24+
"strings"
25+
26+
"github.com/arduino/arduino-cli/cli/errorcodes"
27+
"github.com/arduino/arduino-cli/cli/feedback"
28+
"github.com/arduino/arduino-cli/table"
29+
"github.com/arduino/arduino-cloud-cli/command/device"
30+
"github.com/arduino/arduino-cloud-cli/config"
31+
"github.com/sirupsen/logrus"
32+
"github.com/spf13/cobra"
33+
)
34+
35+
type showFlags struct {
36+
deviceId string
37+
}
38+
39+
func initShowCommand() *cobra.Command {
40+
flags := &showFlags{}
41+
showCommand := &cobra.Command{
42+
Use: "show",
43+
Short: "Show device properties",
44+
Long: "Show device properties on Arduino IoT Cloud",
45+
Run: func(cmd *cobra.Command, args []string) {
46+
if err := runShowCommand(flags); err != nil {
47+
feedback.Errorf("Error during device show: %v", err)
48+
os.Exit(errorcodes.ErrGeneric)
49+
}
50+
},
51+
}
52+
showCommand.Flags().StringVarP(&flags.deviceId, "device-id", "d", "", "device ID")
53+
54+
showCommand.MarkFlagRequired("device-id")
55+
56+
return showCommand
57+
}
58+
59+
func runShowCommand(flags *showFlags) error {
60+
logrus.Info("Show device")
61+
62+
cred, err := config.RetrieveCredentials()
63+
if err != nil {
64+
return fmt.Errorf("retrieving credentials: %w", err)
65+
}
66+
67+
dev, _, err := device.Show(context.TODO(), flags.deviceId, cred)
68+
if err != nil {
69+
return err
70+
}
71+
72+
feedback.PrintResult(showResult{dev})
73+
return nil
74+
}
75+
76+
type showResult struct {
77+
device *device.DeviceInfo
78+
}
79+
80+
func (r showResult) Data() interface{} {
81+
return r.device
82+
}
83+
84+
func (r showResult) String() string {
85+
if r.device == nil {
86+
return "No device found."
87+
}
88+
t := table.New()
89+
t.SetHeader("Name", "ID", "Board", "FQBN", "SerialNumber", "Status", "Connection type", "Thing", "Tags")
90+
t.AddRow(
91+
r.device.Name,
92+
r.device.ID,
93+
r.device.Board,
94+
r.device.FQBN,
95+
r.device.Serial,
96+
dereferenceString(r.device.Status),
97+
dereferenceString(r.device.ConnectionType),
98+
dereferenceString(r.device.ThingID),
99+
strings.Join(r.device.Tags, ","),
100+
)
101+
return t.Render()
102+
}

Diff for: cli/template/apply.go

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// This file is part of arduino-cloud-cli.
2+
//
3+
// Copyright (C) 2024 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as published
7+
// by the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
package template
19+
20+
import (
21+
"fmt"
22+
"os"
23+
"strings"
24+
25+
"github.com/arduino/arduino-cli/cli/errorcodes"
26+
"github.com/arduino/arduino-cli/cli/feedback"
27+
"github.com/arduino/arduino-cloud-cli/command/template"
28+
"github.com/arduino/arduino-cloud-cli/config"
29+
"github.com/spf13/cobra"
30+
)
31+
32+
type applyFlags struct {
33+
templateId string
34+
templatePrefix string
35+
deviceId string
36+
netCredentials string
37+
applyOta bool
38+
}
39+
40+
func initTemplateApplyCommand() *cobra.Command {
41+
flags := &applyFlags{}
42+
applyCommand := &cobra.Command{
43+
Use: "apply",
44+
Short: "Apply custom template",
45+
Long: "Given a template, apply it and create all the resources defined in it",
46+
Run: func(cmd *cobra.Command, args []string) {
47+
if err := runTemplateApplyCommand(flags); err != nil {
48+
feedback.Errorf("Error during template apply: %v", err)
49+
os.Exit(errorcodes.ErrGeneric)
50+
}
51+
},
52+
}
53+
54+
applyCommand.Flags().StringVarP(&flags.templateId, "template-id", "t", "", "Template ID")
55+
applyCommand.Flags().StringVarP(&flags.templatePrefix, "prefix", "p", "", "Prefix to apply to the name of created resources")
56+
applyCommand.Flags().StringVarP(&flags.deviceId, "device-id", "d", "", "Device ID")
57+
applyCommand.Flags().StringVarP(&flags.netCredentials, "network-credentials", "n", "", "Comma separated network credentials used to configure device with format <key>=<value>. Supported values: SECRET_SSID | SECRET_OPTIONAL_PASS | SECRET_DEVICE_KEY")
58+
59+
applyCommand.MarkFlagRequired("template-id")
60+
applyCommand.MarkFlagRequired("prefix")
61+
applyCommand.MarkFlagRequired("device-id")
62+
63+
flags.applyOta = false
64+
65+
return applyCommand
66+
}
67+
68+
func runTemplateApplyCommand(flags *applyFlags) error {
69+
cred, err := config.RetrieveCredentials()
70+
if err != nil {
71+
return fmt.Errorf("retrieving credentials: %w", err)
72+
}
73+
74+
deviceNetCredentials, err := parseCredentials(flags.netCredentials)
75+
if err != nil {
76+
return fmt.Errorf("parsing network credentials: %w", err)
77+
}
78+
79+
return template.ApplyCustomTemplates(cred, flags.templateId, flags.deviceId, flags.templatePrefix, deviceNetCredentials, flags.applyOta)
80+
}
81+
82+
func parseCredentials(credentials string) (map[string]string, error) {
83+
credentialsMap := make(map[string]string)
84+
if credentials == "" {
85+
return credentialsMap, nil
86+
}
87+
credentialsArray := strings.Split(credentials, ",")
88+
for _, credential := range credentialsArray {
89+
credentialArray := strings.Split(credential, "=")
90+
if len(credentialArray) != 2 {
91+
return nil, fmt.Errorf("invalid network credential: %s", credential)
92+
}
93+
credentialsMap[credentialArray[0]] = credentialArray[1]
94+
}
95+
return credentialsMap, nil
96+
}

Diff for: cli/template/export.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// This file is part of arduino-cloud-cli.
2+
//
3+
// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as published
7+
// by the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
package template
19+
20+
import (
21+
"fmt"
22+
"os"
23+
24+
"github.com/arduino/arduino-cli/cli/errorcodes"
25+
"github.com/arduino/arduino-cli/cli/feedback"
26+
"github.com/arduino/arduino-cloud-cli/command/template"
27+
"github.com/arduino/arduino-cloud-cli/config"
28+
"github.com/spf13/cobra"
29+
)
30+
31+
type exportFlags struct {
32+
templateId string
33+
path string
34+
}
35+
36+
func initTemplateExportCommand() *cobra.Command {
37+
flags := &exportFlags{}
38+
uploadCommand := &cobra.Command{
39+
Use: "export",
40+
Short: "Export template",
41+
Long: "Export template to a file",
42+
Run: func(cmd *cobra.Command, args []string) {
43+
if err := runTemplateExportCommand(flags); err != nil {
44+
feedback.Errorf("Error during template export: %v", err)
45+
os.Exit(errorcodes.ErrGeneric)
46+
}
47+
},
48+
}
49+
50+
uploadCommand.Flags().StringVarP(&flags.templateId, "template-id", "t", "", "Template id")
51+
uploadCommand.Flags().StringVarP(&flags.path, "directory", "d", "", "Output directory")
52+
53+
uploadCommand.MarkFlagRequired("template-id")
54+
55+
return uploadCommand
56+
}
57+
58+
func runTemplateExportCommand(flags *exportFlags) error {
59+
cred, err := config.RetrieveCredentials()
60+
if err != nil {
61+
return fmt.Errorf("retrieving credentials: %w", err)
62+
}
63+
return template.ExportCustomTemplate(cred, flags.templateId, flags.path)
64+
}

0 commit comments

Comments
 (0)