github.com/r3labs/terraform@v0.8.4/terraform/semantics.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/hashicorp/go-multierror"
     8  	"github.com/hashicorp/terraform/config"
     9  	"github.com/hashicorp/terraform/dag"
    10  )
    11  
    12  // GraphSemanticChecker is the interface that semantic checks across
    13  // the entire Terraform graph implement.
    14  //
    15  // The graph should NOT be modified by the semantic checker.
    16  type GraphSemanticChecker interface {
    17  	Check(*dag.Graph) error
    18  }
    19  
    20  // UnorderedSemanticCheckRunner is an implementation of GraphSemanticChecker
    21  // that runs a list of SemanticCheckers against the vertices of the graph
    22  // in no specified order.
    23  type UnorderedSemanticCheckRunner struct {
    24  	Checks []SemanticChecker
    25  }
    26  
    27  func (sc *UnorderedSemanticCheckRunner) Check(g *dag.Graph) error {
    28  	var err error
    29  	for _, v := range g.Vertices() {
    30  		for _, check := range sc.Checks {
    31  			if e := check.Check(g, v); e != nil {
    32  				err = multierror.Append(err, e)
    33  			}
    34  		}
    35  	}
    36  
    37  	return err
    38  }
    39  
    40  // SemanticChecker is the interface that semantic checks across the
    41  // Terraform graph implement. Errors are accumulated. Even after an error
    42  // is returned, child vertices in the graph will still be visited.
    43  //
    44  // The graph should NOT be modified by the semantic checker.
    45  //
    46  // The order in which vertices are visited is left unspecified, so the
    47  // semantic checks should not rely on that.
    48  type SemanticChecker interface {
    49  	Check(*dag.Graph, dag.Vertex) error
    50  }
    51  
    52  // SemanticCheckModulesExist is an implementation of SemanticChecker that
    53  // verifies that all the modules that are referenced in the graph exist.
    54  type SemanticCheckModulesExist struct{}
    55  
    56  // TODO: test
    57  func (*SemanticCheckModulesExist) Check(g *dag.Graph, v dag.Vertex) error {
    58  	mn, ok := v.(*GraphNodeConfigModule)
    59  	if !ok {
    60  		return nil
    61  	}
    62  
    63  	if mn.Tree == nil {
    64  		return fmt.Errorf(
    65  			"module '%s' not found", mn.Module.Name)
    66  	}
    67  
    68  	return nil
    69  }
    70  
    71  // smcUserVariables does all the semantic checks to verify that the
    72  // variables given satisfy the configuration itself.
    73  func smcUserVariables(c *config.Config, vs map[string]interface{}) []error {
    74  	var errs []error
    75  
    76  	cvs := make(map[string]*config.Variable)
    77  	for _, v := range c.Variables {
    78  		cvs[v.Name] = v
    79  	}
    80  
    81  	// Check that all required variables are present
    82  	required := make(map[string]struct{})
    83  	for _, v := range c.Variables {
    84  		if v.Required() {
    85  			required[v.Name] = struct{}{}
    86  		}
    87  	}
    88  	for k, _ := range vs {
    89  		delete(required, k)
    90  	}
    91  	if len(required) > 0 {
    92  		for k, _ := range required {
    93  			errs = append(errs, fmt.Errorf(
    94  				"Required variable not set: %s", k))
    95  		}
    96  	}
    97  
    98  	// Check that types match up
    99  	for name, proposedValue := range vs {
   100  		// Check for "map.key" fields. These stopped working with Terraform
   101  		// 0.7 but we do this to surface a better error message informing
   102  		// the user what happened.
   103  		if idx := strings.Index(name, "."); idx > 0 {
   104  			key := name[:idx]
   105  			if _, ok := cvs[key]; ok {
   106  				errs = append(errs, fmt.Errorf(
   107  					"%s: Overriding map keys with the format `name.key` is no "+
   108  						"longer allowed. You may still override keys by setting "+
   109  						"`name = { key = value }`. The maps will be merged. This "+
   110  						"behavior appeared in 0.7.0.", name))
   111  				continue
   112  			}
   113  		}
   114  
   115  		schema, ok := cvs[name]
   116  		if !ok {
   117  			continue
   118  		}
   119  
   120  		declaredType := schema.Type()
   121  
   122  		switch declaredType {
   123  		case config.VariableTypeString:
   124  			switch proposedValue.(type) {
   125  			case string:
   126  				continue
   127  			}
   128  		case config.VariableTypeMap:
   129  			switch v := proposedValue.(type) {
   130  			case map[string]interface{}:
   131  				continue
   132  			case []map[string]interface{}:
   133  				// if we have a list of 1 map, it will get coerced later as needed
   134  				if len(v) == 1 {
   135  					continue
   136  				}
   137  			}
   138  		case config.VariableTypeList:
   139  			switch proposedValue.(type) {
   140  			case []interface{}:
   141  				continue
   142  			}
   143  		}
   144  		errs = append(errs, fmt.Errorf("variable %s should be type %s, got %s",
   145  			name, declaredType.Printable(), hclTypeName(proposedValue)))
   146  	}
   147  
   148  	// TODO(mitchellh): variables that are unknown
   149  
   150  	return errs
   151  }