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  }