diff --git a/pkg/iac/scanners/terraform/parser/evaluator.go b/pkg/iac/scanners/terraform/parser/evaluator.go index dd78659711..ca1195b0d7 100644 --- a/pkg/iac/scanners/terraform/parser/evaluator.go +++ b/pkg/iac/scanners/terraform/parser/evaluator.go @@ -137,6 +137,8 @@ func (e *evaluator) EvaluateAll(ctx context.Context) (terraform.Modules, map[str // expand out resources and modules via count, for-each and dynamic // (not a typo, we do this twice so every order is processed) + // TODO: using a module in for_each or count does not work, + // because the child module is evaluated later e.blocks = e.expandBlocks(e.blocks) e.blocks = e.expandBlocks(e.blocks) @@ -239,10 +241,17 @@ func (e *evaluator) evaluateSubmodule(ctx context.Context, sm *submodule) bool { sm.modules, sm.fsMap = sm.eval.EvaluateAll(ctx) outputs := sm.eval.exportOutputs() + valueMap := e.ctx.Get("module").AsValueMap() + if valueMap == nil { + valueMap = make(map[string]cty.Value) + } + // lastState needs to be captured after applying outputs – so that they // don't get treated as changes – but before running post-submodule // evaluation, so that changes from that can trigger re-evaluations of // the submodule if/when they feed back into inputs. + ref := sm.definition.Definition.Reference() + e.ctx.Set(blockInstanceValues(sm.definition.Definition, valueMap, outputs), "module", ref.NameLabel()) e.ctx.Set(outputs, "module", sm.definition.Name) sm.lastState = sm.definition.inputVars() e.evaluateSteps() @@ -564,7 +573,7 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value { if valueMap == nil { valueMap = make(map[string]cty.Value) } - valueMap[ref.NameLabel()] = blockInstanceValues(b, valueMap) + valueMap[ref.NameLabel()] = blockInstanceValues(b, valueMap, b.Values()) // Update the map of all blocks with the same type. values[ref.TypeLabel()] = cty.ObjectVal(valueMap) @@ -588,7 +597,7 @@ func (e *evaluator) getResources() map[string]cty.Value { typeValues = make(map[string]cty.Value) values[ref.TypeLabel()] = typeValues } - typeValues[ref.NameLabel()] = blockInstanceValues(b, typeValues) + typeValues[ref.NameLabel()] = blockInstanceValues(b, typeValues, b.Values()) } return lo.MapValues(values, func(v map[string]cty.Value, _ string) cty.Value { @@ -600,14 +609,14 @@ func (e *evaluator) getResources() map[string]cty.Value { // If the count argument is used, a tuple is returned where the index corresponds to the argument index. // If the for_each argument is used, an object is returned where the key corresponds to the argument key. // In other cases, the values of the block itself are returned. -func blockInstanceValues(b *terraform.Block, typeValues map[string]cty.Value) cty.Value { +func blockInstanceValues(b *terraform.Block, typeValues map[string]cty.Value, values cty.Value) cty.Value { ref := b.Reference() key := ref.RawKey() switch { case key.Type().Equals(cty.Number) && b.GetAttribute("count") != nil: idx, _ := key.AsBigFloat().Int64() - return insertTupleElement(typeValues[ref.NameLabel()], int(idx), b.Values()) + return insertTupleElement(typeValues[ref.NameLabel()], int(idx), values) case isForEachKey(key) && b.GetAttribute("for_each") != nil: keyStr := ref.Key() @@ -621,11 +630,10 @@ func blockInstanceValues(b *terraform.Block, typeValues map[string]cty.Value) ct instances = make(map[string]cty.Value) } - instances[keyStr] = b.Values() + instances[keyStr] = values return cty.ObjectVal(instances) - default: - return b.Values() + return values } } diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go index a2b30a94b3..c8ab1738e3 100644 --- a/pkg/iac/scanners/terraform/parser/parser_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -1707,11 +1707,13 @@ resource "test_resource" "this" { func TestPopulateContextWithBlockInstances(t *testing.T) { tests := []struct { - name string - files map[string]string + name string + blockType string + files map[string]string }{ { - name: "data blocks with count", + name: "data blocks with count", + blockType: "data", files: map[string]string{ "main.tf": `data "d" "foo" { count = 1 @@ -1730,7 +1732,8 @@ data "c" "foo" { }, }, { - name: "resource blocks with count", + name: "resource blocks with count", + blockType: "resource", files: map[string]string{ "main.tf": `resource "d" "foo" { count = 1 @@ -1749,7 +1752,33 @@ resource "c" "foo" { }, }, { - name: "data blocks with for_each", + name: "module block with count", + blockType: "data", + files: map[string]string{ + "main.tf": `module "a" { + source = "./modules/a" + count = 2 + inp = "Index ${count.index}" +} + +data "b" "foo" { + count = 1 + value = module.a[0].value +} + +data "c" "foo" { + count = 1 + value = data.b.foo[0].value +}`, + "modules/a/main.tf": `variable "inp" {} +output "value" { + value = var.inp +}`, + }, + }, + { + name: "data blocks with for_each", + blockType: "data", files: map[string]string{ "main.tf": `data "d" "foo" { for_each = toset([0]) @@ -1768,7 +1797,8 @@ data "c" "foo" { }, }, { - name: "resource blocks with for_each", + name: "resource blocks with for_each", + blockType: "resource", files: map[string]string{ "main.tf": `resource "d" "foo" { for_each = toset([0]) @@ -1783,6 +1813,25 @@ resource "b" "foo" { resource "c" "foo" { for_each = b.foo value = each.value.value +}`, + }, + }, + { + name: "module block with for_each", + blockType: "data", + files: map[string]string{ + "main.tf": `module "a" { + for_each = toset([0]) + source = "./modules/a" + inp = "Index ${each.key}" +} + +data "b" "foo" { + value = module.a["0"].value +}`, + "modules/a/main.tf": `variable "inp" {} +output "value" { + value = var.inp }`, }, }, @@ -1791,8 +1840,8 @@ resource "c" "foo" { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { modules := parse(t, tt.files) - require.Len(t, modules, 1) - for _, b := range modules.GetBlocks() { + require.GreaterOrEqual(t, len(modules), 1) + for _, b := range modules.GetBlocks().OfType(tt.blockType) { attr := b.GetAttribute("value") assert.Equal(t, "Index 0", attr.Value().AsString()) }