github.com/huaweicloud/golangsdk@v0.0.0-20210831081626-d823fe11ceba/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  	"github.com/huaweicloud/golangsdk"
    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 := golangsdk.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  	t.Bin = body
    85  	return nil
    86  }
    87  
    88  // get the basepath of the TE
    89  func getBasePath() (string, error) {
    90  	basePath, err := filepath.Abs(".")
    91  	if err != nil {
    92  		return "", err
    93  	}
    94  	u, err := golangsdk.NormalizePathURL("", basePath)
    95  	if err != nil {
    96  		return "", err
    97  	}
    98  	return u, nil
    99  }
   100  
   101  // get a an HTTP client to retrieve URL's. This client allows the use of `file`
   102  // scheme since we may need to fetch files from users filesystem
   103  func getHTTPClient() Client {
   104  	transport := &http.Transport{}
   105  	transport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
   106  	return &http.Client{Transport: transport}
   107  }
   108  
   109  // Parse will parse the contents and then validate. The contents MUST be either JSON or YAML.
   110  func (t *TE) Parse() error {
   111  	if err := t.Fetch(); err != nil {
   112  		return err
   113  	}
   114  	if jerr := json.Unmarshal(t.Bin, &t.Parsed); jerr != nil {
   115  		if yerr := yaml.Unmarshal(t.Bin, &t.Parsed); yerr != nil {
   116  			return ErrInvalidDataFormat{}
   117  		}
   118  	}
   119  	return t.Validate()
   120  }
   121  
   122  // Validate validates the contents of TE
   123  func (t *TE) Validate() error {
   124  	return nil
   125  }
   126  
   127  // igfunc is a parameter used by GetFileContents and GetRRFileContents to check
   128  // for valid URL's.
   129  type igFunc func(string, interface{}) bool
   130  
   131  // convert map[interface{}]interface{} to map[string]interface{}
   132  func toStringKeys(m interface{}) (map[string]interface{}, error) {
   133  	switch m.(type) {
   134  	case map[string]interface{}, map[interface{}]interface{}:
   135  		typedMap := make(map[string]interface{})
   136  		if _, ok := m.(map[interface{}]interface{}); ok {
   137  			for k, v := range m.(map[interface{}]interface{}) {
   138  				typedMap[k.(string)] = v
   139  			}
   140  		} else {
   141  			typedMap = m.(map[string]interface{})
   142  		}
   143  		return typedMap, nil
   144  	default:
   145  		return nil, golangsdk.ErrUnexpectedType{Expected: "map[string]interface{}/map[interface{}]interface{}", Actual: fmt.Sprintf("%v", reflect.TypeOf(m))}
   146  	}
   147  }
   148  
   149  // fix the reference to files by replacing relative URL's by absolute
   150  // URL's
   151  func (t *TE) fixFileRefs() {
   152  	tStr := string(t.Bin)
   153  	if t.fileMaps == nil {
   154  		return
   155  	}
   156  	for k, v := range t.fileMaps {
   157  		tStr = strings.Replace(tStr, k, v, -1)
   158  	}
   159  	t.Bin = []byte(tStr)
   160  }