]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/config/module/tree.go
vendor: github.com/hashicorp/terraform/...@v0.10.0
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / config / module / tree.go
CommitLineData
bae9f6d2
JC
1package module
2
3import (
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.
16const 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.
23type 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.
32func 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).
37func 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.
53func 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.
63func (t *Tree) Config() *config.Config {
64 return t.config
65}
66
67// Child returns the child with the given path (by name).
68func (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.
89func (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.
101func (t *Tree) DeepEach(cb func(*Tree)) {
102 t.lock.RLock()
103 defer t.lock.RUnlock()
104 t.deepEach(cb)
105}
106
107func (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.
115func (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.
125func (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.
139func (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).
158func (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.
236func (t *Tree) Path() []string {
237 return t.path
238}
239
240// String gives a nice output to describe the tree.
241func (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.
275func (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.
402type treeError struct {
403 Name []string
404 Errs []error
405 Children []*treeError
406}
407
408func (e *treeError) Add(err error) {
409 e.Errs = append(e.Errs, err)
410}
411
412func (e *treeError) AddChild(err *treeError) {
413 e.Children = append(e.Children, err)
414}
415
416func (e *treeError) ErrOrNil() error {
417 if len(e.Errs) > 0 || len(e.Children) > 0 {
418 return e
419 }
420 return nil
421}
422
423func (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}