@@ -3,7 +3,6 @@ package parser
33import (
44 "context"
55 "errors"
6- "fmt"
76 "io/fs"
87 "reflect"
98 "time"
@@ -148,7 +147,8 @@ func (e *evaluator) EvaluateAll(ctx context.Context) (terraform.Modules, map[str
148147 }
149148 }
150149
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)
152152 e .blocks = e .expandBlocks (e .blocks )
153153 e .blocks = e .expandBlocks (e .blocks )
154154
@@ -204,7 +204,7 @@ func (e *evaluator) isModuleLocal() bool {
204204}
205205
206206func (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 )... )
208208}
209209
210210func (e * evaluator ) expandDynamicBlocks (blocks ... * terraform.Block ) terraform.Blocks {
@@ -219,103 +219,84 @@ func (e *evaluator) expandDynamicBlock(b *terraform.Block) {
219219 e .expandDynamicBlock (sub )
220220 }
221221 for _ , sub := range b .AllBlocks ().OfType ("dynamic" ) {
222+ if sub .IsExpanded () {
223+ continue
224+ }
222225 blockName := sub .TypeLabel ()
223- expanded := e .expandBlockForEaches (terraform.Blocks {sub })
226+ expanded := e .expandBlockForEaches (terraform.Blocks {sub }, true )
224227 for _ , ex := range expanded {
225228 if content := ex .GetBlock ("content" ); content .IsNotNil () {
226229 _ = e .expandDynamicBlocks (content )
227230 b .InjectBlock (content , blockName )
228231 }
229232 }
233+ sub .MarkExpanded ()
230234 }
231235}
232236
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-
269237func isBlockSupportsForEachMetaArgument (block * terraform.Block ) bool {
270238 return slices .Contains ([]string {"module" , "resource" , "data" , "dynamic" }, block .Type ())
271239}
272240
273- func (e * evaluator ) expandBlockForEaches (blocks terraform.Blocks ) terraform.Blocks {
241+ func (e * evaluator ) expandBlockForEaches (blocks terraform.Blocks , isDynamic bool ) terraform.Blocks {
274242 var forEachFiltered terraform.Blocks
275243
276244 for _ , block := range blocks {
277245
278246 forEachAttr := block .GetAttribute ("for_each" )
279247
280- if forEachAttr .IsNil () || block .IsCountExpanded () || ! isBlockSupportsForEachMetaArgument (block ) {
248+ if forEachAttr .IsNil () || block .IsExpanded () || ! isBlockSupportsForEachMetaArgument (block ) {
281249 forEachFiltered = append (forEachFiltered , block )
282250 continue
283251 }
284252
285253 forEachVal := forEachAttr .Value ()
286254
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 () {
289256 continue
290257 }
291258
292259 clones := make (map [string ]cty.Value )
293260 _ = forEachAttr .Each (func (key cty.Value , val cty.Value ) {
294261
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 {
296265 e .debug .Log (
297266 `Invalid "for-each" argument: map key (or set value) is not a string, but %s` ,
298267 key .Type ().FriendlyName (),
299268 )
300269 return
301270 }
302271
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 )
304286
305287 ctx := clone .Context ()
306288
307289 e .copyVariables (block , clone )
308290
309- ctx .SetByDot (key , "each.key" )
291+ ctx .SetByDot (idx , "each.key" )
310292 ctx .SetByDot (val , "each.value" )
311-
312- ctx .Set (key , block .TypeLabel (), "key" )
293+ ctx .Set (idx , block .TypeLabel (), "key" )
313294 ctx .Set (val , block .TypeLabel (), "value" )
314295
315296 forEachFiltered = append (forEachFiltered , clone )
316297
317298 values := clone .Values ()
318- clones [key .AsString ()] = values
299+ clones [idx .AsString ()] = values
319300 e .ctx .SetByDot (values , clone .GetMetadata ().Reference ())
320301 })
321302
@@ -341,7 +322,7 @@ func (e *evaluator) expandBlockCounts(blocks terraform.Blocks) terraform.Blocks
341322 var countFiltered terraform.Blocks
342323 for _ , block := range blocks {
343324 countAttr := block .GetAttribute ("count" )
344- if countAttr .IsNil () || block .IsCountExpanded () || ! isBlockSupportsCountMetaArgument (block ) {
325+ if countAttr .IsNil () || block .IsExpanded () || ! isBlockSupportsCountMetaArgument (block ) {
345326 countFiltered = append (countFiltered , block )
346327 continue
347328 }
0 commit comments