func createStackForTerragruntConfigPaths(path string, terragruntConfigPaths []string, terragruntOptions *options.TerragruntOptions, howThesePathsWereFound string) (*Stack, error) {
	if len(terragruntConfigPaths) == 0 {
		return nil, errors.WithStackTrace(NoTerraformModulesFound)
	}
	modules, err := ResolveTerraformModules(terragruntConfigPaths, terragruntOptions, howThesePathsWereFound)
	if err != nil {
		return nil, err
	}
	stack := &Stack{Path: path, Modules: modules}
	if err := stack.CheckForCycles(); err != nil {
		return nil, err
	}
	return stack, nil
}
Find all the Terraform modules in the folders that contain the given Terragrunt config files and assemble those
modules into a Stack object that can be applied or destroyed in a single command
package configstack
import (
	"bytes"
	"fmt"
	"io"
	"sort"
	"strings"
	"github.com/gruntwork-io/terragrunt/config"
	"github.com/gruntwork-io/terragrunt/errors"
	"github.com/gruntwork-io/terragrunt/options"
	"github.com/gruntwork-io/terragrunt/util"
	"github.com/sirupsen/logrus"
)
type Stack struct {
	Path    string
	Modules []*TerraformModule
}
func (stack *Stack) String() string {
	modules := []string{}
	for _, module := range stack.Modules {
		modules = append(modules, fmt.Sprintf("  => %s", module.String()))
	}
	sort.Strings(modules)
	return fmt.Sprintf("Stack at %s:\n%s", stack.Path, strings.Join(modules, "\n"))
}
func (stack *Stack) LogModuleDeployOrder(logger *logrus.Entry, terraformCommand string) error {
	outStr := fmt.Sprintf("The stack at %s will be processed in the following order for command %s:\n", stack.Path, terraformCommand)
	runGraph, err := stack.getModuleRunGraph(terraformCommand)
	if err != nil {
		return err
	}
	for i, group := range runGraph {
		outStr += fmt.Sprintf("Group %d\n", i+1)
		for _, module := range group {
			outStr += fmt.Sprintf("- Module %s\n", module.Path)
		}
		outStr += "\n"
	}
	logger.Info(outStr)
	return nil
}
func (stack *Stack) Graph(terragruntOptions *options.TerragruntOptions) {
	WriteDot(terragruntOptions.Writer, terragruntOptions, stack.Modules)
}
func (stack *Stack) Run(terragruntOptions *options.TerragruntOptions) error {
	stackCmd := terragruntOptions.TerraformCommand
	if util.ListContainsElement(config.TERRAFORM_COMMANDS_NEED_INPUT, stackCmd) {
		terragruntOptions.TerraformCliArgs = util.StringListInsert(terragruntOptions.TerraformCliArgs, "-input=false", 1)
		stack.syncTerraformCliArgs(terragruntOptions)
	}
	switch stackCmd {
	case "apply", "destroy":
		terragruntOptions.TerraformCliArgs = util.StringListInsert(terragruntOptions.TerraformCliArgs, "-auto-approve", 1)
		stack.syncTerraformCliArgs(terragruntOptions)
	}
	if stackCmd == "plan" {
		errorStreams := make([]bytes.Buffer, len(stack.Modules))
		for n, module := range stack.Modules {
			if !terragruntOptions.NonInteractive {  
				module.TerragruntOptions.ErrWriter = io.MultiWriter(&errorStreams[n], module.TerragruntOptions.ErrWriter)
			} else {
				module.TerragruntOptions.ErrWriter = &errorStreams[n]
			}
		}
		defer stack.summarizePlanAllErrors(terragruntOptions, errorStreams)
	}
	if terragruntOptions.IgnoreDependencyOrder {
		return RunModulesIgnoreOrder(stack.Modules, terragruntOptions.Parallelism)
	} else if stackCmd == "destroy" {
		return RunModulesReverseOrder(stack.Modules, terragruntOptions.Parallelism)
	} else {
		return RunModules(stack.Modules, terragruntOptions.Parallelism)
	}
}
func (stack *Stack) summarizePlanAllErrors(terragruntOptions *options.TerragruntOptions, errorStreams []bytes.Buffer) {
	for i, errorStream := range errorStreams {
		output := errorStream.String()
		if len(output) == 0 {
			continue
		}
		terragruntOptions.Logger.Infoln(output)
		if strings.Contains(output, "Error running plan:") {
			if strings.Contains(output, ": Resource 'data.terraform_remote_state.") {
				var dependenciesMsg string
				if len(stack.Modules[i].Dependencies) > 0 {
					dependenciesMsg = fmt.Sprintf(" contains dependencies to %v and", stack.Modules[i].Config.Dependencies.Paths)
				}
				terragruntOptions.Logger.Infof("%v%v refers to remote state "+
					"you may have to apply your changes in the dependencies prior running terragrunt plan-all.\n",
					stack.Modules[i].Path,
					dependenciesMsg,
				)
			}
		}
	}
}
func (stack *Stack) CheckForCycles() error {
	return CheckForCycles(stack.Modules)
}
func FindStackInSubfolders(terragruntOptions *options.TerragruntOptions) (*Stack, error) {
	terragruntConfigFiles, err := config.FindConfigFilesInPath(terragruntOptions.WorkingDir, terragruntOptions)
	if err != nil {
		return nil, err
	}
	howThesePathsWereFound := fmt.Sprintf("Terragrunt config file found in a subdirectory of %s", terragruntOptions.WorkingDir)
	return createStackForTerragruntConfigPaths(terragruntOptions.WorkingDir, terragruntConfigFiles, terragruntOptions, howThesePathsWereFound)
}
func (stack *Stack) syncTerraformCliArgs(terragruntOptions *options.TerragruntOptions) {
	for _, module := range stack.Modules {
		module.TerragruntOptions.TerraformCliArgs = terragruntOptions.TerraformCliArgs
	}
}
func (stack *Stack) getModuleRunGraph(terraformCommand string) ([][]*TerraformModule, error) {
	var moduleRunGraph map[string]*runningModule
	var graphErr error
	switch terraformCommand {
	case "destroy":
		moduleRunGraph, graphErr = toRunningModules(stack.Modules, ReverseOrder)
	default:
		moduleRunGraph, graphErr = toRunningModules(stack.Modules, NormalOrder)
	}
	if graphErr != nil {
		return nil, graphErr
	}
	const maxDepth = 1000
	groups := [][]*TerraformModule{}
	for len(moduleRunGraph) > 0 && len(groups) < maxDepth {
		currentIterationDeploy := []*TerraformModule{}
		next := map[string]*runningModule{}
		removeDep := []string{}
		for path, module := range moduleRunGraph {
			if module.Module.AssumeAlreadyApplied {
				removeDep = append(removeDep, path)
			} else if len(module.Dependencies) == 0 {
				currentIterationDeploy = append(currentIterationDeploy, module.Module)
				removeDep = append(removeDep, path)
			} else {
				next[path] = module
			}
		}
		for _, module := range next {
			for _, path := range removeDep {
				_, hasDep := module.Dependencies[path]
				if hasDep {
					delete(module.Dependencies, path)
				}
			}
		}
		sort.Slice(
			currentIterationDeploy,
			func(i, j int) bool {
				return currentIterationDeploy[i].Path < currentIterationDeploy[j].Path
			},
		)
		moduleRunGraph = next
		if len(currentIterationDeploy) > 0 {
			groups = append(groups, currentIterationDeploy)
		}
	}
	return groups, nil
}