aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/terraform/semantics.go
blob: 20f1d8a27463df298ac792aa3a0534b513d68f0c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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
}