Skip to content

[WIRE-422] Custom templates support #157

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 21 commits into from
Jul 29, 2024
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: github.com/arduino/iot-client-go/v2
version: v2.0.2
version: v2.0.3
type: go
summary:
homepage: https://pkg.go.dev/github.com/arduino/iot-client-go/v2
Expand Down
49 changes: 47 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ Here are the FQBNs of the Arduino boards that can be provisioned with this comma
* `arduino:samd:mkrnb1500`
* `arduino:mbed_opta:opta`
* `arduino:mbed_giga:giga`
* `arduino:esp32:nano_nora`
* `arduino:renesas_uno:unor4wifi`

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

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

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)):
This schedule a new OTA. Its ID is printed as output.
It is possible to check status for scheduled/executed OTAs using status command.

```bash
arduino-cloud-cli ota upload --device-id <deviceID> --file <sketch-file.ino.bin> --deferred
arduino-cloud-cli ota status --ota-id <otaID>
```

or by device

```bash
arduino-cloud-cli ota status --device-id <deviceID>
```

### Mass upload
Expand Down Expand Up @@ -383,3 +392,39 @@ Create a dashboard: dashboards can be created only starting from a template. Sup
```bash
arduino-cloud-cli dashboard create --name <dashboardName> --template <template.(json|yaml)> --override <thing-0>=<actualThingID>,<thing-1>=<otherActualThingID>
```

## Custom templates

### List custom templates

Use following command to list available custom templates

```bash
arduino-cloud-cli template list
```

### Export custom template

Given list command, it is possible to get custom template ID. Given its ID, use following command to export it as '.tino' archive:

```bash
arduino-cloud-cli template export -t <templateID>
```

it is possible to specify output directory with '-d' flag

### Import custom template

To import a custom template, use command:

```bash
arduino-cloud-cli template import -f <template .tino archive>
```

### Template apply

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).

