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