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