]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package schema |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "strings" | |
6 | ||
7 | "github.com/hashicorp/terraform/terraform" | |
8 | "github.com/mitchellh/mapstructure" | |
9 | ) | |
10 | ||
11 | // DiffFieldReader reads fields out of a diff structures. | |
12 | // | |
13 | // It also requires access to a Reader that reads fields from the structure | |
14 | // that the diff was derived from. This is usually the state. This is required | |
15 | // because a diff on its own doesn't have complete data about full objects | |
16 | // such as maps. | |
17 | // | |
18 | // The Source MUST be the data that the diff was derived from. If it isn't, | |
19 | // the behavior of this struct is undefined. | |
20 | // | |
21 | // Reading fields from a DiffFieldReader is identical to reading from | |
22 | // Source except the diff will be applied to the end result. | |
23 | // | |
24 | // The "Exists" field on the result will be set to true if the complete | |
25 | // field exists whether its from the source, diff, or a combination of both. | |
26 | // It cannot be determined whether a retrieved value is composed of | |
27 | // diff elements. | |
28 | type DiffFieldReader struct { | |
29 | Diff *terraform.InstanceDiff | |
30 | Source FieldReader | |
31 | Schema map[string]*Schema | |
15c0b25d AP |
32 | |
33 | // cache for memoizing ReadField calls. | |
34 | cache map[string]cachedFieldReadResult | |
35 | } | |
36 | ||
37 | type cachedFieldReadResult struct { | |
38 | val FieldReadResult | |
39 | err error | |
bae9f6d2 JC |
40 | } |
41 | ||
42 | func (r *DiffFieldReader) ReadField(address []string) (FieldReadResult, error) { | |
15c0b25d AP |
43 | if r.cache == nil { |
44 | r.cache = make(map[string]cachedFieldReadResult) | |
45 | } | |
46 | ||
47 | // Create the cache key by joining around a value that isn't a valid part | |
48 | // of an address. This assumes that the Source and Schema are not changed | |
49 | // for the life of this DiffFieldReader. | |
50 | cacheKey := strings.Join(address, "|") | |
51 | if cached, ok := r.cache[cacheKey]; ok { | |
52 | return cached.val, cached.err | |
53 | } | |
54 | ||
bae9f6d2 JC |
55 | schemaList := addrToSchema(address, r.Schema) |
56 | if len(schemaList) == 0 { | |
15c0b25d | 57 | r.cache[cacheKey] = cachedFieldReadResult{} |
bae9f6d2 JC |
58 | return FieldReadResult{}, nil |
59 | } | |
60 | ||
15c0b25d AP |
61 | var res FieldReadResult |
62 | var err error | |
63 | ||
bae9f6d2 JC |
64 | schema := schemaList[len(schemaList)-1] |
65 | switch schema.Type { | |
66 | case TypeBool, TypeInt, TypeFloat, TypeString: | |
15c0b25d | 67 | res, err = r.readPrimitive(address, schema) |
bae9f6d2 | 68 | case TypeList: |
15c0b25d | 69 | res, err = readListField(r, address, schema) |
bae9f6d2 | 70 | case TypeMap: |
15c0b25d | 71 | res, err = r.readMap(address, schema) |
bae9f6d2 | 72 | case TypeSet: |
15c0b25d | 73 | res, err = r.readSet(address, schema) |
bae9f6d2 | 74 | case typeObject: |
15c0b25d | 75 | res, err = readObjectField(r, address, schema.Elem.(map[string]*Schema)) |
bae9f6d2 JC |
76 | default: |
77 | panic(fmt.Sprintf("Unknown type: %#v", schema.Type)) | |
78 | } | |
15c0b25d AP |
79 | |
80 | r.cache[cacheKey] = cachedFieldReadResult{ | |
81 | val: res, | |
82 | err: err, | |
83 | } | |
84 | return res, err | |
bae9f6d2 JC |
85 | } |
86 | ||
87 | func (r *DiffFieldReader) readMap( | |
88 | address []string, schema *Schema) (FieldReadResult, error) { | |
89 | result := make(map[string]interface{}) | |
90 | resultSet := false | |
91 | ||
92 | // First read the map from the underlying source | |
93 | source, err := r.Source.ReadField(address) | |
94 | if err != nil { | |
95 | return FieldReadResult{}, err | |
96 | } | |
97 | if source.Exists { | |
863486a6 AG |
98 | // readMap may return a nil value, or an unknown value placeholder in |
99 | // some cases, causing the type assertion to panic if we don't assign the ok value | |
100 | result, _ = source.Value.(map[string]interface{}) | |
bae9f6d2 JC |
101 | resultSet = true |
102 | } | |
103 | ||
104 | // Next, read all the elements we have in our diff, and apply | |
105 | // the diff to our result. | |
106 | prefix := strings.Join(address, ".") + "." | |
107 | for k, v := range r.Diff.Attributes { | |
108 | if !strings.HasPrefix(k, prefix) { | |
109 | continue | |
110 | } | |
111 | if strings.HasPrefix(k, prefix+"%") { | |
112 | // Ignore the count field | |
113 | continue | |
114 | } | |
115 | ||
116 | resultSet = true | |
117 | ||
118 | k = k[len(prefix):] | |
119 | if v.NewRemoved { | |
120 | delete(result, k) | |
121 | continue | |
122 | } | |
123 | ||
124 | result[k] = v.New | |
125 | } | |
126 | ||
15c0b25d AP |
127 | key := address[len(address)-1] |
128 | err = mapValuesToPrimitive(key, result, schema) | |
bae9f6d2 JC |
129 | if err != nil { |
130 | return FieldReadResult{}, nil | |
131 | } | |
132 | ||
133 | var resultVal interface{} | |
134 | if resultSet { | |
135 | resultVal = result | |
136 | } | |
137 | ||
138 | return FieldReadResult{ | |
139 | Value: resultVal, | |
140 | Exists: resultSet, | |
141 | }, nil | |
142 | } | |
143 | ||
144 | func (r *DiffFieldReader) readPrimitive( | |
145 | address []string, schema *Schema) (FieldReadResult, error) { | |
146 | result, err := r.Source.ReadField(address) | |
147 | if err != nil { | |
148 | return FieldReadResult{}, err | |
149 | } | |
150 | ||
151 | attrD, ok := r.Diff.Attributes[strings.Join(address, ".")] | |
152 | if !ok { | |
153 | return result, nil | |
154 | } | |
155 | ||
156 | var resultVal string | |
157 | if !attrD.NewComputed { | |
158 | resultVal = attrD.New | |
159 | if attrD.NewExtra != nil { | |
160 | result.ValueProcessed = resultVal | |
161 | if err := mapstructure.WeakDecode(attrD.NewExtra, &resultVal); err != nil { | |
162 | return FieldReadResult{}, err | |
163 | } | |
164 | } | |
165 | } | |
166 | ||
167 | result.Computed = attrD.NewComputed | |
168 | result.Exists = true | |
169 | result.Value, err = stringToPrimitive(resultVal, false, schema) | |
170 | if err != nil { | |
171 | return FieldReadResult{}, err | |
172 | } | |
173 | ||
174 | return result, nil | |
175 | } | |
176 | ||
177 | func (r *DiffFieldReader) readSet( | |
178 | address []string, schema *Schema) (FieldReadResult, error) { | |
107c1cdb ND |
179 | // copy address to ensure we don't modify the argument |
180 | address = append([]string(nil), address...) | |
181 | ||
bae9f6d2 JC |
182 | prefix := strings.Join(address, ".") + "." |
183 | ||
184 | // Create the set that will be our result | |
185 | set := schema.ZeroValue().(*Set) | |
186 | ||
187 | // Go through the map and find all the set items | |
188 | for k, d := range r.Diff.Attributes { | |
189 | if d.NewRemoved { | |
190 | // If the field is removed, we always ignore it | |
191 | continue | |
192 | } | |
193 | if !strings.HasPrefix(k, prefix) { | |
194 | continue | |
195 | } | |
196 | if strings.HasSuffix(k, "#") { | |
197 | // Ignore any count field | |
198 | continue | |
199 | } | |
200 | ||
201 | // Split the key, since it might be a sub-object like "idx.field" | |
202 | parts := strings.Split(k[len(prefix):], ".") | |
203 | idx := parts[0] | |
204 | ||
205 | raw, err := r.ReadField(append(address, idx)) | |
206 | if err != nil { | |
207 | return FieldReadResult{}, err | |
208 | } | |
209 | if !raw.Exists { | |
210 | // This shouldn't happen because we just verified it does exist | |
211 | panic("missing field in set: " + k + "." + idx) | |
212 | } | |
213 | ||
214 | set.Add(raw.Value) | |
215 | } | |
216 | ||
217 | // Determine if the set "exists". It exists if there are items or if | |
218 | // the diff explicitly wanted it empty. | |
219 | exists := set.Len() > 0 | |
220 | if !exists { | |
221 | // We could check if the diff value is "0" here but I think the | |
222 | // existence of "#" on its own is enough to show it existed. This | |
223 | // protects us in the future from the zero value changing from | |
224 | // "0" to "" breaking us (if that were to happen). | |
225 | if _, ok := r.Diff.Attributes[prefix+"#"]; ok { | |
226 | exists = true | |
227 | } | |
228 | } | |
229 | ||
230 | if !exists { | |
231 | result, err := r.Source.ReadField(address) | |
232 | if err != nil { | |
233 | return FieldReadResult{}, err | |
234 | } | |
235 | if result.Exists { | |
236 | return result, nil | |
237 | } | |
238 | } | |
239 | ||
240 | return FieldReadResult{ | |
241 | Value: set, | |
242 | Exists: exists, | |
243 | }, nil | |
244 | } |