github.com/r3labs/terraform@v0.8.4/terraform/eval_variable.go (about) 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 // TODO: test 118 func (n *EvalVariableBlock) Eval(ctx EvalContext) (interface{}, error) { 119 // Clear out the existing mapping 120 for k, _ := range n.VariableValues { 121 delete(n.VariableValues, k) 122 } 123 124 // Get our configuration 125 rc := *n.Config 126 for k, v := range rc.Config { 127 var vString string 128 if err := hilmapstructure.WeakDecode(v, &vString); err == nil { 129 n.VariableValues[k] = vString 130 continue 131 } 132 133 var vMap map[string]interface{} 134 if err := hilmapstructure.WeakDecode(v, &vMap); err == nil { 135 n.VariableValues[k] = vMap 136 continue 137 } 138 139 var vSlice []interface{} 140 if err := hilmapstructure.WeakDecode(v, &vSlice); err == nil { 141 n.VariableValues[k] = vSlice 142 continue 143 } 144 145 return nil, fmt.Errorf("Variable value for %s is not a string, list or map type", k) 146 } 147 148 for _, path := range rc.ComputedKeys { 149 log.Printf("[DEBUG] Setting Unknown Variable Value for computed key: %s", path) 150 err := n.setUnknownVariableValueForPath(path) 151 if err != nil { 152 return nil, err 153 } 154 } 155 156 return nil, nil 157 } 158 159 func (n *EvalVariableBlock) setUnknownVariableValueForPath(path string) error { 160 pathComponents := strings.Split(path, ".") 161 162 if len(pathComponents) < 1 { 163 return fmt.Errorf("No path comoponents in %s", path) 164 } 165 166 if len(pathComponents) == 1 { 167 // Special case the "top level" since we know the type 168 if _, ok := n.VariableValues[pathComponents[0]]; !ok { 169 n.VariableValues[pathComponents[0]] = config.UnknownVariableValue 170 } 171 return nil 172 } 173 174 // Otherwise find the correct point in the tree and then set to unknown 175 var current interface{} = n.VariableValues[pathComponents[0]] 176 for i := 1; i < len(pathComponents); i++ { 177 switch current.(type) { 178 case []interface{}, []map[string]interface{}: 179 tCurrent := current.([]interface{}) 180 index, err := strconv.Atoi(pathComponents[i]) 181 if err != nil { 182 return fmt.Errorf("Cannot convert %s to slice index in path %s", 183 pathComponents[i], path) 184 } 185 current = tCurrent[index] 186 case map[string]interface{}: 187 tCurrent := current.(map[string]interface{}) 188 if val, hasVal := tCurrent[pathComponents[i]]; hasVal { 189 current = val 190 continue 191 } 192 193 tCurrent[pathComponents[i]] = config.UnknownVariableValue 194 break 195 } 196 } 197 198 return nil 199 } 200 201 // EvalCoerceMapVariable is an EvalNode implementation that recognizes a 202 // specific ambiguous HCL parsing situation and resolves it. In HCL parsing, a 203 // bare map literal is indistinguishable from a list of maps w/ one element. 204 // 205 // We take all the same inputs as EvalTypeCheckVariable above, since we need 206 // both the target type and the proposed value in order to properly coerce. 207 type EvalCoerceMapVariable struct { 208 Variables map[string]interface{} 209 ModulePath []string 210 ModuleTree *module.Tree 211 } 212 213 // Eval implements the EvalNode interface. See EvalCoerceMapVariable for 214 // details. 215 func (n *EvalCoerceMapVariable) Eval(ctx EvalContext) (interface{}, error) { 216 currentTree := n.ModuleTree 217 for _, pathComponent := range n.ModulePath[1:] { 218 currentTree = currentTree.Children()[pathComponent] 219 } 220 targetConfig := currentTree.Config() 221 222 prototypes := make(map[string]config.VariableType) 223 for _, variable := range targetConfig.Variables { 224 prototypes[variable.Name] = variable.Type() 225 } 226 227 for name, declaredType := range prototypes { 228 if declaredType != config.VariableTypeMap { 229 continue 230 } 231 232 proposedValue, ok := n.Variables[name] 233 if !ok { 234 continue 235 } 236 237 if list, ok := proposedValue.([]interface{}); ok && len(list) == 1 { 238 if m, ok := list[0].(map[string]interface{}); ok { 239 log.Printf("[DEBUG] EvalCoerceMapVariable: "+ 240 "Coercing single element list into map: %#v", m) 241 n.Variables[name] = m 242 } 243 } 244 } 245 246 return nil, nil 247 } 248 249 // hclTypeName returns the name of the type that would represent this value in 250 // a config file, or falls back to the Go type name if there's no corresponding 251 // HCL type. This is used for formatted output, not for comparing types. 252 func hclTypeName(i interface{}) string { 253 switch k := reflect.Indirect(reflect.ValueOf(i)).Kind(); k { 254 case reflect.Bool: 255 return "boolean" 256 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 257 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 258 reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64: 259 return "number" 260 case reflect.Array, reflect.Slice: 261 return "list" 262 case reflect.Map: 263 return "map" 264 case reflect.String: 265 return "string" 266 default: 267 // fall back to the Go type if there's no match 268 return k.String() 269 } 270 }