Skip to content

Commit 3422ae0

Browse files
Diff cross tests for provider upgrades (#2910)
This PR adds a `DiffProviderUpgradedSchema` option to `Diff` cross-tests which allows us to test transitions between two different resource schemas. It uses the same `pulumitest` mechanism (`t.Run`) as the upgrade tests. I've also added a repro for #1667 Part of #2914
1 parent 27d43d6 commit 3422ae0

File tree

3 files changed

+112
-22
lines changed

3 files changed

+112
-22
lines changed

pkg/internal/tests/cross-tests/diff.go

+7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
type diffOpts struct {
1111
deleteBeforeReplace bool
1212
disableAccurateBridgePreviews bool
13+
resource2 *schema.Resource
1314
}
1415

1516
// An option that can be used to customize [Diff].
@@ -25,6 +26,11 @@ func DiffDisableAccurateBridgePreviews() DiffOption {
2526
return func(o *diffOpts) { o.disableAccurateBridgePreviews = true }
2627
}
2728

29+
// DiffProviderUpgradedSchema specifies the second provider schema to use for the diff.
30+
func DiffProviderUpgradedSchema(resource2 *schema.Resource) DiffOption {
31+
return func(o *diffOpts) { o.resource2 = resource2 }
32+
}
33+
2834
func Diff(
2935
t T, resource *schema.Resource, config1, config2 map[string]cty.Value, opts ...DiffOption,
3036
) crosstestsimpl.DiffResult {
@@ -42,5 +48,6 @@ func Diff(
4248
Config2: config2Cty,
4349
DeleteBeforeReplace: o.deleteBeforeReplace,
4450
DisableAccurateBridgePreviews: o.disableAccurateBridgePreviews,
51+
Resource2: o.resource2,
4552
})
4653
}

pkg/internal/tests/cross-tests/diff_check.go

+52-22
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@
1515
package crosstests
1616

