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  }