func containsTerragruntModule(path string, info os.FileInfo, terragruntOptions *options.TerragruntOptions) (bool, error) {
	if !info.IsDir() {
		return false, nil
	}
	if util.ContainsPath(path, options.TerragruntCacheDir) {
		return false, nil
	}
	dataDir := terragruntOptions.TerraformDataDir()
	if filepath.IsAbs(dataDir) {
		if util.HasPathPrefix(path, dataDir) {
			return false, nil
		}
	} else {
		if util.ContainsPath(path, dataDir) {
			return false, nil
		}
	}
	canonicalPath, err := util.CanonicalPath(path, "")
	if err != nil {
		return false, err
	}
	canonicalDownloadPath, err := util.CanonicalPath(terragruntOptions.DownloadDir, "")
	if err != nil {
		return false, err
	}
	if strings.Contains(canonicalPath, canonicalDownloadPath) {
		return false, err
	}
	return util.FileExists(GetDefaultConfigPath(path)), nil
}
Returns true if the given path with the given FileInfo contains a Terragrunt module and false otherwise. A path
contains a Terragrunt module if it contains a Terragrunt configuration file (terragrunt.hcl, terragrunt.hcl.json)
and is not a cache, data, or download dir.
package config
import (
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"reflect"
	"strings"
	"github.com/hashicorp/go-getter"
	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/hcl/v2/hclparse"
	"github.com/hashicorp/hcl/v2/hclwrite"
	"github.com/mitchellh/mapstructure"
	"github.com/sirupsen/logrus"
	"github.com/zclconf/go-cty/cty"
	"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/util"
)
const DefaultTerragruntConfigPath = "terragrunt.hcl"
const DefaultTerragruntJsonConfigPath = "terragrunt.hcl.json"
type TerragruntConfig struct {
	Terraform                   *TerraformConfig
	TerraformBinary             string
	TerraformVersionConstraint  string
	TerragruntVersionConstraint string
	RemoteState                 *remote.RemoteState
	Dependencies                *ModuleDependencies
	DownloadDir                 string
	PreventDestroy              *bool
	Skip                        bool
	IamRole                     string
	IamAssumeRoleDuration       *int64
	IamAssumeRoleSessionName    string
	Inputs                      map[string]interface{}
	Locals                      map[string]interface{}
	TerragruntDependencies      []Dependency
	GenerateConfigs             map[string]codegen.GenerateConfig
	RetryableErrors             []string
	RetryMaxAttempts            *int
	RetrySleepIntervalSec       *int
	IsPartial bool
	ProcessedIncludes map[string]IncludeConfig
}
func (conf *TerragruntConfig) String() string {
	return fmt.Sprintf("TerragruntConfig{Terraform = %v, RemoteState = %v, Dependencies = %v, PreventDestroy = %v}", conf.Terraform, conf.RemoteState, conf.Dependencies, conf.PreventDestroy)
}
func (conf *TerragruntConfig) GetIAMRoleOptions() options.IAMRoleOptions {
	configIAMRoleOptions := options.IAMRoleOptions{
		RoleARN:               conf.IamRole,
		AssumeRoleSessionName: conf.IamAssumeRoleSessionName,
	}
	if conf.IamAssumeRoleDuration != nil {
		configIAMRoleOptions.AssumeRoleDuration = *conf.IamAssumeRoleDuration
	}
	return configIAMRoleOptions
}
type terragruntConfigFile struct {
	Terraform                   *TerraformConfig `hcl:"terraform,block"`
	TerraformBinary             *string          `hcl:"terraform_binary,attr"`
	TerraformVersionConstraint  *string          `hcl:"terraform_version_constraint,attr"`
	TerragruntVersionConstraint *string          `hcl:"terragrunt_version_constraint,attr"`
	Inputs                      *cty.Value       `hcl:"inputs,attr"`
	RemoteState     *remoteStateConfigFile `hcl:"remote_state,block"`
	RemoteStateAttr *cty.Value             `hcl:"remote_state,optional"`
	Dependencies             *ModuleDependencies `hcl:"dependencies,block"`
	DownloadDir              *string             `hcl:"download_dir,attr"`
	PreventDestroy           *bool               `hcl:"prevent_destroy,attr"`
	Skip                     *bool               `hcl:"skip,attr"`
	IamRole                  *string             `hcl:"iam_role,attr"`
	IamAssumeRoleDuration    *int64              `hcl:"iam_assume_role_duration,attr"`
	IamAssumeRoleSessionName *string             `hcl:"iam_assume_role_session_name,attr"`
	TerragruntDependencies   []Dependency        `hcl:"dependency,block"`
	GenerateAttrs  *cty.Value                `hcl:"generate,optional"`
	GenerateBlocks []terragruntGenerateBlock `hcl:"generate,block"`
	RetryableErrors       []string `hcl:"retryable_errors,optional"`
	RetryMaxAttempts      *int     `hcl:"retry_max_attempts,optional"`
	RetrySleepIntervalSec *int     `hcl:"retry_sleep_interval_sec,optional"`
	Locals  *terragruntLocal          `hcl:"locals,block"`
	Include []terragruntIncludeIgnore `hcl:"include,block"`
}
type terragruntLocal struct {
	Remain hcl.Body `hcl:",remain"`
}
type terragruntIncludeIgnore struct {
	Name   string   `hcl:"name,label"`
	Remain hcl.Body `hcl:",remain"`
}
type remoteStateConfigFile struct {
	Backend                       string                     `hcl:"backend,attr"`
	DisableInit                   *bool                      `hcl:"disable_init,attr"`
	DisableDependencyOptimization *bool                      `hcl:"disable_dependency_optimization,attr"`
	Generate                      *remoteStateConfigGenerate `hcl:"generate,attr"`
	Config                        cty.Value                  `hcl:"config,attr"`
}
func (remoteState *remoteStateConfigFile) String() string {
	return fmt.Sprintf("remoteStateConfigFile{Backend = %v, Config = %v}", remoteState.Backend, remoteState.Config)
}
func (remoteState *remoteStateConfigFile) toConfig() (*remote.RemoteState, error) {
	remoteStateConfig, err := parseCtyValueToMap(remoteState.Config)
	if err != nil {
		return nil, err
	}
	config := &remote.RemoteState{}
	config.Backend = remoteState.Backend
	if remoteState.Generate != nil {
		config.Generate = &remote.RemoteStateGenerate{
			Path:     remoteState.Generate.Path,
			IfExists: remoteState.Generate.IfExists,
		}
	}
	config.Config = remoteStateConfig
	if remoteState.DisableInit != nil {
		config.DisableInit = *remoteState.DisableInit
	}
	if remoteState.DisableDependencyOptimization != nil {
		config.DisableDependencyOptimization = *remoteState.DisableDependencyOptimization
	}
	config.FillDefaults()
	if err := config.Validate(); err != nil {
		return nil, err
	}
	return config, err
}
type remoteStateConfigGenerate struct {
	Path     string `cty:"path"`
	IfExists string `cty:"if_exists"`
}
type terragruntGenerateBlock struct {
	Name             string  `hcl:",label"`
	Path             string  `hcl:"path,attr" mapstructure:"path"`
	IfExists         string  `hcl:"if_exists,attr" mapstructure:"if_exists"`
	CommentPrefix    *string `hcl:"comment_prefix,attr" mapstructure:"comment_prefix"`
	Contents         string  `hcl:"contents,attr" mapstructure:"contents"`
	DisableSignature *bool   `hcl:"disable_signature,attr" mapstructure:"disable_signature"`
}
type IncludeConfig struct {
	Name          string  `hcl:"name,label"`
	Path          string  `hcl:"path,attr"`
	Expose        *bool   `hcl:"expose,attr"`
	MergeStrategy *string `hcl:"merge_strategy,attr"`
}
func (cfg *IncludeConfig) String() string {
	return fmt.Sprintf("IncludeConfig{Path = %s, Expose = %v, MergeStrategy = %v}", cfg.Path, cfg.Expose, cfg.MergeStrategy)
}
func (cfg *IncludeConfig) GetExpose() bool {
	if cfg == nil || cfg.Expose == nil {
		return false
	}
	return *cfg.Expose
}
func (cfg *IncludeConfig) GetMergeStrategy() (MergeStrategyType, error) {
	if cfg.MergeStrategy == nil {
		return ShallowMerge, nil
	}
	strategy := *cfg.MergeStrategy
	switch strategy {
	case string(NoMerge):
		return NoMerge, nil
	case string(ShallowMerge):
		return ShallowMerge, nil
	case string(DeepMerge):
		return DeepMerge, nil
	default:
		return NoMerge, errors.WithStackTrace(InvalidMergeStrategyType(strategy))
	}
}
type MergeStrategyType string
const (
	NoMerge      MergeStrategyType = "no_merge"
	ShallowMerge MergeStrategyType = "shallow"
	DeepMerge    MergeStrategyType = "deep"
)
type ModuleDependencies struct {
	Paths []string `hcl:"paths,attr" cty:"paths"`
}
func (deps *ModuleDependencies) Merge(source *ModuleDependencies) {
	if source == nil {
		return
	}
	for _, path := range source.Paths {
		if !util.ListContainsElement(deps.Paths, path) {
			deps.Paths = append(deps.Paths, path)
		}
	}
}
func (deps *ModuleDependencies) String() string {
	return fmt.Sprintf("ModuleDependencies{Paths = %v}", deps.Paths)
}
type Hook struct {
	Name       string   `hcl:"name,label" cty:"name"`
	Commands   []string `hcl:"commands,attr" cty:"commands"`
	Execute    []string `hcl:"execute,attr" cty:"execute"`
	RunOnError *bool    `hcl:"run_on_error,attr" cty:"run_on_error"`
	WorkingDir *string  `hcl:"working_dir,attr" cty:"working_dir"`
}
func (conf *Hook) String() string {
	return fmt.Sprintf("Hook{Name = %s, Commands = %v}", conf.Name, len(conf.Commands))
}
type TerraformConfig struct {
	ExtraArgs   []TerraformExtraArguments `hcl:"extra_arguments,block"`
	Source      *string                   `hcl:"source,attr"`
	BeforeHooks []Hook                    `hcl:"before_hook,block"`
	AfterHooks  []Hook                    `hcl:"after_hook,block"`
}
func (conf *TerraformConfig) String() string {
	return fmt.Sprintf("TerraformConfig{Source = %v}", conf.Source)
}
func (conf *TerraformConfig) GetBeforeHooks() []Hook {
	if conf == nil {
		return nil
	}
	return conf.BeforeHooks
}
func (conf *TerraformConfig) GetAfterHooks() []Hook {
	if conf == nil {
		return nil
	}
	return conf.AfterHooks
}
func (conf *TerraformConfig) ValidateHooks() error {
	allHooks := append(conf.GetBeforeHooks(), conf.GetAfterHooks()...)
	for _, curHook := range allHooks {
		if len(curHook.Execute) < 1 || curHook.Execute[0] == "" {
			return InvalidArgError(fmt.Sprintf("Error with hook %s. Need at least one non-empty argument in 'execute'.", curHook.Name))
		}
	}
	return nil
}
type TerraformExtraArguments struct {
	Name             string             `hcl:"name,label" cty:"name"`
	Arguments        *[]string          `hcl:"arguments,attr" cty:"arguments"`
	RequiredVarFiles *[]string          `hcl:"required_var_files,attr" cty:"required_var_files"`
	OptionalVarFiles *[]string          `hcl:"optional_var_files,attr" cty:"optional_var_files"`
	Commands         []string           `hcl:"commands,attr" cty:"commands"`
	EnvVars          *map[string]string `hcl:"env_vars,attr" cty:"env_vars"`
}
func (conf *TerraformExtraArguments) String() string {
	return fmt.Sprintf(
		"TerraformArguments{Name = %s, Arguments = %v, Commands = %v, EnvVars = %v}",
		conf.Name,
		conf.Arguments,
		conf.Commands,
		conf.EnvVars)
}
func (conf *TerraformExtraArguments) GetVarFiles(logger *logrus.Entry) []string {
	varFiles := []string{}
	if conf.RequiredVarFiles != nil {
		for _, file := range util.RemoveDuplicatesFromListKeepLast(*conf.RequiredVarFiles) {
			varFiles = append(varFiles, file)
		}
	}
	if conf.OptionalVarFiles != nil {
		for _, file := range util.RemoveDuplicatesFromListKeepLast(*conf.OptionalVarFiles) {
			if util.FileExists(file) {
				varFiles = append(varFiles, file)
			} else {
				logger.Debugf("Skipping var-file %s as it does not exist", file)
			}
		}
	}
	return varFiles
}
func GetTerraformSourceUrl(terragruntOptions *options.TerragruntOptions, terragruntConfig *TerragruntConfig) (string, error) {
	if terragruntOptions.Source != "" {
		return terragruntOptions.Source, nil
	} else if terragruntConfig.Terraform != nil && terragruntConfig.Terraform.Source != nil {
		return adjustSourceWithMap(terragruntOptions.SourceMap, *terragruntConfig.Terraform.Source, terragruntOptions.OriginalTerragruntConfigPath)
	} else {
		return "", nil
	}
}
func adjustSourceWithMap(sourceMap map[string]string, source string, modulePath string) (string, error) {
	if len(sourceMap) == 0 {
		return source, nil
	}
	moduleUrl, moduleSubdir := getter.SourceDirSubdir(source)
	if moduleUrl == "" && moduleSubdir == "" {
		return "", errors.WithStackTrace(InvalidSourceUrlWithMap{ModulePath: modulePath, ModuleSourceUrl: source})
	}
	if moduleUrl == "" {
		return source, nil
	}
	moduleUrlParsed, err := url.Parse(moduleUrl)
	if err != nil {
		return source, err
	}
	moduleUrlParsed.RawQuery = ""
	moduleUrlQuery := moduleUrlParsed.String()
	sourcePath, hasKey := sourceMap[moduleUrlQuery]
	if hasKey == false {
		return source, nil
	}
	if moduleSubdir == "" {
		moduleSubdirFromUrl, err := getModulePathFromSourceUrl(moduleUrl)
		if err != nil {
			return moduleSubdirFromUrl, err
		}
		moduleSubdir = moduleSubdirFromUrl
	}
	return util.JoinTerraformModulePath(sourcePath, moduleSubdir), nil
}
func DefaultConfigPath(workingDir string) string {
	return util.JoinPath(workingDir, DefaultTerragruntConfigPath)
}
func DefaultJsonConfigPath(workingDir string) string {
	return util.JoinPath(workingDir, DefaultTerragruntJsonConfigPath)
}
func GetDefaultConfigPath(workingDir string) string {
	if util.FileNotExists(DefaultConfigPath(workingDir)) && util.FileExists(DefaultJsonConfigPath(workingDir)) {
		return DefaultJsonConfigPath(workingDir)
	}
	return DefaultConfigPath(workingDir)
}
func FindConfigFilesInPath(rootPath string, terragruntOptions *options.TerragruntOptions) ([]string, error) {
	configFiles := []string{}
	err := filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() && info.Name() == options.TerragruntCacheDir {
			return filepath.SkipDir
		}
		isTerragruntModule, err := containsTerragruntModule(path, info, terragruntOptions)
		if err != nil {
			return err
		}
		if isTerragruntModule {
			configFiles = append(configFiles, GetDefaultConfigPath(path))
		}
		return nil
	})
	return configFiles, err
}