]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package config |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "reflect" | |
6 | "strings" | |
7 | ||
8 | "github.com/hashicorp/hil" | |
9 | "github.com/hashicorp/hil/ast" | |
10 | "github.com/mitchellh/reflectwalk" | |
11 | ) | |
12 | ||
13 | // interpolationWalker implements interfaces for the reflectwalk package | |
14 | // (github.com/mitchellh/reflectwalk) that can be used to automatically | |
15 | // execute a callback for an interpolation. | |
16 | type interpolationWalker struct { | |
17 | // F is the function to call for every interpolation. It can be nil. | |
18 | // | |
19 | // If Replace is true, then the return value of F will be used to | |
20 | // replace the interpolation. | |
21 | F interpolationWalkerFunc | |
22 | Replace bool | |
23 | ||
24 | // ContextF is an advanced version of F that also receives the | |
25 | // location of where it is in the structure. This lets you do | |
26 | // context-aware validation. | |
27 | ContextF interpolationWalkerContextFunc | |
28 | ||
29 | key []string | |
30 | lastValue reflect.Value | |
31 | loc reflectwalk.Location | |
32 | cs []reflect.Value | |
33 | csKey []reflect.Value | |
34 | csData interface{} | |
35 | sliceIndex []int | |
36 | unknownKeys []string | |
37 | } | |
38 | ||
39 | // interpolationWalkerFunc is the callback called by interpolationWalk. | |
40 | // It is called with any interpolation found. It should return a value | |
41 | // to replace the interpolation with, along with any errors. | |
42 | // | |
43 | // If Replace is set to false in interpolationWalker, then the replace | |
44 | // value can be anything as it will have no effect. | |
45 | type interpolationWalkerFunc func(ast.Node) (interface{}, error) | |
46 | ||
47 | // interpolationWalkerContextFunc is called by interpolationWalk if | |
48 | // ContextF is set. This receives both the interpolation and the location | |
49 | // where the interpolation is. | |
50 | // | |
51 | // This callback can be used to validate the location of the interpolation | |
52 | // within the configuration. | |
53 | type interpolationWalkerContextFunc func(reflectwalk.Location, ast.Node) | |
54 | ||
55 | func (w *interpolationWalker) Enter(loc reflectwalk.Location) error { | |
56 | w.loc = loc | |
57 | return nil | |
58 | } | |
59 | ||
60 | func (w *interpolationWalker) Exit(loc reflectwalk.Location) error { | |
61 | w.loc = reflectwalk.None | |
62 | ||
63 | switch loc { | |
64 | case reflectwalk.Map: | |
65 | w.cs = w.cs[:len(w.cs)-1] | |
66 | case reflectwalk.MapValue: | |
67 | w.key = w.key[:len(w.key)-1] | |
68 | w.csKey = w.csKey[:len(w.csKey)-1] | |
69 | case reflectwalk.Slice: | |
70 | // Split any values that need to be split | |
71 | w.splitSlice() | |
72 | w.cs = w.cs[:len(w.cs)-1] | |
73 | case reflectwalk.SliceElem: | |
74 | w.csKey = w.csKey[:len(w.csKey)-1] | |
75 | w.sliceIndex = w.sliceIndex[:len(w.sliceIndex)-1] | |
76 | } | |
77 | ||
78 | return nil | |
79 | } | |
80 | ||
81 | func (w *interpolationWalker) Map(m reflect.Value) error { | |
82 | w.cs = append(w.cs, m) | |
83 | return nil | |
84 | } | |
85 | ||
86 | func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error { | |
87 | w.csData = k | |
88 | w.csKey = append(w.csKey, k) | |
89 | ||
90 | if l := len(w.sliceIndex); l > 0 { | |
91 | w.key = append(w.key, fmt.Sprintf("%d.%s", w.sliceIndex[l-1], k.String())) | |
92 | } else { | |
93 | w.key = append(w.key, k.String()) | |
94 | } | |
95 | ||
96 | w.lastValue = v | |
97 | return nil | |
98 | } | |
99 | ||
100 | func (w *interpolationWalker) Slice(s reflect.Value) error { | |
101 | w.cs = append(w.cs, s) | |
102 | return nil | |
103 | } | |
104 | ||
105 | func (w *interpolationWalker) SliceElem(i int, elem reflect.Value) error { | |
106 | w.csKey = append(w.csKey, reflect.ValueOf(i)) | |
107 | w.sliceIndex = append(w.sliceIndex, i) | |
108 | return nil | |
109 | } | |
110 | ||
111 | func (w *interpolationWalker) Primitive(v reflect.Value) error { | |
112 | setV := v | |
113 | ||
114 | // We only care about strings | |
115 | if v.Kind() == reflect.Interface { | |
116 | setV = v | |
117 | v = v.Elem() | |
118 | } | |
119 | if v.Kind() != reflect.String { | |
120 | return nil | |
121 | } | |
122 | ||
123 | astRoot, err := hil.Parse(v.String()) | |
124 | if err != nil { | |
125 | return err | |
126 | } | |
127 | ||
128 | // If the AST we got is just a literal string value with the same | |
129 | // value then we ignore it. We have to check if its the same value | |
130 | // because it is possible to input a string, get out a string, and | |
131 | // have it be different. For example: "foo-$${bar}" turns into | |
132 | // "foo-${bar}" | |
133 | if n, ok := astRoot.(*ast.LiteralNode); ok { | |
134 | if s, ok := n.Value.(string); ok && s == v.String() { | |
135 | return nil | |
136 | } | |
137 | } | |
138 | ||
139 | if w.ContextF != nil { | |
140 | w.ContextF(w.loc, astRoot) | |
141 | } | |
142 | ||
143 | if w.F == nil { | |
144 | return nil | |
145 | } | |
146 | ||
147 | replaceVal, err := w.F(astRoot) | |
148 | if err != nil { | |
149 | return fmt.Errorf( | |
150 | "%s in:\n\n%s", | |
151 | err, v.String()) | |
152 | } | |
153 | ||
154 | if w.Replace { | |
155 | // We need to determine if we need to remove this element | |
156 | // if the result contains any "UnknownVariableValue" which is | |
157 | // set if it is computed. This behavior is different if we're | |
158 | // splitting (in a SliceElem) or not. | |
159 | remove := false | |
160 | if w.loc == reflectwalk.SliceElem { | |
161 | switch typedReplaceVal := replaceVal.(type) { | |
162 | case string: | |
163 | if typedReplaceVal == UnknownVariableValue { | |
164 | remove = true | |
165 | } | |
166 | case []interface{}: | |
167 | if hasUnknownValue(typedReplaceVal) { | |
168 | remove = true | |
169 | } | |
170 | } | |
171 | } else if replaceVal == UnknownVariableValue { | |
172 | remove = true | |
173 | } | |
174 | ||
175 | if remove { | |
176 | w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, ".")) | |
177 | } | |
178 | ||
179 | resultVal := reflect.ValueOf(replaceVal) | |
180 | switch w.loc { | |
181 | case reflectwalk.MapKey: | |
182 | m := w.cs[len(w.cs)-1] | |
183 | ||
184 | // Delete the old value | |
185 | var zero reflect.Value | |
186 | m.SetMapIndex(w.csData.(reflect.Value), zero) | |
187 | ||
188 | // Set the new key with the existing value | |
189 | m.SetMapIndex(resultVal, w.lastValue) | |
190 | ||
191 | // Set the key to be the new key | |
192 | w.csData = resultVal | |
193 | case reflectwalk.MapValue: | |
194 | // If we're in a map, then the only way to set a map value is | |
195 | // to set it directly. | |
196 | m := w.cs[len(w.cs)-1] | |
197 | mk := w.csData.(reflect.Value) | |
198 | m.SetMapIndex(mk, resultVal) | |
199 | default: | |
200 | // Otherwise, we should be addressable | |
201 | setV.Set(resultVal) | |
202 | } | |
203 | } | |
204 | ||
205 | return nil | |
206 | } | |
207 | ||
208 | func (w *interpolationWalker) replaceCurrent(v reflect.Value) { | |
209 | // if we don't have at least 2 values, we're not going to find a map, but | |
210 | // we could panic. | |
211 | if len(w.cs) < 2 { | |
212 | return | |
213 | } | |
214 | ||
215 | c := w.cs[len(w.cs)-2] | |
216 | switch c.Kind() { | |
217 | case reflect.Map: | |
218 | // Get the key and delete it | |
219 | k := w.csKey[len(w.csKey)-1] | |
220 | c.SetMapIndex(k, v) | |
221 | } | |
222 | } | |
223 | ||
224 | func hasUnknownValue(variable []interface{}) bool { | |
225 | for _, value := range variable { | |
226 | if strVal, ok := value.(string); ok { | |
227 | if strVal == UnknownVariableValue { | |
228 | return true | |
229 | } | |
230 | } | |
231 | } | |
232 | return false | |
233 | } | |
234 | ||
235 | func (w *interpolationWalker) splitSlice() { | |
236 | raw := w.cs[len(w.cs)-1] | |
237 | ||
238 | var s []interface{} | |
239 | switch v := raw.Interface().(type) { | |
240 | case []interface{}: | |
241 | s = v | |
242 | case []map[string]interface{}: | |
243 | return | |
244 | } | |
245 | ||
246 | split := false | |
247 | for _, val := range s { | |
248 | if varVal, ok := val.(ast.Variable); ok && varVal.Type == ast.TypeList { | |
249 | split = true | |
250 | } | |
251 | if _, ok := val.([]interface{}); ok { | |
252 | split = true | |
253 | } | |
254 | } | |
255 | ||
256 | if !split { | |
257 | return | |
258 | } | |
259 | ||
260 | result := make([]interface{}, 0) | |
261 | for _, v := range s { | |
262 | switch val := v.(type) { | |
263 | case ast.Variable: | |
264 | switch val.Type { | |
265 | case ast.TypeList: | |
266 | elements := val.Value.([]ast.Variable) | |
267 | for _, element := range elements { | |
268 | result = append(result, element.Value) | |
269 | } | |
270 | default: | |
271 | result = append(result, val.Value) | |
272 | } | |
273 | case []interface{}: | |
15c0b25d | 274 | result = append(result, val...) |
bae9f6d2 JC |
275 | default: |
276 | result = append(result, v) | |
277 | } | |
278 | } | |
279 | ||
280 | w.replaceCurrent(reflect.ValueOf(result)) | |
281 | } |