1717
import (
18-
"os"
19-
"path/filepath"
18+
"context"
2019

2120
"github.com/hashicorp/terraform-plugin-go/tftypes"
2221
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
22+
"github.com/pulumi/providertest/providers"
23+
"github.com/pulumi/providertest/pulumitest"
24+
"github.com/pulumi/providertest/pulumitest/optrun"
25+
"github.com/pulumi/providertest/pulumitest/opttest"
2326
"github.com/pulumi/pulumi/sdk/v3/go/auto/optpreview"
2427
"github.com/stretchr/testify/require"
2528

@@ -44,49 +47,76 @@ type diffTestCase struct {
4447
ObjectType *tftypes.Object
4548
DeleteBeforeReplace bool
4649
DisableAccurateBridgePreviews bool
50+
51+
// Optional second schema to use as an upgrade test with a different schema.
52+
Resource2 *schema.Resource
4753
}
4854

4955
func runDiffCheck(t T, tc diffTestCase) crosstestsimpl.DiffResult {
5056
t.Helper()
5157
tfwd := t.TempDir()
5258

5359
lifecycleArgs := lifecycleArgs{CreateBeforeDestroy: !tc.DeleteBeforeReplace}
60+
resource1 := tc.Resource
61+
resource2 := tc.Resource2
62+
if resource2 == nil {
63+
resource2 = resource1
64+
}
65+
66+
tfConfig1 := coalesceInputs(t, resource1.Schema, tc.Config1)
67+
tfd := newTFResDriver(t, tfwd, defProviderShortName, defRtype, resource1)
68+
_ = tfd.writePlanApply(t, resource1.Schema, defRtype, "example", tfConfig1, lifecycleArgs)
5469

55-
tfConfig1 := coalesceInputs(t, tc.Resource.Schema, tc.Config1)
56-
tfConfig2 := coalesceInputs(t, tc.Resource.Schema, tc.Config2)
57-
tfd := newTFResDriver(t, tfwd, defProviderShortName, defRtype, tc.Resource)
58-
_ = tfd.writePlanApply(t, tc.Resource.Schema, defRtype, "example", tfConfig1, lifecycleArgs)
59-
tfDiffPlan := tfd.writePlanApply(t, tc.Resource.Schema, defRtype, "example", tfConfig2, lifecycleArgs)
70+
tfConfig2 := coalesceInputs(t, resource2.Schema, tc.Config2)
71+
tfd2 := newTFResDriver(t, tfwd, defProviderShortName, defRtype, resource2)
72+
tfDiffPlan := tfd2.writePlanApply(t, resource2.Schema, defRtype, "example", tfConfig2, lifecycleArgs)
6073

61-
resMap := map[string]*schema.Resource{defRtype: tc.Resource}
62-
tfp := &schema.Provider{ResourcesMap: resMap}
74+
tfp1 := &schema.Provider{ResourcesMap: map[string]*schema.Resource{defRtype: resource1}}
75+
tfp2 := &schema.Provider{ResourcesMap: map[string]*schema.Resource{defRtype: resource2}}
6376

6477
opts := []pulcheck.BridgedProviderOpt{}
6578
if !tc.DisableAccurateBridgePreviews {
6679
opts = append(opts, pulcheck.EnableAccurateBridgePreviews())
6780
}
6881

69-
bridgedProvider := pulcheck.BridgedProvider(t, defProviderShortName, tfp, opts...)
82+
bridgedProvider1 := pulcheck.BridgedProvider(t, defProviderShortName, tfp1, opts...)
83+
bridgedProvider2 := pulcheck.BridgedProvider(t, defProviderShortName, tfp2, opts...)
7084
if tc.DeleteBeforeReplace {
71-
bridgedProvider.Resources[defRtype].DeleteBeforeReplace = true
85+
bridgedProvider1.Resources[defRtype].DeleteBeforeReplace = true
86+
bridgedProvider2.Resources[defRtype].DeleteBeforeReplace = true
7287
}
7388

7489
pd := &pulumiDriver{
7590
name: defProviderShortName,
7691
pulumiResourceToken: defRtoken,
7792
tfResourceName: defRtype,
7893
}
79-
80-
yamlProgram := pd.generateYAML(t, crosstestsimpl.InferPulumiValue(t,
81-
bridgedProvider.P.ResourcesMap().Get(defRtype).Schema(), nil, tfConfig1))
82-
pt := pulcheck.PulCheck(t, bridgedProvider, string(yamlProgram))
83-
pt.Up(t)
84-
85-
yamlProgram = pd.generateYAML(t, crosstestsimpl.InferPulumiValue(t,
86-
bridgedProvider.P.ResourcesMap().Get(defRtype).Schema(), nil, tfConfig2))
87-
err := os.WriteFile(filepath.Join(pt.CurrentStack().Workspace().WorkDir(), "Pulumi.yaml"), yamlProgram, 0o600)
88-
require.NoErrorf(t, err, "writing Pulumi.yaml")
89-
94+
yamlProgram1 := pd.generateYAML(t, crosstestsimpl.InferPulumiValue(t,
95+
bridgedProvider1.P.ResourcesMap().Get(defRtype).Schema(), nil, tfConfig1))
96+
97+
yamlProgram2 := pd.generateYAML(t, crosstestsimpl.InferPulumiValue(t,
98+
bridgedProvider2.P.ResourcesMap().Get(defRtype).Schema(), nil, tfConfig2))
99+
100+
// We initialize the second provider as it will be used in the preview.
101+
// It is temporarily overwritten by the first provider in the Run function.
102+
pt := pulcheck.PulCheck(t, bridgedProvider2, string(yamlProgram1))
103+
pt.Run(
104+
t,
105+
func(test *pulumitest.PulumiTest) {
106+
test.Up(t)
107+
},
108+
optrun.WithOpts(
109+
opttest.AttachProvider(
110+
defProviderShortName,
111+
func(ctx context.Context, pt providers.PulumiTest) (providers.Port, error) {
112+
handle, err := pulcheck.StartPulumiProvider(ctx, bridgedProvider1)
113+
require.NoError(t, err)
114+
return providers.Port(handle.Port), nil
115+
},
116+
),
117+
),
118+
)
119+
pt.WritePulumiYaml(t, string(yamlProgram2))
90120
previewRes := pt.Preview(t, optpreview.Diff())
91121
require.Empty(t, previewRes.StdErr, "preview should not have errors")
92122

pkg/internal/tests/cross-tests/diff_cross_test.go

+53
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"fmt"
2121
"hash/crc32"
2222
"slices"
23+
"strconv"
2324
"strings"
2425
"testing"
2526

@@ -1734,3 +1735,55 @@ func TestDetailedDiffReplacementComputedProperty(t *testing.T) {
17341735
})
17351736
})
17361737
}
1738+
1739+
func TestDiffProviderUpgradeBasic(t *testing.T) {
1740+
t.Parallel()
1741+
1742+
res1 := &schema.Resource{
1743+
Schema: map[string]*schema.Schema{"prop": {Type: schema.TypeString, Optional: true}},
1744+
}
1745+
1746+
res2 := &schema.Resource{
1747+
Schema: map[string]*schema.Schema{"prop": {Type: schema.TypeInt, Optional: true}},
1748+
SchemaVersion: 1,
1749+
StateUpgraders: []schema.StateUpgrader{
1750+
{
1751+
Version: 0,
1752+
Type: res1.CoreConfigSchema().ImpliedType(),
1753+
Upgrade: func(ctx context.Context, rawState map[string]any, meta interface{}) (map[string]any, error) {
1754+
if rawState == nil {
1755+
rawState = map[string]interface{}{}
1756+
}
1757+
1758+
if _, ok := rawState["prop"]; ok {
1759+
stringVar, ok := rawState["prop"].(string)
1760+
var intVar int
1761+
// TODO[pulumi/pulumi-terraform-bridge#1667]: This is a workaround to handle
1762+
// the fact that we use the new schema to decode the state
1763+
if !ok {
1764+
floatVar := rawState["prop"].(float64)
1765+
intVar = int(floatVar)
1766+
} else {
1767+
var err error
1768+
intVar, err = strconv.Atoi(stringVar)
1769+
if err != nil {
1770+
return nil, err
1771+
}
1772+
}
1773+
rawState["prop"] = intVar
1774+
}
1775+
1776+
return rawState, nil
1777+
},
1778+
},
1779+
},
1780+
}
1781+
1782+
res := Diff(t, res1,
1783+
map[string]cty.Value{"prop": cty.StringVal("1")},
1784+
map[string]cty.Value{"prop": cty.NumberIntVal(1)},
1785+
DiffProviderUpgradedSchema(res2),
1786+
)
1787+
1788+
require.Equal(t, []string{"no-op"}, res.TFDiff.Actions)
1789+
}

0 commit comments

Comments
 (0)