blob: 420a7fab98b9a033eb1fa72cd88d85cac0da16fc [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
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050017// Client is an interface that expects a Get method similar to http.Get. This
18// is needed for unit testing, since we can mock an http client. Thus, the
19// client will usually be an http.Client EXCEPT in unit tests.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050020type Client interface {
21 Get(string) (*http.Response, error)
22}
23
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050024// Base structure for both Template and Environment
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050025type TE struct {
26 // Bin stores the contents of the template or environment.
27 Bin []byte
28 // URL stores the URL of the template. This is allowed to be a 'file://'
29 // for local files.
30 URL string
31 // Parsed contains a parsed version of Bin. Since there are 2 different
32 // fields referring to the same value, you must be careful when accessing
33 // this filed.
34 Parsed map[string]interface{}
35 // Files contains a mapping between the urls in templates to their contents.
36 Files map[string]string
37 // fileMaps is a map used internally when determining Files.
38 fileMaps map[string]string
39 // baseURL represents the location of the template or environment file.
40 baseURL string
41 // client is an interface which allows TE to fetch contents from URLS
42 client Client
43}
44
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050045// Fetch fetches the contents of a TE from its URL. Once a TE structure has a
46// URL, call the fetch method to fetch the contents.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050047func (t *TE) Fetch() error {
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050048 // if the baseURL is not provided, use the current directors as the base URL
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050049 if t.baseURL == "" {
50 u, err := getBasePath()
51 if err != nil {
52 return err
53 }
54 t.baseURL = u
55 }
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050056
57 // if the contents are already present, do nothing.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050058 if t.Bin != nil {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050059 return nil
60 }
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050061
62 // get a fqdn from the URL using the baseURL of the template. For local files,
63 // the URL's will have the `file` scheme.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050064 u, err := gophercloud.NormalizePathURL(t.baseURL, t.URL)
65 if err != nil {
66 return err
67 }
68 t.URL = u
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050069
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050070 // get an HTTP client if none present
71 if t.client == nil {
72 t.client = getHTTPClient()
73 }
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050074
75 // use the client to fetch the contents of the template
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050076 resp, err := t.client.Get(t.URL)
77 if err != nil {
78 return err
79 }
80 defer resp.Body.Close()
81 body, err := ioutil.ReadAll(resp.Body)
82 if err != nil {
83 return err
84 }
85 t.Bin = body
86 return nil
87}
88
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050089// get the basepath of the template.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050090func getBasePath() (string, error) {
91 basePath, err := filepath.Abs(".")
92 if err != nil {
93 return "", err
94 }
95 u, err := gophercloud.NormalizePathURL("", basePath)
96 if err != nil {
97 return "", err
98 }
99 return u, nil
100}
101
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500102// get a an HTTP client to retrieve URL's. This client allows the use of `file`
103// scheme since we may need to fetch templates from users filesystem
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500104func getHTTPClient() Client {
105 transport := &http.Transport{}
106 transport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
107 return &http.Client{Transport: transport}
108}
109
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500110// Parse will parse the contents and then validate. The contents MUST be either JSON or YAML.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500111func (t *TE) Parse() error {
112 if err := t.Fetch(); err != nil {
113 return err
114 }
115 if jerr := json.Unmarshal(t.Bin, &t.Parsed); jerr != nil {
116 if yerr := yaml.Unmarshal(t.Bin, &t.Parsed); yerr != nil {
117 return errors.New(fmt.Sprintf("Data in neither json nor yaml format."))
118 }
119 }
120 return t.Validate()
121}
122
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500123// base Validate method, always returns nil
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500124func (t *TE) Validate() error {
125 return nil
126}
127
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500128// igfunc is a parameter used by GetFileContents and GetRRFileContents to check
129// for valid template URL's.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500130type igFunc func(string, interface{}) bool
131
132// convert map[interface{}]interface{} to map[string]interface{}
133func toStringKeys(m interface{}) (map[string]interface{}, error) {
134 switch m.(type) {
135 case map[string]interface{}, map[interface{}]interface{}:
136 typed_map := make(map[string]interface{})
137 if _, ok := m.(map[interface{}]interface{}); ok {
138 for k, v := range m.(map[interface{}]interface{}) {
139 typed_map[k.(string)] = v
140 }
141 } else {
142 typed_map = m.(map[string]interface{})
143 }
144 return typed_map, nil
145 default:
146 return nil, errors.New(fmt.Sprintf("Expected a map of type map[string]interface{} or map[interface{}]interface{}, actual type: %v", reflect.TypeOf(m)))
147
148 }
149}
150
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500151// fix the template reference to files by replacing relative URL's by absolute
152// URL's
Pratik Mallyaa979f5b2015-09-22 03:10:55 -0500153func (t *TE) fixFileRefs() {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500154 t_str := string(t.Bin)
155 if t.fileMaps == nil {
156 return
157 }
158 for k, v := range t.fileMaps {
159 t_str = strings.Replace(t_str, k, v, -1)
160 }
161 t.Bin = []byte(t_str)
162}