github.com/opentelekomcloud/gophertelekomcloud@v0.9.3/openstack/rts/v1/stacks/utils.go (about) 1 package stacks 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "path/filepath" 9 "reflect" 10 "strings" 11 12 "gopkg.in/yaml.v2" 13 14 "github.com/opentelekomcloud/gophertelekomcloud" 15 ) 16 17 // 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. 20 type Client interface { 21 Get(string) (*http.Response, error) 22 } 23 24 // TE is a base structure for both Template and Environment 25 type 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 45 // 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. 47 func (t *TE) Fetch() error { 48 // if the baseURL is not provided, use the current directors as the base URL 49 if t.baseURL == "" { 50 u, err := getBasePath() 51 if err != nil { 52 return err 53 } 54 t.baseURL = u 55 } 56 57 // if the contents are already present, do nothing. 58 if t.Bin != nil { 59 return nil 60 } 61 62 // get a fqdn from the URL using the baseURL of the TE. For local files, 63 // the URL's will have the `file` scheme. 64 u, err := golangsdk.NormalizePathURL(t.baseURL, t.URL) 65 if err != nil { 66 return err 67 } 68 t.URL = u 69 70 // get an HTTP client if none present 71 if t.client == nil { 72 t.client = getHTTPClient() 73 } 74 75 // use the client to fetch the contents of the TE 76 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 89 // get the basepath of the TE 90 func getBasePath() (string, error) { 91 basePath, err := filepath.Abs(".") 92 if err != nil { 93 return "", err 94 } 95 u, err := golangsdk.NormalizePathURL("", basePath) 96 if err != nil { 97 return "", err 98 } 99 return u, nil 100 } 101 102 // get a an HTTP client to retrieve URL's. This client allows the use of `file` 103 // scheme since we may need to fetch files from users filesystem 104 func getHTTPClient() Client { 105 transport := &http.Transport{} 106 transport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/"))) 107 return &http.Client{Transport: transport} 108 } 109 110 // Parse will parse the contents and then validate. The contents MUST be either JSON or YAML. 111 func (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 ErrInvalidDataFormat{} 118 } 119 } 120 return t.Validate() 121 } 122 123 // Validate validates the contents of TE 124 func (t *TE) Validate() error { 125 return nil 126 } 127 128 // igfunc is a parameter used by GetFileContents and GetRRFileContents to check 129 // for valid URL's. 130 type igFunc func(string, interface{}) bool 131 132 // convert map[interface{}]interface{} to map[string]interface{} 133 func toStringKeys(m interface{}) (map[string]interface{}, error) { 134 switch m.(type) { 135 case map[string]interface{}, map[interface{}]interface{}: 136 typedMap := make(map[string]interface{}) 137 if _, ok := m.(map[interface{}]interface{}); ok { 138 for k, v := range m.(map[interface{}]interface{}) { 139 typedMap[k.(string)] = v 140 } 141 } else { 142 typedMap = m.(map[string]interface{}) 143 } 144 return typedMap, nil 145 default: 146 return nil, golangsdk.ErrUnexpectedType{Expected: "map[string]interface{}/map[interface{}]interface{}", Actual: fmt.Sprintf("%v", reflect.TypeOf(m))} 147 } 148 } 149 150 // fix the reference to files by replacing relative URL's by absolute 151 // URL's 152 func (t *TE) fixFileRefs() { 153 tStr := string(t.Bin) 154 if t.fileMaps == nil { 155 return 156 } 157 for k, v := range t.fileMaps { 158 tStr = strings.Replace(tStr, k, v, -1) 159 } 160 t.Bin = []byte(tStr) 161 }