mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
[Backport][v1.10] for_each inside dynamic blocks can now call provider-defined functions (#3442)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
This commit is contained in:
@@ -4,6 +4,7 @@ BUG FIXES:
|
|||||||
|
|
||||||
* Fix crash in tofu test when using deprecated outputs ([#3249](https://github.com/opentofu/opentofu/pull/3249))
|
* Fix crash in tofu test when using deprecated outputs ([#3249](https://github.com/opentofu/opentofu/pull/3249))
|
||||||
* Fix missing provider functions when parentheses are used ([#3402](https://github.com/opentofu/opentofu/pull/3402))
|
* Fix missing provider functions when parentheses are used ([#3402](https://github.com/opentofu/opentofu/pull/3402))
|
||||||
|
* `for_each` inside `dynamic` blocks can now call provider-defined functions. ([#3429](https://github.com/opentofu/opentofu/issues/3429))
|
||||||
|
|
||||||
## 1.10.6
|
## 1.10.6
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ func (s *Scope) ExpandBlock(body hcl.Body, schema *configschema.Block) (hcl.Body
|
|||||||
spec := schema.DecoderSpec()
|
spec := schema.DecoderSpec()
|
||||||
|
|
||||||
traversals := dynblock.ExpandVariablesHCLDec(body, spec)
|
traversals := dynblock.ExpandVariablesHCLDec(body, spec)
|
||||||
|
// using ExpandFunctionsHCLDec to extract strictly the functions that are referenced inside the `dynamic`
|
||||||
|
// block, since that is what is needed to be injected into the expansion evalCtx for the expansion to work
|
||||||
|
traversals = append(traversals, filterProviderFunctions(dynblock.ExpandFunctionsHCLDec(body, spec))...)
|
||||||
refs, diags := References(s.ParseRef, traversals)
|
refs, diags := References(s.ParseRef, traversals)
|
||||||
|
|
||||||
ctx, ctxDiags := s.EvalContext(refs)
|
ctx, ctxDiags := s.EvalContext(refs)
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -5625,3 +5627,110 @@ check "http_check" {
|
|||||||
t.Fatal(diags.Err())
|
t.Fatal(diags.Err())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestContext2Apply_callingProviderFunctionFromDynamicBlock checks that a
|
||||||
|
// provider function can be used by referencing it in a dynamic block inside
|
||||||
|
// a resource.
|
||||||
|
func TestContext2Apply_callingProviderFunctionFromDynamicBlock(t *testing.T) {
|
||||||
|
m := testModuleInline(t, map[string]string{
|
||||||
|
"main.tf": `
|
||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
test = {
|
||||||
|
source = "example.com/foo/test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
urls = ["foo:80", "bar:81"]
|
||||||
|
}
|
||||||
|
resource "test_resource" "res" {
|
||||||
|
dynamic "allow" {
|
||||||
|
iterator = item
|
||||||
|
for_each = {
|
||||||
|
for z in local.urls :
|
||||||
|
z => provider::test::extract_port(z) if contains([80, 81], provider::test::extract_port(z))
|
||||||
|
}
|
||||||
|
content {
|
||||||
|
port = item.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
|
||||||
|
p := MockProvider{}
|
||||||
|
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||||
|
ResourceTypes: map[string]providers.Schema{
|
||||||
|
"test_resource": {
|
||||||
|
Block: &configschema.Block{
|
||||||
|
BlockTypes: map[string]*configschema.NestedBlock{
|
||||||
|
"allow": {
|
||||||
|
Nesting: configschema.NestingList,
|
||||||
|
Block: configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"port": {
|
||||||
|
Type: cty.Number,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Functions: map[string]providers.FunctionSpec{
|
||||||
|
"extract_port": {
|
||||||
|
Parameters: []providers.FunctionParameterSpec{
|
||||||
|
{
|
||||||
|
Name: "in",
|
||||||
|
Type: cty.String,
|
||||||
|
AllowNullValue: false,
|
||||||
|
AllowUnknownValues: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Return: cty.Number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p.CallFunctionFn = func(request providers.CallFunctionRequest) providers.CallFunctionResponse {
|
||||||
|
// Since there is only a single function defined, we don't want to make this implementation more complex than
|
||||||
|
// needed, so we have only the implementation for that.
|
||||||
|
v := request.Arguments[0].AsString()
|
||||||
|
idx := strings.LastIndex(v, ":")
|
||||||
|
if idx >= 0 {
|
||||||
|
v = v[idx+1:]
|
||||||
|
}
|
||||||
|
port, err := strconv.ParseFloat(v, 64)
|
||||||
|
if err != nil {
|
||||||
|
return providers.CallFunctionResponse{
|
||||||
|
Error: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return providers.CallFunctionResponse{
|
||||||
|
Result: cty.NumberVal(big.NewFloat(port)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx := testContext2(t, &ContextOpts{
|
||||||
|
Providers: map[addrs.Provider]providers.Factory{
|
||||||
|
addrs.MustParseProviderSourceString("example.com/foo/test"): testProviderFuncFixed(&p),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assertState := func(t *testing.T, s *states.State) {
|
||||||
|
res := s.Resource(mustAbsResourceAddr("test_resource.res"))
|
||||||
|
diff := cmp.Diff(`{"allow":[{"port":81},{"port":80}]}`, string(res.Instances[addrs.NoKey].Current.AttrsJSON))
|
||||||
|
if diff != "" {
|
||||||
|
t.Fatalf("wrong expected resource change found (-wanted, +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
plan, diags := ctx.Plan(context.Background(), m, states.NewState(), SimplePlanOpts(plans.NormalMode, nil))
|
||||||
|
assertNoErrors(t, diags)
|
||||||
|
assertState(t, plan.PlannedState)
|
||||||
|
|
||||||
|
state, diags := ctx.Apply(context.Background(), plan, m)
|
||||||
|
assertNoErrors(t, diags)
|
||||||
|
assertState(t, state)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user