Skip to content

Commit a1a538e

Browse files
authored
feat: Adding render preview for expansion (#6737)
* feat: Adding `render` preview for `expansion` * chore: Refactoring `WriteTo` * chore: Adding `duplicate-dependency-labels` strict control * docs: Cleaning up docs gating
1 parent d38317c commit a1a538e

15 files changed

Lines changed: 1165 additions & 342 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
version: "v1.1.4"
3+
category: "new-features"
4+
---
5+
6+
#### `duplicate-dependency-labels` strict control
7+
8+
Declaring two `dependency` blocks with the same label in one `terragrunt.hcl` configuration file parsed without error, and then quietly resolved every reference to that label to whichever block came last. The blocks before it were silently overridden:
9+
10+
```hcl
11+
dependency "vpc" {
12+
config_path = "../vpc-us-east-1"
13+
}
14+
15+
dependency "vpc" {
16+
config_path = "../vpc-us-west-2"
17+
}
18+
19+
inputs = {
20+
# Reads ../vpc-us-west-2.
21+
vpc_id = dependency.vpc.outputs.vpc_id
22+
}
23+
```
24+
25+
Terragrunt now warns when it finds this. With the new [`duplicate-dependency-labels`](/reference/strict-controls/active#duplicate-dependency-labels) strict control enabled, the warning becomes an error naming the address the blocks share:
26+
27+
```bash
28+
terragrunt run plan --strict-control duplicate-dependency-labels
29+
```
30+
31+
```text
32+
/path/to/terragrunt.hcl: dependency vpc is declared more than once; every dependency needs an address of its own
33+
```
34+
35+
Give each block a label of its own. A configuration that was relying on the shadowing to pick the last block should keep only that block.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
version: "v1.1.4"
3+
category: "experiments-updated"
4+
---
5+
6+
#### `render` previews what an expanded `dependency` block expanded to
7+
8+
With the [`block-iteration`](/reference/experiments/active#block-iteration) experiment enabled, a `dependency` block that carries an `expansion` block now renders as it was written, followed by the elements it expanded into, commented out and with their bodies resolved:
9+
10+
```bash
11+
$ terragrunt render --experiment block-iteration
12+
dependency "aurora" {
13+
expansion {
14+
for_each = toset(["web", "api"])
15+
}
16+
17+
config_path = "../aurora-${each.key}"
18+
}
19+
20+
# Expands to:
21+
#
22+
# dependency "aurora" {
23+
# config_path = "../aurora-api"
24+
# }
25+
#
26+
# dependency "aurora" {
27+
# config_path = "../aurora-web"
28+
# }
29+
```
30+
31+
The elements are comments because they aren't valid Terragrunt HCL configurations (you are not allowed to use the same dependency label twice in Terragrunt configurations), the previews are there to help you understand how expansion will resolve.

docs/src/data/commands/render.mdx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ flags:
1818
- render-all
1919
---
2020

21+
import { Aside } from '@astrojs/starlight/components';
22+
import Before from '@components/Before.astro';
23+
import Since from '@components/Since.astro';
24+
2125
Render the Terragrunt configuration in the current working directory, with as much work done as possible beforehand (that is, with all includes merged, dependencies resolved/interpolated, function calls executed, etc).
2226

2327
The only supported format at the moment is JSON, but support for HCL will be added in a future version.
@@ -83,3 +87,67 @@ terragrunt render --all --json -w
8387
```
8488
8589
This will render all configurations discovered from the current working directory and write the rendered configurations to `terragrunt.rendered.json` files adjacent to the configurations they are derived from.
90+
91+
## Expanded dependencies
92+
93+
<Before version="1.1.4">
94+
Previewing how a `dependency` block expands will be supported in v1.1.4.
95+
</Before>
96+
97+
<Since version="1.1.4">
98+
99+
<Aside type="tip" title="Experimental">
100+
Expanding a `dependency` block over a `count` or `for_each` is gated behind the [`block-iteration`](/reference/experiments/active#block-iteration) experiment. Enable it with `--experiment=block-iteration` or `TG_EXPERIMENT=block-iteration`.
101+
</Aside>
102+
103+
A `dependency` block that carries an `expansion` block renders as it was written, references and all, followed by the elements it expanded into. Each element is commented out, with its body resolved against the iteration it came from:
104+
105+
```bash
106+
$ terragrunt render --experiment block-iteration
107+
dependency "aurora" {
108+
expansion {
109+
for_each = toset(["web", "api"])
110+
}
111+
112+
config_path = "../aurora-${each.key}"
113+
}
114+
115+
# Expands to:
116+
#
117+
# dependency "aurora" {
118+
# config_path = "../aurora-api"
119+
# }
120+
#
121+
# dependency "aurora" {
122+
# config_path = "../aurora-web"
123+
# }
124+
```
125+
126+
`count` reads the same way:
127+
128+
```bash
129+
$ terragrunt render --experiment block-iteration
130+
dependency "shard" {
131+
expansion {
132+
count = 2
133+
}
134+
135+
config_path = "../shard-${count.index}"
136+
}
137+
138+
# Expands to:
139+
#
140+
# dependency "shard" {
141+
# config_path = "../shard-0"
142+
# }
143+
#
144+
# dependency "shard" {
145+
# config_path = "../shard-1"
146+
# }
147+
```
148+
149+
The expanded blocks are in comments because they are not a valid Terragrunt configuration: they all carry one label, and only the last block with a given label can be referenced. Terragrunt warns about that, and rejects it outright under the `duplicate-dependency-labels` [strict control](/reference/strict-controls/active#duplicate-dependency-labels). They are present as comments to help you predict how Terragrunt will expand the block with an `expansion` block.
150+
151+
A `dependency` block with no `expansion` block renders as it always has.
152+
153+
</Since>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
name: duplicate-dependency-labels
3+
status: active
4+
since: "1.1.4"
5+
---
6+
7+
Throw an error when two `dependency` blocks in one configuration claim the same address.
8+
9+
### `duplicate-dependency-labels` - Reason
10+
11+
Two `dependency` blocks written with the same label both parse, and Terragrunt then resolves every reference to that label to whichever block came last. The blocks before it are unreachable, with nothing to say so:
12+
13+
```hcl
14+
dependency "vpc" {
15+
config_path = "../vpc-us-east-1"
16+
}
17+
18+
dependency "vpc" {
19+
config_path = "../vpc-us-west-2"
20+
}
21+
22+
inputs = {
23+
# Reads ../vpc-us-west-2. The first block may as well not be there.
24+
vpc_id = dependency.vpc.outputs.vpc_id
25+
}
26+
```
27+
28+
Terragrunt warns about this by default. Enabling the control turns it into an error naming the address the blocks share:
29+
30+
```text
31+
/path/to/terragrunt.hcl: dependency vpc is declared more than once; every dependency needs an address of its own
32+
```
33+
34+
Give each block a label of its own. A configuration that was relying on the shadowing to pick the last block should keep only that block.
35+
36+
Blocks are compared by the address they resolve to rather than by label alone, so the elements of a block expanded with the [`block-iteration`](/reference/experiments/active#block-iteration) experiment, which all carry the label the block was written with, remain valid.

internal/strict/controls/controls.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ const (
9494
LegacyGCSPublicPrefix = "legacy-gcs-public-prefix"
9595

9696
OptionalHooks = "optional-hooks"
97+
98+
// DuplicateDependencyLabels is the control that prevents two `dependency` blocks in one
99+
// configuration from claiming the same address.
100+
DuplicateDependencyLabels = "duplicate-dependency-labels"
97101
)
98102

99103
// LegacyGCSDeprecationWarning is the warning text emitted when a plain
@@ -234,6 +238,15 @@ func New() strict.Controls {
234238
Warning: "Using an `include` block without a label is deprecated. Please use the `include` block with a label instead. For more information, see https://docs.terragrunt.com/migrate/bare-include/",
235239
},
236240

241+
&Control{
242+
Name: DuplicateDependencyLabels,
243+
Description: "Prevents two `dependency` blocks in one configuration from claiming the same address.",
244+
Error: errors.New( //nolint:staticcheck // user-facing message intentionally written as full sentences
245+
"Two `dependency` blocks address the same dependency. Give each block a label of its own.",
246+
),
247+
Warning: "Two `dependency` blocks address the same dependency, so only the last of them can be referenced and the rest are unreachable. Give each block a label of its own. In a future version of Terragrunt, this will result in an error.",
248+
},
249+
237250
&Control{
238251
Name: DoubleStar,
239252
Description: "Use the `**` glob pattern to select all files in a directory and its subdirectories.",

internal/strict/controls/controls_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,16 @@ func TestControlEvaluate(t *testing.T) {
248248
require.ErrorIs(t, err, bootErr)
249249
})
250250
}
251+
252+
// TestDuplicateDependencyLabelsControlIsRegistered pins that the control the dependency
253+
// decoder looks up by name is one the registry hands back.
254+
func TestDuplicateDependencyLabelsControlIsRegistered(t *testing.T) {
255+
t.Parallel()
256+
257+
ctrl := controls.New().Find(controls.DuplicateDependencyLabels)
258+
259+
if assert.NotNil(t, ctrl, "duplicate-dependency-labels must be registered") {
260+
assert.Equal(t, strict.ActiveStatus, ctrl.GetStatus())
261+
assert.Error(t, ctrl.(*controls.Control).Error)
262+
}
263+
}

0 commit comments

Comments
 (0)