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 }