From f36af34fd4ed5c62a36aa3bca1e8ad1114889ee8 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Wed, 25 Aug 2021 16:27:39 +0200 Subject: [PATCH 1/2] Add thing extract command --- cli/thing/extract.go | 43 ++++++++++++++++++++ cli/thing/thing.go | 1 + command/thing/extract.go | 86 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 cli/thing/extract.go create mode 100644 command/thing/extract.go diff --git a/cli/thing/extract.go b/cli/thing/extract.go new file mode 100644 index 00000000..0d82a90a --- /dev/null +++ b/cli/thing/extract.go @@ -0,0 +1,43 @@ +package thing + +import ( + "fmt" + + "github.com/arduino/iot-cloud-cli/command/thing" + "github.com/spf13/cobra" +) + +var extractFlags struct { + id string + outfile string +} + +func initExtractCommand() *cobra.Command { + extractCommand := &cobra.Command{ + Use: "extract", + Short: "Extract and save a thing", + Long: "Extract a thing from Arduino IoT Cloud and save it in a template file", + RunE: runExtractCommand, + } + extractCommand.Flags().StringVarP(&extractFlags.id, "id", "i", "", "Thing ID") + extractCommand.Flags().StringVarP(&extractFlags.outfile, "outfile", "o", "", "Template file destination path") + extractCommand.MarkFlagRequired("id") + return extractCommand +} + +func runExtractCommand(cmd *cobra.Command, args []string) error { + fmt.Printf("Extracting thing %s\n", extractFlags.id) + + params := &thing.ExtractParams{ID: extractFlags.id} + if extractFlags.outfile != "" { + params.Outfile = &extractFlags.outfile + } + + err := thing.Extract(params) + if err != nil { + return err + } + + fmt.Println("Thing successfully extracted") + return nil +} diff --git a/cli/thing/thing.go b/cli/thing/thing.go index 329c7489..b371c985 100644 --- a/cli/thing/thing.go +++ b/cli/thing/thing.go @@ -14,6 +14,7 @@ func NewCommand() *cobra.Command { thingCommand.AddCommand(initCreateCommand()) thingCommand.AddCommand(initListCommand()) thingCommand.AddCommand(initDeleteCommand()) + thingCommand.AddCommand(initExtractCommand()) return thingCommand } diff --git a/command/thing/extract.go b/command/thing/extract.go new file mode 100644 index 00000000..2cefa446 --- /dev/null +++ b/command/thing/extract.go @@ -0,0 +1,86 @@ +package thing + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + + iotclient "github.com/arduino/iot-client-go" + "github.com/arduino/iot-cloud-cli/internal/config" + "github.com/arduino/iot-cloud-cli/internal/iot" +) + +// ExtractParams contains the parameters needed to +// extract a thing from Arduino IoT Cloud and save it on local storage. +// Output indicates the destination path of the extraction. +type ExtractParams struct { + ID string + Outfile *string +} + +// Extract command is used to extract a thing template +// from a thing on Arduino IoT Cloud. +func Extract(params *ExtractParams) error { + conf, err := config.Retrieve() + if err != nil { + return err + } + iotClient, err := iot.NewClient(conf.Client, conf.Secret) + if err != nil { + return err + } + + thing, err := iotClient.GetThing(params.ID) + if err != nil { + err = fmt.Errorf("%s: %w", "cannot extract thing: ", err) + return err + } + + template, err := templateFromThing(thing) + if err != nil { + return err + } + + if params.Outfile == nil { + outfile := thing.Name + "-template.json" + params.Outfile = &outfile + } + err = ioutil.WriteFile(*params.Outfile, template, os.FileMode(0644)) + if err != nil { + err = fmt.Errorf("%s: %w", "cannot write outfile: ", err) + return err + } + + return nil +} + +func templateFromThing(thing *iotclient.ArduinoThing) ([]byte, error) { + template := make(map[string]interface{}) + template["properties_count"] = thing.PropertiesCount + + var props []map[string]interface{} + for _, p := range thing.Properties { + prop := make(map[string]interface{}) + prop["max_value"] = p.MaxValue + prop["min_value"] = p.MinValue + prop["name"] = p.Name + prop["permission"] = p.Permission + prop["persist"] = p.Persist + prop["tag"] = p.Tag + prop["type"] = p.Type + prop["update_parameter"] = p.UpdateParameter + prop["update_strategy"] = p.UpdateStrategy + prop["variable_name"] = p.VariableName + props = append(props, prop) + } + template["properties"] = props + + // Extract json template from thing structure + file, err := json.MarshalIndent(template, "", " ") + if err != nil { + err = fmt.Errorf("%s: %w", "thing marshal failure: ", err) + return nil, err + } + return file, nil +} From 905479c86e4e9a45c41ed088f7bd384d66463307 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Thu, 26 Aug 2021 14:48:39 +0200 Subject: [PATCH 2/2] Update readme --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dc5f3cac..73ec756a 100644 --- a/README.md +++ b/README.md @@ -70,4 +70,8 @@ Print only the thing associated to the passed device: Delete a thing with the following command: -`$ iot-cloud-cli thing delete --device-id ` \ No newline at end of file +`$ iot-cloud-cli thing delete --device-id ` + +Extract a template from an existing thing: + +`$ iot-cloud-cli thing extract --id --outfile `