Skip to content

Commit 7864bb0

Browse files
authored
Pass PlannedPrivate from PlanResourceChange to ApplyResourceChange (#2407)
The `PlannedPrivate` field of the PRC response includes metadata that's required for correctly re-assembling the diff. For example, it includes `NewExtra` which includes the original value for attributes with a `StateFunc`. Without `PlannedPrivate`, those attributes get saved to state encoded twice. This can lead to mangled data in state and perma-diffs. Fixes #2408
1 parent 69fe479 commit 7864bb0

File tree

3 files changed

+99
-15
lines changed

3 files changed

+99
-15
lines changed

pkg/tests/cross-tests/input_cross_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,3 +601,28 @@ func TestCreateDoesNotPanicWithStateUpgraders(t *testing.T) {
601601
},
602602
})
603603
}
604+
605+
// TestStateFunc ensures that resources with a StateFunc set on their schema are correctly handled. This includes
606+
// ensuring that the PlannedPrivate blob is passed from PlanResourceChange to ApplyResourceChange. If this is passed
607+
// correctly, the provider will see the original value of the field, rather than the value that was produced by the StateFunc.
608+
func TestStateFunc(t *testing.T) {
609+
input := "hello"
610+
611+
runCreateInputCheck(t, inputTestCase{
612+
Resource: &schema.Resource{
613+
Schema: map[string]*schema.Schema{
614+
"test": {
615+
Type: schema.TypeString,
616+
Optional: true,
617+
ForceNew: true,
618+
StateFunc: func(v interface{}) string {
619+
return v.(string) + " world"
620+
},
621+
},
622+
},
623+
},
624+
Config: map[string]any{
625+
"test": input,
626+
},
627+
})
628+
}

pkg/tests/schema_pulumi_test.go

100755100644
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3090,3 +3090,47 @@ resource "prov_test" "mainRes" {
30903090
})
30913091
}
30923092
}
3093+
3094+
func TestStateFunc(t *testing.T) {
3095+
resMap := map[string]*schema.Resource{
3096+
"prov_test": {
3097+
CreateContext: func(ctx context.Context, d *schema.ResourceData, i interface{}) diag.Diagnostics {
3098+
d.SetId("id")
3099+
var diags diag.Diagnostics
3100+
v, ok := d.GetOk("test")
3101+
assert.True(t, ok, "test property not set")
3102+
3103+
err := d.Set("test", v.(string)+" world")
3104+
require.NoError(t, err)
3105+
return diags
3106+
},
3107+
Schema: map[string]*schema.Schema{
3108+
"test": {
3109+
Type: schema.TypeString,
3110+
Optional: true,
3111+
ForceNew: true,
3112+
StateFunc: func(v interface{}) string {
3113+
return v.(string) + " world"
3114+
},
3115+
},
3116+
},
3117+
},
3118+
}
3119+
tfp := &schema.Provider{ResourcesMap: resMap}
3120+
bridgedProvider := pulcheck.BridgedProvider(t, "prov", tfp)
3121+
program := `
3122+
name: test
3123+
runtime: yaml
3124+
resources:
3125+
mainRes:
3126+
type: prov:index:Test
3127+
properties:
3128+
test: "hello"
3129+
outputs:
3130+
testOut: ${mainRes.test}
3131+
`
3132+
pt := pulcheck.PulCheck(t, bridgedProvider, program)
3133+
res := pt.Up()
3134+
require.Equal(t, "hello world", res.Outputs["testOut"].Value)
3135+
pt.Preview(optpreview.ExpectNoChanges())
3136+
}

pkg/tfshim/sdk-v2/provider2.go

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"maps"
78
"sort"
89
"strings"
910
"time"
@@ -111,8 +112,9 @@ func (s *v2InstanceState2) Meta() map[string]interface{} {
111112
type v2InstanceDiff2 struct {
112113
v2InstanceDiff
113114

114-
config cty.Value
115-
plannedState cty.Value
115+
config cty.Value
116+
plannedState cty.Value
117+
plannedPrivate map[string]interface{}
116118
}
117119

118120
func (d *v2InstanceDiff2) String() string {
@@ -129,7 +131,8 @@ func (d *v2InstanceDiff2) GoString() string {
129131
},
130132
config: %#v,
131133
plannedState: %#v,
132-
}`, d.v2InstanceDiff.tf, d.config, d.plannedState)
134+
plannedPrivate: %#v,
135+
}`, d.v2InstanceDiff.tf, d.config, d.plannedState, d.plannedPrivate)
133136
}
134137

135138
var _ shim.InstanceDiff = (*v2InstanceDiff2)(nil)
@@ -258,12 +261,14 @@ func (p *planResourceChangeImpl) Diff(
258261
if err != nil {
259262
return nil, err
260263
}
264+
261265
return &v2InstanceDiff2{
262266
v2InstanceDiff: v2InstanceDiff{
263267
tf: plan.PlannedDiff,
264268
},
265-
config: cfg,
266-
plannedState: plan.PlannedState,
269+
config: cfg,
270+
plannedState: plan.PlannedState,
271+
plannedPrivate: plan.PlannedPrivate,
267272
}, nil
268273
}
269274

@@ -283,7 +288,17 @@ func (p *planResourceChangeImpl) Apply(
283288
}
284289
diff := p.unpackDiff(ty, d)
285290
cfg, st, pl := diff.config, state.stateValue, diff.plannedState
286-
priv := diff.v2InstanceDiff.tf.Meta
291+
292+
// Merge plannedPrivate and v2InstanceDiff.tf.Meta into a single map. This is necessary because
293+
// timeouts are stored in the Meta and not in plannedPrivate.
294+
priv := make(map[string]interface{})
295+
if len(diff.plannedPrivate) > 0 {
296+
maps.Copy(priv, diff.plannedPrivate)
297+
}
298+
if len(diff.v2InstanceDiff.tf.Meta) > 0 {
299+
maps.Copy(priv, diff.v2InstanceDiff.tf.Meta)
300+
}
301+
287302
resp, err := p.server.ApplyResourceChange(ctx, t, ty, cfg, st, pl, priv, meta)
288303
if err != nil {
289304
return nil, err
@@ -475,9 +490,9 @@ func (s *grpcServer) PlanResourceChange(
475490
providerMeta *cty.Value,
476491
ignores shim.IgnoreChanges,
477492
) (*struct {
478-
PlannedState cty.Value
479-
PlannedMeta map[string]interface{}
480-
PlannedDiff *terraform.InstanceDiff
493+
PlannedState cty.Value
494+
PlannedPrivate map[string]interface{}
495+
PlannedDiff *terraform.InstanceDiff
481496
}, error) {
482497
configVal, err := msgpack.Marshal(config, ty)
483498
if err != nil {
@@ -548,13 +563,13 @@ func (s *grpcServer) PlanResourceChange(
548563
}
549564
}
550565
return &struct {
551-
PlannedState cty.Value
552-
PlannedMeta map[string]interface{}
553-
PlannedDiff *terraform.InstanceDiff
566+
PlannedState cty.Value
567+
PlannedPrivate map[string]interface{}
568+
PlannedDiff *terraform.InstanceDiff
554569
}{
555-
PlannedState: plannedState,
556-
PlannedMeta: meta,
557-
PlannedDiff: resp.InstanceDiff,
570+
PlannedState: plannedState,
571+
PlannedPrivate: meta,
572+
PlannedDiff: resp.InstanceDiff,
558573
}, nil
559574
}
560575

0 commit comments

Comments
 (0)