fix(misconf): populate context correctly for module instances (#8656)

Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
This commit is contained in:
Nikita Pivkin
2025-04-12 09:20:11 +06:00
committed by GitHub
parent b7dfd64987
commit efd177b300
2 changed files with 72 additions and 15 deletions

View File

@@ -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
}
}

View File

@@ -1708,10 +1708,12 @@ func TestPopulateContextWithBlockInstances(t *testing.T) {
tests := []struct {
name string
blockType string
files map[string]string
}{
{
name: "data blocks with count",
blockType: "data",
files: map[string]string{
"main.tf": `data "d" "foo" {
count = 1
@@ -1731,6 +1733,7 @@ data "c" "foo" {
},
{
name: "resource blocks with count",
blockType: "resource",
files: map[string]string{
"main.tf": `resource "d" "foo" {
count = 1
@@ -1745,11 +1748,37 @@ resource "b" "foo" {
resource "c" "foo" {
count = 1
value = b.foo[0].value
}`,
},
},
{
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])
@@ -1769,6 +1798,7 @@ data "c" "foo" {
},
{
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())
}