github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/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  }