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