| 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 | 
 | } |