]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/eval_variable.go
Initial transfer of provider code
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / eval_variable.go
1 package terraform
2
3 import (
4 "fmt"
5 "log"
6 "reflect"
7 "strconv"
8 "strings"
9
10 "github.com/hashicorp/terraform/config"
11 "github.com/hashicorp/terraform/config/module"
12 "github.com/hashicorp/terraform/helper/hilmapstructure"
13 )
14
15 // EvalTypeCheckVariable is an EvalNode which ensures that the variable
16 // values which are assigned as inputs to a module (including the root)
17 // match the types which are either declared for the variables explicitly
18 // or inferred from the default values.
19 //
20 // In order to achieve this three things are required:
21 // - a map of the proposed variable values
22 // - the configuration tree of the module in which the variable is
23 // declared
24 // - the path to the module (so we know which part of the tree to
25 // compare the values against).
26 type EvalTypeCheckVariable struct {
27 Variables map[string]interface{}
28 ModulePath []string
29 ModuleTree *module.Tree
30 }
31
32 func (n *EvalTypeCheckVariable) Eval(ctx EvalContext) (interface{}, error) {
33 currentTree := n.ModuleTree
34 for _, pathComponent := range n.ModulePath[1:] {
35 currentTree = currentTree.Children()[pathComponent]
36 }
37 targetConfig := currentTree.Config()
38
39 prototypes := make(map[string]config.VariableType)
40 for _, variable := range targetConfig.Variables {
41 prototypes[variable.Name] = variable.Type()
42 }
43
44 // Only display a module in an error message if we are not in the root module
45 modulePathDescription := fmt.Sprintf(" in module %s", strings.Join(n.ModulePath[1:], "."))
46 if len(n.ModulePath) == 1 {
47 modulePathDescription = ""
48 }
49
50 for name, declaredType := range prototypes {
51 proposedValue, ok := n.Variables[name]
52 if !ok {
53 // This means the default value should be used as no overriding value
54 // has been set. Therefore we should continue as no check is necessary.
55 continue
56 }
57
58 if proposedValue == config.UnknownVariableValue {
59 continue
60 }
61
62 switch declaredType {
63 case config.VariableTypeString:
64 switch proposedValue.(type) {
65 case string:
66 continue
67 default:
68 return nil, fmt.Errorf("variable %s%s should be type %s, got %s",
69 name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue))
70 }
71 case config.VariableTypeMap:
72 switch proposedValue.(type) {
73 case map[string]interface{}:
74 continue
75 default:
76 return nil, fmt.Errorf("variable %s%s should be type %s, got %s",
77 name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue))
78 }
79 case config.VariableTypeList:
80 switch proposedValue.(type) {
81 case []interface{}:
82 continue
83 default:
84 return nil, fmt.Errorf("variable %s%s should be type %s, got %s",
85 name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue))
86 }
87 default:
88 return nil, fmt.Errorf("variable %s%s should be type %s, got type string",
89 name, modulePathDescription, declaredType.Printable())
90 }
91 }
92
93 return nil, nil
94 }
95
96 // EvalSetVariables is an EvalNode implementation that sets the variables
97 // explicitly for interpolation later.
98 type EvalSetVariables struct {
99 Module *string
100 Variables map[string]interface{}
101 }
102
103 // TODO: test
104 func (n *EvalSetVariables) Eval(ctx EvalContext) (interface{}, error) {
105 ctx.SetVariables(*n.Module, n.Variables)
106 return nil, nil
107 }
108
109 // EvalVariableBlock is an EvalNode implementation that evaluates the
110 // given configuration, and uses the final values as a way to set the
111 // mapping.
112 type EvalVariableBlock struct {
113 Config **ResourceConfig
114 VariableValues map[string]interface{}
115 }
116
117 func (n *EvalVariableBlock) Eval(ctx EvalContext) (interface{}, error) {
118 // Clear out the existing mapping
119 for k, _ := range n.VariableValues {
120 delete(n.VariableValues, k)
121 }
122
123 // Get our configuration
124 rc := *n.Config
125 for k, v := range rc.Config {
126 vKind := reflect.ValueOf(v).Type().Kind()
127
128 switch vKind {
129 case reflect.Slice:
130 var vSlice []interface{}
131 if err := hilmapstructure.WeakDecode(v, &vSlice); err == nil {
132 n.VariableValues[k] = vSlice
133 continue
134 }
135 case reflect.Map:
136 var vMap map[string]interface{}
137 if err := hilmapstructure.WeakDecode(v, &vMap); err == nil {
138 n.VariableValues[k] = vMap
139 continue
140 }
141 default:
142 var vString string
143 if err := hilmapstructure.WeakDecode(v, &vString); err == nil {
144 n.VariableValues[k] = vString
145 continue
146 }
147 }
148
149 return nil, fmt.Errorf("Variable value for %s is not a string, list or map type", k)
150 }
151
152 for _, path := range rc.ComputedKeys {
153 log.Printf("[DEBUG] Setting Unknown Variable Value for computed key: %s", path)
154 err := n.setUnknownVariableValueForPath(path)
155 if err != nil {
156 return nil, err
157 }
158 }
159
160 return nil, nil
161 }
162
163 func (n *EvalVariableBlock) setUnknownVariableValueForPath(path string) error {
164 pathComponents := strings.Split(path, ".")
165
166 if len(pathComponents) < 1 {
167 return fmt.Errorf("No path comoponents in %s", path)
168 }
169
170 if len(pathComponents) == 1 {
171 // Special case the "top level" since we know the type
172 if _, ok := n.VariableValues[pathComponents[0]]; !ok {
173 n.VariableValues[pathComponents[0]] = config.UnknownVariableValue
174 }
175 return nil
176 }
177
178 // Otherwise find the correct point in the tree and then set to unknown
179 var current interface{} = n.VariableValues[pathComponents[0]]
180 for i := 1; i < len(pathComponents); i++ {
181 switch tCurrent := current.(type) {
182 case []interface{}:
183 index, err := strconv.Atoi(pathComponents[i])
184 if err != nil {
185 return fmt.Errorf("Cannot convert %s to slice index in path %s",
186 pathComponents[i], path)
187 }
188 current = tCurrent[index]
189 case []map[string]interface{}:
190 index, err := strconv.Atoi(pathComponents[i])
191 if err != nil {
192 return fmt.Errorf("Cannot convert %s to slice index in path %s",
193 pathComponents[i], path)
194 }
195 current = tCurrent[index]
196 case map[string]interface{}:
197 if val, hasVal := tCurrent[pathComponents[i]]; hasVal {
198 current = val
199 continue
200 }
201
202 tCurrent[pathComponents[i]] = config.UnknownVariableValue
203 break
204 }
205 }
206
207 return nil
208 }
209
210 // EvalCoerceMapVariable is an EvalNode implementation that recognizes a
211 // specific ambiguous HCL parsing situation and resolves it. In HCL parsing, a
212 // bare map literal is indistinguishable from a list of maps w/ one element.
213 //
214 // We take all the same inputs as EvalTypeCheckVariable above, since we need
215 // both the target type and the proposed value in order to properly coerce.
216 type EvalCoerceMapVariable struct {
217 Variables map[string]interface{}
218 ModulePath []string
219 ModuleTree *module.Tree
220 }
221
222 // Eval implements the EvalNode interface. See EvalCoerceMapVariable for
223 // details.
224 func (n *EvalCoerceMapVariable) Eval(ctx EvalContext) (interface{}, error) {
225 currentTree := n.ModuleTree
226 for _, pathComponent := range n.ModulePath[1:] {
227 currentTree = currentTree.Children()[pathComponent]
228 }
229 targetConfig := currentTree.Config()
230
231 prototypes := make(map[string]config.VariableType)
232 for _, variable := range targetConfig.Variables {
233 prototypes[variable.Name] = variable.Type()
234 }
235
236 for name, declaredType := range prototypes {
237 if declaredType != config.VariableTypeMap {
238 continue
239 }
240
241 proposedValue, ok := n.Variables[name]
242 if !ok {
243 continue
244 }
245
246 if list, ok := proposedValue.([]interface{}); ok && len(list) == 1 {
247 if m, ok := list[0].(map[string]interface{}); ok {
248 log.Printf("[DEBUG] EvalCoerceMapVariable: "+
249 "Coercing single element list into map: %#v", m)
250 n.Variables[name] = m
251 }
252 }
253 }
254
255 return nil, nil
256 }
257
258 // hclTypeName returns the name of the type that would represent this value in
259 // a config file, or falls back to the Go type name if there's no corresponding
260 // HCL type. This is used for formatted output, not for comparing types.
261 func hclTypeName(i interface{}) string {
262 switch k := reflect.Indirect(reflect.ValueOf(i)).Kind(); k {
263 case reflect.Bool:
264 return "boolean"
265 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
266 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
267 reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64:
268 return "number"
269 case reflect.Array, reflect.Slice:
270 return "list"
271 case reflect.Map:
272 return "map"
273 case reflect.String:
274 return "string"
275 default:
276 // fall back to the Go type if there's no match
277 return k.String()
278 }
279 }