44 "context"
55 "errors"
66 "io/fs"
7+ "maps"
78 "reflect"
89 "slices"
910
@@ -548,7 +549,6 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value {
548549 values := make (map [string ]cty.Value )
549550
550551 for _ , b := range blocksOfType {
551-
552552 switch b .Type () {
553553 case "variable" : // variables are special in that their value comes from the "default" attribute
554554 val , err := e .evaluateVariable (b )
@@ -563,9 +563,7 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value {
563563 }
564564 values [b .Label ()] = val
565565 case "locals" , "moved" , "import" :
566- for key , val := range b .Values ().AsValueMap () {
567- values [key ] = val
568- }
566+ maps .Copy (values , b .Values ().AsValueMap ())
569567 case "provider" , "module" , "check" :
570568 if b .Label () == "" {
571569 continue
@@ -576,19 +574,27 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value {
576574 continue
577575 }
578576
579- blockMap , ok := values [b .Labels ()[0 ]]
577+ // Data blocks should all be loaded into the top level 'values'
578+ // object. The hierarchy of the map is:
579+ // values = map[<type>]map[<name>] =
580+ // Block -> Block's attributes as a cty.Object
581+ // Tuple(Block) -> Instances of the block
582+ // Object(Block) -> Field values are instances of the block
583+ ref := b .Reference ()
584+ typeValues , ok := values [ref .TypeLabel ()]
580585 if ! ok {
581- values [ b . Labels ()[ 0 ]] = cty .ObjectVal (make (map [string ]cty.Value ))
582- blockMap = values [b . Labels ()[ 0 ]]
586+ typeValues = cty .ObjectVal (make (map [string ]cty.Value ))
587+ values [ref . TypeLabel ()] = typeValues
583588 }
584589
585- valueMap := blockMap .AsValueMap ()
590+ valueMap := typeValues .AsValueMap ()
586591 if valueMap == nil {
587592 valueMap = make (map [string ]cty.Value )
588593 }
594+ valueMap [ref .NameLabel ()] = blockInstanceValues (b , valueMap )
589595
590- valueMap [ b . Labels ()[ 1 ]] = b . Values ()
591- values [b . Labels ()[ 0 ] ] = cty .ObjectVal (valueMap )
596+ // Update the map of all blocks with the same type.
597+ values [ref . TypeLabel () ] = cty .ObjectVal (valueMap )
592598 }
593599 }
594600
@@ -599,23 +605,57 @@ func (e *evaluator) getResources() map[string]cty.Value {
599605 values := make (map [string ]map [string ]cty.Value )
600606
601607 for _ , b := range e .blocks {
602- if b .Type () != "resource" {
603- continue
604- }
605-
606- if len (b .Labels ()) < 2 {
608+ if b .Type () != "resource" || len (b .Labels ()) < 2 {
607609 continue
608610 }
609611
610- val , exists := values [b .Labels ()[0 ]]
612+ ref := b .Reference ()
613+ typeValues , exists := values [ref .TypeLabel ()]
611614 if ! exists {
612- val = make (map [string ]cty.Value )
613- values [b . Labels ()[ 0 ]] = val
615+ typeValues = make (map [string ]cty.Value )
616+ values [ref . TypeLabel ()] = typeValues
614617 }
615- val [ b . Labels ()[ 1 ]] = b . Values ( )
618+ typeValues [ ref . NameLabel ()] = blockInstanceValues ( b , typeValues )
616619 }
617620
618621 return lo .MapValues (values , func (v map [string ]cty.Value , _ string ) cty.Value {
619622 return cty .ObjectVal (v )
620623 })
621624}
625+
626+ // blockInstanceValues returns a cty.Value containing the values of the block instances.
627+ // If the count argument is used, a tuple is returned where the index corresponds to the argument index.
628+ // If the for_each argument is used, an object is returned where the key corresponds to the argument key.
629+ // In other cases, the values of the block itself are returned.
630+ func blockInstanceValues (b * terraform.Block , typeValues map [string ]cty.Value ) cty.Value {
631+ ref := b .Reference ()
632+ key := ref .RawKey ()
633+
634+ switch {
635+ case key .Type ().Equals (cty .Number ) && b .GetAttribute ("count" ) != nil :
636+ idx , _ := key .AsBigFloat ().Int64 ()
637+ return insertTupleElement (typeValues [ref .NameLabel ()], int (idx ), b .Values ())
638+ case isForEachKey (key ) && b .GetAttribute ("for_each" ) != nil :
639+ keyStr := ref .Key ()
640+
641+ instancesVal , exists := typeValues [ref .NameLabel ()]
642+ if ! exists || ! instancesVal .CanIterateElements () {
643+ instancesVal = cty .EmptyObjectVal
644+ }
645+
646+ instances := instancesVal .AsValueMap ()
647+ if instances == nil {
648+ instances = make (map [string ]cty.Value )
649+ }
650+
651+ instances [keyStr ] = b .Values ()
652+ return cty .ObjectVal (instances )
653+
654+ default :
655+ return b .Values ()
656+ }
657+ }
658+
659+ func isForEachKey (key cty.Value ) bool {
660+ return key .Type ().Equals (cty .Number ) || key .Type ().Equals (cty .String )
661+ }
0 commit comments