-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtftags.go
240 lines (220 loc) · 6.64 KB
/
tftags.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
package tftags
import (
"errors"
"fmt"
"log"
"reflect"
"strings"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
computedTag = "computed"
subTag = "sub"
)
// Get accepts two argument. d contians ResourceData and v is the output struct
func Get(d resourceData, output interface{}) error {
rv := reflect.Indirect(reflect.ValueOf(output))
if !rv.CanSet() {
return errors.New("input is not settable")
}
// currently only struct type is supported
if rv.Kind() != reflect.Struct {
return errors.New("only struct type is supported")
}
recursiveGet(rv, d, "", nil, false)
return nil
}
// recursively run over the schema and populate the ouput struct. SchemaMap maps all the
// values in schema into an interface. path will the complete path to a value
func recursiveGet(rv reflect.Value, d resourceData, path string, schemaMap interface{}, isSub bool) {
switch rv.Kind() {
case reflect.Struct:
// for type struct loop through all values and check tags 'tf'
t := rv.Type()
for i := 0; i < t.NumField(); i++ {
if value, ok := t.Field(i).Tag.Lookup("tf"); ok {
splitTags := strings.Split(value, ",")
var newPath string
if path != "" {
newPath = path + "." + splitTags[0]
// if sub then it is *Set or List.
if isSub {
// find the hash value and include in path if it is a set
setData, ok := schemaMap.(*schema.Set)
if ok {
newPath = fmt.Sprintf("%s.%d.%s", path, setData.F(setData.List()[0]), splitTags[0])
} else {
_, ok := schemaMap.([]interface{})
// get first element of the list. If the list contains more than one elements, except
// first element everything will be skipped
if ok {
newPath = fmt.Sprintf("%s.0.%s", path, splitTags[0])
} else {
log.Panicf("sub element should be either list or set, but got %T", schemaMap)
}
}
}
} else {
newPath = splitTags[0]
}
// Get corresponding data from schema
if val, ok := d.GetOk(newPath); ok {
// iterate to the corresponding field and call recursiveGet again
recursiveGet(rv.Field(i), d, newPath, val, searchTags(splitTags, subTag))
} else if newPath == "id" {
recursiveGet(rv.Field(i), d, newPath, d.Id(), searchTags(splitTags, subTag))
}
}
}
case reflect.Slice:
var sArray reflect.Value
// if the output contains field slice, check is it is a set or list
schemaSet, isSet := schemaMap.(*schema.Set)
if isSet {
sArray = reflect.ValueOf(schemaSet.List())
} else {
sArray = reflect.ValueOf(schemaMap)
}
slice := reflect.MakeSlice(rv.Type(), sArray.Len(), sArray.Cap())
rv.Set(slice)
for i := 0; i < rv.Len(); i++ {
// set path
var newPath string
if isSet {
newPath = fmt.Sprintf("%s.%d", path, schemaSet.F(schemaSet.List()[i]))
} else {
newPath = fmt.Sprintf("%s.%d", path, i)
}
// recursively set each elements in slice
recursiveGet(
rv.Index(i),
d,
newPath,
sArray.Index(i).Interface(),
false)
}
case reflect.Map:
// if output is map and schemaMap also map then allocates new map
// to output
rvMap := reflect.ValueOf(schemaMap)
rv.Set(reflect.MakeMap(rv.Type()))
for _, key := range rvMap.MapKeys() {
// Set index and value directly here
rv.SetMapIndex(key, rvMap.MapIndex(key))
}
case reflect.Ptr:
ptrType := reflect.New(rv.Type().Elem())
rv.Set(ptrType)
recursiveGet(rv.Elem(), d, path, schemaMap, isSub)
default:
compareAndSet(rv, schemaMap)
}
}
func Set(d resourceData, v interface{}) error {
rv := reflect.Indirect(reflect.ValueOf(v))
// currently only struct type is supported
if rv.Kind() != reflect.Struct {
return errors.New("only struct type is supported")
}
// var result interface{}
recursiveSet(rv, d, false)
return nil
}
func recursiveSet(rv reflect.Value, d resourceData, computed bool) interface{} {
switch rv.Kind() {
case reflect.Struct:
if rv.IsZero() {
return nil
}
t := rv.Type()
// subMap holds the result of sub element
subMap := make(map[string]interface{})
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if value, ok := field.Tag.Lookup("tf"); ok {
splitTags := strings.Split(value, ",")
// Check is this is a sub element
isSub := searchTags(splitTags, subTag)
// if computed is true then it indicates it is a child struct
if computed {
// If this is child struct we don't need to call d.Set(). Set() function
// will only called for a top level element. For the child element assign
// to a map
subMap[splitTags[0]] = checkSub(rv, i, d, isSub)
} else if searchTags(splitTags, computedTag) { // Check computed tags
// checkSub will call recursiveSet for the entire data structure and return
// result. This result will be toplevel value, hence can be Set
result := checkSub(rv, i, d, isSub)
// since this is a top level element set the element
if !isEmpty(result) {
if splitTags[0] == "id" {
d.SetId(toString(result))
} else {
d.Set(splitTags[0], result)
}
}
}
}
}
return subMap
case reflect.Slice:
// if rv.Len() == 0 {
// return nil
// }
result := make([]interface{}, rv.Len())
// iterate through array and figure it out values. Value can be map, struct,
// slice or primitive data type
for i := 0; i < rv.Len(); i++ {
result[i] = recursiveSet(rv.Index(i), d, computed)
}
return result
case reflect.Map:
if rv.Len() == 0 {
return nil
}
result := make(map[string]interface{})
iter := rv.MapRange()
for iter.Next() {
k := iter.Key()
v := iter.Value()
result[k.String()] = v.Interface()
}
return result
case reflect.Ptr:
// if a pointer, recursive set value of rv
return recursiveSet(reflect.Indirect(rv), d, computed)
}
// Primitive data type
return rv.Interface()
}
// checkSub check whether this is a sub element and recusively
// allocates output struct regarding it
func checkSub(rv reflect.Value, i int, d resourceData, isSub bool) interface{} {
var result interface{}
// if isSub allocates as array
// isSub will denotes this is a sub element where schema contains array with one element
// but input data structure having struct
if isSub {
r := recursiveSet(rv.Field(i), d, true)
if isEmpty(r) {
return nil
}
result = []interface{}{r}
} else {
result = recursiveSet(rv.Field(i), d, true)
}
return result
}
// searchTags search items in tags
func searchTags(slice []string, item string) bool {
for i := 1; i < len(slice); i++ {
if slice[i] == item {
return true
}
}
return false
}
// isEmpty checks given iterface is empty or not
func isEmpty(n interface{}) bool {
return n == nil || reflect.ValueOf(n).IsZero()
}