func setupTerragruntOptionsForBareTerraform(originalOptions *options.TerragruntOptions, workingDir string, configPath string, iamRoleOpts options.IAMRoleOptions) (*options.TerragruntOptions, error) {
	targetTGOptions := cloneTerragruntOptionsForDependency(originalOptions, configPath)
	targetTGOptions.WorkingDir = workingDir
	targetTGOptions.Writer = ioutil.Discard
	targetTGOptions.IAMRoleOptions = options.MergeIAMRoleOptions(iamRoleOpts, targetTGOptions.OriginalIAMRoleOptions)
	if err := aws_helper.AssumeRoleAndUpdateEnvIfNecessary(targetTGOptions); err != nil {
		return nil, err
	}
	return targetTGOptions, nil
}
setupTerragruntOptionsForBareTerraform sets up a new TerragruntOptions struct that can be used to run terraform
without going through the full RunTerragrunt operation.
package config
import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"github.com/hashicorp/go-getter"
	"github.com/hashicorp/hcl/v2"
	"github.com/zclconf/go-cty/cty"
	"github.com/zclconf/go-cty/cty/gocty"
	ctyjson "github.com/zclconf/go-cty/cty/json"
	"golang.org/x/sync/errgroup"
	"github.com/gruntwork-io/terragrunt/aws_helper"
	"github.com/gruntwork-io/terragrunt/cli/tfsource"
	"github.com/gruntwork-io/terragrunt/codegen"
	"github.com/gruntwork-io/terragrunt/errors"
	"github.com/gruntwork-io/terragrunt/options"
	"github.com/gruntwork-io/terragrunt/remote"
	"github.com/gruntwork-io/terragrunt/shell"
	"github.com/gruntwork-io/terragrunt/util"
)
type Dependency struct {
	Name                                string     `hcl:",label" cty:"name"`
	ConfigPath                          string     `hcl:"config_path,attr" cty:"config_path"`
	SkipOutputs                         *bool      `hcl:"skip_outputs,attr" cty:"skip"`
	MockOutputs                         *cty.Value `hcl:"mock_outputs,attr" cty:"mock_outputs"`
	MockOutputsAllowedTerraformCommands *[]string  `hcl:"mock_outputs_allowed_terraform_commands,attr" cty:"mock_outputs_allowed_terraform_commands"`
	MockOutputsMergeWithState           *bool      `hcl:"mock_outputs_merge_with_state,attr" cty:"mock_outputs_merge_with_state"`
	RenderedOutputs *cty.Value `cty:"outputs"`
}
func (targetDepConfig *Dependency) DeepMerge(sourceDepConfig Dependency) error {
	if sourceDepConfig.ConfigPath != "" {
		targetDepConfig.ConfigPath = sourceDepConfig.ConfigPath
	}
	if sourceDepConfig.SkipOutputs != nil {
		targetDepConfig.SkipOutputs = sourceDepConfig.SkipOutputs
	}
	if sourceDepConfig.MockOutputs != nil {
		if targetDepConfig.MockOutputs == nil {
			targetDepConfig.MockOutputs = sourceDepConfig.MockOutputs
		} else {
			newMockOutputs, err := deepMergeCtyMaps(*targetDepConfig.MockOutputs, *sourceDepConfig.MockOutputs)
			if err != nil {
				return err
			}
			targetDepConfig.MockOutputs = newMockOutputs
		}
	}
	if sourceDepConfig.MockOutputsAllowedTerraformCommands != nil {
		if targetDepConfig.MockOutputsAllowedTerraformCommands == nil {
			targetDepConfig.MockOutputsAllowedTerraformCommands = sourceDepConfig.MockOutputsAllowedTerraformCommands
		} else {
			mergedCmds := append(*targetDepConfig.MockOutputsAllowedTerraformCommands, *sourceDepConfig.MockOutputsAllowedTerraformCommands...)
			targetDepConfig.MockOutputsAllowedTerraformCommands = &mergedCmds
		}
	}
	return nil
}
func (dependencyConfig Dependency) shouldGetOutputs() bool {
	return dependencyConfig.SkipOutputs == nil || !(*dependencyConfig.SkipOutputs)
}
func (dependencyConfig Dependency) shouldMergeMockOutputsWithState(terragruntOptions *options.TerragruntOptions) bool {
	shouldMergeMocksAttr := dependencyConfig.MockOutputsMergeWithState != nil && (*dependencyConfig.MockOutputsMergeWithState)
	allowedCommand :=
		dependencyConfig.MockOutputsAllowedTerraformCommands == nil ||
			len(*dependencyConfig.MockOutputsAllowedTerraformCommands) == 0 ||
			util.ListContainsElement(*dependencyConfig.MockOutputsAllowedTerraformCommands, terragruntOptions.OriginalTerraformCommand)
	return shouldMergeMocksAttr && allowedCommand
}
func (dependencyConfig *Dependency) setRenderedOutputs(terragruntOptions *options.TerragruntOptions) error {
	if dependencyConfig == nil {
		return nil
	}
	if (*dependencyConfig).shouldGetOutputs() || (*dependencyConfig).shouldReturnMockOutputs(terragruntOptions) {
		outputVal, err := getTerragruntOutputIfAppliedElseConfiguredDefault(*dependencyConfig, terragruntOptions)
		if err != nil {
			return err
		}
		dependencyConfig.RenderedOutputs = outputVal
	}
	return nil
}
var jsonOutputCache = sync.Map{}
var outputLocks = sync.Map{}
func decodeAndRetrieveOutputs(
	file *hcl.File,
	filename string,
	terragruntOptions *options.TerragruntOptions,
	trackInclude *TrackInclude,
	extensions EvalContextExtensions,
) (*cty.Value, error) {
	decodedDependency := terragruntDependency{}
	if err := decodeHcl(file, filename, &decodedDependency, terragruntOptions, extensions); err != nil {
		return nil, err
	}
	if trackInclude != nil {
		mergedDecodedDependency, err := handleIncludeForDependency(decodedDependency, trackInclude, terragruntOptions)
		if err != nil {
			return nil, err
		}
		decodedDependency = *mergedDecodedDependency
	}
	if err := checkForDependencyBlockCycles(filename, decodedDependency, terragruntOptions); err != nil {
		return nil, err
	}
	return dependencyBlocksToCtyValue(decodedDependency.Dependencies, terragruntOptions)
}
func dependencyBlocksToModuleDependencies(decodedDependencyBlocks []Dependency) *ModuleDependencies {
	if len(decodedDependencyBlocks) == 0 {
		return nil
	}
	paths := []string{}
	for _, decodedDependencyBlock := range decodedDependencyBlocks {
		configPath := decodedDependencyBlock.ConfigPath
		if util.IsFile(configPath) && filepath.Base(configPath) == DefaultTerragruntConfigPath {
			configPath = filepath.Dir(configPath)
		}
		paths = append(paths, configPath)
	}
	return &ModuleDependencies{Paths: paths}
}
func checkForDependencyBlockCycles(filename string, decodedDependency terragruntDependency, terragruntOptions *options.TerragruntOptions) error {
	visitedPaths := []string{}
	currentTraversalPaths := []string{filename}
	for _, dependency := range decodedDependency.Dependencies {
		dependencyPath := getCleanedTargetConfigPath(dependency.ConfigPath, filename)
		dependencyOptions := cloneTerragruntOptionsForDependency(terragruntOptions, dependencyPath)
		if err := checkForDependencyBlockCyclesUsingDFS(dependencyPath, &visitedPaths, &currentTraversalPaths, dependencyOptions); err != nil {
			return err
		}
	}
	return nil
}
func checkForDependencyBlockCyclesUsingDFS(
	currentConfigPath string,
	visitedPaths *[]string,
	currentTraversalPaths *[]string,
	terragruntOptions *options.TerragruntOptions,
) error {
	if util.ListContainsElement(*visitedPaths, currentConfigPath) {
		return nil
	}
	if util.ListContainsElement(*currentTraversalPaths, currentConfigPath) {
		return errors.WithStackTrace(DependencyCycle(append(*currentTraversalPaths, currentConfigPath)))
	}
	*currentTraversalPaths = append(*currentTraversalPaths, currentConfigPath)
	dependencyPaths, err := getDependencyBlockConfigPathsByFilepath(currentConfigPath, terragruntOptions)
	if err != nil {
		return err
	}
	for _, dependency := range dependencyPaths {
		nextPath := getCleanedTargetConfigPath(dependency, currentConfigPath)
		nextOptions := cloneTerragruntOptionsForDependency(terragruntOptions, nextPath)
		if err := checkForDependencyBlockCyclesUsingDFS(nextPath, visitedPaths, currentTraversalPaths, nextOptions); err != nil {
			return err
		}
	}
	*visitedPaths = append(*visitedPaths, currentConfigPath)
	*currentTraversalPaths = util.RemoveElementFromList(*currentTraversalPaths, currentConfigPath)
	return nil
}
func getDependencyBlockConfigPathsByFilepath(configPath string, terragruntOptions *options.TerragruntOptions) ([]string, error) {
	tgConfig, err := PartialParseConfigFile(configPath, terragruntOptions, nil, []PartialDecodeSectionType{DependencyBlock})
	if err != nil {
		return nil, err
	}
	if tgConfig.Dependencies == nil {
		return []string{}, nil
	}
	return tgConfig.Dependencies.Paths, nil
}
func dependencyBlocksToCtyValue(dependencyConfigs []Dependency, terragruntOptions *options.TerragruntOptions) (*cty.Value, error) {
	paths := []string{}
	dependencyMap := map[string]cty.Value{}
	lock := sync.Mutex{}
	dependencyErrGroup, _ := errgroup.WithContext(context.Background())
	for _, dependencyConfig := range dependencyConfigs {
		dependencyConfig := dependencyConfig  
		dependencyErrGroup.Go(func() error {
			dependencyEncodingMap := map[string]cty.Value{}
			if err := dependencyConfig.setRenderedOutputs(terragruntOptions); err != nil {
				return err
			}
			if dependencyConfig.RenderedOutputs != nil {
				paths = append(paths, dependencyConfig.ConfigPath)
				dependencyEncodingMap["outputs"] = *dependencyConfig.RenderedOutputs
			}
			dependencyEncodingMapEncoded, err := gocty.ToCtyValue(dependencyEncodingMap, generateTypeFromValuesMap(dependencyEncodingMap))
			if err != nil {
				err = TerragruntOutputListEncodingError{Paths: paths, Err: err}
				return err
			}
			lock.Lock()
			defer lock.Unlock()
			dependencyMap[dependencyConfig.Name] = dependencyEncodingMapEncoded
			return nil
		})
	}
	if err := dependencyErrGroup.Wait(); err != nil {
		return nil, err
	}
	convertedOutput, err := gocty.ToCtyValue(dependencyMap, generateTypeFromValuesMap(dependencyMap))
	if err != nil {
		err = TerragruntOutputListEncodingError{Paths: paths, Err: err}
	}
	return &convertedOutput, errors.WithStackTrace(err)
}
func getTerragruntOutputIfAppliedElseConfiguredDefault(dependencyConfig Dependency, terragruntOptions *options.TerragruntOptions) (*cty.Value, error) {
	if dependencyConfig.shouldGetOutputs() {
		outputVal, isEmpty, err := getTerragruntOutput(dependencyConfig, terragruntOptions)
		if err != nil {
			return nil, err
		}
		if !isEmpty && dependencyConfig.shouldMergeMockOutputsWithState(terragruntOptions) {
			stateOutputsMap, err := parseCtyValueToMap(*outputVal)
			if err != nil {
				return outputVal, err
			}
			mockOutputsMap, err := parseCtyValueToMap(*dependencyConfig.MockOutputs)
			if err != nil {
				return outputVal, err
			}
			for key, mockValue := range mockOutputsMap {
				_, ok := stateOutputsMap[key]
				if !ok {
					stateOutputsMap[key] = mockValue
				}
			}
			mergeOutputVal, err := convertToCtyWithJson(stateOutputsMap)
			return &mergeOutputVal, err
		} else if !isEmpty {
			return outputVal, err
		}
	}
	targetConfig := getCleanedTargetConfigPath(dependencyConfig.ConfigPath, terragruntOptions.TerragruntConfigPath)
	currentConfig := terragruntOptions.TerragruntConfigPath
	if dependencyConfig.shouldReturnMockOutputs(terragruntOptions) {
		terragruntOptions.Logger.Debugf("WARNING: config %s is a dependency of %s that has no outputs, but mock outputs provided and returning those in dependency output.",
			targetConfig,
			currentConfig,
		)
		return dependencyConfig.MockOutputs, nil
	}
	err := TerragruntOutputTargetNoOutputs{
		targetConfig:  targetConfig,
		currentConfig: currentConfig,
	}
	return nil, err
}
func (dependencyConfig Dependency) shouldReturnMockOutputs(terragruntOptions *options.TerragruntOptions) bool {
	defaultOutputsSet := dependencyConfig.MockOutputs != nil
	allowedCommand :=
		dependencyConfig.MockOutputsAllowedTerraformCommands == nil ||
			len(*dependencyConfig.MockOutputsAllowedTerraformCommands) == 0 ||
			util.ListContainsElement(*dependencyConfig.MockOutputsAllowedTerraformCommands, terragruntOptions.OriginalTerraformCommand)
	return defaultOutputsSet && allowedCommand
}
func getTerragruntOutput(dependencyConfig Dependency, terragruntOptions *options.TerragruntOptions) (*cty.Value, bool, error) {
	targetConfig := getCleanedTargetConfigPath(dependencyConfig.ConfigPath, terragruntOptions.TerragruntConfigPath)
	if !util.FileExists(targetConfig) {
		return nil, true, errors.WithStackTrace(DependencyConfigNotFound{Path: targetConfig})
	}
	jsonBytes, err := getOutputJsonWithCaching(targetConfig, terragruntOptions)
	if err != nil {
		return nil, true, err
	}
	isEmpty := string(jsonBytes) == "{}"
	outputMap, err := terraformOutputJsonToCtyValueMap(targetConfig, jsonBytes)
	if err != nil {
		return nil, isEmpty, err
	}
	convertedOutput, err := gocty.ToCtyValue(outputMap, generateTypeFromValuesMap(outputMap))
	if err != nil {
		err = TerragruntOutputEncodingError{Path: targetConfig, Err: err}
	}
	return &convertedOutput, isEmpty, errors.WithStackTrace(err)
}
func getOutputJsonWithCaching(targetConfig string, terragruntOptions *options.TerragruntOptions) ([]byte, error) {
	rawActualLock, _ := outputLocks.LoadOrStore(targetConfig, &sync.Mutex{})
	actualLock := rawActualLock.(*sync.Mutex)
	defer actualLock.Unlock()
	actualLock.Lock()
	terragruntOptions.Logger.Debugf("Getting output of dependency %s for config %s", targetConfig, terragruntOptions.TerragruntConfigPath)
	rawJsonBytes, hasRun := jsonOutputCache.Load(targetConfig)
	if hasRun {
		terragruntOptions.Logger.Debugf("%s was run before. Using cached output.", targetConfig)
		return rawJsonBytes.([]byte), nil
	}
	newJsonBytes, err := getTerragruntOutputJson(terragruntOptions, targetConfig)
	if err != nil {
		return nil, err
	}
	jsonOutputCache.Store(targetConfig, newJsonBytes)
	return newJsonBytes, nil
}
func cloneTerragruntOptionsForDependency(terragruntOptions *options.TerragruntOptions, targetConfig string) *options.TerragruntOptions {
	targetOptions := terragruntOptions.Clone(targetConfig)
	targetOptions.OriginalTerragruntConfigPath = targetConfig
	if targetOptions.IAMRoleOptions != targetOptions.OriginalIAMRoleOptions {
		targetOptions.IAMRoleOptions = options.IAMRoleOptions{}
	}
	return targetOptions
}
func cloneTerragruntOptionsForDependencyOutput(terragruntOptions *options.TerragruntOptions, targetConfig string) (*options.TerragruntOptions, error) {
	targetOptions := cloneTerragruntOptionsForDependency(terragruntOptions, targetConfig)
	targetOptions.TerraformCommand = "output"
	targetOptions.TerraformCliArgs = []string{"output", "-json"}
	_, originalDefaultDownloadDir, err := options.DefaultWorkingAndDownloadDirs(terragruntOptions.TerragruntConfigPath)
	if err != nil {
		return nil, errors.WithStackTrace(err)
	}
	if terragruntOptions.DownloadDir == originalDefaultDownloadDir {
		_, downloadDir, err := options.DefaultWorkingAndDownloadDirs(targetConfig)
		if err != nil {
			return nil, errors.WithStackTrace(err)
		}
		targetOptions.DownloadDir = downloadDir
	}
	partialTerragruntConfig, err := PartialParseConfigFile(
		targetConfig,
		targetOptions,
		nil,
		[]PartialDecodeSectionType{TerragruntVersionConstraints},
	)
	if err != nil {
		return nil, err
	}
	if terragruntOptions.TerraformPath == options.TERRAFORM_DEFAULT_PATH && partialTerragruntConfig.TerraformBinary != "" {
		targetOptions.TerraformPath = partialTerragruntConfig.TerraformBinary
	}
	if terragruntOptions.Source != "" {
		partialParseIncludedConfig, err := PartialParseConfigFile(
			targetConfig,
			targetOptions,
			nil,
			[]PartialDecodeSectionType{TerraformBlock},
		)
		if err != nil {
			return nil, err
		}
		moduleUrl, _ := getter.SourceDirSubdir(terragruntOptions.Source)
		targetSource, err := GetTerragruntSourceForModule(moduleUrl, filepath.Dir(targetConfig), partialParseIncludedConfig)
		if err != nil {
			return nil, err
		}
		targetOptions.Source = targetSource
	}
	return targetOptions, nil
}
func getTerragruntOutputJson(terragruntOptions *options.TerragruntOptions, targetConfig string) ([]byte, error) {
	targetTGOptions, err := cloneTerragruntOptionsForDependencyOutput(terragruntOptions, targetConfig)
	if err != nil {
		return nil, err
	}
	remoteStateTGConfig, err := PartialParseConfigFile(targetConfig, targetTGOptions, nil, []PartialDecodeSectionType{RemoteStateBlock, TerragruntFlags})
	if err != nil || !canGetRemoteState(remoteStateTGConfig.RemoteState) {
		terragruntOptions.Logger.Debugf("Could not parse remote_state block from target config %s", targetConfig)
		terragruntOptions.Logger.Debugf("Falling back to terragrunt output.")
		return runTerragruntOutputJson(targetTGOptions, targetConfig)
	}
	isInit, workingDir, err := terragruntAlreadyInit(targetTGOptions, targetConfig)
	if err != nil {
		return nil, err
	}
	if isInit {
		return getTerragruntOutputJsonFromInitFolder(targetTGOptions, workingDir, remoteStateTGConfig.GetIAMRoleOptions())
	}
	return getTerragruntOutputJsonFromRemoteState(targetTGOptions, targetConfig, remoteStateTGConfig.RemoteState, remoteStateTGConfig.GetIAMRoleOptions())
}
func canGetRemoteState(remoteState *remote.RemoteState) bool {
	return remoteState != nil && !remoteState.DisableDependencyOptimization
}
func terragruntAlreadyInit(terragruntOptions *options.TerragruntOptions, configPath string) (bool, string, error) {
	terraformBlockTGConfig, err := PartialParseConfigFile(configPath, terragruntOptions, nil, []PartialDecodeSectionType{TerraformSource})
	if err != nil {
		return false, "", err
	}
	var workingDir string
	sourceUrl, err := GetTerraformSourceUrl(terragruntOptions, terraformBlockTGConfig)
	if err != nil {
		return false, "", err
	}
	if sourceUrl == "" || sourceUrl == "." {
		if util.IsDir(configPath) {
			workingDir = configPath
		} else {
			workingDir = filepath.Dir(configPath)
		}
	} else {
		terraformSource, err := tfsource.NewTerraformSource(sourceUrl, terragruntOptions.DownloadDir, terragruntOptions.WorkingDir, terragruntOptions.Logger)
		if err != nil {
			return false, "", err
		}
		workingDir = terraformSource.WorkingDir
	}
	return util.FileExists(filepath.Join(workingDir, ".terraform")), workingDir, nil
}
func getTerragruntOutputJsonFromInitFolder(terragruntOptions *options.TerragruntOptions, terraformWorkingDir string, iamRoleOpts options.IAMRoleOptions) ([]byte, error) {
	targetConfig := terragruntOptions.TerragruntConfigPath
	terragruntOptions.Logger.Debugf("Detected module %s is already init-ed. Retrieving outputs directly from working directory.", targetConfig)
	targetTGOptions, err := setupTerragruntOptionsForBareTerraform(terragruntOptions, terraformWorkingDir, targetConfig, iamRoleOpts)
	if err != nil {
		return nil, err
	}
	out, err := shell.RunTerraformCommandWithOutput(targetTGOptions, "output", "-json")
	if err != nil {
		return nil, err
	}
	jsonString := out.Stdout
	jsonBytes := []byte(strings.TrimSpace(jsonString))
	terragruntOptions.Logger.Debugf("Retrieved output from %s as json: %s", targetConfig, jsonString)
	return jsonBytes, nil
}
func getTerragruntOutputJsonFromRemoteState(
	terragruntOptions *options.TerragruntOptions,
	targetConfig string,
	remoteState *remote.RemoteState,
	iamRoleOpts options.IAMRoleOptions,
) ([]byte, error) {
	terragruntOptions.Logger.Debugf("Detected remote state block with generate config. Resolving dependency by pulling remote state.")
	if err := util.EnsureDirectory(terragruntOptions.DownloadDir); err != nil {
		return nil, err
	}
	tempWorkDir, err := ioutil.TempDir(terragruntOptions.DownloadDir, "")
	if err != nil {
		return nil, err
	}
	defer os.RemoveAll(tempWorkDir)
	terragruntOptions.Logger.Debugf("Setting dependency working directory to %s", tempWorkDir)
	targetTGOptions, err := setupTerragruntOptionsForBareTerraform(terragruntOptions, tempWorkDir, targetConfig, iamRoleOpts)
	if err != nil {
		return nil, err
	}
	if remoteState.Generate == nil {
		remoteState.Generate = &remote.RemoteStateGenerate{
			Path:     "backend.tf",
			IfExists: codegen.ExistsOverwriteTerragruntStr,
		}
	}
	if err := remoteState.GenerateTerraformCode(targetTGOptions); err != nil {
		return nil, err
	}
	terragruntOptions.Logger.Debugf("Generated remote state configuration in working dir %s", tempWorkDir)
	runTerraformInitForDependencyOutput(targetTGOptions, tempWorkDir, targetConfig)
	out, err := shell.RunTerraformCommandWithOutput(targetTGOptions, "output", "-json")
	if err != nil {
		return nil, err
	}
	jsonString := out.Stdout
	jsonBytes := []byte(strings.TrimSpace(jsonString))
	terragruntOptions.Logger.Debugf("Retrieved output from %s as json: %s", targetConfig, jsonString)
	return jsonBytes, nil
}