]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/helper/schema/field_reader_config.go
update vendor and go.mod
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / helper / schema / field_reader_config.go
CommitLineData
bae9f6d2
JC
1package schema
2
3import (
4 "fmt"
107c1cdb 5 "log"
bae9f6d2
JC
6 "strconv"
7 "strings"
8 "sync"
9
10 "github.com/hashicorp/terraform/terraform"
11 "github.com/mitchellh/mapstructure"
12)
13
14// ConfigFieldReader reads fields out of an untyped map[string]string to the
15// best of its ability. It also applies defaults from the Schema. (The other
16// field readers do not need default handling because they source fully
17// populated data structures.)
18type ConfigFieldReader struct {
19 Config *terraform.ResourceConfig
20 Schema map[string]*Schema
21
22 indexMaps map[string]map[string]int
23 once sync.Once
24}
25
26func (r *ConfigFieldReader) ReadField(address []string) (FieldReadResult, error) {
27 r.once.Do(func() { r.indexMaps = make(map[string]map[string]int) })
28 return r.readField(address, false)
29}
30
31func (r *ConfigFieldReader) readField(
32 address []string, nested bool) (FieldReadResult, error) {
33 schemaList := addrToSchema(address, r.Schema)
34 if len(schemaList) == 0 {
35 return FieldReadResult{}, nil
36 }
37
38 if !nested {
39 // If we have a set anywhere in the address, then we need to
40 // read that set out in order and actually replace that part of
41 // the address with the real list index. i.e. set.50 might actually
42 // map to set.12 in the config, since it is in list order in the
43 // config, not indexed by set value.
44 for i, v := range schemaList {
45 // Sets are the only thing that cause this issue.
46 if v.Type != TypeSet {
47 continue
48 }
49
50 // If we're at the end of the list, then we don't have to worry
51 // about this because we're just requesting the whole set.
52 if i == len(schemaList)-1 {
53 continue
54 }
55
56 // If we're looking for the count, then ignore...
57 if address[i+1] == "#" {
58 continue
59 }
60
61 indexMap, ok := r.indexMaps[strings.Join(address[:i+1], ".")]
62 if !ok {
63 // Get the set so we can get the index map that tells us the
64 // mapping of the hash code to the list index
65 _, err := r.readSet(address[:i+1], v)
66 if err != nil {
67 return FieldReadResult{}, err
68 }
69 indexMap = r.indexMaps[strings.Join(address[:i+1], ".")]
70 }
71
72 index, ok := indexMap[address[i+1]]
73 if !ok {
74 return FieldReadResult{}, nil
75 }
76
77 address[i+1] = strconv.FormatInt(int64(index), 10)
78 }
79 }
80
81 k := strings.Join(address, ".")
82 schema := schemaList[len(schemaList)-1]
83
84 // If we're getting the single element of a promoted list, then
85 // check to see if we have a single element we need to promote.
86 if address[len(address)-1] == "0" && len(schemaList) > 1 {
87 lastSchema := schemaList[len(schemaList)-2]
88 if lastSchema.Type == TypeList && lastSchema.PromoteSingle {
89 k := strings.Join(address[:len(address)-1], ".")
90 result, err := r.readPrimitive(k, schema)
91 if err == nil {
92 return result, nil
93 }
94 }
95 }
96
107c1cdb
ND
97 if protoVersion5 {
98 switch schema.Type {
99 case TypeList, TypeSet, TypeMap, typeObject:
100 // Check if the value itself is unknown.
101 // The new protocol shims will add unknown values to this list of
102 // ComputedKeys. This is the only way we have to indicate that a
103 // collection is unknown in the config
104 for _, unknown := range r.Config.ComputedKeys {
105 if k == unknown {
106 log.Printf("[DEBUG] setting computed for %q from ComputedKeys", k)
107 return FieldReadResult{Computed: true, Exists: true}, nil
108 }
109 }
110 }
111 }
112
bae9f6d2
JC
113 switch schema.Type {
114 case TypeBool, TypeFloat, TypeInt, TypeString:
115 return r.readPrimitive(k, schema)
116 case TypeList:
117 // If we support promotion then we first check if we have a lone
118 // value that we must promote.
119 // a value that is alone.
120 if schema.PromoteSingle {
121 result, err := r.readPrimitive(k, schema.Elem.(*Schema))
122 if err == nil && result.Exists {
123 result.Value = []interface{}{result.Value}
124 return result, nil
125 }
126 }
127
128 return readListField(&nestedConfigFieldReader{r}, address, schema)
129 case TypeMap:
130 return r.readMap(k, schema)
131 case TypeSet:
132 return r.readSet(address, schema)
133 case typeObject:
134 return readObjectField(
135 &nestedConfigFieldReader{r},
136 address, schema.Elem.(map[string]*Schema))
137 default:
138 panic(fmt.Sprintf("Unknown type: %s", schema.Type))
139 }
140}
141
142func (r *ConfigFieldReader) readMap(k string, schema *Schema) (FieldReadResult, error) {
143 // We want both the raw value and the interpolated. We use the interpolated
144 // to store actual values and we use the raw one to check for
145 // computed keys. Actual values are obtained in the switch, depending on
146 // the type of the raw value.
147 mraw, ok := r.Config.GetRaw(k)
148 if !ok {
149 // check if this is from an interpolated field by seeing if it exists
150 // in the config
151 _, ok := r.Config.Get(k)
152 if !ok {
153 // this really doesn't exist
154 return FieldReadResult{}, nil
155 }
156
157 // We couldn't fetch the value from a nested data structure, so treat the
158 // raw value as an interpolation string. The mraw value is only used
159 // for the type switch below.
160 mraw = "${INTERPOLATED}"
161 }
162
163 result := make(map[string]interface{})
164 computed := false
165 switch m := mraw.(type) {
166 case string:
167 // This is a map which has come out of an interpolated variable, so we
168 // can just get the value directly from config. Values cannot be computed
169 // currently.
170 v, _ := r.Config.Get(k)
171
172 // If this isn't a map[string]interface, it must be computed.
173 mapV, ok := v.(map[string]interface{})
174 if !ok {
175 return FieldReadResult{
176 Exists: true,
177 Computed: true,
178 }, nil
179 }
180
181 // Otherwise we can proceed as usual.
182 for i, iv := range mapV {
183 result[i] = iv
184 }
185 case []interface{}:
186 for i, innerRaw := range m {
187 for ik := range innerRaw.(map[string]interface{}) {
188 key := fmt.Sprintf("%s.%d.%s", k, i, ik)
189 if r.Config.IsComputed(key) {
190 computed = true
191 break
192 }
193
194 v, _ := r.Config.Get(key)
195 result[ik] = v
196 }
197 }
198 case []map[string]interface{}:
199 for i, innerRaw := range m {
200 for ik := range innerRaw {
201 key := fmt.Sprintf("%s.%d.%s", k, i, ik)
202 if r.Config.IsComputed(key) {
203 computed = true
204 break
205 }
206
207 v, _ := r.Config.Get(key)
208 result[ik] = v
209 }
210 }
211 case map[string]interface{}:
212 for ik := range m {
213 key := fmt.Sprintf("%s.%s", k, ik)
214 if r.Config.IsComputed(key) {
215 computed = true
216 break
217 }
218
219 v, _ := r.Config.Get(key)
220 result[ik] = v
221 }
863486a6
AG
222 case nil:
223 // the map may have been empty on the configuration, so we leave the
224 // empty result
bae9f6d2
JC
225 default:
226 panic(fmt.Sprintf("unknown type: %#v", mraw))
227 }
228
15c0b25d 229 err := mapValuesToPrimitive(k, result, schema)
bae9f6d2
JC
230 if err != nil {
231 return FieldReadResult{}, nil
232 }
233
234 var value interface{}
235 if !computed {
236 value = result
237 }
238
239 return FieldReadResult{
240 Value: value,
241 Exists: true,
242 Computed: computed,
243 }, nil
244}
245
246func (r *ConfigFieldReader) readPrimitive(
247 k string, schema *Schema) (FieldReadResult, error) {
248 raw, ok := r.Config.Get(k)
249 if !ok {
250 // Nothing in config, but we might still have a default from the schema
251 var err error
252 raw, err = schema.DefaultValue()
253 if err != nil {
254 return FieldReadResult{}, fmt.Errorf("%s, error loading default: %s", k, err)
255 }
256
257 if raw == nil {
258 return FieldReadResult{}, nil
259 }
260 }
261
262 var result string
263 if err := mapstructure.WeakDecode(raw, &result); err != nil {
264 return FieldReadResult{}, err
265 }
266
267 computed := r.Config.IsComputed(k)
268 returnVal, err := stringToPrimitive(result, computed, schema)
269 if err != nil {
270 return FieldReadResult{}, err
271 }
272
273 return FieldReadResult{
274 Value: returnVal,
275 Exists: true,
276 Computed: computed,
277 }, nil
278}
279
280func (r *ConfigFieldReader) readSet(
281 address []string, schema *Schema) (FieldReadResult, error) {
282 indexMap := make(map[string]int)
283 // Create the set that will be our result
284 set := schema.ZeroValue().(*Set)
285
286 raw, err := readListField(&nestedConfigFieldReader{r}, address, schema)
287 if err != nil {
288 return FieldReadResult{}, err
289 }
290 if !raw.Exists {
291 return FieldReadResult{Value: set}, nil
292 }
293
294 // If the list is computed, the set is necessarilly computed
295 if raw.Computed {
296 return FieldReadResult{
297 Value: set,
298 Exists: true,
299 Computed: raw.Computed,
300 }, nil
301 }
302
303 // Build up the set from the list elements
304 for i, v := range raw.Value.([]interface{}) {
305 // Check if any of the keys in this item are computed
306 computed := r.hasComputedSubKeys(
307 fmt.Sprintf("%s.%d", strings.Join(address, "."), i), schema)
308
309 code := set.add(v, computed)
310 indexMap[code] = i
311 }
312
313 r.indexMaps[strings.Join(address, ".")] = indexMap
314
315 return FieldReadResult{
316 Value: set,
317 Exists: true,
318 }, nil
319}
320
321// hasComputedSubKeys walks through a schema and returns whether or not the
322// given key contains any subkeys that are computed.
323func (r *ConfigFieldReader) hasComputedSubKeys(key string, schema *Schema) bool {
324 prefix := key + "."
325
326 switch t := schema.Elem.(type) {
327 case *Resource:
328 for k, schema := range t.Schema {
329 if r.Config.IsComputed(prefix + k) {
330 return true
331 }
332
333 if r.hasComputedSubKeys(prefix+k, schema) {
334 return true
335 }
336 }
337 }
338
339 return false
340}
341
342// nestedConfigFieldReader is a funny little thing that just wraps a
343// ConfigFieldReader to call readField when ReadField is called so that
344// we don't recalculate the set rewrites in the address, which leads to
345// an infinite loop.
346type nestedConfigFieldReader struct {
347 Reader *ConfigFieldReader
348}
349
350func (r *nestedConfigFieldReader) ReadField(
351 address []string) (FieldReadResult, error) {
352 return r.Reader.readField(address, true)
353}