github.com/sfdevops1/terrra4orm@v0.11.12-beta1/config/module/tree.go (about) 1 package module 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "log" 8 "path/filepath" 9 "strings" 10 "sync" 11 12 "github.com/hashicorp/terraform/tfdiags" 13 14 getter "github.com/hashicorp/go-getter" 15 "github.com/hashicorp/terraform/config" 16 ) 17 18 // RootName is the name of the root tree. 19 const RootName = "root" 20 21 // Tree represents the module import tree of configurations. 22 // 23 // This Tree structure can be used to get (download) new modules, load 24 // all the modules without getting, flatten the tree into something 25 // Terraform can use, etc. 26 type Tree struct { 27 name string 28 config *config.Config 29 children map[string]*Tree 30 path []string 31 lock sync.RWMutex 32 33 // version is the final version of the config loaded for the Tree's module 34 version string 35 // source is the "source" string used to load this module. It's possible 36 // for a module source to change, but the path remains the same, preventing 37 // it from being reloaded. 38 source string 39 // parent allows us to walk back up the tree and determine if there are any 40 // versioned ancestor modules which may effect the stored location of 41 // submodules 42 parent *Tree 43 } 44 45 // NewTree returns a new Tree for the given config structure. 46 func NewTree(name string, c *config.Config) *Tree { 47 return &Tree{config: c, name: name} 48 } 49 50 // NewEmptyTree returns a new tree that is empty (contains no configuration). 51 func NewEmptyTree() *Tree { 52 t := &Tree{config: &config.Config{}} 53 54 // We do this dummy load so that the tree is marked as "loaded". It 55 // should never fail because this is just about a no-op. If it does fail 56 // we panic so we can know its a bug. 57 if err := t.Load(&Storage{Mode: GetModeGet}); err != nil { 58 panic(err) 59 } 60 61 return t 62 } 63 64 // NewTreeModule is like NewTree except it parses the configuration in 65 // the directory and gives it a specific name. Use a blank name "" to specify 66 // the root module. 67 func NewTreeModule(name, dir string) (*Tree, error) { 68 c, err := config.LoadDir(dir) 69 if err != nil { 70 return nil, err 71 } 72 73 return NewTree(name, c), nil 74 } 75 76 // Config returns the configuration for this module. 77 func (t *Tree) Config() *config.Config { 78 return t.config 79 } 80 81 // Child returns the child with the given path (by name). 82 func (t *Tree) Child(path []string) *Tree { 83 if t == nil { 84 return nil 85 } 86 87 if len(path) == 0 { 88 return t 89 } 90 91 c := t.Children()[path[0]] 92 if c == nil { 93 return nil 94 } 95 96 return c.Child(path[1:]) 97 } 98 99 // Children returns the children of this tree (the modules that are 100 // imported by this root). 101 // 102 // This will only return a non-nil value after Load is called. 103 func (t *Tree) Children() map[string]*Tree { 104 t.lock.RLock() 105 defer t.lock.RUnlock() 106 return t.children 107 } 108 109 // DeepEach calls the provided callback for the receiver and then all of 110 // its descendents in the tree, allowing an operation to be performed on 111 // all modules in the tree. 112 // 113 // Parents will be visited before their children but otherwise the order is 114 // not defined. 115 func (t *Tree) DeepEach(cb func(*Tree)) { 116 t.lock.RLock() 117 defer t.lock.RUnlock() 118 t.deepEach(cb) 119 } 120 121 func (t *Tree) deepEach(cb func(*Tree)) { 122 cb(t) 123 for _, c := range t.children { 124 c.deepEach(cb) 125 } 126 } 127 128 // Loaded says whether or not this tree has been loaded or not yet. 129 func (t *Tree) Loaded() bool { 130 t.lock.RLock() 131 defer t.lock.RUnlock() 132 return t.children != nil 133 } 134 135 // Modules returns the list of modules that this tree imports. 136 // 137 // This is only the imports of _this_ level of the tree. To retrieve the 138 // full nested imports, you'll have to traverse the tree. 139 func (t *Tree) Modules() []*Module { 140 result := make([]*Module, len(t.config.Modules)) 141 for i, m := range t.config.Modules { 142 result[i] = &Module{ 143 Name: m.Name, 144 Version: m.Version, 145 Source: m.Source, 146 Providers: m.Providers, 147 } 148 } 149 150 return result 151 } 152 153 // Name returns the name of the tree. This will be "<root>" for the root 154 // tree and then the module name given for any children. 155 func (t *Tree) Name() string { 156 if t.name == "" { 157 return RootName 158 } 159 160 return t.name 161 } 162 163 // Load loads the configuration of the entire tree. 164 // 165 // The parameters are used to tell the tree where to find modules and 166 // whether it can download/update modules along the way. 167 // 168 // Calling this multiple times will reload the tree. 169 // 170 // Various semantic-like checks are made along the way of loading since 171 // module trees inherently require the configuration to be in a reasonably 172 // sane state: no circular dependencies, proper module sources, etc. A full 173 // suite of validations can be done by running Validate (after loading). 174 func (t *Tree) Load(s *Storage) error { 175 t.lock.Lock() 176 defer t.lock.Unlock() 177 178 children, err := t.getChildren(s) 179 if err != nil { 180 return err 181 } 182 183 // Go through all the children and load them. 184 for _, c := range children { 185 if err := c.Load(s); err != nil { 186 return err 187 } 188 } 189 190 // Set our tree up 191 t.children = children 192 193 return nil 194 } 195 196 func (t *Tree) getChildren(s *Storage) (map[string]*Tree, error) { 197 children := make(map[string]*Tree) 198 199 // Go through all the modules and get the directory for them. 200 for _, m := range t.Modules() { 201 if _, ok := children[m.Name]; ok { 202 return nil, fmt.Errorf( 203 "module %s: duplicated. module names must be unique", m.Name) 204 } 205 206 // Determine the path to this child 207 modPath := make([]string, len(t.path), len(t.path)+1) 208 copy(modPath, t.path) 209 modPath = append(modPath, m.Name) 210 211 log.Printf("[TRACE] module source: %q", m.Source) 212 213 // add the module path to help indicate where modules with relative 214 // paths are being loaded from 215 s.output(fmt.Sprintf("- module.%s", strings.Join(modPath, "."))) 216 217 // Lookup the local location of the module. 218 // dir is the local directory where the module is stored 219 mod, err := s.findRegistryModule(m.Source, m.Version) 220 if err != nil { 221 return nil, err 222 } 223 224 // The key is the string that will be used to uniquely id the Source in 225 // the local storage. The prefix digit can be incremented to 226 // invalidate the local module storage. 227 key := "1." + t.versionedPathKey(m) 228 if mod.Version != "" { 229 key += "." + mod.Version 230 } 231 232 // Check for the exact key if it's not a registry module 233 if !mod.registry { 234 mod.Dir, err = s.findModule(key) 235 if err != nil { 236 return nil, err 237 } 238 } 239 240 if mod.Dir != "" && s.Mode != GetModeUpdate { 241 // We found it locally, but in order to load the Tree we need to 242 // find out if there was another subDir stored from detection. 243 subDir, err := s.getModuleRoot(mod.Dir) 244 if err != nil { 245 // If there's a problem with the subdir record, we'll let the 246 // recordSubdir method fix it up. Any other filesystem errors 247 // will turn up again below. 248 log.Println("[WARN] error reading subdir record:", err) 249 } 250 251 fullDir := filepath.Join(mod.Dir, subDir) 252 253 child, err := NewTreeModule(m.Name, fullDir) 254 if err != nil { 255 return nil, fmt.Errorf("module %s: %s", m.Name, err) 256 } 257 child.path = modPath 258 child.parent = t 259 child.version = mod.Version 260 child.source = m.Source 261 children[m.Name] = child 262 continue 263 } 264 265 // Split out the subdir if we have one. 266 // Terraform keeps the entire requested tree, so that modules can 267 // reference sibling modules from the same archive or repo. 268 rawSource, subDir := getter.SourceDirSubdir(m.Source) 269 270 // we haven't found a source, so fallback to the go-getter detectors 271 source := mod.url 272 if source == "" { 273 source, err = getter.Detect(rawSource, t.config.Dir, getter.Detectors) 274 if err != nil { 275 return nil, fmt.Errorf("module %s: %s", m.Name, err) 276 } 277 } 278 279 log.Printf("[TRACE] detected module source %q", source) 280 281 // Check if the detector introduced something new. 282 // For example, the registry always adds a subdir of `//*`, 283 // indicating that we need to strip off the first component from the 284 // tar archive, though we may not yet know what it is called. 285 source, detectedSubDir := getter.SourceDirSubdir(source) 286 if detectedSubDir != "" { 287 subDir = filepath.Join(detectedSubDir, subDir) 288 } 289 290 output := "" 291 switch s.Mode { 292 case GetModeUpdate: 293 output = fmt.Sprintf(" Updating source %q", m.Source) 294 default: 295 output = fmt.Sprintf(" Getting source %q", m.Source) 296 } 297 s.output(output) 298 299 dir, ok, err := s.getStorage(key, source) 300 if err != nil { 301 return nil, err 302 } 303 if !ok { 304 return nil, fmt.Errorf("module %s: not found, may need to run 'terraform init'", m.Name) 305 } 306 307 log.Printf("[TRACE] %q stored in %q", source, dir) 308 309 // expand and record the subDir for later 310 fullDir := dir 311 if subDir != "" { 312 fullDir, err = getter.SubdirGlob(dir, subDir) 313 if err != nil { 314 return nil, err 315 } 316 317 // +1 to account for the pathsep 318 if len(dir)+1 > len(fullDir) { 319 return nil, fmt.Errorf("invalid module storage path %q", fullDir) 320 } 321 subDir = fullDir[len(dir)+1:] 322 } 323 324 // add new info to the module record 325 mod.Key = key 326 mod.Dir = dir 327 mod.Root = subDir 328 329 // record the module in our manifest 330 if err := s.recordModule(mod); err != nil { 331 return nil, err 332 } 333 334 child, err := NewTreeModule(m.Name, fullDir) 335 if err != nil { 336 return nil, fmt.Errorf("module %s: %s", m.Name, err) 337 } 338 child.path = modPath 339 child.parent = t 340 child.version = mod.Version 341 child.source = m.Source 342 children[m.Name] = child 343 } 344 345 return children, nil 346 } 347 348 // Path is the full path to this tree. 349 func (t *Tree) Path() []string { 350 return t.path 351 } 352 353 // String gives a nice output to describe the tree. 354 func (t *Tree) String() string { 355 var result bytes.Buffer 356 path := strings.Join(t.path, ", ") 357 if path != "" { 358 path = fmt.Sprintf(" (path: %s)", path) 359 } 360 result.WriteString(t.Name() + path + "\n") 361 362 cs := t.Children() 363 if cs == nil { 364 result.WriteString(" not loaded") 365 } else { 366 // Go through each child and get its string value, then indent it 367 // by two. 368 for _, c := range cs { 369 r := strings.NewReader(c.String()) 370 scanner := bufio.NewScanner(r) 371 for scanner.Scan() { 372 result.WriteString(" ") 373 result.WriteString(scanner.Text()) 374 result.WriteString("\n") 375 } 376 } 377 } 378 379 return result.String() 380 } 381 382 // Validate does semantic checks on the entire tree of configurations. 383 // 384 // This will call the respective config.Config.Validate() functions as well 385 // as verifying things such as parameters/outputs between the various modules. 386 // 387 // Load must be called prior to calling Validate or an error will be returned. 388 func (t *Tree) Validate() tfdiags.Diagnostics { 389 var diags tfdiags.Diagnostics 390 391 if !t.Loaded() { 392 diags = diags.Append(fmt.Errorf( 393 "tree must be loaded before calling Validate", 394 )) 395 return diags 396 } 397 398 // Terraform core does not handle root module children named "root". 399 // We plan to fix this in the future but this bug was brought up in 400 // the middle of a release and we don't want to introduce wide-sweeping 401 // changes at that time. 402 if len(t.path) == 1 && t.name == "root" { 403 diags = diags.Append(fmt.Errorf( 404 "root module cannot contain module named 'root'", 405 )) 406 return diags 407 } 408 409 // Validate our configuration first. 410 diags = diags.Append(t.config.Validate()) 411 412 // If we're the root, we do extra validation. This validation usually 413 // requires the entire tree (since children don't have parent pointers). 414 if len(t.path) == 0 { 415 if err := t.validateProviderAlias(); err != nil { 416 diags = diags.Append(err) 417 } 418 } 419 420 // Get the child trees 421 children := t.Children() 422 423 // Validate all our children 424 for _, c := range children { 425 childDiags := c.Validate() 426 diags = diags.Append(childDiags) 427 if diags.HasErrors() { 428 continue 429 } 430 } 431 432 // Go over all the modules and verify that any parameters are valid 433 // variables into the module in question. 434 for _, m := range t.config.Modules { 435 tree, ok := children[m.Name] 436 if !ok { 437 // This should never happen because Load watches us 438 panic("module not found in children: " + m.Name) 439 } 440 441 // Build the variables that the module defines 442 requiredMap := make(map[string]struct{}) 443 varMap := make(map[string]struct{}) 444 for _, v := range tree.config.Variables { 445 varMap[v.Name] = struct{}{} 446 447 if v.Required() { 448 requiredMap[v.Name] = struct{}{} 449 } 450 } 451 452 // Compare to the keys in our raw config for the module 453 for k, _ := range m.RawConfig.Raw { 454 if _, ok := varMap[k]; !ok { 455 diags = diags.Append(fmt.Errorf( 456 "module %q: %q is not a valid argument", 457 m.Name, k, 458 )) 459 } 460 461 // Remove the required 462 delete(requiredMap, k) 463 } 464 465 // If we have any required left over, they aren't set. 466 for k, _ := range requiredMap { 467 diags = diags.Append(fmt.Errorf( 468 "module %q: missing required argument %q", 469 m.Name, k, 470 )) 471 } 472 } 473 474 // Go over all the variables used and make sure that any module 475 // variables represent outputs properly. 476 for source, vs := range t.config.InterpolatedVariables() { 477 for _, v := range vs { 478 mv, ok := v.(*config.ModuleVariable) 479 if !ok { 480 continue 481 } 482 483 tree, ok := children[mv.Name] 484 if !ok { 485 diags = diags.Append(fmt.Errorf( 486 "%s: reference to undefined module %q", 487 source, mv.Name, 488 )) 489 continue 490 } 491 492 found := false 493 for _, o := range tree.config.Outputs { 494 if o.Name == mv.Field { 495 found = true 496 break 497 } 498 } 499 if !found { 500 diags = diags.Append(fmt.Errorf( 501 "%s: %q is not a valid output for module %q", 502 source, mv.Field, mv.Name, 503 )) 504 } 505 } 506 } 507 508 return diags 509 } 510 511 // versionedPathKey returns a path string with every levels full name, version 512 // and source encoded. This is to provide a unique key for our module storage, 513 // since submodules need to know which versions of their ancestor modules they 514 // are loaded from. 515 // For example, if module A has a subdirectory B, if module A's source or 516 // version is updated B's storage key must reflect this change in order for the 517 // correct version of B's source to be loaded. 518 func (t *Tree) versionedPathKey(m *Module) string { 519 path := make([]string, len(t.path)+1) 520 path[len(path)-1] = m.Name + ";" + m.Source 521 // We're going to load these in order for easier reading and debugging, but 522 // in practice they only need to be unique and consistent. 523 524 p := t 525 i := len(path) - 2 526 for ; i >= 0; i-- { 527 if p == nil { 528 break 529 } 530 // we may have been loaded under a blank Tree, so always check for a name 531 // too. 532 if p.name == "" { 533 break 534 } 535 seg := p.name 536 if p.version != "" { 537 seg += "#" + p.version 538 } 539 540 if p.source != "" { 541 seg += ";" + p.source 542 } 543 544 path[i] = seg 545 p = p.parent 546 } 547 548 key := strings.Join(path, "|") 549 return key 550 } 551 552 // treeError is an error use by Tree.Validate to accumulates all 553 // validation errors. 554 type treeError struct { 555 Name []string 556 Errs []error 557 Children []*treeError 558 } 559 560 func (e *treeError) Add(err error) { 561 e.Errs = append(e.Errs, err) 562 } 563 564 func (e *treeError) AddChild(err *treeError) { 565 e.Children = append(e.Children, err) 566 } 567 568 func (e *treeError) ErrOrNil() error { 569 if len(e.Errs) > 0 || len(e.Children) > 0 { 570 return e 571 } 572 return nil 573 } 574 575 func (e *treeError) Error() string { 576 name := strings.Join(e.Name, ".") 577 var out bytes.Buffer 578 fmt.Fprintf(&out, "module %s: ", name) 579 580 if len(e.Errs) == 1 { 581 // single like error 582 out.WriteString(e.Errs[0].Error()) 583 } else { 584 // multi-line error 585 for _, err := range e.Errs { 586 fmt.Fprintf(&out, "\n %s", err) 587 } 588 } 589 590 if len(e.Children) > 0 { 591 // start the next error on a new line 592 out.WriteString("\n ") 593 } 594 for _, child := range e.Children { 595 out.WriteString(child.Error()) 596 } 597 598 return out.String() 599 }