Fixed the mismatch between arguments sent to the for_each evaluator (#3564)

Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
Signed-off-by: Diogenes Fernandes <diofeher@gmail.com>
Co-authored-by: Diogenes Fernandes <diofeher@gmail.com>
This commit is contained in:
Andrei Ciobanu
2025-12-10 17:03:06 +02:00
committed by GitHub
parent b2c6b935e0
commit 0fceb5ef85
2 changed files with 70 additions and 2 deletions

View File

@@ -121,11 +121,11 @@ func (ri *ImportResolver) ValidateImportIDs(ctx context.Context, importTarget *I
rootCtx := evalCtx.WithPath(addrs.RootModuleInstance)
if importTarget.Config.ForEach != nil {
const unknownsNotAllowed = false
const unknownsAllowed = true
const tupleAllowed = true
// The import target has a for_each attribute, so we need to expand it
forEachVal, evalDiags := evaluateForEachExpressionValue(ctx, importTarget.Config.ForEach, rootCtx, unknownsNotAllowed, tupleAllowed, nil)
forEachVal, evalDiags := evaluateForEachExpressionValue(ctx, importTarget.Config.ForEach, rootCtx, unknownsAllowed, tupleAllowed, nil)
diags = diags.Append(evalDiags)
if diags.HasErrors() {
return diags
@@ -134,6 +134,9 @@ func (ri *ImportResolver) ValidateImportIDs(ctx context.Context, importTarget *I
// We are building an instances.RepetitionData based on each for_each key and val combination
var repetitions []instances.RepetitionData
if !forEachVal.IsKnown() {
return diags
}
it := forEachVal.ElementIterator()
for it.Next() {
k, v := it.Element()

View File

@@ -2576,3 +2576,68 @@ resource "test_object" "t" {
t.Fatalf("expected error, got: %q\n", diags.Err().Error())
}
}
func TestContext2Validate_importWithForEachOnUnknown(t *testing.T) {
// This tests checks that a validate run works correctly when the import block is configured with a
// for_each statement on unknown values.
// In this case, the validation is skipped since the expansion cannot be performed.
// Related to https://github.com/opentofu/opentofu/issues/3563
m := testModuleInline(t, map[string]string{
"main.tf": `
variable "server_ids" {
type = list(string)
default = ["one", "two"]
}
resource "test_object" "a" {
test_string = "foo"
count = 2
}
import {
to = test_object.a[tonumber(each.key)]
id = each.value
for_each = {
for idx, item in var.server_ids: idx => item
}
}
`,
})
p := simpleMockProvider()
hook := new(MockHook)
ctx := testContext2(t, &ContextOpts{
Hooks: []Hook{hook},
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
p.ReadResourceFn = func(request providers.ReadResourceRequest) providers.ReadResourceResponse {
return providers.ReadResourceResponse{
NewState: cty.ObjectVal(map[string]cty.Value{
"test_string": cty.StringVal("foo"),
"test_number": cty.NullVal(cty.Number),
"test_bool": cty.NullVal(cty.Bool),
"test_list": cty.NullVal(cty.List(cty.String)),
"test_map": cty.NullVal(cty.Map(cty.String)),
}),
}
}
p.ImportResourceStateFn = func(request providers.ImportResourceStateRequest) providers.ImportResourceStateResponse {
return providers.ImportResourceStateResponse{
ImportedResources: []providers.ImportedResource{
{
TypeName: "test_object",
State: cty.ObjectVal(map[string]cty.Value{
"test_string": cty.StringVal("foo"),
"test_number": cty.NullVal(cty.Number),
"test_bool": cty.NullVal(cty.Bool),
"test_list": cty.NullVal(cty.List(cty.String)),
"test_map": cty.NullVal(cty.Map(cty.String)),
}),
},
},
}
}
diags := ctx.Validate(context.Background(), m)
if diags.HasErrors() {
t.Fatalf("unexpected errors\n%s", diags.Err().Error())
}
}