diff --git a/command/thing/create.go b/command/thing/create.go index 6467e4d5..4be2bac7 100644 --- a/command/thing/create.go +++ b/command/thing/create.go @@ -18,16 +18,11 @@ package thing import ( - "encoding/json" "errors" - "fmt" - "io/ioutil" - "os" "github.com/arduino/arduino-cloud-cli/internal/config" "github.com/arduino/arduino-cloud-cli/internal/iot" - iotclient "github.com/arduino/iot-client-go" - "gopkg.in/yaml.v3" + "github.com/arduino/arduino-cloud-cli/internal/template" ) // CreateParams contains the parameters needed to create a new thing. @@ -47,7 +42,7 @@ func Create(params *CreateParams) (*ThingInfo, error) { return nil, err } - thing, err := loadTemplate(params.Template) + thing, err := template.LoadThing(params.Template) if err != nil { return nil, err } @@ -69,45 +64,3 @@ func Create(params *CreateParams) (*ThingInfo, error) { return getThingInfo(newThing), nil } - -func loadTemplate(file string) (*iotclient.Thing, error) { - templateFile, err := os.Open(file) - if err != nil { - return nil, err - } - defer templateFile.Close() - - templateBytes, err := ioutil.ReadAll(templateFile) - if err != nil { - return nil, err - } - - template := make(map[string]interface{}) - - // Extract template trying all the supported formats: json and yaml - if err = json.Unmarshal([]byte(templateBytes), &template); err != nil { - if err = yaml.Unmarshal([]byte(templateBytes), &template); err != nil { - return nil, errors.New("reading template file: template format is not valid") - } - } - - // Adapt thing template to thing structure - delete(template, "id") - template["properties"] = template["variables"] - delete(template, "variables") - - // Convert template into thing structure exploiting json marshalling/unmarshalling - thing := &iotclient.Thing{} - - t, err := json.Marshal(template) - if err != nil { - return nil, fmt.Errorf("%s: %w", "extracting template", err) - } - - err = json.Unmarshal(t, &thing) - if err != nil { - return nil, fmt.Errorf("%s: %w", "creating thing structure from template", err) - } - - return thing, nil -} diff --git a/command/thing/extract.go b/command/thing/extract.go index eb1b9770..dfeb47f4 100644 --- a/command/thing/extract.go +++ b/command/thing/extract.go @@ -18,17 +18,13 @@ package thing import ( - "encoding/json" "errors" "fmt" - "io/ioutil" - "os" "strings" "github.com/arduino/arduino-cloud-cli/internal/config" "github.com/arduino/arduino-cloud-cli/internal/iot" - iotclient "github.com/arduino/iot-client-go" - "gopkg.in/yaml.v3" + "github.com/arduino/arduino-cloud-cli/internal/template" ) // ExtractParams contains the parameters needed to @@ -62,62 +58,13 @@ func Extract(params *ExtractParams) error { return err } - template, err := templateFromThing(thing) + templ, err := template.FromThing(thing) if err != nil { return err } - err = templateToFile(template, params) - if err != nil { - return err - } - - return nil -} - -func templateFromThing(thing *iotclient.ArduinoThing) (map[string]interface{}, error) { - template := make(map[string]interface{}) - template["name"] = thing.Name - - // Extract template from thing structure - var props []map[string]interface{} - for _, p := range thing.Properties { - prop := make(map[string]interface{}) - prop["name"] = p.Name - prop["permission"] = p.Permission - prop["type"] = p.Type - prop["update_parameter"] = p.UpdateParameter - prop["update_strategy"] = p.UpdateStrategy - prop["variable_name"] = p.VariableName - props = append(props, prop) - } - template["variables"] = props - - return template, nil -} - -func templateToFile(template map[string]interface{}, params *ExtractParams) error { - var file []byte - var err error - - if params.Format == "json" { - file, err = json.MarshalIndent(template, "", " ") - if err != nil { - return fmt.Errorf("%s: %w", "thing marshal failure: ", err) - } - - } else if params.Format == "yaml" { - file, err = yaml.Marshal(template) - if err != nil { - return fmt.Errorf("%s: %w", "thing marshal failure: ", err) - } - - } else { - return errors.New("format is not valid: only 'json' and 'yaml' are supported") - } - if params.Outfile == nil { - name, ok := template["name"].(string) + name, ok := templ["name"].(string) if name == "" || !ok { return errors.New("thing template does not have a valid name") } @@ -125,9 +72,9 @@ func templateToFile(template map[string]interface{}, params *ExtractParams) erro params.Outfile = &outfile } - err = ioutil.WriteFile(*params.Outfile, file, os.FileMode(0644)) + err = template.ToFile(templ, *params.Outfile, params.Format) if err != nil { - return fmt.Errorf("%s: %w", "cannot write outfile: ", err) + return fmt.Errorf("saving template: %w", err) } return nil diff --git a/internal/template/extract.go b/internal/template/extract.go new file mode 100644 index 00000000..b9790561 --- /dev/null +++ b/internal/template/extract.go @@ -0,0 +1,81 @@ +// 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 . + +package template + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + + iotclient "github.com/arduino/iot-client-go" + "gopkg.in/yaml.v3" +) + +// FromThing extracts a template of type map[string]interface{} from a thing. +func FromThing(thing *iotclient.ArduinoThing) (map[string]interface{}, error) { + template := make(map[string]interface{}) + template["name"] = thing.Name + + // Extract template from thing structure + var props []map[string]interface{} + for _, p := range thing.Properties { + prop := make(map[string]interface{}) + prop["name"] = p.Name + prop["permission"] = p.Permission + prop["type"] = p.Type + prop["update_parameter"] = p.UpdateParameter + prop["update_strategy"] = p.UpdateStrategy + prop["variable_name"] = p.VariableName + props = append(props, prop) + } + template["variables"] = props + + return template, nil +} + +// ToFile takes a generic template and saves it into a file, +// in the specified format (yaml or json). +func ToFile(template map[string]interface{}, outfile string, format string) error { + var file []byte + var err error + + if format == "json" { + file, err = json.MarshalIndent(template, "", " ") + if err != nil { + return fmt.Errorf("%s: %w", "template marshal failure: ", err) + } + + } else if format == "yaml" { + file, err = yaml.Marshal(template) + if err != nil { + return fmt.Errorf("%s: %w", "template marshal failure: ", err) + } + + } else { + return errors.New("format is not valid: only 'json' and 'yaml' are supported") + } + + err = ioutil.WriteFile(outfile, file, os.FileMode(0644)) + if err != nil { + return fmt.Errorf("%s: %w", "cannot write outfile: ", err) + } + + return nil +} diff --git a/internal/template/load.go b/internal/template/load.go new file mode 100644 index 00000000..b6a27df4 --- /dev/null +++ b/internal/template/load.go @@ -0,0 +1,84 @@ +// 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 . + +package template + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + + iotclient "github.com/arduino/iot-client-go" + "gopkg.in/yaml.v3" +) + +// loadTemplate loads a template file and puts it into a generic template +// of type map[string]interface{}. +// The input template should be in json or yaml format. +func loadTemplate(file string) (map[string]interface{}, error) { + templateFile, err := os.Open(file) + if err != nil { + return nil, err + } + defer templateFile.Close() + + templateBytes, err := ioutil.ReadAll(templateFile) + if err != nil { + return nil, err + } + + template := make(map[string]interface{}) + + // Extract template trying all the supported formats: json and yaml + if err = json.Unmarshal([]byte(templateBytes), &template); err != nil { + if err = yaml.Unmarshal([]byte(templateBytes), &template); err != nil { + return nil, errors.New("reading template file: template format is not valid") + } + } + + return template, nil +} + +// LoadThing loads a thing from a thing template file. +func LoadThing(file string) (*iotclient.Thing, error) { + template, err := loadTemplate(file) + if err != nil { + return nil, err + } + + // Adapt thing template to thing structure + delete(template, "id") + template["properties"] = template["variables"] + delete(template, "variables") + + // Convert template into thing structure exploiting json marshalling/unmarshalling + thing := &iotclient.Thing{} + + t, err := json.Marshal(template) + if err != nil { + return nil, fmt.Errorf("%s: %w", "extracting template", err) + } + + err = json.Unmarshal(t, &thing) + if err != nil { + return nil, fmt.Errorf("%s: %w", "creating thing structure from template", err) + } + + return thing, nil +}