blob: 4cf5aae41a87cfe387c4858f6a2e23cc9bb63009 [file] [log] [blame]
Pratik Mallya5fddb2a2015-09-14 14:04:49 -05001package stacks
2
3import (
Pratik Mallya5fddb2a2015-09-14 14:04:49 -05004 "fmt"
Pratik Mallya5fddb2a2015-09-14 14:04:49 -05005 "reflect"
6 "strings"
Jon Perrittfea90732016-03-15 02:57:05 -05007
8 "github.com/gophercloud/gophercloud"
Pratik Mallya5fddb2a2015-09-14 14:04:49 -05009)
10
Pratik Mallya3de347f2015-09-22 12:25:59 -050011// Template is a structure that represents OpenStack Heat templates
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050012type Template struct {
13 TE
14}
15
Pratik Mallya3de347f2015-09-22 12:25:59 -050016// 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.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050018var TemplateFormatVersions = map[string]bool{
19 "HeatTemplateFormatVersion": true,
20 "heat_template_version": true,
21 "AWSTemplateFormatVersion": true,
22}
23
Pratik Mallya3de347f2015-09-22 12:25:59 -050024// Validate validates the contents of the Template
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050025func (t *Template) Validate() error {
26 if t.Parsed == nil {
27 if err := t.Parse(); err != nil {
28 return err
29 }
30 }
Jon Perrittfea90732016-03-15 02:57:05 -050031 var invalid string
Pratik Mallya3de347f2015-09-22 12:25:59 -050032 for key := range t.Parsed {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050033 if _, ok := TemplateFormatVersions[key]; ok {
34 return nil
35 }
Jon Perrittfea90732016-03-15 02:57:05 -050036 invalid = key
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050037 }
Jon Perrittfea90732016-03-15 02:57:05 -050038 return ErrInvalidTemplateFormatVersion{Version: invalid}
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050039}
40
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050041// 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.
Pratik Mallyaa979f5b2015-09-22 03:10:55 -050047func (t *Template) getFileContents(te interface{}, ignoreIf igFunc, recurse bool) error {
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050048 // initialize template if empty
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050049 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) {
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050056 // if te is a map
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050057 case map[string]interface{}, map[interface{}]interface{}:
Pratik Mallya3de347f2015-09-22 12:25:59 -050058 teMap, err := toStringKeys(te)
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050059 if err != nil {
60 return err
61 }
Pratik Mallya3de347f2015-09-22 12:25:59 -050062 for k, v := range teMap {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050063 value, ok := v.(string)
64 if !ok {
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050065 // if the value is not a string, recursively parse that value
Pratik Mallyaa979f5b2015-09-22 03:10:55 -050066 if err := t.getFileContents(v, ignoreIf, recurse); err != nil {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050067 return err
68 }
69 } else if !ignoreIf(k, value) {
70 // at this point, the k, v pair has a reference to an external template.
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050071 // The assumption of heatclient is that value v is a reference
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050072 // to a file in the users environment
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050073
74 // create a new child template
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050075 childTemplate := new(Template)
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050076
77 // initialize child template
78
79 // get the base location of the child template
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050080 baseURL, err := gophercloud.NormalizePathURL(t.baseURL, value)
81 if err != nil {
82 return err
83 }
84 childTemplate.baseURL = baseURL
85 childTemplate.client = t.client
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050086
87 // fetch the contents of the child template
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050088 if err := childTemplate.Parse(); err != nil {
89 return err
90 }
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050091
92 // process child template recursively if required. This is
93 // required if the child template itself contains references to
94 // other templates
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050095 if recurse {
Pratik Mallyaa979f5b2015-09-22 03:10:55 -050096 if err := childTemplate.getFileContents(childTemplate.Parsed, ignoreIf, recurse); err != nil {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050097 return err
98 }
99 }
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500100 // update parent template with current child templates' content.
101 // At this point, the child template has been parsed recursively.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500102 t.fileMaps[value] = childTemplate.URL
103 t.Files[childTemplate.URL] = string(childTemplate.Bin)
104
105 }
106 }
107 return nil
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500108 // if te is a slice, call the function on each element of the slice.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500109 case []interface{}:
Pratik Mallya3de347f2015-09-22 12:25:59 -0500110 teSlice := te.([]interface{})
111 for i := range teSlice {
112 if err := t.getFileContents(teSlice[i], ignoreIf, recurse); err != nil {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500113 return err
114 }
115 }
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500116 // if te is anything else, return
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500117 case string, bool, float64, nil, int:
118 return nil
119 default:
Jon Perrittfea90732016-03-15 02:57:05 -0500120 return gophercloud.ErrUnexpectedType{Actual: fmt.Sprintf("%v", reflect.TypeOf(te))}
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500121 }
122 return nil
123}
124
125// function to choose keys whose values are other template files
126func ignoreIfTemplate(key string, value interface{}) bool {
127 // key must be either `get_file` or `type` for value to be a URL
128 if key != "get_file" && key != "type" {
129 return true
130 }
131 // value must be a string
132 valueString, ok := value.(string)
133 if !ok {
134 return true
135 }
136 // `.template` and `.yaml` are allowed suffixes for template URLs when referred to by `type`
137 if key == "type" && !(strings.HasSuffix(valueString, ".template") || strings.HasSuffix(valueString, ".yaml")) {
138 return true
139 }
140 return false
141}