blob: c08ac303d855e891125ba626fe163cc9e4999cb4 [file] [log] [blame]
package stacks
import (
"fmt"
"github.com/gophercloud/gophercloud"
"reflect"
"strings"
)
// 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
}
}
for key := range t.Parsed {
if _, ok := TemplateFormatVersions[key]; ok {
return nil
}
}
return fmt.Errorf("Template format version not found.")
}
// 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.Parse(); 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.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 fmt.Errorf("%v: Unrecognized type", 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
}