func getOutputJsonWithCaching(targetConfig string, terragruntOptions *options.TerragruntOptions) ([]byte, error) {
	rawActualLock, _ := outputLocks.LoadOrStore(targetConfig, &sync.Mutex{})
	actualLock := rawActualLock.(*sync.Mutex)
	defer actualLock.Unlock()
	actualLock.Lock()
	util.Debugf(terragruntOptions.Logger, "Getting output of dependency %s for config %s", targetConfig, terragruntOptions.TerragruntConfigPath)
	rawJsonBytes, hasRun := jsonOutputCache.Load(targetConfig)
	if hasRun {
		util.Debugf(terragruntOptions.Logger, "%s was run before. Using cached output.", targetConfig)
		return rawJsonBytes.([]byte), nil
	}
	newJsonBytes, err := runTerragruntOutputJson(terragruntOptions, targetConfig)
	if err != nil {
		return nil, err
	}
	jsonOutputCache.Store(targetConfig, newJsonBytes)
	return newJsonBytes, nil
}
getOutputJsonWithCaching will run terragrunt output on the target config if it is not already cached.
package config
import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"path/filepath"
	"strings"
	"sync"
	"github.com/hashicorp/hcl2/hcl"
	"github.com/zclconf/go-cty/cty"
	"github.com/zclconf/go-cty/cty/gocty"
	ctyjson "github.com/zclconf/go-cty/cty/json"
	"github.com/gruntwork-io/terragrunt/errors"
	"github.com/gruntwork-io/terragrunt/options"
	"github.com/gruntwork-io/terragrunt/util"
)
type Dependency struct {
	Name                                string     `hcl:",label"`
	ConfigPath                          string     `hcl:"config_path,attr"`
	SkipOutputs                         *bool      `hcl:"skip_outputs,attr"`
	MockOutputs                         *cty.Value `hcl:"mock_outputs,attr"`
	MockOutputsAllowedTerraformCommands *[]string  `hcl:"mock_outputs_allowed_terraform_commands,attr"`
}
func (dependencyConfig Dependency) shouldGetOutputs() bool {
	return dependencyConfig.SkipOutputs == nil || !(*dependencyConfig.SkipOutputs)
}
var jsonOutputCache = sync.Map{}
var outputLocks = sync.Map{}
func decodeAndRetrieveOutputs(
	file *hcl.File,
	filename string,
	terragruntOptions *options.TerragruntOptions,
	extensions EvalContextExtensions,
) (*cty.Value, error) {
	decodedDependency := terragruntDependency{}
	if err := decodeHcl(file, filename, &decodedDependency, terragruntOptions, extensions); err != nil {
		return nil, err
	}
	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 := cleanDependencyTerragruntConfigPath(filename, dependency.ConfigPath)
		dependencyOptions := terragruntOptions.Clone(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 := cleanDependencyTerragruntConfigPath(currentConfigPath, dependency)
		nextOptions := terragruntOptions.Clone(nextPath)
		if err := checkForDependencyBlockCyclesUsingDFS(nextPath, visitedPaths, currentTraversalPaths, nextOptions); err != nil {
			return err
		}
	}
	*visitedPaths = append(*visitedPaths, currentConfigPath)
	*currentTraversalPaths = util.RemoveElementFromList(*currentTraversalPaths, currentConfigPath)
	return nil
}
func cleanDependencyTerragruntConfigPath(currentConfigPath string, dependencyPath string) string {
	if !filepath.IsAbs(dependencyPath) {
		dependencyPath = filepath.Clean(filepath.Join(filepath.Dir(currentConfigPath), dependencyPath))
	}
	if util.IsDir(dependencyPath) {
		dependencyPath = filepath.Join(dependencyPath, DefaultTerragruntConfigPath)
	}
	return dependencyPath
}
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{}
	for _, dependencyConfig := range dependencyConfigs {
		dependencyEncodingMap := map[string]cty.Value{}
		if dependencyConfig.shouldGetOutputs() || shouldReturnMockOutputs(dependencyConfig, terragruntOptions) {
			paths = append(paths, dependencyConfig.ConfigPath)
			outputVal, err := getTerragruntOutputIfAppliedElseConfiguredDefault(dependencyConfig, terragruntOptions)
			if err != nil {
				return nil, err
			}
			dependencyEncodingMap["outputs"] = *outputVal
		}
		dependencyEncodingMapEncoded, err := gocty.ToCtyValue(dependencyEncodingMap, generateTypeFromValuesMap(dependencyEncodingMap))
		if err != nil {
			err = TerragruntOutputListEncodingError{Paths: paths, Err: err}
		}
		dependencyMap[dependencyConfig.Name] = dependencyEncodingMapEncoded
	}
	convertedOutput, err := gocty.ToCtyValue(dependencyMap, generateTypeFromValuesMap(dependencyMap))
	if err != nil {
		err = TerragruntOutputListEncodingError{Paths: paths, Err: err}
	}
	return &convertedOutput, errors.WithStackTrace(err)
}
func getCleanedTargetConfigPath(dependencyConfig Dependency, terragruntOptions *options.TerragruntOptions) string {
	cwd := filepath.Dir(terragruntOptions.TerragruntConfigPath)
	targetConfig := dependencyConfig.ConfigPath
	if !filepath.IsAbs(targetConfig) {
		targetConfig = util.JoinPath(cwd, targetConfig)
	}
	if util.IsDir(targetConfig) {
		targetConfig = util.JoinPath(targetConfig, DefaultTerragruntConfigPath)
	}
	return util.CleanPath(targetConfig)
}
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 {
			return outputVal, err
		}
	}
	targetConfig := getCleanedTargetConfigPath(dependencyConfig, terragruntOptions)
	currentConfig := terragruntOptions.TerragruntConfigPath
	if shouldReturnMockOutputs(dependencyConfig, terragruntOptions) {
		util.Debugf(
			terragruntOptions.Logger,
			"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 shouldReturnMockOutputs(dependencyConfig Dependency, terragruntOptions *options.TerragruntOptions) bool {
	defaultOutputsSet := dependencyConfig.MockOutputs != nil
	allowedCommand :=
		dependencyConfig.MockOutputsAllowedTerraformCommands == nil ||
			len(*dependencyConfig.MockOutputsAllowedTerraformCommands) == 0 ||
			util.ListContainsElement(*dependencyConfig.MockOutputsAllowedTerraformCommands, terragruntOptions.TerraformCommand)
	return defaultOutputsSet && allowedCommand
}
func getTerragruntOutput(dependencyConfig Dependency, terragruntOptions *options.TerragruntOptions) (*cty.Value, bool, error) {
	targetConfig := getCleanedTargetConfigPath(dependencyConfig, terragruntOptions)
	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)
}