github.com/adrian-bl/terraform@v0.7.0-rc2.0.20160705220747-de0a34fc3517/terraform/eval_variable.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 8 "github.com/hashicorp/terraform/config" 9 "github.com/hashicorp/terraform/config/module" 10 "github.com/hashicorp/terraform/helper/hilmapstructure" 11 ) 12 13 // EvalTypeCheckVariable is an EvalNode which ensures that the variable 14 // values which are assigned as inputs to a module (including the root) 15 // match the types which are either declared for the variables explicitly 16 // or inferred from the default values. 17 // 18 // In order to achieve this three things are required: 19 // - a map of the proposed variable values 20 // - the configuration tree of the module in which the variable is 21 // declared 22 // - the path to the module (so we know which part of the tree to 23 // compare the values against). 24 // 25 // Currently since the type system is simple, we currently do not make 26 // use of the values since it is only valid to pass string values. The 27 // structure is in place for extension of the type system, however. 28 type EvalTypeCheckVariable struct { 29 Variables map[string]interface{} 30 ModulePath []string 31 ModuleTree *module.Tree 32 } 33 34 func (n *EvalTypeCheckVariable) Eval(ctx EvalContext) (interface{}, error) { 35 currentTree := n.ModuleTree 36 for _, pathComponent := range n.ModulePath[1:] { 37 currentTree = currentTree.Children()[pathComponent] 38 } 39 targetConfig := currentTree.Config() 40 41 prototypes := make(map[string]config.VariableType) 42 for _, variable := range targetConfig.Variables { 43 prototypes[variable.Name] = variable.Type() 44 } 45 46 // Only display a module in an error message if we are not in the root module 47 modulePathDescription := fmt.Sprintf(" in module %s", strings.Join(n.ModulePath[1:], ".")) 48 if len(n.ModulePath) == 1 { 49 modulePathDescription = "" 50 } 51 52 for name, declaredType := range prototypes { 53 // This is only necessary when we _actually_ check. It is left as a reminder 54 // that at the current time we are dealing with a type system consisting only 55 // of strings and maps - where the only valid inter-module variable type is 56 // string. 57 proposedValue, ok := n.Variables[name] 58 if !ok { 59 // This means the default value should be used as no overriding value 60 // has been set. Therefore we should continue as no check is necessary. 61 continue 62 } 63 64 if proposedValue == config.UnknownVariableValue { 65 continue 66 } 67 68 switch declaredType { 69 case config.VariableTypeString: 70 // This will need actual verification once we aren't dealing with 71 // a map[string]string but this is sufficient for now. 72 switch proposedValue.(type) { 73 case string: 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.VariableTypeMap: 80 switch proposedValue.(type) { 81 case map[string]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 case config.VariableTypeList: 88 switch proposedValue.(type) { 89 case []interface{}: 90 continue 91 default: 92 return nil, fmt.Errorf("variable %s%s should be type %s, got %s", 93 name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue)) 94 } 95 default: 96 // This will need the actual type substituting when we have more than 97 // just strings and maps. 98 return nil, fmt.Errorf("variable %s%s should be type %s, got type string", 99 name, modulePathDescription, declaredType.Printable()) 100 } 101 } 102 103 return nil, nil 104 } 105 106 // EvalSetVariables is an EvalNode implementation that sets the variables 107 // explicitly for interpolation later. 108 type EvalSetVariables struct { 109 Module *string 110 Variables map[string]interface{} 111 } 112 113 // TODO: test 114 func (n *EvalSetVariables) Eval(ctx EvalContext) (interface{}, error) { 115 ctx.SetVariables(*n.Module, n.Variables) 116 return nil, nil 117 } 118 119 // EvalVariableBlock is an EvalNode implementation that evaluates the 120 // given configuration, and uses the final values as a way to set the 121 // mapping. 122 type EvalVariableBlock struct { 123 Config **ResourceConfig 124 VariableValues map[string]interface{} 125 } 126 127 // TODO: test 128 func (n *EvalVariableBlock) Eval(ctx EvalContext) (interface{}, error) { 129 // Clear out the existing mapping 130 for k, _ := range n.VariableValues { 131 delete(n.VariableValues, k) 132 } 133 134 // Get our configuration 135 rc := *n.Config 136 for k, v := range rc.Config { 137 var vString string 138 if err := hilmapstructure.WeakDecode(v, &vString); err == nil { 139 n.VariableValues[k] = vString 140 continue 141 } 142 143 var vMap map[string]interface{} 144 if err := hilmapstructure.WeakDecode(v, &vMap); err == nil { 145 n.VariableValues[k] = vMap 146 continue 147 } 148 149 var vSlice []interface{} 150 if err := hilmapstructure.WeakDecode(v, &vSlice); err == nil { 151 n.VariableValues[k] = vSlice 152 continue 153 } 154 155 return nil, fmt.Errorf("Variable value for %s is not a string, list or map type", k) 156 } 157 for k, _ := range rc.Raw { 158 if _, ok := n.VariableValues[k]; !ok { 159 n.VariableValues[k] = config.UnknownVariableValue 160 } 161 } 162 163 return nil, nil 164 } 165 166 // hclTypeName returns the name of the type that would represent this value in 167 // a config file, or falls back to the Go type name if there's no corresponding 168 // HCL type. This is used for formatted output, not for comparing types. 169 func hclTypeName(i interface{}) string { 170 switch k := reflect.Indirect(reflect.ValueOf(i)).Kind(); k { 171 case reflect.Bool: 172 return "boolean" 173 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 174 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 175 reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64: 176 return "number" 177 case reflect.Array, reflect.Slice: 178 return "list" 179 case reflect.Map: 180 return "map" 181 case reflect.String: 182 return "string" 183 default: 184 // fall back to the Go type if there's no match 185 return k.String() 186 } 187 }