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