github.com/arduino/arduino-cloud-cli@v0.0.0-20240517070944-e7a449561083/internal/template/load.go (about) 1 // This file is part of arduino-cloud-cli. 2 // 3 // Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/) 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published 7 // by the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18 package template 19 20 import ( 21 "context" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "io/ioutil" 26 "os" 27 28 iotclient "github.com/arduino/iot-client-go" 29 "github.com/gofrs/uuid" 30 "gopkg.in/yaml.v3" 31 ) 32 33 // loadTemplate loads a template file and unmarshals it into whatever 34 // is pointed to by the template parameter. If template is nil or 35 // not a pointer, loadTemplate returns an error. 36 // file: path of a template file in json or yaml format. 37 func loadTemplate(file string, template interface{}) error { 38 templateFile, err := os.Open(file) 39 if err != nil { 40 return err 41 } 42 defer templateFile.Close() 43 44 templateBytes, err := ioutil.ReadAll(templateFile) 45 if err != nil { 46 return err 47 } 48 49 // Extract template trying all the supported formats: json and yaml 50 if err = json.Unmarshal([]byte(templateBytes), template); err != nil { 51 if err = yaml.Unmarshal([]byte(templateBytes), template); err != nil { 52 return errors.New("reading template file: template format is not valid") 53 } 54 } 55 56 return nil 57 } 58 59 // LoadThing loads a thing from a thing template file. 60 func LoadThing(file string) (*iotclient.ThingCreate, error) { 61 var template map[string]interface{} 62 err := loadTemplate(file, &template) 63 if err != nil { 64 return nil, err 65 } 66 67 // Adapt thing template to thing structure 68 delete(template, "id") 69 template["properties"] = template["variables"] 70 delete(template, "variables") 71 72 // Convert template into thing structure exploiting json marshalling/unmarshalling 73 thing := &iotclient.ThingCreate{} 74 75 t, err := json.Marshal(template) 76 if err != nil { 77 return nil, fmt.Errorf("%s: %w", "extracting template", err) 78 } 79 80 err = json.Unmarshal(t, &thing) 81 if err != nil { 82 return nil, fmt.Errorf("%s: %w", "creating thing structure from template", err) 83 } 84 85 return thing, nil 86 } 87 88 // LoadDashboard loads a dashboard from a dashboard template file. 89 // It applies the thing overrides specified by the override parameter. 90 // It requires a ThingFetcher to retrieve the actual variable ids. 91 func LoadDashboard(ctx context.Context, file string, override map[string]string, thinger ThingFetcher) (*iotclient.Dashboardv2, error) { 92 template := dashboardTemplate{} 93 err := loadTemplate(file, &template) 94 if err != nil { 95 return nil, err 96 } 97 98 // Adapt the template to the dashboard struct 99 for i, widget := range template.Widgets { 100 // Generate and set a uuid for each widget 101 id, err := uuid.NewV4() 102 if err != nil { 103 return nil, fmt.Errorf("cannot create a uuid for new widget: %w", err) 104 } 105 widget.Id = id.String() 106 filterWidgetOptions(widget.Options) 107 // Even if the widget has no options, its field should exist 108 if widget.Options == nil { 109 widget.Options = make(map[string]interface{}) 110 } 111 // Set the correct variable id, given the thing id and the variable name 112 for j, variable := range widget.Variables { 113 // Check if thing name should be overridden 114 if id, ok := override[variable.ThingID]; ok { 115 variable.ThingID = id 116 } 117 variable.VariableID, err = getVariableID(ctx, variable.ThingID, variable.VariableName, thinger) 118 if err != nil { 119 return nil, err 120 } 121 widget.Variables[j] = variable 122 } 123 template.Widgets[i] = widget 124 } 125 126 // Convert template into dashboard structure exploiting json marshalling/unmarshalling 127 dashboard := &iotclient.Dashboardv2{} 128 t, err := json.Marshal(template) 129 if err != nil { 130 return nil, fmt.Errorf("%s: %w", "extracting template", err) 131 } 132 err = json.Unmarshal(t, &dashboard) 133 if err != nil { 134 return nil, fmt.Errorf("%s: %w", "creating dashboard structure from template", err) 135 } 136 137 return dashboard, nil 138 }