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 161
|
package stacks
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"path/filepath"
"reflect"
"strings"
"github.com/rackspace/gophercloud"
"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 fmt.Errorf("Data in neither json nor yaml format.")
}
}
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, fmt.Errorf("Expected a map of type map[string]interface{} or map[interface{}]interface{}, actual type: %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)
}
|