blob: 32fe95bed17f0b54c7b7f7bed4458a57464a9024 [file] [log] [blame]
Pratik Mallya5fddb2a2015-09-14 14:04:49 -05001package stacks
2
3import (
4 "errors"
5 "fmt"
6 "github.com/rackspace/gophercloud"
7 "reflect"
8 "strings"
9)
10
11type Template struct {
12 TE
13}
14
15var TemplateFormatVersions = map[string]bool{
16 "HeatTemplateFormatVersion": true,
17 "heat_template_version": true,
18 "AWSTemplateFormatVersion": true,
19}
20
21func (t *Template) Validate() error {
22 if t.Parsed == nil {
23 if err := t.Parse(); err != nil {
24 return err
25 }
26 }
27 for key, _ := range t.Parsed {
28 if _, ok := TemplateFormatVersions[key]; ok {
29 return nil
30 }
31 }
32 return errors.New(fmt.Sprintf("Template format version not found."))
33}
34
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050035// GetFileContents recursively parses a template to search for urls. These urls
36// are assumed to point to other templates (known in OpenStack Heat as child
37// templates). The contents of these urls are fetched and stored in the `Files`
38// parameter of the template structure. This is the only way that a user can
39// use child templates that are located in their filesystem; urls located on the
40// web (e.g. on github or swift) can be fetched directly by Heat engine.
Pratik Mallyaa979f5b2015-09-22 03:10:55 -050041func (t *Template) getFileContents(te interface{}, ignoreIf igFunc, recurse bool) error {
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050042 // initialize template if empty
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050043 if t.Files == nil {
44 t.Files = make(map[string]string)
45 }
46 if t.fileMaps == nil {
47 t.fileMaps = make(map[string]string)
48 }
49 switch te.(type) {
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050050 // if te is a map
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050051 case map[string]interface{}, map[interface{}]interface{}:
52 te_map, err := toStringKeys(te)
53 if err != nil {
54 return err
55 }
56 for k, v := range te_map {
57 value, ok := v.(string)
58 if !ok {
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050059 // if the value is not a string, recursively parse that value
Pratik Mallyaa979f5b2015-09-22 03:10:55 -050060 if err := t.getFileContents(v, ignoreIf, recurse); err != nil {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050061 return err
62 }
63 } else if !ignoreIf(k, value) {
64 // at this point, the k, v pair has a reference to an external template.
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050065 // The assumption of heatclient is that value v is a reference
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050066 // to a file in the users environment
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050067
68 // create a new child template
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050069 childTemplate := new(Template)
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050070
71 // initialize child template
72
73 // get the base location of the child template
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050074 baseURL, err := gophercloud.NormalizePathURL(t.baseURL, value)
75 if err != nil {
76 return err
77 }
78 childTemplate.baseURL = baseURL
79 childTemplate.client = t.client
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050080
81 // fetch the contents of the child template
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050082 if err := childTemplate.Parse(); err != nil {
83 return err
84 }
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050085
86 // process child template recursively if required. This is
87 // required if the child template itself contains references to
88 // other templates
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050089 if recurse {
Pratik Mallyaa979f5b2015-09-22 03:10:55 -050090 if err := childTemplate.getFileContents(childTemplate.Parsed, ignoreIf, recurse); err != nil {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050091 return err
92 }
93 }
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050094 // update parent template with current child templates' content.
95 // At this point, the child template has been parsed recursively.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050096 t.fileMaps[value] = childTemplate.URL
97 t.Files[childTemplate.URL] = string(childTemplate.Bin)
98
99 }
100 }
101 return nil
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500102 // if te is a slice, call the function on each element of the slice.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500103 case []interface{}:
104 te_slice := te.([]interface{})
105 for i := range te_slice {
Pratik Mallyaa979f5b2015-09-22 03:10:55 -0500106 if err := t.getFileContents(te_slice[i], ignoreIf, recurse); err != nil {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500107 return err
108 }
109 }
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500110 // if te is anything else, return
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500111 case string, bool, float64, nil, int:
112 return nil
113 default:
114 return errors.New(fmt.Sprintf("%v: Unrecognized type", reflect.TypeOf(te)))
115
116 }
117 return nil
118}
119
120// function to choose keys whose values are other template files
121func ignoreIfTemplate(key string, value interface{}) bool {
122 // key must be either `get_file` or `type` for value to be a URL
123 if key != "get_file" && key != "type" {
124 return true
125 }
126 // value must be a string
127 valueString, ok := value.(string)
128 if !ok {
129 return true
130 }
131 // `.template` and `.yaml` are allowed suffixes for template URLs when referred to by `type`
132 if key == "type" && !(strings.HasSuffix(valueString, ".template") || strings.HasSuffix(valueString, ".yaml")) {
133 return true
134 }
135 return false
136}