]>
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 { | |
98 | result = source.Value.(map[string]interface{}) | |
99 | resultSet = true | |
100 | } | |
101 | ||
102 | // Next, read all the elements we have in our diff, and apply | |
103 | // the diff to our result. | |
104 | prefix := strings.Join(address, ".") + "." | |
105 | for k, v := range r.Diff.Attributes { | |
106 | if !strings.HasPrefix(k, prefix) { | |
107 | continue | |
108 | } | |
109 | if strings.HasPrefix(k, prefix+"%") { | |
110 | // Ignore the count field | |
111 | continue | |
112 | } | |
113 | ||
114 | resultSet = true | |
115 | ||
116 | k = k[len(prefix):] | |
117 | if v.NewRemoved { | |
118 | delete(result, k) | |
119 | continue | |
120 | } | |
121 | ||
122 | result[k] = v.New | |
123 | } | |
124 | ||
15c0b25d AP |
125 | key := address[len(address)-1] |
126 | err = mapValuesToPrimitive(key, result, schema) | |
bae9f6d2 JC |
127 | if err != nil { |
128 | return FieldReadResult{}, nil | |
129 | } | |
130 | ||
131 | var resultVal interface{} | |
132 | if resultSet { | |
133 | resultVal = result | |
134 | } | |
135 | ||
136 | return FieldReadResult{ | |
137 | Value: resultVal, | |
138 | Exists: resultSet, | |
139 | }, nil | |
140 | } | |
141 | ||
142 | func (r *DiffFieldReader) readPrimitive( | |
143 | address []string, schema *Schema) (FieldReadResult, error) { | |
144 | result, err := r.Source.ReadField(address) | |
145 | if err != nil { | |
146 | return FieldReadResult{}, err | |
147 | } | |
148 | ||
149 | attrD, ok := r.Diff.Attributes[strings.Join(address, ".")] | |
150 | if !ok { | |
151 | return result, nil | |
152 | } | |
153 | ||
154 | var resultVal string | |
155 | if !attrD.NewComputed { | |
156 | resultVal = attrD.New | |
157 | if attrD.NewExtra != nil { | |
158 | result.ValueProcessed = resultVal | |
159 | if err := mapstructure.WeakDecode(attrD.NewExtra, &resultVal); err != nil { | |
160 | return FieldReadResult{}, err | |
161 | } | |
162 | } | |
163 | } | |
164 | ||
165 | result.Computed = attrD.NewComputed | |
166 | result.Exists = true | |
167 | result.Value, err = stringToPrimitive(resultVal, false, schema) | |
168 | if err != nil { | |
169 | return FieldReadResult{}, err | |
170 | } | |
171 | ||
172 | return result, nil | |
173 | } | |
174 | ||
175 | func (r *DiffFieldReader) readSet( | |
176 | address []string, schema *Schema) (FieldReadResult, error) { | |
107c1cdb ND |
177 | // copy address to ensure we don't modify the argument |
178 | address = append([]string(nil), address...) | |
179 | ||
bae9f6d2 JC |
180 | prefix := strings.Join(address, ".") + "." |
181 | ||
182 | // Create the set that will be our result | |
183 | set := schema.ZeroValue().(*Set) | |
184 | ||
185 | // Go through the map and find all the set items | |
186 | for k, d := range r.Diff.Attributes { | |
187 | if d.NewRemoved { | |
188 | // If the field is removed, we always ignore it | |
189 | continue | |
190 | } | |
191 | if !strings.HasPrefix(k, prefix) { | |
192 | continue | |
193 | } | |
194 | if strings.HasSuffix(k, "#") { | |
195 | // Ignore any count field | |
196 | continue | |
197 | } | |
198 | ||
199 | // Split the key, since it might be a sub-object like "idx.field" | |
200 | parts := strings.Split(k[len(prefix):], ".") | |
201 | idx := parts[0] | |
202 | ||
203 | raw, err := r.ReadField(append(address, idx)) | |
204 | if err != nil { | |
205 | return FieldReadResult{}, err | |
206 | } | |
207 | if !raw.Exists { | |
208 | // This shouldn't happen because we just verified it does exist | |
209 | panic("missing field in set: " + k + "." + idx) | |
210 | } | |
211 | ||
212 | set.Add(raw.Value) | |
213 | } | |
214 | ||
215 | // Determine if the set "exists". It exists if there are items or if | |
216 | // the diff explicitly wanted it empty. | |
217 | exists := set.Len() > 0 | |
218 | if !exists { | |
219 | // We could check if the diff value is "0" here but I think the | |
220 | // existence of "#" on its own is enough to show it existed. This | |
221 | // protects us in the future from the zero value changing from | |
222 | // "0" to "" breaking us (if that were to happen). | |
223 | if _, ok := r.Diff.Attributes[prefix+"#"]; ok { | |
224 | exists = true | |
225 | } | |
226 | } | |
227 | ||
228 | if !exists { | |
229 | result, err := r.Source.ReadField(address) | |
230 | if err != nil { | |
231 | return FieldReadResult{}, err | |
232 | } | |
233 | if result.Exists { | |
234 | return result, nil | |
235 | } | |
236 | } | |
237 | ||
238 | return FieldReadResult{ | |
239 | Value: set, | |
240 | Exists: exists, | |
241 | }, nil | |
242 | } |