blob: c08ac303d855e891125ba626fe163cc9e4999cb4 [file] [log] [blame]
Pratik Mallya5fddb2a2015-09-14 14:04:49 -05001package stacks
2
3import (
Pratik Mallya5fddb2a2015-09-14 14:04:49 -05004 "fmt"
Jon Perritt27249f42016-02-18 10:35:59 -06005 "github.com/gophercloud/gophercloud"
Pratik Mallya5fddb2a2015-09-14 14:04:49 -05006 "reflect"
7 "strings"
8)
9
Pratik Mallya3de347f2015-09-22 12:25:59 -050010// Template is a structure that represents OpenStack Heat templates
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050011type Template struct {
12 TE
13}
14
Pratik Mallya3de347f2015-09-22 12:25:59 -050015// TemplateFormatVersions is a map containing allowed variations of the template format version
16// Note that this contains the permitted variations of the _keys_ not the values.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050017var TemplateFormatVersions = map[string]bool{
18 "HeatTemplateFormatVersion": true,
19 "heat_template_version": true,
20 "AWSTemplateFormatVersion": true,
21}
22
Pratik Mallya3de347f2015-09-22 12:25:59 -050023// Validate validates the contents of the Template
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050024func (t *Template) Validate() error {
25 if t.Parsed == nil {
26 if err := t.Parse(); err != nil {
27 return err
28 }
29 }
Pratik Mallya3de347f2015-09-22 12:25:59 -050030 for key := range t.Parsed {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050031 if _, ok := TemplateFormatVersions[key]; ok {
32 return nil
33 }
34 }
Pratik Mallya3de347f2015-09-22 12:25:59 -050035 return fmt.Errorf("Template format version not found.")
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050036}
37
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050038// GetFileContents recursively parses a template to search for urls. These urls
39// are assumed to point to other templates (known in OpenStack Heat as child
40// templates). The contents of these urls are fetched and stored in the `Files`
41// parameter of the template structure. This is the only way that a user can
42// use child templates that are located in their filesystem; urls located on the
43// web (e.g. on github or swift) can be fetched directly by Heat engine.
Pratik Mallyaa979f5b2015-09-22 03:10:55 -050044func (t *Template) getFileContents(te interface{}, ignoreIf igFunc, recurse bool) error {
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050045 // initialize template if empty
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050046 if t.Files == nil {
47 t.Files = make(map[string]string)
48 }
49 if t.fileMaps == nil {
50 t.fileMaps = make(map[string]string)
51 }
52 switch te.(type) {
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050053 // if te is a map
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050054 case map[string]interface{}, map[interface{}]interface{}:
Pratik Mallya3de347f2015-09-22 12:25:59 -050055 teMap, err := toStringKeys(te)
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050056 if err != nil {
57 return err
58 }
Pratik Mallya3de347f2015-09-22 12:25:59 -050059 for k, v := range teMap {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050060 value, ok := v.(string)
61 if !ok {
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050062 // if the value is not a string, recursively parse that value
Pratik Mallyaa979f5b2015-09-22 03:10:55 -050063 if err := t.getFileContents(v, ignoreIf, recurse); err != nil {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050064 return err
65 }
66 } else if !ignoreIf(k, value) {
67 // at this point, the k, v pair has a reference to an external template.
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050068 // The assumption of heatclient is that value v is a reference
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050069 // to a file in the users environment
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050070
71 // create a new child template
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050072 childTemplate := new(Template)
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050073
74 // initialize child template
75
76 // get the base location of the child template
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050077 baseURL, err := gophercloud.NormalizePathURL(t.baseURL, value)
78 if err != nil {
79 return err
80 }
81 childTemplate.baseURL = baseURL
82 childTemplate.client = t.client
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050083
84 // fetch the contents of the child template
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050085 if err := childTemplate.Parse(); err != nil {
86 return err
87 }
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050088
89 // process child template recursively if required. This is
90 // required if the child template itself contains references to
91 // other templates
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050092 if recurse {
Pratik Mallyaa979f5b2015-09-22 03:10:55 -050093 if err := childTemplate.getFileContents(childTemplate.Parsed, ignoreIf, recurse); err != nil {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050094 return err
95 }
96 }
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050097 // update parent template with current child templates' content.
98 // At this point, the child template has been parsed recursively.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050099 t.fileMaps[value] = childTemplate.URL
100 t.Files[childTemplate.URL] = string(childTemplate.Bin)
101
102 }
103 }
104 return nil
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500105 // if te is a slice, call the function on each element of the slice.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500106 case []interface{}:
Pratik Mallya3de347f2015-09-22 12:25:59 -0500107 teSlice := te.([]interface{})
108 for i := range teSlice {
109 if err := t.getFileContents(teSlice[i], ignoreIf, recurse); err != nil {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500110 return err
111 }
112 }
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500113 // if te is anything else, return
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500114 case string, bool, float64, nil, int:
115 return nil
116 default:
Pratik Mallya3de347f2015-09-22 12:25:59 -0500117 return fmt.Errorf("%v: Unrecognized type", reflect.TypeOf(te))
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500118
119 }
120 return nil
121}
122
123// function to choose keys whose values are other template files
124func ignoreIfTemplate(key string, value interface{}) bool {
125 // key must be either `get_file` or `type` for value to be a URL
126 if key != "get_file" && key != "type" {
127 return true
128 }
129 // value must be a string
130 valueString, ok := value.(string)
131 if !ok {
132 return true
133 }
134 // `.template` and `.yaml` are allowed suffixes for template URLs when referred to by `type`
135 if key == "type" && !(strings.HasSuffix(valueString, ".template") || strings.HasSuffix(valueString, ".yaml")) {
136 return true
137 }
138 return false
139}