blob: 2b80c6a7a379260e54f68e0c3499ed83353be8e1 [file] [log] [blame]
Pratik Mallya5fddb2a2015-09-14 14:04:49 -05001package stacks
2
3import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "io/ioutil"
8 "net/http"
9 "path/filepath"
10 "reflect"
11 "strings"
12
13 "github.com/rackspace/gophercloud"
14 "gopkg.in/yaml.v2"
15)
16
17type Client interface {
18 Get(string) (*http.Response, error)
19}
20
21type TE struct {
22 // Bin stores the contents of the template or environment.
23 Bin []byte
24 // URL stores the URL of the template. This is allowed to be a 'file://'
25 // for local files.
26 URL string
27 // Parsed contains a parsed version of Bin. Since there are 2 different
28 // fields referring to the same value, you must be careful when accessing
29 // this filed.
30 Parsed map[string]interface{}
31 // Files contains a mapping between the urls in templates to their contents.
32 Files map[string]string
33 // fileMaps is a map used internally when determining Files.
34 fileMaps map[string]string
35 // baseURL represents the location of the template or environment file.
36 baseURL string
37 // client is an interface which allows TE to fetch contents from URLS
38 client Client
39}
40
41func (t *TE) Fetch() error {
42 // get baseURL if not already defined
43 if t.baseURL == "" {
44 u, err := getBasePath()
45 if err != nil {
46 return err
47 }
48 t.baseURL = u
49 }
50 if t.Bin != nil {
51 // already have contents
52 return nil
53 }
54 u, err := gophercloud.NormalizePathURL(t.baseURL, t.URL)
55 if err != nil {
56 return err
57 }
58 t.URL = u
59 // get an HTTP client if none present
60 if t.client == nil {
61 t.client = getHTTPClient()
62 }
63 resp, err := t.client.Get(t.URL)
64 if err != nil {
65 return err
66 }
67 defer resp.Body.Close()
68 body, err := ioutil.ReadAll(resp.Body)
69 if err != nil {
70 return err
71 }
72 t.Bin = body
73 return nil
74}
75
76// get the basepath of the template
77func getBasePath() (string, error) {
78 basePath, err := filepath.Abs(".")
79 if err != nil {
80 return "", err
81 }
82 u, err := gophercloud.NormalizePathURL("", basePath)
83 if err != nil {
84 return "", err
85 }
86 return u, nil
87}
88
89// get a an HTTP client to retrieve URLs
90func getHTTPClient() Client {
91 transport := &http.Transport{}
92 transport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
93 return &http.Client{Transport: transport}
94}
95
96// parse the contents and validate
97func (t *TE) Parse() error {
98 if err := t.Fetch(); err != nil {
99 return err
100 }
101 if jerr := json.Unmarshal(t.Bin, &t.Parsed); jerr != nil {
102 if yerr := yaml.Unmarshal(t.Bin, &t.Parsed); yerr != nil {
103 return errors.New(fmt.Sprintf("Data in neither json nor yaml format."))
104 }
105 }
106 return t.Validate()
107}
108
109// base Validate method, always returns true
110func (t *TE) Validate() error {
111 return nil
112}
113
114type igFunc func(string, interface{}) bool
115
116// convert map[interface{}]interface{} to map[string]interface{}
117func toStringKeys(m interface{}) (map[string]interface{}, error) {
118 switch m.(type) {
119 case map[string]interface{}, map[interface{}]interface{}:
120 typed_map := make(map[string]interface{})
121 if _, ok := m.(map[interface{}]interface{}); ok {
122 for k, v := range m.(map[interface{}]interface{}) {
123 typed_map[k.(string)] = v
124 }
125 } else {
126 typed_map = m.(map[string]interface{})
127 }
128 return typed_map, nil
129 default:
130 return nil, errors.New(fmt.Sprintf("Expected a map of type map[string]interface{} or map[interface{}]interface{}, actual type: %v", reflect.TypeOf(m)))
131
132 }
133}
134
135// fix the template reference to files
136func (t *TE) FixFileRefs() {
137 t_str := string(t.Bin)
138 if t.fileMaps == nil {
139 return
140 }
141 for k, v := range t.fileMaps {
142 t_str = strings.Replace(t_str, k, v, -1)
143 }
144 t.Bin = []byte(t_str)
145}