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 }