Skip to content

Commit 22452e6

Browse files
Separate diff decision from presentation (#2379)
Adds a `DiffEqualDecisionOverride` to the shim layer which allows provider implementations to specify a diff decision instead of relying on the shim layer to do that. Also adds a `DiffEqualDecisionOverride` to the PRC sdkv2 implementation which opentofu does. Also adds a feature flag `EnableAccurateBridgePreview` which guards this feature. will fix once rolled out: #2293 will fix once rolled out: #1501 will undo once rolled out: #1502 as the underlying issue was fixed during the PRC work. Stacked on #2380
1 parent 1cb7594 commit 22452e6

File tree

8 files changed

+110
-32
lines changed

8 files changed

+110
-32
lines changed

pkg/tfbridge/diff_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2077,7 +2077,7 @@ func TestListNestedAddMaxItemsOne(t *testing.T) {
20772077
}
20782078

20792079
type diffTestCase struct {
2080-
resourceSchema map[string]*schema.Schema
2080+
resourceSchema map[string]*v2Schema.Schema
20812081
resourceFields map[string]*SchemaInfo
20822082
state resource.PropertyMap
20832083
inputs resource.PropertyMap
@@ -2088,11 +2088,11 @@ type diffTestCase struct {
20882088

20892089
func diffTest2(t *testing.T, tc diffTestCase) {
20902090
ctx := context.Background()
2091-
res := &schema.Resource{
2091+
res := &v2Schema.Resource{
20922092
Schema: tc.resourceSchema,
20932093
}
2094-
provider := shimv1.NewProvider(&schema.Provider{
2095-
ResourcesMap: map[string]*schema.Resource{
2094+
provider := shimv2.NewProvider(&v2Schema.Provider{
2095+
ResourcesMap: map[string]*v2Schema.Resource{
20962096
"p_resource": res,
20972097
},
20982098
})
@@ -2130,21 +2130,21 @@ func diffTest2(t *testing.T, tc diffTestCase) {
21302130
}
21312131

21322132
func TestChangingMaxItems1FilterProperty(t *testing.T) {
2133-
schema := map[string]*schema.Schema{
2133+
schema := map[string]*v2Schema.Schema{
21342134
"rule": {
2135-
Type: schema.TypeList,
2135+
Type: v2Schema.TypeList,
21362136
Required: true,
21372137
MaxItems: 1000,
2138-
Elem: &schema.Resource{
2139-
Schema: map[string]*schema.Schema{
2138+
Elem: &v2Schema.Resource{
2139+
Schema: map[string]*v2Schema.Schema{
21402140
"filter": {
2141-
Type: schema.TypeList,
2141+
Type: v2Schema.TypeList,
21422142
Optional: true,
21432143
MaxItems: 1,
2144-
Elem: &schema.Resource{
2145-
Schema: map[string]*schema.Schema{
2144+
Elem: &v2Schema.Resource{
2145+
Schema: map[string]*v2Schema.Schema{
21462146
"prefix": {
2147-
Type: schema.TypeString,
2147+
Type: v2Schema.TypeString,
21482148
Optional: true,
21492149
},
21502150
},

pkg/tfbridge/info/info.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ type Provider struct {
159159
// EnableZeroDefaultSchemaVersion makes the provider default
160160
// to version 0 when no version is specified in the state of a resource.
161161
EnableZeroDefaultSchemaVersion bool
162+
// EnableAccurateBridgePreview makes the bridge use an experimental feature
163+
// to generate more accurate diffs and previews for resources
164+
EnableAccurateBridgePreview bool
162165
}
163166

164167
// HclExampler represents a supplemental HCL example for a given resource or function.

pkg/tfbridge/provider.go

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ import (
5656
)
5757

5858
type providerOptions struct {
59-
defaultZeroSchemaVersion bool
59+
defaultZeroSchemaVersion bool
60+
enableAccurateBridgePreview bool
6061
}
6162

6263
type providerOption func(providerOptions) (providerOptions, error)
@@ -68,6 +69,13 @@ func WithDefaultZeroSchemaVersion() providerOption { //nolint:revive
6869
}
6970
}
7071

72+
func withAccurateBridgePreview() providerOption {
73+
return func(opts providerOptions) (providerOptions, error) {
74+
opts.enableAccurateBridgePreview = true
75+
return opts, nil
76+
}
77+
}
78+
7179
func getProviderOptions(opts []providerOption) (providerOptions, error) {
7280
res := providerOptions{}
7381
for _, o := range opts {
@@ -268,6 +276,11 @@ func newProvider(ctx context.Context, host *provider.HostClient,
268276
if info.EnableZeroDefaultSchemaVersion {
269277
opts = append(opts, WithDefaultZeroSchemaVersion())
270278
}
279+
280+
if info.EnableAccurateBridgePreview || cmdutil.IsTruthy(os.Getenv("PULUMI_TF_BRIDGE_ACCURATE_BRIDGE_PREVIEW")) {
281+
opts = append(opts, withAccurateBridgePreview())
282+
}
283+
271284
p := &Provider{
272285
host: host,
273286
module: module,
@@ -1148,15 +1161,28 @@ func (p *Provider) Diff(ctx context.Context, req *pulumirpc.DiffRequest) (*pulum
11481161
dd := makeDetailedDiffExtra(ctx, schema, fields, olds, news, diff)
11491162
detailedDiff, changes := dd.diffs, dd.changes
11501163

1151-
// There are some providers/situations which `makeDetailedDiff` distorts the expected changes, leading
1152-
// to changes being dropped by Pulumi.
1153-
// Until we fix `makeDetailedDiff`, it is safer to refer to the Terraform Diff attribute length for setting
1154-
// the DiffResponse.
1155-
// We will still use `detailedDiff` for diff display purposes.
1164+
if opts.enableAccurateBridgePreview {
1165+
if decision := diff.DiffEqualDecisionOverride(); decision != shim.DiffNoOverride {
1166+
if decision == shim.DiffOverrideNoUpdate {
1167+
changes = pulumirpc.DiffResponse_DIFF_NONE
1168+
} else {
1169+
changes = pulumirpc.DiffResponse_DIFF_SOME
1170+
}
1171+
}
1172+
} else {
1173+
// There are some providers/situations which `makeDetailedDiff` distorts the expected changes, leading
1174+
// to changes being dropped by Pulumi.
1175+
// Until we fix `makeDetailedDiff`, it is safer to refer to the Terraform Diff attribute length for setting
1176+
// the DiffResponse.
1177+
// We will still use `detailedDiff` for diff display purposes.
1178+
1179+
// See also https://github.com/pulumi/pulumi-terraform-bridge/issues/1501.
1180+
if !diff.HasNoChanges() {
1181+
changes = pulumirpc.DiffResponse_DIFF_SOME
1182+
}
1183+
}
11561184

1157-
// See also https://github.com/pulumi/pulumi-terraform-bridge/issues/1501.
1158-
if !diff.HasNoChanges() {
1159-
changes = pulumirpc.DiffResponse_DIFF_SOME
1185+
if changes == pulumirpc.DiffResponse_DIFF_SOME {
11601186
// Perhaps collectionDiffs can shed some light and locate the changes to the end-user.
11611187
for path, diff := range dd.collectionDiffs {
11621188
detailedDiff[path] = diff

pkg/tfshim/sdk-v1/instance_diff.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ type v1InstanceDiff struct {
4343
tf *terraform.InstanceDiff
4444
}
4545

46+
func (d v1InstanceDiff) DiffEqualDecisionOverride() shim.DiffOverride {
47+
return shim.DiffNoOverride
48+
}
49+
4650
func (d v1InstanceDiff) applyTimeoutOptions(opts shim.TimeoutOptions) {
4751
if opts.ResourceTimeout != nil {
4852
err := d.encodeTimeouts(opts.ResourceTimeout)

pkg/tfshim/sdk-v2/instance_diff.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ type v2InstanceDiff struct {
3333
tf *terraform.InstanceDiff
3434
}
3535

36+
func (d v2InstanceDiff) DiffEqualDecisionOverride() shim.DiffOverride {
37+
return shim.DiffNoOverride
38+
}
39+
3640
func (d v2InstanceDiff) applyTimeoutOptions(opts shim.TimeoutOptions) {
3741
// This method is no longer used with PlanResourceChange; we handle timeouts more directly.
3842
if opts.ResourceTimeout != nil {

pkg/tfshim/sdk-v2/provider2.go

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,10 @@ func (s *v2InstanceState2) Meta() map[string]interface{} {
112112
type v2InstanceDiff2 struct {
113113
v2InstanceDiff
114114

115-
config cty.Value
116-
plannedState cty.Value
117-
plannedPrivate map[string]interface{}
115+
config cty.Value
116+
plannedState cty.Value
117+
plannedPrivate map[string]interface{}
118+
diffEqualDecisionOverride shim.DiffOverride
118119
}
119120

120121
func (d *v2InstanceDiff2) String() string {
@@ -146,6 +147,10 @@ func (d *v2InstanceDiff2) ProposedState(
146147
}, nil
147148
}
148149

150+
func (d *v2InstanceDiff2) DiffEqualDecisionOverride() shim.DiffOverride {
151+
return d.diffEqualDecisionOverride
152+
}
153+
149154
// Provides PlanResourceChange handling for select resources.
150155
type planResourceChangeImpl struct {
151156
tf *schema.Provider
@@ -269,13 +274,31 @@ func (p *planResourceChangeImpl) Diff(
269274
TfToken: t,
270275
PlanState: plan.PlannedState,
271276
})
277+
278+
//nolint:lll
279+
// Taken from https://github.com/opentofu/opentofu/blob/864aa9d1d629090cfc4ddf9fdd344d34dee9793e/internal/tofu/node_resource_abstract_instance.go#L1024
280+
// We need to unmark the values to make sure Equals works.
281+
// Equals will return unknown if either value is unknown.
282+
// START
283+
unmarkedPrior, _ := st.UnmarkDeep()
284+
unmarkedPlan, _ := plannedState.UnmarkDeep()
285+
eqV := unmarkedPrior.Equals(unmarkedPlan)
286+
eq := eqV.IsKnown() && eqV.True()
287+
// END
288+
289+
diffOverride := shim.DiffOverrideUpdate
290+
if eq {
291+
diffOverride = shim.DiffOverrideNoUpdate
292+
}
293+
272294
return &v2InstanceDiff2{
273295
v2InstanceDiff: v2InstanceDiff{
274296
tf: plan.PlannedDiff,
275297
},
276-
config: cfg,
277-
plannedState: plannedState,
278-
plannedPrivate: plan.PlannedPrivate,
298+
config: cfg,
299+
plannedState: plannedState,
300+
diffEqualDecisionOverride: diffOverride,
301+
plannedPrivate: plan.PlannedPrivate,
279302
}, err
280303
}
281304

@@ -507,7 +530,8 @@ func (s *grpcServer) PlanResourceChange(
507530
PlannedState cty.Value
508531
PlannedPrivate map[string]interface{}
509532
PlannedDiff *terraform.InstanceDiff
510-
}, error) {
533+
}, error,
534+
) {
511535
configVal, err := msgpack.Marshal(config, ty)
512536
if err != nil {
513537
return nil, err

pkg/tfshim/shim.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,25 @@ type ResourceAttrDiff struct {
4444
Type DiffAttrType
4545
}
4646

47+
type DiffOverride string
48+
49+
const (
50+
DiffNoOverride DiffOverride = "no-override"
51+
DiffOverrideNoUpdate DiffOverride = "no-update"
52+
DiffOverrideUpdate DiffOverride = "update"
53+
)
54+
4755
type InstanceDiff interface {
4856
Attribute(key string) *ResourceAttrDiff
4957
HasNoChanges() bool
5058
ProposedState(res Resource, priorState InstanceState) (InstanceState, error)
5159
Destroy() bool
5260
RequiresNew() bool
61+
62+
// DiffEqualDecisionOverride can return a non-null value to override the default decision of if the diff is equal.
63+
//
64+
// DiffEqualDecisionOverride is only respected when EnableAccurateBridgePreview is set.
65+
DiffEqualDecisionOverride() DiffOverride
5366
}
5467

5568
type ValueType int

pkg/tfshim/tfplugin5/instance_diff.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ type instanceDiff struct {
2828
attributes map[string]shim.ResourceAttrDiff
2929
}
3030

31+
func (d instanceDiff) DiffEqualDecisionOverride() shim.DiffOverride {
32+
return shim.DiffNoOverride
33+
}
34+
3135
func (d instanceDiff) applyTimeoutOptions(opts shim.TimeoutOptions) {
3236
if opts.ResourceTimeout != nil {
3337
err := d.encodeTimeouts(opts.ResourceTimeout)
@@ -39,8 +43,8 @@ func (d instanceDiff) applyTimeoutOptions(opts shim.TimeoutOptions) {
3943
}
4044

4145
func newInstanceDiff(config, prior, planned cty.Value, meta map[string]interface{},
42-
requiresReplace []*proto.AttributePath) *instanceDiff {
43-
46+
requiresReplace []*proto.AttributePath,
47+
) *instanceDiff {
4448
attributes, requiresNew := computeDiff(prior, planned, requiresReplace)
4549
return &instanceDiff{
4650
config: config,
@@ -216,8 +220,8 @@ func rangeValue(val cty.Value, each func(k, v cty.Value)) {
216220
}
217221

218222
func computeDiff(prior, planned cty.Value,
219-
requiresReplace []*proto.AttributePath) (map[string]shim.ResourceAttrDiff, bool) {
220-
223+
requiresReplace []*proto.AttributePath,
224+
) (map[string]shim.ResourceAttrDiff, bool) {
221225
requiresNew := stringSet{}
222226
for _, path := range requiresReplace {
223227
requiresNew.add(pathString(path))

0 commit comments

Comments
 (0)