-
Notifications
You must be signed in to change notification settings - Fork 75
/
utils.go
489 lines (422 loc) · 12.5 KB
/
utils.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
package common
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"time"
ver "github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
golangsdk "github.com/opentelekomcloud/gophertelekomcloud"
"github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/cfg"
"github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/fmterr"
)
func LooksLikeJsonString(s interface{}) bool {
return regexp.MustCompile(`^\s*{`).MatchString(s.(string))
}
func Base64IfNot(src string) string {
_, err := base64.StdEncoding.DecodeString(src)
if err == nil {
return src
}
return base64.StdEncoding.EncodeToString([]byte(src))
}
type versionSlice []*ver.Version
func (v versionSlice) Len() int {
return len(v)
}
func (v versionSlice) Swap(i, j int) {
v[i], v[j] = v[j], v[i]
}
func (v versionSlice) Less(i, j int) bool {
return v[i].LessThan(v[j])
}
func (v versionSlice) ToStringSlice() []string {
res := make([]string, len(v))
for i, version := range v {
res[i] = version.Original()
}
return res
}
func sortAsStringSlice(src []string) []string {
res := make([]string, len(src))
copy(res, src)
sort.Sort(sort.Reverse(sort.StringSlice(res)))
return res
}
// SortVersions sorts versions from newer to older.
// If non-version-like string will be found in the slice,
// slice will be sorted as string slice in reversed order (z-a)
func SortVersions(src []string) []string {
verSlice := make(versionSlice, len(src))
for i, v := range src {
val, err := ver.NewVersion(v)
if err != nil {
return sortAsStringSlice(src) // in case it's not version-like
}
verSlice[i] = val
}
sort.Sort(sort.Reverse(verSlice))
return verSlice.ToStringSlice()
}
// BuildRequest takes an opts struct and builds a request body for
// Gophercloud to execute
func BuildRequest(opts interface{}, parent string) (map[string]interface{}, error) {
b, err := golangsdk.BuildRequestBody(opts, "")
if err != nil {
return nil, err
}
b = AddValueSpecs(b)
return map[string]interface{}{parent: b}, nil
}
// CheckDeleted checks the error to see if it's a 404 (Not Found) and, if so,
// sets the resource ID to the empty string instead of throwing an error.
func CheckDeleted(d *schema.ResourceData, err error, msg string) error {
_, ok := err.(golangsdk.ErrDefault404)
if ok {
d.SetId("")
return nil
}
return fmt.Errorf("%s: %s", msg, err)
}
// CheckDeletedDiag checks the error to see if it's a 404 (Not Found) and, if so,
// sets the resource ID to the empty string instead of throwing an error.
func CheckDeletedDiag(d *schema.ResourceData, err error, msg string) diag.Diagnostics {
if _, ok := err.(golangsdk.ErrDefault404); ok {
d.SetId("")
return nil
}
return fmterr.Errorf("%s: %s", msg, err)
}
// AddValueSpecs expands the 'value_specs' object and removes 'value_specs'
// from the request body.
func AddValueSpecs(body map[string]interface{}) map[string]interface{} {
if body["value_specs"] != nil {
for k, v := range body["value_specs"].(map[string]interface{}) {
body[k] = v
}
delete(body, "value_specs")
}
return body
}
// MapValueSpecs converts ResourceData into a map
func MapValueSpecs(d cfg.SchemaOrDiff) map[string]string {
m := make(map[string]string)
for key, val := range d.Get("value_specs").(map[string]interface{}) {
m[key] = val.(string)
}
return m
}
// MapResourceProp converts ResourceData property into a map
func MapResourceProp(d *schema.ResourceData, prop string) map[string]interface{} {
m := make(map[string]interface{})
for key, val := range d.Get(prop).(map[string]interface{}) {
m[key] = val.(string)
}
return m
}
func CheckForRetryableError(err error) *resource.RetryError {
switch err.(type) {
case golangsdk.ErrDefault409, golangsdk.ErrDefault500, golangsdk.ErrDefault503:
return resource.RetryableError(err)
default:
return resource.NonRetryableError(err)
}
}
func IsResourceNotFound(err error) bool {
if err == nil {
return false
}
_, ok := err.(golangsdk.ErrDefault404)
return ok
}
func ExpandToStringSlice(v []interface{}) []string {
s := make([]string, 0, len(v))
for _, val := range v {
if strVal, ok := val.(string); ok && strVal != "" {
s = append(s, strVal)
}
}
return s
}
// StrSliceContains checks if a given string is contained in a slice
// When anybody asks why Go needs generics, here you go.
func StrSliceContains(haystack []string, needle string) bool {
for _, s := range haystack {
if s == needle {
return true
}
}
return false
}
func GetAllAvailableZones(d *schema.ResourceData) []string {
rawZones := d.Get("available_zones").([]interface{})
zones := make([]string, len(rawZones))
for i, raw := range rawZones {
zones[i] = raw.(string)
}
log.Printf("[DEBUG] getAvailableZones: %#v", zones)
return zones
}
func StringInSlice(str string, slice []string) bool {
for _, v := range slice {
if v == str {
return true
}
}
return false
}
func BuildComponentID(parts ...string) string {
return strings.Join(parts, "/")
}
// StrSlice is used to wrap single string element in slice
func StrSlice(v interface{}) []string {
if v == "" {
return nil
}
return []string{v.(string)}
}
// IntSlice is used to wrap single integer element in slice
func IntSlice(v interface{}) []int {
if v == 0 {
return nil
}
return []int{v.(int)}
}
var (
DataSourceTooFewDiag = diag.Errorf("your query returned no results. Please change your search criteria and try again.")
DataSourceTooManyDiag = diag.Errorf("your query returned more than one result. Please change your search criteria and try again.")
)
// GetSetChanges returns a pair of sets describing removed and added items
func GetSetChanges(d *schema.ResourceData, key string) (removed, added *schema.Set) {
oldOne, newOne := d.GetChange(key)
oldSet := oldOne.(*schema.Set)
newSet := newOne.(*schema.Set)
return oldSet.Difference(newSet), newSet.Difference(oldSet)
}
// CheckNull returns true if schema parameter is empty
func CheckNull(element string, d *schema.ResourceData) bool {
return d.GetRawConfig().GetAttr(element).IsNull()
}
func CompareJsonTemplateAreEquivalent(tem1, tem2 string) (bool, error) {
var obj1 interface{}
err := json.Unmarshal([]byte(tem1), &obj1)
if err != nil {
return false, err
}
canonicalJson1, _ := json.Marshal(obj1)
var obj2 interface{}
err = json.Unmarshal([]byte(tem2), &obj2)
if err != nil {
return false, err
}
canonicalJson2, _ := json.Marshal(obj2)
equal := bytes.Equal(canonicalJson1, canonicalJson2)
if !equal {
log.Printf("[DEBUG] Canonical template are not equal.\nFirst: %s\nSecond: %s\n",
canonicalJson1, canonicalJson2)
}
return equal, nil
}
func ValidateRFC3339Timestamp(v interface{}, _ string) (ws []string, errors []error) {
value := v.(string)
_, err := time.Parse(time.RFC3339, fmt.Sprintf("%sT00:00:00Z", value))
if err != nil {
errors = append(errors, fmt.Errorf(
"%q cannot be parsed as RFC3339 Timestamp Format", value))
}
return
}
// FilterSliceWithField can filter the slice all through a map filter.
// If the field is a nested value, using dot(.) to split them, e.g. "SubBlock.SubField".
// If value in the map is zero, it will be ignored.
func FilterSliceWithField(all interface{}, filter map[string]interface{}) ([]interface{}, error) {
return filterSliceWithFieldRaw(all, filter, true)
}
func filterSliceWithFieldRaw(all interface{}, filter map[string]interface{}, ignoreZero bool) ([]interface{}, error) {
var result []interface{}
var matched bool
allValue := reflect.ValueOf(all)
if allValue.Kind() != reflect.Slice {
return nil, fmt.Errorf("options type is not a slice")
}
newFilter := filter
if ignoreZero {
for key, val := range filter {
keyValue := reflect.ValueOf(val)
if keyValue.IsZero() {
log.Printf("[DEBUG] ignore zero field %s", key)
delete(newFilter, key)
}
}
}
for i := 0; i < allValue.Len(); i++ {
refValue := allValue.Index(i)
if refValue.Kind() == reflect.Ptr {
refValue = refValue.Elem()
}
if refValue.Kind() != reflect.Struct {
return nil, fmt.Errorf("object in slice is not a struct")
}
matched = true
for key, val := range newFilter {
actual, err := getStructField(refValue, key)
if err != nil {
return nil, fmt.Errorf("get slice field %s failed: %s", key, err)
}
actualVal := reflect.ValueOf(actual)
if actualVal.Kind() == reflect.Ptr {
actualVal = actualVal.Elem()
}
if actualVal.Interface() != val {
log.Printf("[DEBUG] can not match slice[%d] field %s: expect %v, but got %v", i, key, val, actualVal)
matched = false
break
}
}
if matched {
result = append(result, refValue.Interface())
}
}
return result, nil
}
func getStructField(v reflect.Value, field string) (interface{}, error) {
var subField interface{}
var err error
structValue := v
parts := strings.Split(field, ".")
for _, key := range parts {
subField, err = getStructFieldRaw(structValue, key)
if err != nil {
return nil, err
}
structValue = reflect.ValueOf(subField)
}
return subField, nil
}
func getStructFieldRaw(v reflect.Value, field string) (interface{}, error) {
if v.Kind() == reflect.Struct {
value := reflect.Indirect(v).FieldByName(field)
if value.IsValid() {
return value.Interface(), nil
}
return nil, fmt.Errorf("reflect: can not find the field %s", field)
}
return nil, fmt.Errorf("reflect: Value is not a struct")
}
// StringToInt convert the string to int, and return the pointer of int value
func StringToInt(i *string) *int {
if i == nil || len(*i) == 0 {
return nil
}
r, err := strconv.Atoi(*i)
if err != nil {
log.Printf("[ERROR] convert the string %q to int failed.", *i)
}
return &r
}
// ExpandToStringListBySet takes the result for a set of strings and returns a []string
func ExpandToStringListBySet(v *schema.Set) []string {
s := make([]string, 0, v.Len())
for _, val := range v.List() {
if strVal, ok := val.(string); ok && strVal != "" {
s = append(s, strVal)
}
}
return s
}
// SliceUnion returns a new slice containing the union of elements from both slices,
// without any duplicates.
func SliceUnion(a, b []string) []string {
var res []string
for _, i := range a {
if !StrSliceContains(res, i) {
res = append(res, i)
}
}
for _, k := range b {
if !StrSliceContains(res, k) {
res = append(res, k)
}
}
return res
}
func RemoveNil(data map[string]interface{}) map[string]interface{} {
withoutNil := make(map[string]interface{})
for k, v := range data {
if v == nil {
continue
}
switch v := v.(type) {
case map[string]interface{}:
if len(v) > 0 {
withoutNil[k] = RemoveNil(v)
}
case []map[string]interface{}:
rv := make([]map[string]interface{}, 0, len(v))
for _, vv := range v {
rst := RemoveNil(vv)
if len(rst) > 0 {
rv = append(rv, rst)
}
}
if len(rv) > 0 {
withoutNil[k] = rv
}
default:
withoutNil[k] = v
}
}
return withoutNil
}
// IsSliceContainsAnyAnotherSliceElement is a method that used to determine whether a list contains any element of
// another list (including its fragments belonging to the current string), returns true if it contains.
// sl: The slice body used to determine the inclusion relationship.
// another: The included slice object used to determine the inclusion relationship.
// ignoreCase: Whether to ignore case.
// isExcat: Whether the inclusion relationship of string objects applies exact matching rules.
func IsSliceContainsAnyAnotherSliceElement(sl, another []string, ignoreCase, isExcat bool) bool {
for _, elem := range sl {
if IsStrContainsSliceElement(elem, another, ignoreCase, isExcat) {
return true
}
}
return false
}
// IsStrContainsSliceElement returns true if the string exists in given slice or contains in one of slice elements when
// open exact flag. Also, you can ignore case for this check.
func IsStrContainsSliceElement(str string, sl []string, ignoreCase, isExcat bool) bool {
if ignoreCase {
str = strings.ToLower(str)
}
for _, s := range sl {
if ignoreCase {
s = strings.ToLower(s)
}
if isExcat && s == str {
return true
}
if !isExcat && strings.Contains(str, s) {
return true
}
}
return false
}
// ExpandToStringList takes the result for an array of strings and returns a []string
func ExpandToStringList(v []interface{}) []string {
s := make([]string, 0, len(v))
for _, val := range v {
if strVal, ok := val.(string); ok && strVal != "" {
s = append(s, strVal)
}
}
return s
}