func WalkComponentTree()

in internal/core/component.go [323:435]


func WalkComponentTree(startingPath string, environments []string, iterator componentIteration, rootInit rootComponentInit) <-chan WalkResult {
	queue := make(chan Component)    // components enqueued to be 'visited' (ie; walked over)
	results := make(chan WalkResult) // To pass WalkResults to
	walking := sync.WaitGroup{}      // Keep track of all nodes being worked on

	// Prepares `component` by loading/de-serializing the component.yaml/json and configs
	// Note: this is only needed for non-inlined components
	prepareComponent := func(c Component) Component {
		logger.Debug(fmt.Sprintf("Preparing component '%s'", c.Name))
		// 1. Parse the component at that path into a Component
		c, err := c.LoadComponent()
		if err != nil {
			results <- WalkResult{Error: err}
		}

		// 2. Load the config for this Component
		if err = c.LoadConfig(environments); err != nil {
			results <- WalkResult{Error: err}
		}
		return c
	}

	// Enqueue the given component
	enqueue := func(c Component) {
		// Increment working counter; MUST happen BEFORE sending to queue or race condition can occur
		walking.Add(1)
		logger.Debug(fmt.Sprintf("Adding subcomponent '%s' to queue with physical path '%s' and logical path '%s'\n", c.Name, c.PhysicalPath, c.LogicalPath))
		queue <- c
	}

	// Mark a component as visited and report it back as a result; decrements the walking counter
	markAsVisited := func(c *Component) {
		results <- WalkResult{Component: c}
		walking.Done()
	}

	// Main worker thread to enqueue root node, wait, and close the channel once all nodes visited
	go func() {
		// Manually enqueue the first root component

		rootComponent := prepareComponent(Component{
			PhysicalPath: startingPath,
			LogicalPath:  "./",
			Config:       NewComponentConfig(startingPath),
		})

		// Init rootComponent
		rootComponent, err := rootInit(startingPath, environments, rootComponent)

		if err != nil {
			results <- WalkResult{Error: err}
		} else {
			enqueue(rootComponent)
		}

		// Close results channel once all nodes visited
		walking.Wait()
		close(results)
	}()

	// Worker thread to pull from queue and call the iterator
	go func() {
		for queuedComponent := range queue {
			go func(c Component) {
				// Decrement working counter; Must happen AFTER the subcomponents are enqueued
				defer markAsVisited(&c)

				// Call the iterator
				err := iterator(c.PhysicalPath, &c)
				if err != nil {
					results <- WalkResult{Error: err}
				}

				// Range over subcomponents; preparing and enqueuing
				for _, subcomponent := range c.Subcomponents {
					// Prep component config
					subcomponent.Config = c.Config.Subcomponents[subcomponent.Name]

					if err = subcomponent.applyDefaultsAndMigrations(); err != nil {
						results <- WalkResult{Error: err}
					}

					// Do not add to the queue if component or subcomponent is Disabled.
					if subcomponent.Config.Disabled {
						logger.Info(emoji.Sprintf(":prohibited: Subcomponent '%s' is disabled", subcomponent.Name))
						continue
					}

					// Depending if the subcomponent is inlined or not; prepare the component to either load
					// config/path info from filesystem (non-inlined) or inherit from parent (inlined)
					if subcomponent.ComponentType == "component" || subcomponent.ComponentType == "" {
						// This subcomponent is not inlined, so set the paths to their relative positions and prepare the configs
						subcomponent.PhysicalPath = path.Join(subcomponent.RelativePathTo(), subcomponent.Path)
						if !filepath.IsAbs(subcomponent.RelativePathTo()) {
							subcomponent.PhysicalPath = path.Join(c.PhysicalPath, subcomponent.PhysicalPath)
						}
						subcomponent.LogicalPath = path.Join(c.LogicalPath, subcomponent.Name)
						subcomponent = prepareComponent(subcomponent)
					} else {
						// This subcomponent is inlined, so it inherits paths from parent and no need to prepareComponent().
						subcomponent.PhysicalPath = c.PhysicalPath
						subcomponent.LogicalPath = c.LogicalPath
					}

					logger.Debug(fmt.Sprintf("Adding subcomponent '%s' to queue with physical path '%s' and logical path '%s'\n", subcomponent.Name, subcomponent.PhysicalPath, subcomponent.LogicalPath))
					enqueue(subcomponent)
				}
			}(queuedComponent)
		}
	}()

	return results
}