package terraform import ( "fmt" "strings" "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/dag" ) // GraphSemanticChecker is the interface that semantic checks across // the entire Terraform graph implement. // // The graph should NOT be modified by the semantic checker. type GraphSemanticChecker interface { Check(*dag.Graph) error } // UnorderedSemanticCheckRunner is an implementation of GraphSemanticChecker // that runs a list of SemanticCheckers against the vertices of the graph // in no specified order. type UnorderedSemanticCheckRunner struct { Checks []SemanticChecker } func (sc *UnorderedSemanticCheckRunner) Check(g *dag.Graph) error { var err error for _, v := range g.Vertices() { for _, check := range sc.Checks { if e := check.Check(g, v); e != nil { err = multierror.Append(err, e) } } } return err } // SemanticChecker is the interface that semantic checks across the // Terraform graph implement. Errors are accumulated. Even after an error // is returned, child vertices in the graph will still be visited. // // The graph should NOT be modified by the semantic checker. // // The order in which vertices are visited is left unspecified, so the // semantic checks should not rely on that. type SemanticChecker interface { Check(*dag.Graph, dag.Vertex) error } // smcUserVariables does all the semantic checks to verify that the // variables given satisfy the configuration itself. func smcUserVariables(c *config.Config, vs map[string]interface{}) []error { var errs []error cvs := make(map[string]*config.Variable) for _, v := range c.Variables { cvs[v.Name] = v } // Check that all required variables are present required := make(map[string]struct{}) for _, v := range c.Variables { if v.Required() { required[v.Name] = struct{}{} } } for k, _ := range vs { delete(required, k) } if len(required) > 0 { for k, _ := range required { errs = append(errs, fmt.Errorf( "Required variable not set: %s", k)) } } // Check that types match up for name, proposedValue := range vs { // Check for "map.key" fields. These stopped working with Terraform // 0.7 but we do this to surface a better error message informing // the user what happened. if idx := strings.Index(name, "."); idx > 0 { key := name[:idx] if _, ok := cvs[key]; ok { errs = append(errs, fmt.Errorf( "%s: Overriding map keys with the format `name.key` is no "+ "longer allowed. You may still override keys by setting "+ "`name = { key = value }`. The maps will be merged. This "+ "behavior appeared in 0.7.0.", name)) continue } } schema, ok := cvs[name] if !ok { continue } declaredType := schema.Type() switch declaredType { case config.VariableTypeString: switch proposedValue.(type) { case string: continue } case config.VariableTypeMap: switch v := proposedValue.(type) { case map[string]interface{}: continue case []map[string]interface{}: // if we have a list of 1 map, it will get coerced later as needed if len(v) == 1 { continue } } case config.VariableTypeList: switch proposedValue.(type) { case []interface{}: continue } } errs = append(errs, fmt.Errorf("variable %s should be type %s, got %s", name, declaredType.Printable(), hclTypeName(proposedValue))) } // TODO(mitchellh): variables that are unknown return errs }