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