/
utils.go
160 lines (144 loc) · 4.25 KB
/
utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package stacks
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"path/filepath"
"reflect"
"strings"
"github.com/gophercloud/gophercloud"
yaml "gopkg.in/yaml.v2"
)
// Client is an interface that expects a Get method similar to http.Get. This
// is needed for unit testing, since we can mock an http client. Thus, the
// client will usually be an http.Client EXCEPT in unit tests.
type Client interface {
Get(string) (*http.Response, error)
}
// TE is a base structure for both Template and Environment
type TE struct {
// Bin stores the contents of the template or environment.
Bin []byte
// URL stores the URL of the template. This is allowed to be a 'file://'
// for local files.
URL string
// Parsed contains a parsed version of Bin. Since there are 2 different
// fields referring to the same value, you must be careful when accessing
// this filed.
Parsed map[string]interface{}
// Files contains a mapping between the urls in templates to their contents.
Files map[string]string
// fileMaps is a map used internally when determining Files.
fileMaps map[string]string
// baseURL represents the location of the template or environment file.
baseURL string
// client is an interface which allows TE to fetch contents from URLS
client Client
}
// Fetch fetches the contents of a TE from its URL. Once a TE structure has a
// URL, call the fetch method to fetch the contents.
func (t *TE) Fetch() error {
// if the baseURL is not provided, use the current directors as the base URL
if t.baseURL == "" {
u, err := getBasePath()
if err != nil {
return err
}
t.baseURL = u
}
// if the contents are already present, do nothing.
if t.Bin != nil {
return nil
}
// get a fqdn from the URL using the baseURL of the TE. For local files,
// the URL's will have the `file` scheme.
u, err := gophercloud.NormalizePathURL(t.baseURL, t.URL)
if err != nil {
return err
}
t.URL = u
// get an HTTP client if none present
if t.client == nil {
t.client = getHTTPClient()
}
// use the client to fetch the contents of the TE
resp, err := t.client.Get(t.URL)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
t.Bin = body
return nil
}
// get the basepath of the TE
func getBasePath() (string, error) {
basePath, err := filepath.Abs(".")
if err != nil {
return "", err
}
u, err := gophercloud.NormalizePathURL("", basePath)
if err != nil {
return "", err
}
return u, nil
}
// get a an HTTP client to retrieve URL's. This client allows the use of `file`
// scheme since we may need to fetch files from users filesystem
func getHTTPClient() Client {
transport := &http.Transport{}
transport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
return &http.Client{Transport: transport}
}
// Parse will parse the contents and then validate. The contents MUST be either JSON or YAML.
func (t *TE) Parse() error {
if err := t.Fetch(); err != nil {
return err
}
if jerr := json.Unmarshal(t.Bin, &t.Parsed); jerr != nil {
if yerr := yaml.Unmarshal(t.Bin, &t.Parsed); yerr != nil {
return ErrInvalidDataFormat{}
}
}
return t.Validate()
}
// Validate validates the contents of TE
func (t *TE) Validate() error {
return nil
}
// igfunc is a parameter used by GetFileContents and GetRRFileContents to check
// for valid URL's.
type igFunc func(string, interface{}) bool
// convert map[interface{}]interface{} to map[string]interface{}
func toStringKeys(m interface{}) (map[string]interface{}, error) {
switch m.(type) {
case map[string]interface{}, map[interface{}]interface{}:
typedMap := make(map[string]interface{})
if _, ok := m.(map[interface{}]interface{}); ok {
for k, v := range m.(map[interface{}]interface{}) {
typedMap[k.(string)] = v
}
} else {
typedMap = m.(map[string]interface{})
}
return typedMap, nil
default:
return nil, gophercloud.ErrUnexpectedType{Expected: "map[string]interface{}/map[interface{}]interface{}", Actual: fmt.Sprintf("%v", reflect.TypeOf(m))}
}
}
// fix the reference to files by replacing relative URL's by absolute
// URL's
func (t *TE) fixFileRefs() {
tStr := string(t.Bin)
if t.fileMaps == nil {
return
}
for k, v := range t.fileMaps {
tStr = strings.Replace(tStr, k, v, -1)
}
t.Bin = []byte(tStr)
}