github.com/gophercloud/gophercloud@v1.11.0/util.go (about)

     1  package gophercloud
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"path/filepath"
     7  	"reflect"
     8  	"strings"
     9  	"time"
    10  )
    11  
    12  // NormalizePathURL is used to convert rawPath to a fqdn, using basePath as
    13  // a reference in the filesystem, if necessary. basePath is assumed to contain
    14  // either '.' when first used, or the file:// type fqdn of the parent resource.
    15  // e.g. myFavScript.yaml => file://opt/lib/myFavScript.yaml
    16  func NormalizePathURL(basePath, rawPath string) (string, error) {
    17  	u, err := url.Parse(rawPath)
    18  	if err != nil {
    19  		return "", err
    20  	}
    21  	// if a scheme is defined, it must be a fqdn already
    22  	if u.Scheme != "" {
    23  		return u.String(), nil
    24  	}
    25  	// if basePath is a url, then child resources are assumed to be relative to it
    26  	bu, err := url.Parse(basePath)
    27  	if err != nil {
    28  		return "", err
    29  	}
    30  	var basePathSys, absPathSys string
    31  	if bu.Scheme != "" {
    32  		basePathSys = filepath.FromSlash(bu.Path)
    33  		absPathSys = filepath.Join(basePathSys, rawPath)
    34  		bu.Path = filepath.ToSlash(absPathSys)
    35  		return bu.String(), nil
    36  	}
    37  
    38  	absPathSys = filepath.Join(basePath, rawPath)
    39  	u.Path = filepath.ToSlash(absPathSys)
    40  	if err != nil {
    41  		return "", err
    42  	}
    43  	u.Scheme = "file"
    44  	return u.String(), nil
    45  }
    46  
    47  // NormalizeURL is an internal function to be used by provider clients.
    48  //
    49  // It ensures that each endpoint URL has a closing `/`, as expected by
    50  // ServiceClient's methods.
    51  func NormalizeURL(url string) string {
    52  	if !strings.HasSuffix(url, "/") {
    53  		return url + "/"
    54  	}
    55  	return url
    56  }
    57  
    58  // RemainingKeys will inspect a struct and compare it to a map. Any struct
    59  // field that does not have a JSON tag that matches a key in the map or
    60  // a matching lower-case field in the map will be returned as an extra.
    61  //
    62  // This is useful for determining the extra fields returned in response bodies
    63  // for resources that can contain an arbitrary or dynamic number of fields.
    64  func RemainingKeys(s interface{}, m map[string]interface{}) (extras map[string]interface{}) {
    65  	extras = make(map[string]interface{})
    66  	for k, v := range m {
    67  		extras[k] = v
    68  	}
    69  
    70  	valueOf := reflect.ValueOf(s)
    71  	typeOf := reflect.TypeOf(s)
    72  	for i := 0; i < valueOf.NumField(); i++ {
    73  		field := typeOf.Field(i)
    74  
    75  		lowerField := strings.ToLower(field.Name)
    76  		delete(extras, lowerField)
    77  
    78  		if tagValue := field.Tag.Get("json"); tagValue != "" && tagValue != "-" {
    79  			delete(extras, tagValue)
    80  		}
    81  	}
    82  
    83  	return
    84  }
    85  
    86  // WaitFor polls a predicate function, once per second, up to a timeout limit.
    87  // This is useful to wait for a resource to transition to a certain state.
    88  // To handle situations when the predicate might hang indefinitely, the
    89  // predicate will be prematurely cancelled after the timeout.
    90  // Resource packages will wrap this in a more convenient function that's
    91  // specific to a certain resource, but it can also be useful on its own.
    92  func WaitFor(timeout int, predicate func() (bool, error)) error {
    93  	type WaitForResult struct {
    94  		Success bool
    95  		Error   error
    96  	}
    97  
    98  	start := time.Now().Unix()
    99  
   100  	for {
   101  		// If a timeout is set, and that's been exceeded, shut it down.
   102  		if timeout >= 0 && time.Now().Unix()-start >= int64(timeout) {
   103  			return fmt.Errorf("A timeout occurred")
   104  		}
   105  
   106  		time.Sleep(1 * time.Second)
   107  
   108  		var result WaitForResult
   109  		ch := make(chan bool, 1)
   110  		go func() {
   111  			defer close(ch)
   112  			satisfied, err := predicate()
   113  			result.Success = satisfied
   114  			result.Error = err
   115  		}()
   116  
   117  		select {
   118  		case <-ch:
   119  			if result.Error != nil {
   120  				return result.Error
   121  			}
   122  			if result.Success {
   123  				return nil
   124  			}
   125  		// If the predicate has not finished by the timeout, cancel it.
   126  		case <-time.After(time.Duration(timeout) * time.Second):
   127  			return fmt.Errorf("A timeout occurred")
   128  		}
   129  	}
   130  }