github.com/gophercloud/gophercloud@v1.11.0/openstack/orchestration/v1/stacks/template.go (about) 1 package stacks 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 8 "github.com/gophercloud/gophercloud" 9 ) 10 11 // Template is a structure that represents OpenStack Heat templates 12 type Template struct { 13 TE 14 } 15 16 // TemplateFormatVersions is a map containing allowed variations of the template format version 17 // Note that this contains the permitted variations of the _keys_ not the values. 18 var TemplateFormatVersions = map[string]bool{ 19 "HeatTemplateFormatVersion": true, 20 "heat_template_version": true, 21 "AWSTemplateFormatVersion": true, 22 } 23 24 // Validate validates the contents of the Template 25 func (t *Template) Validate() error { 26 if t.Parsed == nil { 27 if err := t.Parse(); err != nil { 28 return err 29 } 30 } 31 var invalid string 32 for key := range t.Parsed { 33 if _, ok := TemplateFormatVersions[key]; ok { 34 return nil 35 } 36 invalid = key 37 } 38 return ErrInvalidTemplateFormatVersion{Version: invalid} 39 } 40 41 // GetFileContents recursively parses a template to search for urls. These urls 42 // are assumed to point to other templates (known in OpenStack Heat as child 43 // templates). The contents of these urls are fetched and stored in the `Files` 44 // parameter of the template structure. This is the only way that a user can 45 // use child templates that are located in their filesystem; urls located on the 46 // web (e.g. on github or swift) can be fetched directly by Heat engine. 47 func (t *Template) getFileContents(te interface{}, ignoreIf igFunc, recurse bool) error { 48 // initialize template if empty 49 if t.Files == nil { 50 t.Files = make(map[string]string) 51 } 52 if t.fileMaps == nil { 53 t.fileMaps = make(map[string]string) 54 } 55 switch te.(type) { 56 // if te is a map 57 case map[string]interface{}, map[interface{}]interface{}: 58 teMap, err := toStringKeys(te) 59 if err != nil { 60 return err 61 } 62 for k, v := range teMap { 63 value, ok := v.(string) 64 if !ok { 65 // if the value is not a string, recursively parse that value 66 if err := t.getFileContents(v, ignoreIf, recurse); err != nil { 67 return err 68 } 69 } else if !ignoreIf(k, value) { 70 // at this point, the k, v pair has a reference to an external template. 71 // The assumption of heatclient is that value v is a reference 72 // to a file in the users environment 73 74 // create a new child template 75 childTemplate := new(Template) 76 77 // initialize child template 78 79 // get the base location of the child template 80 baseURL, err := gophercloud.NormalizePathURL(t.baseURL, value) 81 if err != nil { 82 return err 83 } 84 childTemplate.baseURL = baseURL 85 childTemplate.client = t.client 86 87 // fetch the contents of the child template 88 if err := childTemplate.Fetch(); err != nil { 89 return err 90 } 91 92 // process child template recursively if required. This is 93 // required if the child template itself contains references to 94 // other templates 95 if recurse { 96 if err := childTemplate.Parse(); err == nil { 97 if err := childTemplate.Validate(); err == nil { 98 if err := childTemplate.getFileContents(childTemplate.Parsed, ignoreIf, recurse); err != nil { 99 return err 100 } 101 } 102 } 103 } 104 // update parent template with current child templates' content. 105 // At this point, the child template has been parsed recursively. 106 t.fileMaps[value] = childTemplate.URL 107 t.Files[childTemplate.URL] = string(childTemplate.Bin) 108 109 } 110 } 111 return nil 112 // if te is a slice, call the function on each element of the slice. 113 case []interface{}: 114 teSlice := te.([]interface{}) 115 for i := range teSlice { 116 if err := t.getFileContents(teSlice[i], ignoreIf, recurse); err != nil { 117 return err 118 } 119 } 120 // if te is anything else, return 121 case string, bool, float64, nil, int: 122 return nil 123 default: 124 return gophercloud.ErrUnexpectedType{Actual: fmt.Sprintf("%v", reflect.TypeOf(te))} 125 } 126 return nil 127 } 128 129 // function to choose keys whose values are other template files 130 func ignoreIfTemplate(key string, value interface{}) bool { 131 // key must be either `get_file` or `type` for value to be a URL 132 if key != "get_file" && key != "type" { 133 return true 134 } 135 // value must be a string 136 valueString, ok := value.(string) 137 if !ok { 138 return true 139 } 140 // `.template` and `.yaml` are allowed suffixes for template URLs when referred to by `type` 141 if key == "type" && !(strings.HasSuffix(valueString, ".template") || strings.HasSuffix(valueString, ".yaml")) { 142 return true 143 } 144 return false 145 }