```bash
arduino-cloud-cli template apply -d <deviceID> -t <templateID> -p "<name prefix>" -n SECRET_SSID=<ssid>,SECRET_OPTIONAL_PASS=<pwd>
```
2 changes: 2 additions & 0 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/arduino/arduino-cloud-cli/cli/dashboard"
"github.com/arduino/arduino-cloud-cli/cli/device"
"github.com/arduino/arduino-cloud-cli/cli/ota"
"github.com/arduino/arduino-cloud-cli/cli/template"
"github.com/arduino/arduino-cloud-cli/cli/thing"
"github.com/arduino/arduino-cloud-cli/cli/version"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -65,6 +66,7 @@ func Execute() {
cli.AddCommand(thing.NewCommand())
cli.AddCommand(dashboard.NewCommand())
cli.AddCommand(ota.NewCommand())
cli.AddCommand(template.NewCommand())

if err := cli.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
Expand Down
1 change: 1 addition & 0 deletions cli/device/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func NewCommand() *cobra.Command {

deviceCommand.AddCommand(initCreateCommand())
deviceCommand.AddCommand(initListCommand())
deviceCommand.AddCommand(initShowCommand())
deviceCommand.AddCommand(initDeleteCommand())
deviceCommand.AddCommand(tag.InitCreateTagsCommand())
deviceCommand.AddCommand(tag.InitDeleteTagsCommand())
Expand Down
102 changes: 102 additions & 0 deletions cli/device/show.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// This file is part of arduino-cloud-cli.
//
// Copyright (C) 2024 ARDUINO SA (http://www.arduino.cc/)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

package device

import (
"context"
"fmt"
"os"
"strings"

"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cli/table"
"github.com/arduino/arduino-cloud-cli/command/device"
"github.com/arduino/arduino-cloud-cli/config"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

type showFlags struct {
deviceId string
}

func initShowCommand() *cobra.Command {
flags := &showFlags{}
showCommand := &cobra.Command{
Use: "show",
Short: "Show device properties",
Long: "Show device properties on Arduino IoT Cloud",
Run: func(cmd *cobra.Command, args []string) {
if err := runShowCommand(flags); err != nil {
feedback.Errorf("Error during device show: %v", err)
os.Exit(errorcodes.ErrGeneric)
}
},
}
showCommand.Flags().StringVarP(&flags.deviceId, "device-id", "d", "", "device ID")

showCommand.MarkFlagRequired("device-id")

return showCommand
}

func runShowCommand(flags *showFlags) error {
logrus.Info("Show device")

cred, err := config.RetrieveCredentials()
if err != nil {
return fmt.Errorf("retrieving credentials: %w", err)
}

dev, _, err := device.Show(context.TODO(), flags.deviceId, cred)
if err != nil {
return err
}

feedback.PrintResult(showResult{dev})
return nil
}

type showResult struct {
device *device.DeviceInfo
}

func (r showResult) Data() interface{} {
return r.device
}

func (r showResult) String() string {
if r.device == nil {
return "No device found."
}
t := table.New()
t.SetHeader("Name", "ID", "Board", "FQBN", "SerialNumber", "Status", "Connection type", "Thing", "Tags")
t.AddRow(
r.device.Name,
r.device.ID,
r.device.Board,
r.device.FQBN,
r.device.Serial,
dereferenceString(r.device.Status),
dereferenceString(r.device.ConnectionType),
dereferenceString(r.device.ThingID),
strings.Join(r.device.Tags, ","),
)
return t.Render()
}
96 changes: 96 additions & 0 deletions cli/template/apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// This file is part of arduino-cloud-cli.
//
// Copyright (C) 2024 ARDUINO SA (http://www.arduino.cc/)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

package template

import (
"fmt"
"os"
"strings"

"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cloud-cli/command/template"
"github.com/arduino/arduino-cloud-cli/config"
"github.com/spf13/cobra"
)

type applyFlags struct {
templateId string
templatePrefix string
deviceId string
netCredentials string
applyOta bool
}

func initTemplateApplyCommand() *cobra.Command {
flags := &applyFlags{}
applyCommand := &cobra.Command{
Use: "apply",
Short: "Apply custom template",
Long: "Given a template, apply it and create all the resources defined in it",
Run: func(cmd *cobra.Command, args []string) {
if err := runTemplateApplyCommand(flags); err != nil {
feedback.Errorf("Error during template apply: %v", err)
os.Exit(errorcodes.ErrGeneric)
}
},
}

applyCommand.Flags().StringVarP(&flags.templateId, "template-id", "t", "", "Template ID")
applyCommand.Flags().StringVarP(&flags.templatePrefix, "prefix", "p", "", "Prefix to apply to the name of created resources")
applyCommand.Flags().StringVarP(&flags.deviceId, "device-id", "d", "", "Device ID")
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")

applyCommand.MarkFlagRequired("template-id")
applyCommand.MarkFlagRequired("prefix")
applyCommand.MarkFlagRequired("device-id")

flags.applyOta = false

return applyCommand
}

func runTemplateApplyCommand(flags *applyFlags) error {
cred, err := config.RetrieveCredentials()
if err != nil {
return fmt.Errorf("retrieving credentials: %w", err)
}

deviceNetCredentials, err := parseCredentials(flags.netCredentials)
if err != nil {
return fmt.Errorf("parsing network credentials: %w", err)
}

return template.ApplyCustomTemplates(cred, flags.templateId, flags.deviceId, flags.templatePrefix, deviceNetCredentials, flags.applyOta)
}

func parseCredentials(credentials string) (map[string]string, error) {
credentialsMap := make(map[string]string)
if credentials == "" {
return credentialsMap, nil
}
credentialsArray := strings.Split(credentials, ",")
for _, credential := range credentialsArray {
credentialArray := strings.Split(credential, "=")
if len(credentialArray) != 2 {
return nil, fmt.Errorf("invalid network credential: %s", credential)
}
credentialsMap[credentialArray[0]] = credentialArray[1]
}
return credentialsMap, nil
}
64 changes: 64 additions & 0 deletions cli/template/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// This file is part of arduino-cloud-cli.
//
// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

package template

import (
"fmt"
"os"

"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cloud-cli/command/template"
"github.com/arduino/arduino-cloud-cli/config"
"github.com/spf13/cobra"
)

type exportFlags struct {
templateId string
path string
}

func initTemplateExportCommand() *cobra.Command {
flags := &exportFlags{}
uploadCommand := &cobra.Command{
Use: "export",
Short: "Export template",
Long: "Export template to a file",
Run: func(cmd *cobra.Command, args []string) {
if err := runTemplateExportCommand(flags); err != nil {
feedback.Errorf("Error during template export: %v", err)
os.Exit(errorcodes.ErrGeneric)
}
},
}

uploadCommand.Flags().StringVarP(&flags.templateId, "template-id", "t", "", "Template id")
uploadCommand.Flags().StringVarP(&flags.path, "directory", "d", "", "Output directory")

uploadCommand.MarkFlagRequired("template-id")

return uploadCommand
}

func runTemplateExportCommand(flags *exportFlags) error {
cred, err := config.RetrieveCredentials()
if err != nil {
return fmt.Errorf("retrieving credentials: %w", err)
}
return template.ExportCustomTemplate(cred, flags.templateId, flags.path)
}
Loading
Loading