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  }