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

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

Lines changed: 1 addition & 1 deletion
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

README.md

Lines changed: 47 additions & 2 deletions
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+
```

cli/cli.go

Lines changed: 2 additions & 0 deletions
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)

cli/device/device.go

Lines changed: 1 addition & 0 deletions
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())

cli/device/show.go

Lines changed: 102 additions & 0 deletions
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+
}

cli/template/apply.go

Lines changed: 96 additions & 0 deletions
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+
}

cli/template/export.go

Lines changed: 64 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy