Skip to content

Commit ded44cb

Browse files
authored
Support multiple template formats (#30)
Thing templates are now supported in both json and yaml formats. This means that, during the thing creation, the thing template can be passed either as json or yaml file. On the other hand, during the template extraction, the user can specify his preferred format with an appropriate flag. The default value for the format is yaml. $ ./iot-cloud-cli thing extract --format json * Thing template - Support both yaml and json formats * Update docs * Extract template supports multiple formats * thing extract - set yaml as default format * Update readme
1 parent dde0874 commit ded44cb

File tree

6 files changed

+78
-21
lines changed

6 files changed

+78
-21
lines changed

README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ Devices currently present on Arduino IoT Cloud can be retrieved by using this co
4545

4646
Things can be created starting from a template or by cloning another thing.
4747

48-
Create a thing from a thing template. The name parameter is optional. If it is provided then it overrides the name retrieved from the template:
48+
Create a thing from a thing template. Supported template formats are JSON and YAML. The name parameter is optional. If it is provided then it overrides the name retrieved from the template:
4949

50-
`$ iot-cloud-cli thing create --name <thingName> --template <template.json>`
50+
`$ iot-cloud-cli thing create --name <thingName> --template <template.(json|yaml)>`
5151

5252
Create a thing by cloning another thing, here the *name is mandatory*:
5353

@@ -72,9 +72,9 @@ Delete a thing with the following command:
7272

7373
`$ iot-cloud-cli thing delete --device-id <deviceID>`
7474

75-
Extract a template from an existing thing:
75+
Extract a template from an existing thing. The template can be saved in two formats: json or yaml. The default format is yaml:
7676

77-
`$ iot-cloud-cli thing extract --id <thingID> --outfile <templateFile.json>`
77+
`$ iot-cloud-cli thing extract --id <thingID> --outfile <templateFile> --format <yaml|json>`
7878

7979
Bind a thing to an existing device:
8080

cli/thing/create.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ func initCreateCommand() *cobra.Command {
2020
RunE: runCreateCommand,
2121
}
2222
createCommand.Flags().StringVarP(&createFlags.name, "name", "n", "", "Thing name")
23-
createCommand.Flags().StringVarP(&createFlags.template, "template", "t", "", "File containing a thing template")
23+
createCommand.Flags().StringVarP(
24+
&createFlags.template,
25+
"template",
26+
"t",
27+
"",
28+
"File containing a thing template, JSON and YAML format are supported",
29+
)
2430
createCommand.MarkFlagRequired("template")
2531
return createCommand
2632
}

cli/thing/extract.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
var extractFlags struct {
1111
id string
1212
outfile string
13+
format string
1314
}
1415

1516
func initExtractCommand() *cobra.Command {
@@ -21,6 +22,13 @@ func initExtractCommand() *cobra.Command {
2122
}
2223
extractCommand.Flags().StringVarP(&extractFlags.id, "id", "i", "", "Thing ID")
2324
extractCommand.Flags().StringVarP(&extractFlags.outfile, "outfile", "o", "", "Template file destination path")
25+
extractCommand.Flags().StringVar(
26+
&extractFlags.format,
27+
"format",
28+
"yaml",
29+
"Format of template file, can be {json|yaml}. Default is 'yaml'",
30+
)
31+
2432
extractCommand.MarkFlagRequired("id")
2533
return extractCommand
2634
}
@@ -29,7 +37,8 @@ func runExtractCommand(cmd *cobra.Command, args []string) error {
2937
fmt.Printf("Extracting template from thing %s\n", extractFlags.id)
3038

3139
params := &thing.ExtractParams{
32-
ID: extractFlags.id,
40+
ID: extractFlags.id,
41+
Format: extractFlags.format,
3342
}
3443
if extractFlags.outfile != "" {
3544
params.Outfile = &extractFlags.outfile

command/thing/create.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
iotclient "github.com/arduino/iot-client-go"
1111
"github.com/arduino/iot-cloud-cli/internal/config"
1212
"github.com/arduino/iot-cloud-cli/internal/iot"
13+
"gopkg.in/yaml.v3"
1314
)
1415

1516
// CreateParams contains the parameters needed to create a new thing.
@@ -67,9 +68,12 @@ func loadTemplate(file string) (*iotclient.Thing, error) {
6768
}
6869

6970
template := make(map[string]interface{})
70-
err = json.Unmarshal([]byte(templateBytes), &template)
71-
if err != nil {
72-
return nil, fmt.Errorf("%s: %w", "reading template file: template not valid: ", err)
71+
72+
// Extract template trying all the supported formats: json and yaml
73+
if err = json.Unmarshal([]byte(templateBytes), &template); err != nil {
74+
if err = yaml.Unmarshal([]byte(templateBytes), &template); err != nil {
75+
return nil, errors.New("reading template file: template format is not valid")
76+
}
7377
}
7478

7579
// Adapt thing template to thing structure

command/thing/extract.go

+49-12
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,36 @@ package thing
22

33
import (
44
"encoding/json"
5+
"errors"
56
"fmt"
67
"io/ioutil"
78
"os"
9+
"strings"
810

911
iotclient "github.com/arduino/iot-client-go"
1012
"github.com/arduino/iot-cloud-cli/internal/config"
1113
"github.com/arduino/iot-cloud-cli/internal/iot"
14+
"gopkg.in/yaml.v3"
1215
)
1316

1417
// ExtractParams contains the parameters needed to
1518
// extract a thing from Arduino IoT Cloud and save it on local storage.
19+
// Format determines the file format of the template ("json" or "yaml")
1620
// Output indicates the destination path of the extraction.
1721
type ExtractParams struct {
1822
ID string
23+
Format string
1924
Outfile *string
2025
}
2126

2227
// Extract command is used to extract a thing template
2328
// from a thing on Arduino IoT Cloud.
2429
func Extract(params *ExtractParams) error {
30+
params.Format = strings.ToLower(params.Format)
31+
if params.Format != "json" && params.Format != "yaml" {
32+
return errors.New("format is not valid: only 'json' and 'yaml' are supported")
33+
}
34+
2535
conf, err := config.Retrieve()
2636
if err != nil {
2737
return err
@@ -42,23 +52,19 @@ func Extract(params *ExtractParams) error {
4252
return err
4353
}
4454

45-
if params.Outfile == nil {
46-
outfile := thing.Name + "-template.json"
47-
params.Outfile = &outfile
48-
}
49-
err = ioutil.WriteFile(*params.Outfile, template, os.FileMode(0644))
55+
err = templateToFile(template, params)
5056
if err != nil {
51-
err = fmt.Errorf("%s: %w", "cannot write outfile: ", err)
5257
return err
5358
}
5459

5560
return nil
5661
}
5762

58-
func templateFromThing(thing *iotclient.ArduinoThing) ([]byte, error) {
63+
func templateFromThing(thing *iotclient.ArduinoThing) (map[string]interface{}, error) {
5964
template := make(map[string]interface{})
6065
template["name"] = thing.Name
6166

67+
// Extract template from thing structure
6268
var props []map[string]interface{}
6369
for _, p := range thing.Properties {
6470
prop := make(map[string]interface{})
@@ -72,11 +78,42 @@ func templateFromThing(thing *iotclient.ArduinoThing) ([]byte, error) {
7278
}
7379
template["variables"] = props
7480

75-
// Extract json template from thing structure
76-
file, err := json.MarshalIndent(template, "", " ")
81+
return template, nil
82+
}
83+
84+
func templateToFile(template map[string]interface{}, params *ExtractParams) error {
85+
var file []byte
86+
var err error
87+
88+
if params.Format == "json" {
89+
file, err = json.MarshalIndent(template, "", " ")
90+
if err != nil {
91+
return fmt.Errorf("%s: %w", "thing marshal failure: ", err)
92+
}
93+
94+
} else if params.Format == "yaml" {
95+
file, err = yaml.Marshal(template)
96+
if err != nil {
97+
return fmt.Errorf("%s: %w", "thing marshal failure: ", err)
98+
}
99+
100+
} else {
101+
return errors.New("format is not valid: only 'json' and 'yaml' are supported")
102+
}
103+
104+
if params.Outfile == nil {
105+
name, ok := template["name"].(string)
106+
if name == "" || !ok {
107+
return errors.New("thing template does not have a valid name")
108+
}
109+
outfile := name + "." + params.Format
110+
params.Outfile = &outfile
111+
}
112+
113+
err = ioutil.WriteFile(*params.Outfile, file, os.FileMode(0644))
77114
if err != nil {
78-
err = fmt.Errorf("%s: %w", "thing marshal failure: ", err)
79-
return nil, err
115+
return fmt.Errorf("%s: %w", "cannot write outfile: ", err)
80116
}
81-
return file, nil
117+
118+
return nil
82119
}

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ require (
2020
google.golang.org/genproto v0.0.0-20210504143626-3b2ad6ccc450 // indirect
2121
google.golang.org/grpc v1.39.0
2222
google.golang.org/protobuf v1.27.1 // indirect
23+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
2324
)

0 commit comments

Comments
 (0)