Skip to content

Commit 802c382

Browse files
committed
chore: autoinclude cleanup
1 parent d5a8a0c commit 802c382

11 files changed

Lines changed: 200 additions & 30 deletions

File tree

docs/src/data/changelog/v1.1.4/autoinclude-config-path-override.mdx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,27 @@ version: "v1.1.4"
33
category: "bug-fixes"
44
---
55

6-
#### Autoinclude overrides `config_path` set to a `values.*` attribute
6+
#### Autoinclude can replace a dependency whose `config_path` uses `values.*`
77

8-
When a unit source declared `config_path = values.vpc_path` in a `dependency` block and the stack file's `autoinclude` replaced that dependency with a different `config_path`, Terragrunt raised an "Unsupported attribute" error because it tried to evaluate `values.vpc_path` before applying the autoinclude override. The `values` block no longer needed `vpc_path` since the autoinclude provided the path, but the evaluation order meant the error surfaced anyway.
8+
A unit dependency block that set `config_path = values.vpc_path` no longer fails with "Unsupported attribute" when a sibling autoinclude — including one generated from a stack `autoinclude` block — supplies a different `config_path` and the values file does not define `vpc_path`.
99

10-
Terragrunt now detects dependency blocks overridden by a sibling autoinclude and bypasses their initial decode error, allowing the autoinclude replacement to resolve dependencies cleanly.
10+
```hcl
11+
# unit source: units/tgw/terragrunt.hcl
12+
dependency "vpc" {
13+
config_path = values.vpc_path
14+
}
15+
```
16+
17+
```hcl
18+
# stack: stacks/prod/terragrunt.stack.hcl
19+
unit "tgw" {
20+
source = "../../units/tgw"
21+
path = "tgw"
22+
values = { region = "us-east-1" } # vpc_path intentionally absent
23+
autoinclude {
24+
dependency "vpc" {
25+
config_path = stack.mgmt_infra.path
26+
}
27+
}
28+
}
29+
```

pkg/config/autoinclude_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,3 +1597,42 @@ dependency "other" {
15971597
_, err := config.ParseConfigFile(ctx, pctx, l, cfgPath, nil)
15981598
require.Error(t, err, "a non-overridden dep with unresolvable config_path must still fail")
15991599
}
1600+
1601+
// Without any autoinclude file, a dependency referencing an absent values attribute must still fail with the original error pointing at the values attribute, not a downstream dependency.vpc error.
1602+
func TestFoldSiblingAutoIncludeDeps_MissingValuesPathWithoutAutoIncludeStillErrors(t *testing.T) {
1603+
t.Parallel()
1604+
1605+
tmpDir := t.TempDir()
1606+
cfgPath := filepath.Join(tmpDir, config.DefaultTerragruntConfigPath)
1607+
1608+
// No autoinclude file at all.
1609+
require.NoError(t, os.WriteFile(cfgPath, []byte(`
1610+
dependency "vpc" {
1611+
config_path = values.vpc_path
1612+
skip_outputs = true
1613+
mock_outputs = {
1614+
id = "unit-vpc"
1615+
}
1616+
mock_outputs_allowed_terraform_commands = ["init"]
1617+
}
1618+
1619+
inputs = {
1620+
vpc_id = dependency.vpc.outputs.id
1621+
}
1622+
`), 0644))
1623+
1624+
ctx, pctx := newTestParsingContext(t, venvtest.NewOSWithEmptyEnv(), cfgPath)
1625+
pctx.Experiments.EnableExperiment(experiment.StackDependencies)
1626+
pctx.OriginalTerraformCommand = tfInitCommand
1627+
1628+
values := cty.ObjectVal(map[string]cty.Value{
1629+
"region": cty.StringVal("us-east-1"),
1630+
})
1631+
pctx.Values = &values
1632+
1633+
l := logger.CreateLogger()
1634+
1635+
_, err := config.ParseConfigFile(ctx, pctx, l, cfgPath, nil)
1636+
require.Error(t, err, "without autoinclude the values.vpc_path error must not be swallowed")
1637+
assert.Contains(t, err.Error(), "values", "error must reference the unresolvable values variable")
1638+
}

pkg/config/config.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1837,17 +1837,7 @@ func decodeAsTerragruntConfigFile(
18371837
l.Debugf("Deferred attribute access error to autoinclude merge: %v", diagErr)
18381838
}
18391839

1840-
dependencies, err := decodeDependencyBlocks(file, evalContext, pctx.Experiments)
1841-
if err != nil && hasSiblingAutoInclude(pctx) {
1842-
overrides := siblingAutoIncludeDepNames(pctx)
1843-
if len(overrides) > 0 {
1844-
dependencies, err = decodeDependencyBlocks(
1845-
file, evalContext, pctx.Experiments,
1846-
hclparse.WithSkipLabelsOnError(overrides),
1847-
)
1848-
}
1849-
}
1850-
1840+
dependencies, err := decodeDependencyBlocksWithAutoIncludeRetry(file, evalContext, pctx)
18511841
if err != nil {
18521842
return &terragruntConfig, err
18531843
}

pkg/config/dependency.go

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -314,22 +314,9 @@ func decodeAndRetrieveOutputs(
314314
return nil, err
315315
}
316316

