@@ -3,7 +3,6 @@ package parser
3
3
import (
4
4
"context"
5
5
"errors"
6
- "fmt"
7
6
"io/fs"
8
7
"reflect"
9
8
"time"
@@ -148,7 +147,8 @@ func (e *evaluator) EvaluateAll(ctx context.Context) (terraform.Modules, map[str
148
147
}
149
148
}
150
149
151
- // expand out resources and modules via count (not a typo, we do this twice so every order is processed)
150
+ // expand out resources and modules via count, for-each and dynamic
151
+ // (not a typo, we do this twice so every order is processed)
152
152
e .blocks = e .expandBlocks (e .blocks )
153
153
e .blocks = e .expandBlocks (e .blocks )
154
154
@@ -204,7 +204,7 @@ func (e *evaluator) isModuleLocal() bool {
204
204
}
205
205
206
206
func (e * evaluator ) expandBlocks (blocks terraform.Blocks ) terraform.Blocks {
207
- return e .expandDynamicBlocks (e .expandBlockForEaches (e .expandBlockCounts (blocks ))... )
207
+ return e .expandDynamicBlocks (e .expandBlockForEaches (e .expandBlockCounts (blocks ), false )... )
208
208
}
209
209
210
210
func (e * evaluator ) expandDynamicBlocks (blocks ... * terraform.Block ) terraform.Blocks {
@@ -219,103 +219,84 @@ func (e *evaluator) expandDynamicBlock(b *terraform.Block) {
219
219
e .expandDynamicBlock (sub )
220
220
}
221
221
for _ , sub := range b .AllBlocks ().OfType ("dynamic" ) {
222
+ if sub .IsExpanded () {
223
+ continue
224
+ }
222
225
blockName := sub .TypeLabel ()
223
- expanded := e .expandBlockForEaches (terraform.Blocks {sub })
226
+ expanded := e .expandBlockForEaches (terraform.Blocks {sub }, true )
224
227
for _ , ex := range expanded {
225
228
if content := ex .GetBlock ("content" ); content .IsNotNil () {
226
229
_ = e .expandDynamicBlocks (content )
227
230
b .InjectBlock (content , blockName )
228
231
}
229
232
}
233
+ sub .MarkExpanded ()
230
234
}
231
235
}
232
236
233
- func validateForEachArg (arg cty.Value ) error {
234
- if arg .IsNull () {
235
- return errors .New ("arg is null" )
236
- }
237
-
238
- ty := arg .Type ()
239
-
240
- if ! arg .IsKnown () || ty .Equals (cty .DynamicPseudoType ) || arg .LengthInt () == 0 {
241
- return nil
242
- }
243
-
244
- if ! (ty .IsSetType () || ty .IsObjectType () || ty .IsMapType ()) {
245
- return fmt .Errorf ("%s type is not supported: arg is not set or map" , ty .FriendlyName ())
246
- }
247
-
248
- if ty .IsSetType () {
249
- if ! ty .ElementType ().Equals (cty .String ) {
250
- return errors .New ("arg is not set of strings" )
251
- }
252
-
253
- it := arg .ElementIterator ()
254
- for it .Next () {
255
- key , _ := it .Element ()
256
- if key .IsNull () {
257
- return errors .New ("arg is set of strings, but contains null" )
258
- }
259
-
260
- if ! key .IsKnown () {
261
- return errors .New ("arg is set of strings, but contains unknown value" )
262
- }
263
- }
264
- }
265
-
266
- return nil
267
- }
268
-
269
237
func isBlockSupportsForEachMetaArgument (block * terraform.Block ) bool {
270
238
return slices .Contains ([]string {"module" , "resource" , "data" , "dynamic" }, block .Type ())
271
239
}
272
240
273
- func (e * evaluator ) expandBlockForEaches (blocks terraform.Blocks ) terraform.Blocks {
241
+ func (e * evaluator ) expandBlockForEaches (blocks terraform.Blocks , isDynamic bool ) terraform.Blocks {
274
242
var forEachFiltered terraform.Blocks
275
243
276
244
for _ , block := range blocks {
277
245
278
246
forEachAttr := block .GetAttribute ("for_each" )
279
247
280
- if forEachAttr .IsNil () || block .IsCountExpanded () || ! isBlockSupportsForEachMetaArgument (block ) {
248
+ if forEachAttr .IsNil () || block .IsExpanded () || ! isBlockSupportsForEachMetaArgument (block ) {
281
249
forEachFiltered = append (forEachFiltered , block )
282
250
continue
283
251
}
284
252
285
253
forEachVal := forEachAttr .Value ()
286
254
287
- if err := validateForEachArg (forEachVal ); err != nil {
288
- e .debug .Log (`"for_each" argument is invalid: %s` , err .Error ())
255
+ if forEachVal .IsNull () || ! forEachVal .IsKnown () || ! forEachAttr .IsIterable () {
289
256
continue
290
257
}
291
258
292
259
clones := make (map [string ]cty.Value )
293
260
_ = forEachAttr .Each (func (key cty.Value , val cty.Value ) {
294
261
295
- if ! key .Type ().Equals (cty .String ) {
262
+ // instances are identified by a map key (or set member) from the value provided to for_each
263
+ idx , err := convert .Convert (key , cty .String )
264
+ if err != nil {
296
265
e .debug .Log (
297
266
`Invalid "for-each" argument: map key (or set value) is not a string, but %s` ,
298
267
key .Type ().FriendlyName (),
299
268
)
300
269
return
301
270
}
302
271
303
- clone := block .Clone (key )
272
+ // if the argument is a collection but not a map, then the resource identifier
273
+ // is the value of the collection. The exception is the use of for-each inside a dynamic block,
274
+ // because in this case the collection element may not be a primitive value.
275
+ if (forEachVal .Type ().IsCollectionType () || forEachVal .Type ().IsTupleType ()) &&
276
+ ! forEachVal .Type ().IsMapType () && ! isDynamic {
277
+ stringVal , err := convert .Convert (val , cty .String )
278
+ if err != nil {
279
+ e .debug .Log ("Failed to convert for-each arg %v to string" , val )
280
+ return
281
+ }
282
+ idx = stringVal
283
+ }
284
+
285
+ clone := block .Clone (idx )
304
286
305
287
ctx := clone .Context ()
306
288
307
289
e .copyVariables (block , clone )
308
290
309
- ctx .SetByDot (key , "each.key" )
291
+ ctx .SetByDot (idx , "each.key" )
310
292
ctx .SetByDot (val , "each.value" )
311
-
312
- ctx .Set (key , block .TypeLabel (), "key" )
293
+ ctx .Set (idx , block .TypeLabel (), "key" )
313
294
ctx .Set (val , block .TypeLabel (), "value" )
314
295
315
296
forEachFiltered = append (forEachFiltered , clone )
316
297
317
298
values := clone .Values ()
318
- clones [key .AsString ()] = values
299
+ clones [idx .AsString ()] = values
319
300
e .ctx .SetByDot (values , clone .GetMetadata ().Reference ())
320
301
})
321
302
@@ -341,7 +322,7 @@ func (e *evaluator) expandBlockCounts(blocks terraform.Blocks) terraform.Blocks
341
322
var countFiltered terraform.Blocks
342
323
for _ , block := range blocks {
343
324
countAttr := block .GetAttribute ("count" )
344
- if countAttr .IsNil () || block .IsCountExpanded () || ! isBlockSupportsCountMetaArgument (block ) {
325
+ if countAttr .IsNil () || block .IsExpanded () || ! isBlockSupportsCountMetaArgument (block ) {
345
326
countFiltered = append (countFiltered , block )
346
327
continue
347
328
}
0 commit comments