diff --git a/config/config.go b/config/config.go index 31f1f4a9e..a3c783e9a 100644 --- a/config/config.go +++ b/config/config.go @@ -2,12 +2,15 @@ package config import ( "fmt" - "github.com/mitchellh/mapstructure" "os" "path/filepath" "reflect" + "sort" "strings" + zglob "github.com/mattn/go-zglob" + "github.com/mitchellh/mapstructure" + "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclparse" "github.com/sirupsen/logrus" @@ -351,29 +354,60 @@ func GetDefaultConfigPath(workingDir string) string { func FindConfigFilesInPath(rootPath string, terragruntOptions *options.TerragruntOptions) ([]string, error) { configFiles := []string{} - err := filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error { + // Start from the given root path, unless we're given specific directories to include or strict include is on. + directories := []string{rootPath} + if terragruntOptions.StrictInclude || len(terragruntOptions.IncludeDirs) > 0 { + directories = []string{} + } + + // Build a collection of directories based off of any included directories from the given terragrunt options. + for _, directory := range terragruntOptions.IncludeDirs { + matches, err := zglob.Glob(filepath.Join(rootPath, directory)) if err != nil { - return err + return nil, err } - // Skip the Terragrunt cache dir entirely - if info.IsDir() && info.Name() == options.TerragruntCacheDir { - return filepath.SkipDir - } + directories = append(directories, matches...) + } - isTerragruntModule, err := containsTerragruntModule(path, info, terragruntOptions) - if err != nil { - return err - } + // TODO: since the terragrunt include directories can be specified as globs, we need to expand + // those globs before walking over them. - if isTerragruntModule { - configFiles = append(configFiles, GetDefaultConfigPath(path)) - } + for _, directory := range directories { + terragruntOptions.Logger.Logf(logrus.InfoLevel, "walking over directory for terragrunt modules: %s", directory) + err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } - return nil - }) + // Skip the Terragrunt cache dir entirely + if info.IsDir() && info.Name() == options.TerragruntCacheDir { + return filepath.SkipDir + } + + isTerragruntModule, err := containsTerragruntModule(path, info, terragruntOptions) + if err != nil { + return err + } + + if isTerragruntModule { + terragruntOptions.Logger.Logf(logrus.InfoLevel, "found terragrunt module: %s", path) + configFiles = append(configFiles, GetDefaultConfigPath(path)) + } + + return nil + }) + + if err != nil { + return configFiles, err + } + } - return configFiles, err + // NOTE: we're sorting the configuration files as I found that very rarely the order returned by zglob wasn't + // always determinate. I don't know if this is a bug in the zglob module or just side-effect of a race in the module. + // @celestialorb, 2021/04/29 + sort.Strings(configFiles) + return configFiles, nil } // Returns true if the given path with the given FileInfo contains a Terragrunt module and false otherwise. A path diff --git a/config/config_test.go b/config/config_test.go index d0420a25a..6e31d3eb5 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1019,9 +1019,9 @@ func TestFindConfigFilesInPathMultipleConfigs(t *testing.T) { t.Parallel() expected := []string{ - "../test/fixture-config-files/multiple-configs/terragrunt.hcl", "../test/fixture-config-files/multiple-configs/subdir-2/subdir/terragrunt.hcl", "../test/fixture-config-files/multiple-configs/subdir-3/terragrunt.hcl", + "../test/fixture-config-files/multiple-configs/terragrunt.hcl", } terragruntOptions, err := options.NewTerragruntOptionsForTest("test") require.NoError(t, err) @@ -1036,9 +1036,9 @@ func TestFindConfigFilesInPathMultipleJsonConfigs(t *testing.T) { t.Parallel() expected := []string{ - "../test/fixture-config-files/multiple-json-configs/terragrunt.hcl.json", "../test/fixture-config-files/multiple-json-configs/subdir-2/subdir/terragrunt.hcl.json", "../test/fixture-config-files/multiple-json-configs/subdir-3/terragrunt.hcl.json", + "../test/fixture-config-files/multiple-json-configs/terragrunt.hcl.json", } terragruntOptions, err := options.NewTerragruntOptionsForTest("test") require.NoError(t, err) @@ -1053,16 +1053,55 @@ func TestFindConfigFilesInPathMultipleMixedConfigs(t *testing.T) { t.Parallel() expected := []string{ + "../test/fixture-config-files/multiple-mixed-configs/subdir-2/subdir/terragrunt.hcl", + "../test/fixture-config-files/multiple-mixed-configs/subdir-3/terragrunt.hcl.json", "../test/fixture-config-files/multiple-mixed-configs/terragrunt.hcl.json", + } + terragruntOptions, err := options.NewTerragruntOptionsForTest("test") + require.NoError(t, err) + + actual, err := FindConfigFilesInPath("../test/fixture-config-files/multiple-mixed-configs", terragruntOptions) + + assert.Nil(t, err, "Unexpected error: %v", err) + assert.Equal(t, expected, actual) +} + +func TestFindConfigFilesInPathIncludeDirs(t *testing.T) { + t.Parallel() + + expected := []string{ "../test/fixture-config-files/multiple-mixed-configs/subdir-2/subdir/terragrunt.hcl", "../test/fixture-config-files/multiple-mixed-configs/subdir-3/terragrunt.hcl.json", } + terragruntOptions, err := options.NewTerragruntOptionsForTest("test") require.NoError(t, err) + terragruntOptions.StrictInclude = true + terragruntOptions.IncludeDirs = []string{"subdir-2", "subdir-3"} + actual, err := FindConfigFilesInPath("../test/fixture-config-files/multiple-mixed-configs", terragruntOptions) + assert.Nil(t, err, "Unexpected error: %v", err) + + assert.Equal(t, expected, actual) +} + +func TestFindConfigFilesInPathStrictInclude(t *testing.T) { + t.Parallel() + expected := []string{ + "../test/fixture-config-files/multiple-mixed-configs/subdir-2/subdir/terragrunt.hcl", + } + + terragruntOptions, err := options.NewTerragruntOptionsForTest("test") + require.NoError(t, err) + + terragruntOptions.StrictInclude = true + terragruntOptions.IncludeDirs = []string{"subdir-2"} + + actual, err := FindConfigFilesInPath("../test/fixture-config-files/multiple-mixed-configs", terragruntOptions) assert.Nil(t, err, "Unexpected error: %v", err) + assert.Equal(t, expected, actual) } @@ -1085,10 +1124,10 @@ func TestFindConfigFilesIgnoresTerraformDataDir(t *testing.T) { t.Parallel() expected := []string{ - "../test/fixture-config-files/ignore-terraform-data-dir/terragrunt.hcl", "../test/fixture-config-files/ignore-terraform-data-dir/.tf_data/modules/mod/terragrunt.hcl", - "../test/fixture-config-files/ignore-terraform-data-dir/subdir/terragrunt.hcl", "../test/fixture-config-files/ignore-terraform-data-dir/subdir/.tf_data/modules/mod/terragrunt.hcl", + "../test/fixture-config-files/ignore-terraform-data-dir/subdir/terragrunt.hcl", + "../test/fixture-config-files/ignore-terraform-data-dir/terragrunt.hcl", } terragruntOptions, err := options.NewTerragruntOptionsForTest("test") require.NoError(t, err) @@ -1103,9 +1142,9 @@ func TestFindConfigFilesIgnoresTerraformDataDirEnv(t *testing.T) { t.Parallel() expected := []string{ - "../test/fixture-config-files/ignore-terraform-data-dir/terragrunt.hcl", - "../test/fixture-config-files/ignore-terraform-data-dir/subdir/terragrunt.hcl", "../test/fixture-config-files/ignore-terraform-data-dir/subdir/.terraform/modules/mod/terragrunt.hcl", + "../test/fixture-config-files/ignore-terraform-data-dir/subdir/terragrunt.hcl", + "../test/fixture-config-files/ignore-terraform-data-dir/terragrunt.hcl", } terragruntOptions, err := options.NewTerragruntOptionsForTest("test") require.NoError(t, err) @@ -1121,10 +1160,10 @@ func TestFindConfigFilesIgnoresTerraformDataDirEnvPath(t *testing.T) { t.Parallel() expected := []string{ - "../test/fixture-config-files/ignore-terraform-data-dir/terragrunt.hcl", "../test/fixture-config-files/ignore-terraform-data-dir/.tf_data/modules/mod/terragrunt.hcl", - "../test/fixture-config-files/ignore-terraform-data-dir/subdir/terragrunt.hcl", "../test/fixture-config-files/ignore-terraform-data-dir/subdir/.terraform/modules/mod/terragrunt.hcl", + "../test/fixture-config-files/ignore-terraform-data-dir/subdir/terragrunt.hcl", + "../test/fixture-config-files/ignore-terraform-data-dir/terragrunt.hcl", } terragruntOptions, err := options.NewTerragruntOptionsForTest("test") require.NoError(t, err) @@ -1143,10 +1182,10 @@ func TestFindConfigFilesIgnoresTerraformDataDirEnvRoot(t *testing.T) { require.NoError(t, err) expected := []string{ - filepath.Join(cwd, "../test/fixture-config-files/ignore-terraform-data-dir/terragrunt.hcl"), - filepath.Join(cwd, "../test/fixture-config-files/ignore-terraform-data-dir/subdir/terragrunt.hcl"), filepath.Join(cwd, "../test/fixture-config-files/ignore-terraform-data-dir/subdir/.terraform/modules/mod/terragrunt.hcl"), filepath.Join(cwd, "../test/fixture-config-files/ignore-terraform-data-dir/subdir/.tf_data/modules/mod/terragrunt.hcl"), + filepath.Join(cwd, "../test/fixture-config-files/ignore-terraform-data-dir/subdir/terragrunt.hcl"), + filepath.Join(cwd, "../test/fixture-config-files/ignore-terraform-data-dir/terragrunt.hcl"), } workingDir := filepath.Join(cwd, "../test/fixture-config-files/ignore-terraform-data-dir/") terragruntOptions, err := options.NewTerragruntOptionsForTest(workingDir) @@ -1163,8 +1202,8 @@ func TestFindConfigFilesIgnoresDownloadDir(t *testing.T) { t.Parallel() expected := []string{ - "../test/fixture-config-files/multiple-configs/terragrunt.hcl", "../test/fixture-config-files/multiple-configs/subdir-3/terragrunt.hcl", + "../test/fixture-config-files/multiple-configs/terragrunt.hcl", } terragruntOptions, err := options.NewTerragruntOptionsForTest("test") require.NoError(t, err) @@ -1176,6 +1215,26 @@ func TestFindConfigFilesIgnoresDownloadDir(t *testing.T) { assert.Equal(t, expected, actual) } +func TestFindConfigFilesInPathWithGlob(t *testing.T) { + t.Parallel() + + expected := []string{ + "../test/fixture-config-files/multiple-mixed-configs/subdir-2/subdir/terragrunt.hcl", + "../test/fixture-config-files/multiple-mixed-configs/subdir-3/terragrunt.hcl.json", + } + + terragruntOptions, err := options.NewTerragruntOptionsForTest("test") + require.NoError(t, err) + + terragruntOptions.StrictInclude = true + terragruntOptions.IncludeDirs = []string{"subdir-*"} + + actual, err := FindConfigFilesInPath("../test/fixture-config-files/multiple-mixed-configs", terragruntOptions) + assert.Nil(t, err, "Unexpected error: %v", err) + + assert.Equal(t, expected, actual) +} + func mockOptionsForTestWithConfigPath(t *testing.T, configPath string) *options.TerragruntOptions { opts, err := options.NewTerragruntOptionsForTest(configPath) if err != nil {