8 "github.com/hashicorp/terraform/flatmap"
9 "github.com/hashicorp/terraform/terraform"
12 // Validator is a helper that helps you validate the configuration
13 // of your resource, resource provider, etc.
15 // At the most basic level, set the Required and Optional lists to be
16 // specifiers of keys that are required or optional. If a key shows up
17 // that isn't in one of these two lists, then an error is generated.
19 // The "specifiers" allowed in this is a fairly rich syntax to help
20 // describe the format of your configuration:
22 // * Basic keys are just strings. For example: "foo" will match the
25 // * Nested structure keys can be matched by doing
26 // "listener.*.foo". This will verify that there is at least one
27 // listener element that has the "foo" key set.
29 // * The existence of a nested structure can be checked by simply
30 // doing "listener.*" which will verify that there is at least
31 // one element in the "listener" structure. This is NOT
32 // validating that "listener" is an array. It is validating
33 // that it is a nested structure in the configuration.
35 type Validator struct {
40 func (v *Validator) Validate(
41 c *terraform.ResourceConfig) (ws []string, es []error) {
42 // Flatten the configuration so it is easier to reason about
43 flat := flatmap.Flatten(c.Raw)
45 keySet := make(map[string]validatorKey)
46 for i, vs := range [][]string{v.Required, v.Optional} {
48 for _, k := range vs {
49 vk, err := newValidatorKey(k, req)
59 purged := make([]string, 0)
60 for _, kv := range keySet {
61 p, w, e := kv.Validate(flat)
69 purged = append(purged, p...)
72 // Delete all the keys we processed in order to find
74 for _, p := range purged {
78 // The rest are unknown
79 for k, _ := range flat {
80 es = append(es, fmt.Errorf("Unknown configuration: %s", k))
86 type validatorKey interface {
87 // Validate validates the given configuration and returns viewed keys,
88 // warnings, and errors.
89 Validate(map[string]string) ([]string, []string, []error)
92 func newValidatorKey(k string, req bool) (validatorKey, error) {
93 var result validatorKey
95 parts := strings.Split(k, ".")
96 if len(parts) > 1 && parts[1] == "*" {
97 result = &nestedValidatorKey{
102 result = &basicValidatorKey{
111 // basicValidatorKey validates keys that are basic such as "foo"
112 type basicValidatorKey struct {
117 func (v *basicValidatorKey) Validate(
118 m map[string]string) ([]string, []string, []error) {
119 for k, _ := range m {
120 // If we have the exact key its a match
122 return []string{k}, nil, nil
130 return nil, nil, []error{fmt.Errorf(
131 "Key not found: %s", v.Key)}
134 type nestedValidatorKey struct {
139 func (v *nestedValidatorKey) validate(
142 offset int) ([]string, []string, []error) {
143 if offset >= len(v.Parts) {
144 // We're at the end. Look for a specific key.
145 v2 := &basicValidatorKey{Key: prefix, Required: v.Required}
146 return v2.Validate(m)
149 current := v.Parts[offset]
151 // If we're at offset 0, special case to start at the next one.
153 return v.validate(m, current, offset+1)
156 // Determine if we're doing a "for all" or a specific key
158 // We're looking at a specific key, continue on.
159 return v.validate(m, prefix+"."+current, offset+1)
162 // We're doing a "for all", so we loop over.
163 countStr, ok := m[prefix+".#"]
166 // It wasn't required, so its no problem.
170 return nil, nil, []error{fmt.Errorf(
171 "Key not found: %s", prefix)}
174 count, err := strconv.ParseInt(countStr, 0, 0)
176 // This shouldn't happen if flatmap works properly
177 panic("invalid flatmap array")
182 u := make([]string, 1, count+1)
184 for i := 0; i < int(count); i++ {
185 prefix := fmt.Sprintf("%s.%d", prefix, i)
187 // Mark that we saw this specific key
188 u = append(u, prefix)
190 // Mark all prefixes of this
191 for k, _ := range m {
192 if !strings.HasPrefix(k, prefix+".") {
198 // If we have more parts, then validate deeper
199 if offset+1 < len(v.Parts) {
200 u2, w2, e2 := v.validate(m, prefix, offset+1)
211 func (v *nestedValidatorKey) Validate(
212 m map[string]string) ([]string, []string, []error) {
213 return v.validate(m, "", 0)