]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package module |
2 | ||
3 | import ( | |
4 | "bufio" | |
5 | "bytes" | |
6 | "fmt" | |
7 | "path/filepath" | |
8 | "strings" | |
9 | "sync" | |
10 | ||
11 | "github.com/hashicorp/go-getter" | |
12 | "github.com/hashicorp/terraform/config" | |
13 | ) | |
14 | ||
15 | // RootName is the name of the root tree. | |
16 | const RootName = "root" | |
17 | ||
18 | // Tree represents the module import tree of configurations. | |
19 | // | |
20 | // This Tree structure can be used to get (download) new modules, load | |
21 | // all the modules without getting, flatten the tree into something | |
22 | // Terraform can use, etc. | |
23 | type Tree struct { | |
24 | name string | |
25 | config *config.Config | |
26 | children map[string]*Tree | |
27 | path []string | |
28 | lock sync.RWMutex | |
29 | } | |
30 | ||
31 | // NewTree returns a new Tree for the given config structure. | |
32 | func NewTree(name string, c *config.Config) *Tree { | |
33 | return &Tree{config: c, name: name} | |
34 | } | |
35 | ||
36 | // NewEmptyTree returns a new tree that is empty (contains no configuration). | |
37 | func NewEmptyTree() *Tree { | |
38 | t := &Tree{config: &config.Config{}} | |
39 | ||
40 | // We do this dummy load so that the tree is marked as "loaded". It | |
41 | // should never fail because this is just about a no-op. If it does fail | |
42 | // we panic so we can know its a bug. | |
43 | if err := t.Load(nil, GetModeGet); err != nil { | |
44 | panic(err) | |
45 | } | |
46 | ||
47 | return t | |
48 | } | |
49 | ||
50 | // NewTreeModule is like NewTree except it parses the configuration in | |
51 | // the directory and gives it a specific name. Use a blank name "" to specify | |
52 | // the root module. | |
53 | func NewTreeModule(name, dir string) (*Tree, error) { | |
54 | c, err := config.LoadDir(dir) | |
55 | if err != nil { | |
56 | return nil, err | |
57 | } | |
58 | ||
59 | return NewTree(name, c), nil | |
60 | } | |
61 | ||
62 | // Config returns the configuration for this module. | |
63 | func (t *Tree) Config() *config.Config { | |
64 | return t.config | |
65 | } | |
66 | ||
67 | // Child returns the child with the given path (by name). | |
68 | func (t *Tree) Child(path []string) *Tree { | |
69 | if t == nil { | |
70 | return nil | |
71 | } | |
72 | ||
73 | if len(path) == 0 { | |
74 | return t | |
75 | } | |
76 | ||
77 | c := t.Children()[path[0]] | |
78 | if c == nil { | |
79 | return nil | |
80 | } | |
81 | ||
82 | return c.Child(path[1:]) | |
83 | } | |
84 | ||
85 | // Children returns the children of this tree (the modules that are | |
86 | // imported by this root). | |
87 | // | |
88 | // This will only return a non-nil value after Load is called. | |
89 | func (t *Tree) Children() map[string]*Tree { | |
90 | t.lock.RLock() | |
91 | defer t.lock.RUnlock() | |
92 | return t.children | |
93 | } | |
94 | ||
c680a8e1 RS |
95 | // DeepEach calls the provided callback for the receiver and then all of |
96 | // its descendents in the tree, allowing an operation to be performed on | |
97 | // all modules in the tree. | |
98 | // | |
99 | // Parents will be visited before their children but otherwise the order is | |
100 | // not defined. | |
101 | func (t *Tree) DeepEach(cb func(*Tree)) { | |
102 | t.lock.RLock() | |
103 | defer t.lock.RUnlock() | |
104 | t.deepEach(cb) | |
105 | } | |
106 | ||
107 | func (t *Tree) deepEach(cb func(*Tree)) { | |
108 | cb(t) | |
109 | for _, c := range t.children { | |
110 | c.deepEach(cb) | |
111 | } | |
112 | } | |
113 | ||
bae9f6d2 JC |
114 | // Loaded says whether or not this tree has been loaded or not yet. |
115 | func (t *Tree) Loaded() bool { | |
116 | t.lock.RLock() | |
117 | defer t.lock.RUnlock() | |
118 | return t.children != nil | |
119 | } | |
120 | ||
121 | // Modules returns the list of modules that this tree imports. | |
122 | // | |
123 | // This is only the imports of _this_ level of the tree. To retrieve the | |
124 | // full nested imports, you'll have to traverse the tree. | |
125 | func (t *Tree) Modules() []*Module { | |
126 | result := make([]*Module, len(t.config.Modules)) | |
127 | for i, m := range t.config.Modules { | |
128 | result[i] = &Module{ | |
129 | Name: m.Name, | |
130 | Source: m.Source, | |
131 | } | |
132 | } | |
133 | ||
134 | return result | |
135 | } | |
136 | ||
137 | // Name returns the name of the tree. This will be "<root>" for the root | |
138 | // tree and then the module name given for any children. | |
139 | func (t *Tree) Name() string { | |
140 | if t.name == "" { | |
141 | return RootName | |
142 | } | |
143 | ||
144 | return t.name | |
145 | } | |
146 | ||
147 | // Load loads the configuration of the entire tree. | |
148 | // | |
149 | // The parameters are used to tell the tree where to find modules and | |
150 | // whether it can download/update modules along the way. | |
151 | // | |
152 | // Calling this multiple times will reload the tree. | |
153 | // | |
154 | // Various semantic-like checks are made along the way of loading since | |
155 | // module trees inherently require the configuration to be in a reasonably | |
156 | // sane state: no circular dependencies, proper module sources, etc. A full | |
157 | // suite of validations can be done by running Validate (after loading). | |
158 | func (t *Tree) Load(s getter.Storage, mode GetMode) error { | |
159 | t.lock.Lock() | |
160 | defer t.lock.Unlock() | |
161 | ||
162 | // Reset the children if we have any | |
163 | t.children = nil | |
164 | ||
165 | modules := t.Modules() | |
166 | children := make(map[string]*Tree) | |
167 | ||
168 | // Go through all the modules and get the directory for them. | |
169 | for _, m := range modules { | |
170 | if _, ok := children[m.Name]; ok { | |
171 | return fmt.Errorf( | |
172 | "module %s: duplicated. module names must be unique", m.Name) | |
173 | } | |
174 | ||
175 | // Determine the path to this child | |
176 | path := make([]string, len(t.path), len(t.path)+1) | |
177 | copy(path, t.path) | |
178 | path = append(path, m.Name) | |
179 | ||
180 | // Split out the subdir if we have one | |
181 | source, subDir := getter.SourceDirSubdir(m.Source) | |
182 | ||
183 | source, err := getter.Detect(source, t.config.Dir, getter.Detectors) | |
184 | if err != nil { | |
185 | return fmt.Errorf("module %s: %s", m.Name, err) | |
186 | } | |
187 | ||
188 | // Check if the detector introduced something new. | |
189 | source, subDir2 := getter.SourceDirSubdir(source) | |
190 | if subDir2 != "" { | |
191 | subDir = filepath.Join(subDir2, subDir) | |
192 | } | |
193 | ||
194 | // Get the directory where this module is so we can load it | |
195 | key := strings.Join(path, ".") | |
196 | key = fmt.Sprintf("root.%s-%s", key, m.Source) | |
197 | dir, ok, err := getStorage(s, key, source, mode) | |
198 | if err != nil { | |
199 | return err | |
200 | } | |
201 | if !ok { | |
202 | return fmt.Errorf( | |
203 | "module %s: not found, may need to be downloaded using 'terraform get'", m.Name) | |
204 | } | |
205 | ||
206 | // If we have a subdirectory, then merge that in | |
207 | if subDir != "" { | |
208 | dir = filepath.Join(dir, subDir) | |
209 | } | |
210 | ||
211 | // Load the configurations.Dir(source) | |
212 | children[m.Name], err = NewTreeModule(m.Name, dir) | |
213 | if err != nil { | |
214 | return fmt.Errorf( | |
215 | "module %s: %s", m.Name, err) | |
216 | } | |
217 | ||
218 | // Set the path of this child | |
219 | children[m.Name].path = path | |
220 | } | |
221 | ||
222 | // Go through all the children and load them. | |
223 | for _, c := range children { | |
224 | if err := c.Load(s, mode); err != nil { | |
225 | return err | |
226 | } | |
227 | } | |
228 | ||
229 | // Set our tree up | |
230 | t.children = children | |
231 | ||
232 | return nil | |
233 | } | |
234 | ||
235 | // Path is the full path to this tree. | |
236 | func (t *Tree) Path() []string { | |
237 | return t.path | |
238 | } | |
239 | ||
240 | // String gives a nice output to describe the tree. | |
241 | func (t *Tree) String() string { | |
242 | var result bytes.Buffer | |
243 | path := strings.Join(t.path, ", ") | |
244 | if path != "" { | |
245 | path = fmt.Sprintf(" (path: %s)", path) | |
246 | } | |
247 | result.WriteString(t.Name() + path + "\n") | |
248 | ||
249 | cs := t.Children() | |
250 | if cs == nil { | |
251 | result.WriteString(" not loaded") | |
252 | } else { | |
253 | // Go through each child and get its string value, then indent it | |
254 | // by two. | |
255 | for _, c := range cs { | |
256 | r := strings.NewReader(c.String()) | |
257 | scanner := bufio.NewScanner(r) | |
258 | for scanner.Scan() { | |
259 | result.WriteString(" ") | |
260 | result.WriteString(scanner.Text()) | |
261 | result.WriteString("\n") | |
262 | } | |
263 | } | |
264 | } | |
265 | ||
266 | return result.String() | |
267 | } | |
268 | ||
269 | // Validate does semantic checks on the entire tree of configurations. | |
270 | // | |
271 | // This will call the respective config.Config.Validate() functions as well | |
272 | // as verifying things such as parameters/outputs between the various modules. | |
273 | // | |
274 | // Load must be called prior to calling Validate or an error will be returned. | |
275 | func (t *Tree) Validate() error { | |
276 | if !t.Loaded() { | |
277 | return fmt.Errorf("tree must be loaded before calling Validate") | |
278 | } | |
279 | ||
280 | // If something goes wrong, here is our error template | |
281 | newErr := &treeError{Name: []string{t.Name()}} | |
282 | ||
283 | // Terraform core does not handle root module children named "root". | |
284 | // We plan to fix this in the future but this bug was brought up in | |
285 | // the middle of a release and we don't want to introduce wide-sweeping | |
286 | // changes at that time. | |
287 | if len(t.path) == 1 && t.name == "root" { | |
288 | return fmt.Errorf("root module cannot contain module named 'root'") | |
289 | } | |
290 | ||
291 | // Validate our configuration first. | |
292 | if err := t.config.Validate(); err != nil { | |
293 | newErr.Add(err) | |
294 | } | |
295 | ||
296 | // If we're the root, we do extra validation. This validation usually | |
297 | // requires the entire tree (since children don't have parent pointers). | |
298 | if len(t.path) == 0 { | |
299 | if err := t.validateProviderAlias(); err != nil { | |
300 | newErr.Add(err) | |
301 | } | |
302 | } | |
303 | ||
304 | // Get the child trees | |
305 | children := t.Children() | |
306 | ||
307 | // Validate all our children | |
308 | for _, c := range children { | |
309 | err := c.Validate() | |
310 | if err == nil { | |
311 | continue | |
312 | } | |
313 | ||
314 | verr, ok := err.(*treeError) | |
315 | if !ok { | |
316 | // Unknown error, just return... | |
317 | return err | |
318 | } | |
319 | ||
320 | // Append ourselves to the error and then return | |
321 | verr.Name = append(verr.Name, t.Name()) | |
322 | newErr.AddChild(verr) | |
323 | } | |
324 | ||
325 | // Go over all the modules and verify that any parameters are valid | |
326 | // variables into the module in question. | |
327 | for _, m := range t.config.Modules { | |
328 | tree, ok := children[m.Name] | |
329 | if !ok { | |
330 | // This should never happen because Load watches us | |
331 | panic("module not found in children: " + m.Name) | |
332 | } | |
333 | ||
334 | // Build the variables that the module defines | |
335 | requiredMap := make(map[string]struct{}) | |
336 | varMap := make(map[string]struct{}) | |
337 | for _, v := range tree.config.Variables { | |
338 | varMap[v.Name] = struct{}{} | |
339 | ||
340 | if v.Required() { | |
341 | requiredMap[v.Name] = struct{}{} | |
342 | } | |
343 | } | |
344 | ||
345 | // Compare to the keys in our raw config for the module | |
346 | for k, _ := range m.RawConfig.Raw { | |
347 | if _, ok := varMap[k]; !ok { | |
348 | newErr.Add(fmt.Errorf( | |
349 | "module %s: %s is not a valid parameter", | |
350 | m.Name, k)) | |
351 | } | |
352 | ||
353 | // Remove the required | |
354 | delete(requiredMap, k) | |
355 | } | |
356 | ||
357 | // If we have any required left over, they aren't set. | |
358 | for k, _ := range requiredMap { | |
359 | newErr.Add(fmt.Errorf( | |
360 | "module %s: required variable %q not set", | |
361 | m.Name, k)) | |
362 | } | |
363 | } | |
364 | ||
365 | // Go over all the variables used and make sure that any module | |
366 | // variables represent outputs properly. | |
367 | for source, vs := range t.config.InterpolatedVariables() { | |
368 | for _, v := range vs { | |
369 | mv, ok := v.(*config.ModuleVariable) | |
370 | if !ok { | |
371 | continue | |
372 | } | |
373 | ||
374 | tree, ok := children[mv.Name] | |
375 | if !ok { | |
376 | newErr.Add(fmt.Errorf( | |
377 | "%s: undefined module referenced %s", | |
378 | source, mv.Name)) | |
379 | continue | |
380 | } | |
381 | ||
382 | found := false | |
383 | for _, o := range tree.config.Outputs { | |
384 | if o.Name == mv.Field { | |
385 | found = true | |
386 | break | |
387 | } | |
388 | } | |
389 | if !found { | |
390 | newErr.Add(fmt.Errorf( | |
391 | "%s: %s is not a valid output for module %s", | |
392 | source, mv.Field, mv.Name)) | |
393 | } | |
394 | } | |
395 | } | |
396 | ||
397 | return newErr.ErrOrNil() | |
398 | } | |
399 | ||
400 | // treeError is an error use by Tree.Validate to accumulates all | |
401 | // validation errors. | |
402 | type treeError struct { | |
403 | Name []string | |
404 | Errs []error | |
405 | Children []*treeError | |
406 | } | |
407 | ||
408 | func (e *treeError) Add(err error) { | |
409 | e.Errs = append(e.Errs, err) | |
410 | } | |
411 | ||
412 | func (e *treeError) AddChild(err *treeError) { | |
413 | e.Children = append(e.Children, err) | |
414 | } | |
415 | ||
416 | func (e *treeError) ErrOrNil() error { | |
417 | if len(e.Errs) > 0 || len(e.Children) > 0 { | |
418 | return e | |
419 | } | |
420 | return nil | |
421 | } | |
422 | ||
423 | func (e *treeError) Error() string { | |
424 | name := strings.Join(e.Name, ".") | |
425 | var out bytes.Buffer | |
426 | fmt.Fprintf(&out, "module %s: ", name) | |
427 | ||
428 | if len(e.Errs) == 1 { | |
429 | // single like error | |
430 | out.WriteString(e.Errs[0].Error()) | |
431 | } else { | |
432 | // multi-line error | |
433 | for _, err := range e.Errs { | |
434 | fmt.Fprintf(&out, "\n %s", err) | |
435 | } | |
436 | } | |
437 | ||
438 | if len(e.Children) > 0 { | |
439 | // start the next error on a new line | |
440 | out.WriteString("\n ") | |
441 | } | |
442 | for _, child := range e.Children { | |
443 | out.WriteString(child.Error()) | |
444 | } | |
445 | ||
446 | return out.String() | |
447 | } |