blob: 7b476a9c39297820dbdd66b3d23804d5a029f253 [file] [log] [blame]
Pratik Mallya5fddb2a2015-09-14 14:04:49 -05001package stacks
2
3import (
4 "encoding/json"
Pratik Mallya5fddb2a2015-09-14 14:04:49 -05005 "fmt"
6 "io/ioutil"
7 "net/http"
8 "path/filepath"
9 "reflect"
10 "strings"
11
12 "github.com/rackspace/gophercloud"
13 "gopkg.in/yaml.v2"
14)
15
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050016// Client is an interface that expects a Get method similar to http.Get. This
17// is needed for unit testing, since we can mock an http client. Thus, the
18// client will usually be an http.Client EXCEPT in unit tests.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050019type Client interface {
20 Get(string) (*http.Response, error)
21}
22
Pratik Mallya3de347f2015-09-22 12:25:59 -050023// TE is a base structure for both Template and Environment
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050024type TE struct {
25 // Bin stores the contents of the template or environment.
26 Bin []byte
27 // URL stores the URL of the template. This is allowed to be a 'file://'
28 // for local files.
29 URL string
30 // Parsed contains a parsed version of Bin. Since there are 2 different
31 // fields referring to the same value, you must be careful when accessing
32 // this filed.
33 Parsed map[string]interface{}
34 // Files contains a mapping between the urls in templates to their contents.
35 Files map[string]string
36 // fileMaps is a map used internally when determining Files.
37 fileMaps map[string]string
38 // baseURL represents the location of the template or environment file.
39 baseURL string
40 // client is an interface which allows TE to fetch contents from URLS
41 client Client
42}
43
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050044// Fetch fetches the contents of a TE from its URL. Once a TE structure has a
45// URL, call the fetch method to fetch the contents.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050046func (t *TE) Fetch() error {
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050047 // if the baseURL is not provided, use the current directors as the base URL
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050048 if t.baseURL == "" {
49 u, err := getBasePath()
50 if err != nil {
51 return err
52 }
53 t.baseURL = u
54 }
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050055
56 // if the contents are already present, do nothing.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050057 if t.Bin != nil {
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050058 return nil
59 }
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050060
Pratik Mallya3de347f2015-09-22 12:25:59 -050061 // get a fqdn from the URL using the baseURL of the TE. For local files,
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050062 // the URL's will have the `file` scheme.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050063 u, err := gophercloud.NormalizePathURL(t.baseURL, t.URL)
64 if err != nil {
65 return err
66 }
67 t.URL = u
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050068
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050069 // get an HTTP client if none present
70 if t.client == nil {
71 t.client = getHTTPClient()
72 }
Pratik Mallyabfc6eda2015-09-21 15:01:18 -050073
Pratik Mallya3de347f2015-09-22 12:25:59 -050074 // use the client to fetch the contents of the TE
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050075 resp, err := t.client.Get(t.URL)
76 if err != nil {
77 return err
78 }
79 defer resp.Body.Close()
80 body, err := ioutil.ReadAll(resp.Body)
81 if err != nil {
82 return err
83 }
84 t.Bin = body
85 return nil
86}
87
Pratik Mallya3de347f2015-09-22 12:25:59 -050088// get the basepath of the TE
Pratik Mallya5fddb2a2015-09-14 14:04:49 -050089func getBasePath() (string, error) {
90 basePath, err := filepath.Abs(".")
91 if err != nil {
92 return "", err
93 }
94 u, err := gophercloud.NormalizePathURL("", basePath)
95 if err != nil {
96 return "", err
97 }
98 return u, nil
99}
100
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500101// get a an HTTP client to retrieve URL's. This client allows the use of `file`
Pratik Mallya3de347f2015-09-22 12:25:59 -0500102// scheme since we may need to fetch files from users filesystem
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500103func getHTTPClient() Client {
104 transport := &http.Transport{}
105 transport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
106 return &http.Client{Transport: transport}
107}
108
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500109// Parse will parse the contents and then validate. The contents MUST be either JSON or YAML.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500110func (t *TE) Parse() error {
111 if err := t.Fetch(); err != nil {
112 return err
113 }
114 if jerr := json.Unmarshal(t.Bin, &t.Parsed); jerr != nil {
115 if yerr := yaml.Unmarshal(t.Bin, &t.Parsed); yerr != nil {
Pratik Mallya3de347f2015-09-22 12:25:59 -0500116 return fmt.Errorf("Data in neither json nor yaml format.")
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500117 }
118 }
119 return t.Validate()
120}
121
Pratik Mallya3de347f2015-09-22 12:25:59 -0500122// Validate validates the contents of TE
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500123func (t *TE) Validate() error {
124 return nil
125}
126
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500127// igfunc is a parameter used by GetFileContents and GetRRFileContents to check
Pratik Mallya3de347f2015-09-22 12:25:59 -0500128// for valid URL's.
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500129type igFunc func(string, interface{}) bool
130
131// convert map[interface{}]interface{} to map[string]interface{}
132func toStringKeys(m interface{}) (map[string]interface{}, error) {
133 switch m.(type) {
134 case map[string]interface{}, map[interface{}]interface{}:
Pratik Mallya3de347f2015-09-22 12:25:59 -0500135 typedMap := make(map[string]interface{})
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500136 if _, ok := m.(map[interface{}]interface{}); ok {
137 for k, v := range m.(map[interface{}]interface{}) {
Pratik Mallya3de347f2015-09-22 12:25:59 -0500138 typedMap[k.(string)] = v
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500139 }
140 } else {
Pratik Mallya3de347f2015-09-22 12:25:59 -0500141 typedMap = m.(map[string]interface{})
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500142 }
Pratik Mallya3de347f2015-09-22 12:25:59 -0500143 return typedMap, nil
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500144 default:
Pratik Mallya3de347f2015-09-22 12:25:59 -0500145 return nil, fmt.Errorf("Expected a map of type map[string]interface{} or map[interface{}]interface{}, actual type: %v", reflect.TypeOf(m))
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500146
147 }
148}
149
Pratik Mallya3de347f2015-09-22 12:25:59 -0500150// fix the reference to files by replacing relative URL's by absolute
Pratik Mallyabfc6eda2015-09-21 15:01:18 -0500151// URL's
Pratik Mallyaa979f5b2015-09-22 03:10:55 -0500152func (t *TE) fixFileRefs() {
Pratik Mallya3de347f2015-09-22 12:25:59 -0500153 tStr := string(t.Bin)
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500154 if t.fileMaps == nil {
155 return
156 }
157 for k, v := range t.fileMaps {
Pratik Mallya3de347f2015-09-22 12:25:59 -0500158 tStr = strings.Replace(tStr, k, v, -1)
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500159 }
Pratik Mallya3de347f2015-09-22 12:25:59 -0500160 t.Bin = []byte(tStr)
Pratik Mallya5fddb2a2015-09-14 14:04:49 -0500161}