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  }