]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package config |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "strconv" | |
6 | "strings" | |
7 | ||
8 | "github.com/hashicorp/terraform/flatmap" | |
9 | "github.com/hashicorp/terraform/terraform" | |
10 | ) | |
11 | ||
12 | // Validator is a helper that helps you validate the configuration | |
13 | // of your resource, resource provider, etc. | |
14 | // | |
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. | |
18 | // | |
19 | // The "specifiers" allowed in this is a fairly rich syntax to help | |
20 | // describe the format of your configuration: | |
21 | // | |
22 | // * Basic keys are just strings. For example: "foo" will match the | |
23 | // "foo" key. | |
24 | // | |
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. | |
28 | // | |
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. | |
34 | // | |
35 | type Validator struct { | |
36 | Required []string | |
37 | Optional []string | |
38 | } | |
39 | ||
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) | |
44 | ||
45 | keySet := make(map[string]validatorKey) | |
46 | for i, vs := range [][]string{v.Required, v.Optional} { | |
47 | req := i == 0 | |
48 | for _, k := range vs { | |
49 | vk, err := newValidatorKey(k, req) | |
50 | if err != nil { | |
51 | es = append(es, err) | |
52 | continue | |
53 | } | |
54 | ||
55 | keySet[k] = vk | |
56 | } | |
57 | } | |
58 | ||
59 | purged := make([]string, 0) | |
60 | for _, kv := range keySet { | |
61 | p, w, e := kv.Validate(flat) | |
62 | if len(w) > 0 { | |
63 | ws = append(ws, w...) | |
64 | } | |
65 | if len(e) > 0 { | |
66 | es = append(es, e...) | |
67 | } | |
68 | ||
69 | purged = append(purged, p...) | |
70 | } | |
71 | ||
72 | // Delete all the keys we processed in order to find | |
73 | // the unknown keys. | |
74 | for _, p := range purged { | |
75 | delete(flat, p) | |
76 | } | |
77 | ||
78 | // The rest are unknown | |
79 | for k, _ := range flat { | |
80 | es = append(es, fmt.Errorf("Unknown configuration: %s", k)) | |
81 | } | |
82 | ||
83 | return | |
84 | } | |
85 | ||
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) | |
90 | } | |
91 | ||
92 | func newValidatorKey(k string, req bool) (validatorKey, error) { | |
93 | var result validatorKey | |
94 | ||
95 | parts := strings.Split(k, ".") | |
96 | if len(parts) > 1 && parts[1] == "*" { | |
97 | result = &nestedValidatorKey{ | |
98 | Parts: parts, | |
99 | Required: req, | |
100 | } | |
101 | } else { | |
102 | result = &basicValidatorKey{ | |
103 | Key: k, | |
104 | Required: req, | |
105 | } | |
106 | } | |
107 | ||
108 | return result, nil | |
109 | } | |
110 | ||
111 | // basicValidatorKey validates keys that are basic such as "foo" | |
112 | type basicValidatorKey struct { | |
113 | Key string | |
114 | Required bool | |
115 | } | |
116 | ||
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 | |
121 | if k == v.Key { | |
122 | return []string{k}, nil, nil | |
123 | } | |
124 | } | |
125 | ||
126 | if !v.Required { | |
127 | return nil, nil, nil | |
128 | } | |
129 | ||
130 | return nil, nil, []error{fmt.Errorf( | |
131 | "Key not found: %s", v.Key)} | |
132 | } | |
133 | ||
134 | type nestedValidatorKey struct { | |
135 | Parts []string | |
136 | Required bool | |
137 | } | |
138 | ||
139 | func (v *nestedValidatorKey) validate( | |
140 | m map[string]string, | |
141 | prefix string, | |
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) | |
147 | } | |
148 | ||
149 | current := v.Parts[offset] | |
150 | ||
151 | // If we're at offset 0, special case to start at the next one. | |
152 | if offset == 0 { | |
153 | return v.validate(m, current, offset+1) | |
154 | } | |
155 | ||
156 | // Determine if we're doing a "for all" or a specific key | |
157 | if current != "*" { | |
158 | // We're looking at a specific key, continue on. | |
159 | return v.validate(m, prefix+"."+current, offset+1) | |
160 | } | |
161 | ||
162 | // We're doing a "for all", so we loop over. | |
163 | countStr, ok := m[prefix+".#"] | |
164 | if !ok { | |
165 | if !v.Required { | |
166 | // It wasn't required, so its no problem. | |
167 | return nil, nil, nil | |
168 | } | |
169 | ||
170 | return nil, nil, []error{fmt.Errorf( | |
171 | "Key not found: %s", prefix)} | |
172 | } | |
173 | ||
174 | count, err := strconv.ParseInt(countStr, 0, 0) | |
175 | if err != nil { | |
176 | // This shouldn't happen if flatmap works properly | |
177 | panic("invalid flatmap array") | |
178 | } | |
179 | ||
180 | var e []error | |
181 | var w []string | |
182 | u := make([]string, 1, count+1) | |
183 | u[0] = prefix + ".#" | |
184 | for i := 0; i < int(count); i++ { | |
185 | prefix := fmt.Sprintf("%s.%d", prefix, i) | |
186 | ||
187 | // Mark that we saw this specific key | |
188 | u = append(u, prefix) | |
189 | ||
190 | // Mark all prefixes of this | |
191 | for k, _ := range m { | |
192 | if !strings.HasPrefix(k, prefix+".") { | |
193 | continue | |
194 | } | |
195 | u = append(u, k) | |
196 | } | |
197 | ||
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) | |
201 | ||
202 | u = append(u, u2...) | |
203 | w = append(w, w2...) | |
204 | e = append(e, e2...) | |
205 | } | |
206 | } | |
207 | ||
208 | return u, w, e | |
209 | } | |
210 | ||
211 | func (v *nestedValidatorKey) Validate( | |
212 | m map[string]string) ([]string, []string, []error) { | |
213 | return v.validate(m, "", 0) | |
214 | } |