10 "github.com/hashicorp/terraform/config"
11 "github.com/hashicorp/terraform/config/module"
12 "github.com/hashicorp/terraform/helper/hilmapstructure"
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.
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
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{}
29 ModuleTree *module.Tree
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]
37 targetConfig := currentTree.Config()
39 prototypes := make(map[string]config.VariableType)
40 for _, variable := range targetConfig.Variables {
41 prototypes[variable.Name] = variable.Type()
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 = ""
50 for name, declaredType := range prototypes {
51 proposedValue, ok := n.Variables[name]
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.
58 if proposedValue == config.UnknownVariableValue {
63 case config.VariableTypeString:
64 switch proposedValue.(type) {
68 return nil, fmt.Errorf("variable %s%s should be type %s, got %s",
69 name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue))
71 case config.VariableTypeMap:
72 switch proposedValue.(type) {
73 case map[string]interface{}:
76 return nil, fmt.Errorf("variable %s%s should be type %s, got %s",
77 name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue))
79 case config.VariableTypeList:
80 switch proposedValue.(type) {
84 return nil, fmt.Errorf("variable %s%s should be type %s, got %s",
85 name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue))
88 return nil, fmt.Errorf("variable %s%s should be type %s, got type string",
89 name, modulePathDescription, declaredType.Printable())
96 // EvalSetVariables is an EvalNode implementation that sets the variables
97 // explicitly for interpolation later.
98 type EvalSetVariables struct {
100 Variables map[string]interface{}
104 func (n *EvalSetVariables) Eval(ctx EvalContext) (interface{}, error) {
105 ctx.SetVariables(*n.Module, n.Variables)
109 // EvalVariableBlock is an EvalNode implementation that evaluates the
110 // given configuration, and uses the final values as a way to set the
112 type EvalVariableBlock struct {
113 Config **ResourceConfig
114 VariableValues map[string]interface{}
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)
123 // Get our configuration
125 for k, v := range rc.Config {
126 vKind := reflect.ValueOf(v).Type().Kind()
130 var vSlice []interface{}
131 if err := hilmapstructure.WeakDecode(v, &vSlice); err == nil {
132 n.VariableValues[k] = vSlice
136 var vMap map[string]interface{}
137 if err := hilmapstructure.WeakDecode(v, &vMap); err == nil {
138 n.VariableValues[k] = vMap
143 if err := hilmapstructure.WeakDecode(v, &vString); err == nil {
144 n.VariableValues[k] = vString
149 return nil, fmt.Errorf("Variable value for %s is not a string, list or map type", k)
152 for _, path := range rc.ComputedKeys {
153 log.Printf("[DEBUG] Setting Unknown Variable Value for computed key: %s", path)
154 err := n.setUnknownVariableValueForPath(path)
163 func (n *EvalVariableBlock) setUnknownVariableValueForPath(path string) error {
164 pathComponents := strings.Split(path, ".")
166 if len(pathComponents) < 1 {
167 return fmt.Errorf("No path comoponents in %s", path)
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
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) {
183 index, err := strconv.Atoi(pathComponents[i])
185 return fmt.Errorf("Cannot convert %s to slice index in path %s",
186 pathComponents[i], path)
188 current = tCurrent[index]
189 case []map[string]interface{}:
190 index, err := strconv.Atoi(pathComponents[i])
192 return fmt.Errorf("Cannot convert %s to slice index in path %s",
193 pathComponents[i], path)
195 current = tCurrent[index]
196 case map[string]interface{}:
197 if val, hasVal := tCurrent[pathComponents[i]]; hasVal {
202 tCurrent[pathComponents[i]] = config.UnknownVariableValue
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.
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{}
219 ModuleTree *module.Tree
222 // Eval implements the EvalNode interface. See EvalCoerceMapVariable for
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]
229 targetConfig := currentTree.Config()
231 prototypes := make(map[string]config.VariableType)
232 for _, variable := range targetConfig.Variables {
233 prototypes[variable.Name] = variable.Type()
236 for name, declaredType := range prototypes {
237 if declaredType != config.VariableTypeMap {
241 proposedValue, ok := n.Variables[name]
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
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 {
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:
269 case reflect.Array, reflect.Slice:
276 // fall back to the Go type if there's no match