github.com/richardbowden/terraform@v0.6.12-0.20160901200758-30ea22c25211/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  }