Skip to content

Commit

Permalink
fix: incorrect detected paths in Terragrunt modules. (#1993)
Browse files Browse the repository at this point in the history
## What this PR does / why we need it:

Due to an incorrect assumption in the Terragrunt module detection, the
dependency paths were wrong.

Thanks @LittleWat for the bug report and for providing a reproducible
repository.

## Which issue(s) this PR fixes:
Fixes #1979 

## Special notes for your reviewer:

## Does this PR introduce a user-facing change?
```
yes, fixes a bug in the Terragrunt module detection.
```
  • Loading branch information
i4ki authored Dec 3, 2024
2 parents a2fdb72 + 8e19113 commit bff03fd
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Given a version number `MAJOR.MINOR.PATCH`, we increment the:
- Improve the error reporting of the Outputs Sharing feature.
- Fix crash in the Terragrunt integration when the project had modules with dependency paths outside the current Terramate project.
- A warning will be shown for such configurations.
- Fix the Terragrunt scanner not supporting nested modules.

## v0.11.3

Expand Down
58 changes: 44 additions & 14 deletions tg/tg_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,21 @@ func ScanModules(rootdir string, dir project.Path, trackDependencies bool) (Modu
continue
}

// first module is the module at cfgfile directory.
tgMod := stack.Modules[0]
var tgMod *configstack.TerraformModule
for _, m := range stack.Modules {
if m.Path == cfgOpts.WorkingDir {
tgMod = m
break
}
}

// sanity check
if tgMod == nil {
panic(errors.E(errors.ErrInternal, "%s found but module not found in subfolders. Please report this bug.", cfgfile))
}

mod.Source = *tgConfig.Terraform.Source

dependsOn := map[project.Path]struct{}{}

_, err = os.Lstat(mod.Source)
Expand Down Expand Up @@ -177,20 +189,29 @@ func ScanModules(rootdir string, dir project.Path, trackDependencies bool) (Modu
// "dependency" block is TerragruntDependencies
// they get automatically added into tgConfig.Dependencies.Paths
for _, dep := range tgConfig.TerragruntDependencies {
logger.Trace().Str("dep-path", dep.ConfigPath).Msg("found dependency")
if dep.Enabled != nil && !*dep.Enabled {
continue
}
depPath := dep.ConfigPath
if !filepath.IsAbs(depPath) {
depPath = filepath.Join(tgMod.Path, depPath)
depAbsPath := dep.ConfigPath
if !filepath.IsAbs(depAbsPath) {
depAbsPath = filepath.Join(tgMod.Path, depAbsPath)
}
if depPath != rootdir && !strings.HasPrefix(depPath, rootdir+string(filepath.Separator)) {
warnDependencyOutsideProject(mod, depPath, "dependency.config_path")

logger.Trace().
Str("mod-path", tgMod.Path).
Str("dep-path", dep.ConfigPath).
Str("dep-abs-path", depAbsPath).
Msg("found dependency (in dependency.config_path)")

if depAbsPath != rootdir && !strings.HasPrefix(depAbsPath, rootdir+string(filepath.Separator)) {
warnDependencyOutsideProject(mod, depAbsPath, "dependency.config_path")

continue
}
dependsOn[project.PrjAbsPath(rootdir, depPath)] = struct{}{}

depProjectPath := project.PrjAbsPath(rootdir, depAbsPath)

dependsOn[depProjectPath] = struct{}{}
}
}

Expand All @@ -202,17 +223,26 @@ func ScanModules(rootdir string, dir project.Path, trackDependencies bool) (Modu

if tgConfig.Dependencies != nil {
for _, depPath := range tgConfig.Dependencies.Paths {
depAbsPath := depPath
if !filepath.IsAbs(depPath) {
depPath = filepath.Join(tgMod.Path, depPath)
depAbsPath = filepath.Join(tgMod.Path, filepath.FromSlash(depPath))
}
if depPath != rootdir && !strings.HasPrefix(depPath, rootdir+string(filepath.Separator)) {
warnDependencyOutsideProject(mod, depPath, "dependencies.paths")

logger.Trace().
Str("mod-path", mod.Path.String()).
Str("dep-path", depPath).
Str("dep-abs-path", depAbsPath).
Msg("found dependency (in dependencies.paths)")

if depPath != rootdir && !strings.HasPrefix(depAbsPath, rootdir+string(filepath.Separator)) {
warnDependencyOutsideProject(mod, depAbsPath, "dependencies.paths")

continue
}

p := project.PrjAbsPath(rootdir, depPath)
mod.After = append(mod.After, p)
depProjectPath := project.PrjAbsPath(rootdir, depAbsPath)

mod.After = append(mod.After, depProjectPath)
}
mod.After.Sort()
}
Expand Down
91 changes: 91 additions & 0 deletions tg/tg_module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,97 @@ func TestTerragruntScanModules(t *testing.T) {
},
},
},
{
name: "nested Terraform modules",
layout: []string{
`f:terragrunt/terragrunt.hcl:` + Doc(
Block("remote_state",
Expr("generate", `{"path": "backend.tf", if_exists: "overwrite_terragrunt"}`),
),
Block("backend",
Labels("provider"),
Str("path", "provider.tf"),
Str("if_exists", "overwrite_terragrunt"),
Str("content", "a"),
),
).String(),
`f:terragrunt/dev/a1/b1/terragrunt.hcl:` + Doc(
Terraform(
Str("source", "https://some.etc/prj"),
),
Expr("inputs", `[]`),
).String(),
`f:terragrunt/dev/a2/b2/c2_1/d2/terragrunt.hcl:` + Doc(
Terraform(
Str("source", "https://some.etc/prj"),
),
Block("dependencies",
Expr("paths", `["../../../../a1/b1"]`),
),
Block("dependency", Labels("b1"),
Str("config_path", "../../../../a1/b1"),
Expr("mock_outputs_allowed_terraform_commands", `["validate", "plan", "refresh"]`),
Str("mock_outputs_merge_strategy_with_state", "shallow"),
),
).String(),
`f:terragrunt/dev/a2/b2/c2_2/terragrunt.hcl:` + Doc(
Terraform(
Str("source", "https://some.etc/prj"),
),
Block("dependencies",
Expr("paths", `["../c2_3", "../../../a1/b1"]`),
),
Block("dependency", Labels("c2_3"),
Str("config_path", "../c2_3"),
Expr("mock_outputs_allowed_terraform_commands", `["validate", "plan", "refresh"]`),
Str("mock_outputs_merge_strategy_with_state", "shallow"),
),
Expr("inputs", `[]`),
).String(),
`f:terragrunt/dev/a2/b2/c2_3/terragrunt.hcl:` + Doc(
Terraform(
Str("source", "https://some.etc/prj"),
),
).String(),
},
want: want{
modules: tg.Modules{
{
Path: project.NewPath("/terragrunt/dev/a1/b1"),
ConfigFile: project.NewPath("/terragrunt/dev/a1/b1/terragrunt.hcl"),
Source: "https://some.etc/prj",
},
{
Path: project.NewPath("/terragrunt/dev/a2/b2/c2_1/d2"),
ConfigFile: project.NewPath("/terragrunt/dev/a2/b2/c2_1/d2/terragrunt.hcl"),
Source: "https://some.etc/prj",
After: project.Paths{
project.NewPath("/terragrunt/dev/a1/b1"),
},
DependsOn: project.Paths{
project.NewPath("/terragrunt/dev/a1/b1"),
},
},
{
Path: project.NewPath("/terragrunt/dev/a2/b2/c2_2"),
ConfigFile: project.NewPath("/terragrunt/dev/a2/b2/c2_2/terragrunt.hcl"),
Source: "https://some.etc/prj",
After: project.Paths{
project.NewPath("/terragrunt/dev/a1/b1"),
project.NewPath("/terragrunt/dev/a2/b2/c2_3"),
},
DependsOn: project.Paths{
project.NewPath("/terragrunt/dev/a2/b2/c2_3"),
},
},
{
Path: project.NewPath("/terragrunt/dev/a2/b2/c2_3"),
ConfigFile: project.NewPath("/terragrunt/dev/a2/b2/c2_3/terragrunt.hcl"),
Source: "https://some.etc/prj",
},
},
},
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
Expand Down

0 comments on commit bff03fd

Please sign in to comment.