317-
dependencies, err := decodeDependencyBlocks(file, evalParsingContext, pctx.Experiments)
318-
if err != nil && hasSiblingAutoInclude(pctx) {
319-
// When a sibling autoinclude overrides dependency blocks, the unit source may
320-
// reference values.* attributes the override makes unnecessary. Skip overridden
321-
// blocks on retry; foldSiblingAutoIncludeDeps fills them in below.
322-
overrides := siblingAutoIncludeDepNames(pctx)
323-
if len(overrides) > 0 {
324-
dependencies, err = decodeDependencyBlocks(
325-
file, evalParsingContext, pctx.Experiments,
326-
hclparse.WithSkipLabelsOnError(overrides),
327-
)
328-
}
329-
330-
if err != nil {
331-
return nil, err
332-
}
317+
dependencies, err := decodeDependencyBlocksWithAutoIncludeRetry(file, evalParsingContext, pctx)
318+
if err != nil {
319+
return nil, err
333320
}
334321

335322
decodedDependency := TerragruntDependency{Dependencies: dependencies}
@@ -2220,6 +2207,26 @@ func parseAutoIncludeFileCached(
22202207
return file, nil
22212208
}
22222209

2210+
// decodeDependencyBlocksWithAutoIncludeRetry decodes dependency blocks, retrying with label-skip when a sibling autoinclude overrides some of them and the first decode fails on unresolvable attributes. Used by both decodeAndRetrieveOutputs and decodeAsTerragruntConfigFile.
2211+
func decodeDependencyBlocksWithAutoIncludeRetry(
2212+
file *hclparse.File,
2213+
evalContext *hcl.EvalContext,
2214+
pctx *ParsingContext,
2215+
) (Dependencies, error) {
2216+
deps, err := decodeDependencyBlocks(file, evalContext, pctx.Experiments)
2217+
if err != nil && hasSiblingAutoInclude(pctx) {
2218+
overrides := siblingAutoIncludeDepNames(pctx)
2219+
if len(overrides) > 0 {
2220+
deps, err = decodeDependencyBlocks(
2221+
file, evalContext, pctx.Experiments,
2222+
hclparse.WithSkipLabelsOnError(overrides),
2223+
)
2224+
}
2225+
}
2226+
2227+
return deps, err
2228+
}
2229+
22232230
// siblingAutoIncludeDepNames extracts dependency block labels from the sibling autoinclude file via a lightweight HCL parse. Returns nil when no autoinclude is registered, the file is absent, or it cannot be parsed.
22242231
func siblingAutoIncludeDepNames(pctx *ParsingContext) map[string]bool {
22252232
if !hasSiblingAutoInclude(pctx) {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
variable "vpc_id" {
2+
type = string
3+
}
4+
5+
output "app_vpc" {
6+
value = var.vpc_id
7+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
terraform {
2+
source = "."
3+
}
4+
5+
# The config_path references values.vpc_path, which the stack autoinclude will override.
6+
# Without the fix, this fails with "Unsupported attribute" because values.vpc_path is evaluated
7+
# before the autoinclude replacement is applied (issue 6692).
8+
dependency "vpc" {
9+
config_path = values.vpc_path
10+
11+
mock_outputs_allowed_terraform_commands = ["init", "plan", "validate"]
12+
mock_outputs = {
13+
vpc_id = "from-unit-mock"
14+
}
15+
}
16+
17+
remote_state {
18+
backend = "local"
19+
generate = {
20+
path = "backend.tf"
21+
if_exists = "overwrite_terragrunt"
22+
}
23+
config = {
24+
path = "${dependency.vpc.outputs.vpc_id}.tfstate"
25+
}
26+
}
27+
28+
inputs = {
29+
vpc_id = dependency.vpc.outputs.vpc_id
30+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
output "vpc_id" {
2+
value = "vpc-12345"
3+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
terraform {
2+
source = "."
3+
}
4+
5+
remote_state {
6+
backend = "local"
7+
generate = {
8+
path = "backend.tf"
9+
if_exists = "overwrite_terragrunt"
10+
}
11+
config = {
12+
path = "vpc.tfstate"
13+
}
14+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
unit "vpc" {
2+
source = "../catalog/vpc"
3+
path = "vpc"
4+
}
5+
6+
unit "app" {
7+
source = "../catalog/app"
8+
path = "app"
9+
10+
# values intentionally omits vpc_path — the autoinclude provides the dependency override.
11+
values = {
12+
region = "us-east-1"
13+
}
14+
15+
autoinclude {
16+
dependency "vpc" {
17+
config_path = unit.vpc.path
18+
19+
mock_outputs_allowed_terraform_commands = ["init", "plan", "validate"]
20+
mock_outputs = {
21+
vpc_id = "from-autoinclude"
22+
}
23+
}
24+
}
25+
}

test/integration_stack_dependencies_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ const (
7272
testFixtureStackDepsHCLValidateAutoInc = "fixtures/stacks/stack-deps-hclvalidate-autoinclude"
7373
testFixtureStackDepsAutoIncTemplateLiteral = "fixtures/stacks/stack-deps-autoinclude-template-literal"
7474
testFixtureStackDepsAutoIncObjectKey = "fixtures/stacks/stack-deps-autoinclude-object-key"
75+
testFixtureStackDepsAutoIncConfigPathValues = "fixtures/stacks/stack-deps-autoinclude-config-path-values"
7576
)
7677

7778
// TestStackDepsAutoIncludeGenerationAndDAG tests parsing, autoinclude generation,

0 commit comments

Comments
 (0)