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 ` 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 +}