github.com/LorbusChris/terraform@v0.11.12-beta1/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 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 }