From be3b5e6fa97f21c992ad104d8b08226bd5f81d80 Mon Sep 17 00:00:00 2001 From: VenelinMartinov Date: Thu, 19 Sep 2024 14:48:13 +0100 Subject: [PATCH] Enable PRC by default (#2380) This PR enables PlanResourceChange by default in the bridge. A bunch of tests had to be fixed up to work with PRC. fixes https://github.com/pulumi/pulumi-terraform-bridge/issues/1785 --- pf/tests/provider_create_test.go | 3 +- pkg/tests/regress_1020_test.go | 13 +- pkg/tests/regress_hcloud_175_test.go | 5 +- pkg/tests/schema_pulumi_test.go | 124 +++++++ pkg/tfbridge/diff_test.go | 27 +- pkg/tfbridge/provider_test.go | 275 +++++++++++---- pkg/tfbridge/schema_test.go | 192 +++++------ pkg/tfbridge/tests/provider_test.go | 439 ------------------------ pkg/tfshim/sdk-v2/provider.go | 8 +- pkg/tfshim/sdk-v2/provider_diff_test.go | 10 +- 10 files changed, 460 insertions(+), 636 deletions(-) diff --git a/pf/tests/provider_create_test.go b/pf/tests/provider_create_test.go index 55d63b856..5e563961a 100644 --- a/pf/tests/provider_create_test.go +++ b/pf/tests/provider_create_test.go @@ -156,7 +156,8 @@ func TestMuxedAliasCreate(t *testing.T) { "id": "4", "fair": true, "number": 4, - "suggestionUpdated": false + "suggestionUpdated": false, + "suggestion": "*" } }, "metadata": { diff --git a/pkg/tests/regress_1020_test.go b/pkg/tests/regress_1020_test.go index 48bbb7101..bc73e99db 100644 --- a/pkg/tests/regress_1020_test.go +++ b/pkg/tests/regress_1020_test.go @@ -188,7 +188,8 @@ func TestRegress1020(t *testing.T) { "addresses": [ "2001:0db8:85a3:0000:0000:8a2e:0370:7334/32" ], - "id": "", + "id": "04da6b54-80e4-46f7-96ec-b56ff0331ba9", + "rules": [], "ipAddressVersion": "IPV6", "name": "ip6_sample-e8442ad", "scope": "CLOUDFRONT" @@ -201,11 +202,6 @@ func TestRegress1020(t *testing.T) { testutils.Replay(t, server(p), createTestCase) }) - t.Run("can preview Create when using PlanState", func(t *testing.T) { - p := shimv2.NewProvider(tfProvider, shimv2.WithDiffStrategy(shimv2.PlanState)) - testutils.Replay(t, server(p), createTestCase) - }) - diffTestCase := ` { "method": "/pulumirpc.ResourceProvider/Diff", @@ -250,9 +246,4 @@ func TestRegress1020(t *testing.T) { p := shimv2.NewProvider(tfProvider) testutils.Replay(t, server(p), diffTestCase) }) - - t.Run("can compute an Update plan in Diff when using PlanState", func(t *testing.T) { - p := shimv2.NewProvider(tfProvider, shimv2.WithDiffStrategy(shimv2.PlanState)) - testutils.Replay(t, server(p), diffTestCase) - }) } diff --git a/pkg/tests/regress_hcloud_175_test.go b/pkg/tests/regress_hcloud_175_test.go index 6f97325c9..18c3b3342 100644 --- a/pkg/tests/regress_hcloud_175_test.go +++ b/pkg/tests/regress_hcloud_175_test.go @@ -115,7 +115,10 @@ func TestRegressHCloud175(t *testing.T) { "id": "*", "ipRange": "10.0.1.0/24", "networkZone": "eu-central", - "type": "cloud" + "type": "cloud", + "gateway": "*", + "vswitchId": "*", + "networkId": "*" } } }` diff --git a/pkg/tests/schema_pulumi_test.go b/pkg/tests/schema_pulumi_test.go index 734e7b6e0..63265ab23 100644 --- a/pkg/tests/schema_pulumi_test.go +++ b/pkg/tests/schema_pulumi_test.go @@ -4199,3 +4199,127 @@ outputs: assert.Equal(t, "val", out.Outputs["keyValue"].Value) assert.Equal(t, "", out.Outputs["emptyValue"].Value) } + +func TestUnknownSetElementDiff(t *testing.T) { + resMap := map[string]*schema.Resource{ + "prov_test": { + Schema: map[string]*schema.Schema{ + "test": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + "prov_aux": { + Schema: map[string]*schema.Schema{ + "aux": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + }, + CreateContext: func(_ context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics { + d.SetId("aux") + err := d.Set("aux", "aux") + require.NoError(t, err) + return nil + }, + }, + } + tfp := &schema.Provider{ResourcesMap: resMap} + + runTest := func(t *testing.T, PRC bool, expectedOutput autogold.Value) { + opts := []pulcheck.BridgedProviderOpt{} + if !PRC { + opts = append(opts, pulcheck.DisablePlanResourceChange()) + } + bridgedProvider := pulcheck.BridgedProvider(t, "prov", tfp, opts...) + originalProgram := ` +name: test +runtime: yaml +resources: + mainRes: + type: prov:index:Test +outputs: + testOut: ${mainRes.tests} + ` + + programWithUnknown := ` +name: test +runtime: yaml +resources: + auxRes: + type: prov:index:Aux + mainRes: + type: prov:index:Test + properties: + tests: + - ${auxRes.aux} +outputs: + testOut: ${mainRes.tests} +` + pt := pulcheck.PulCheck(t, bridgedProvider, originalProgram) + pt.Up() + pulumiYamlPath := filepath.Join(pt.CurrentStack().Workspace().WorkDir(), "Pulumi.yaml") + + err := os.WriteFile(pulumiYamlPath, []byte(programWithUnknown), 0o600) + require.NoError(t, err) + + res := pt.Preview(optpreview.Diff()) + // Test that the test property is unknown at preview time + expectedOutput.Equal(t, res.StdOut) + resUp := pt.Up() + // assert that the property gets resolved + require.Equal(t, + []interface{}{"aux"}, + resUp.Outputs["testOut"].Value, + ) + } + + t.Run("PRC enabled", func(t *testing.T) { + // TODO[pulumi/pulumi-terraform-bridge#2427]: Incorrect detailed diff with unknown elements + t.Skip("Skipping until pulumi/pulumi-terraform-bridge#2427 is resolved") + runTest(t, true, autogold.Expect(`Previewing update (test): + pulumi:pulumi:Stack: (same) + [urn=urn:pulumi:test::test::pulumi:pulumi:Stack::test-test] + + prov:index/aux:Aux: (create) + [urn=urn:pulumi:test::test::prov:index/aux:Aux::auxRes] + ~ prov:index/test:Test: (update) + [id=newid] + [urn=urn:pulumi:test::test::prov:index/test:Test::mainRes] + + tests: [ + + [0]: output + ] + --outputs:-- + + testOut: output +Resources: + + 1 to create + ~ 1 to update + 2 changes. 1 unchanged +`)) + }) + + t.Run("PRC disabled", func(t *testing.T) { + runTest(t, false, autogold.Expect(`Previewing update (test): + pulumi:pulumi:Stack: (same) + [urn=urn:pulumi:test::test::pulumi:pulumi:Stack::test-test] + + prov:index/aux:Aux: (create) + [urn=urn:pulumi:test::test::prov:index/aux:Aux::auxRes] + ~ prov:index/test:Test: (update) + [id=newid] + [urn=urn:pulumi:test::test::prov:index/test:Test::mainRes] + + tests: [ + + [0]: output + ] + --outputs:-- + + testOut: output +Resources: + + 1 to create + ~ 1 to update + 2 changes. 1 unchanged +`)) + }) +} diff --git a/pkg/tfbridge/diff_test.go b/pkg/tfbridge/diff_test.go index 45219d31a..2d1571108 100644 --- a/pkg/tfbridge/diff_test.go +++ b/pkg/tfbridge/diff_test.go @@ -76,15 +76,16 @@ func TestCustomizeDiff(t *testing.T) { return err }, } - provider := shimv2.NewProvider(&v2Schema.Provider{ + v2Provider := v2Schema.Provider{ ResourcesMap: map[string]*v2Schema.Resource{ "resource": customDiffRes, }, - }) + } + provider := shimv2.NewProvider(&v2Provider) // Convert the inputs and state to TF config and resource attributes. r := Resource{ - TF: shimv2.NewResource(customDiffRes), + TF: provider.ResourcesMap().Get("resource"), Schema: &ResourceInfo{Fields: info}, } tfState, err := makeTerraformStateWithOpts(ctx, r, "id", stateMap, @@ -118,15 +119,16 @@ func TestCustomizeDiff(t *testing.T) { noCustomDiffRes := &v2Schema.Resource{ Schema: tfs, } - provider := shimv2.NewProvider(&v2Schema.Provider{ + v2Provider := v2Schema.Provider{ ResourcesMap: map[string]*v2Schema.Resource{ "resource": noCustomDiffRes, }, - }) + } + provider := shimv2.NewProvider(&v2Provider) // Convert the inputs and state to TF config and resource attributes. r := Resource{ - TF: shimv2.NewResource(noCustomDiffRes), + TF: provider.ResourcesMap().Get("resource"), Schema: &ResourceInfo{Fields: info}, } tfState, err := makeTerraformStateWithOpts(ctx, r, "id", stateMap, @@ -180,7 +182,7 @@ func TestCustomizeDiff(t *testing.T) { // Convert the inputs and state to TF config and resource attributes. r := Resource{ - TF: shimv2.NewResource(customDiffRes), + TF: provider.ResourcesMap().Get("resource"), Schema: &ResourceInfo{Fields: info}, } tfState, err := makeTerraformStateWithOpts(ctx, r, "id", stateMap, @@ -245,16 +247,17 @@ func v2Setup(tfs map[string]*v2Schema.Schema) ( return d.SetNewComputed("outp") }, } - provider := shimv2.NewProvider(&v2Schema.Provider{ + v2Provider := v2Schema.Provider{ ResourcesMap: map[string]*v2Schema.Resource{ "resource": res, }, - }) + } + provider := shimv2.NewProvider(&v2Provider) info := map[string]*SchemaInfo{} // Convert the inputs and state to TF config and resource attributes. r := Resource{ - TF: shimv2.NewResource(res), + TF: provider.ResourcesMap().Get("resource"), Schema: &ResourceInfo{Fields: info}, } @@ -1464,6 +1467,8 @@ func TestNestedComputedSetUpdate(t *testing.T) { } func TestNestedComputedSetAdd(t *testing.T) { + // TODO[pulumi/pulumi-terraform-bridge#2427]: Incorrect detailed diff with unknown elements + t.Skip("Skipping until pulumi/pulumi-terraform-bridge#2427 is resolved") diffTest(t, map[string]*v2Schema.Schema{ "prop": {Type: v2Schema.TypeSet, Elem: &v2Schema.Schema{Type: v2Schema.TypeString}}, @@ -1539,6 +1544,8 @@ func TestNestedComputedSetIntUpdateReplace(t *testing.T) { } func TestNestedComputedSetIntAdd(t *testing.T) { + // TODO[pulumi/pulumi-terraform-bridge#2427]: Incorrect detailed diff with unknown elements + t.Skip("Skipping until pulumi/pulumi-terraform-bridge#2427 is resolved") diffTest(t, map[string]*v2Schema.Schema{ "prop": {Type: v2Schema.TypeSet, Elem: &v2Schema.Schema{Type: v2Schema.TypeInt}}, diff --git a/pkg/tfbridge/provider_test.go b/pkg/tfbridge/provider_test.go index bf8a35d18..abdad8099 100644 --- a/pkg/tfbridge/provider_test.go +++ b/pkg/tfbridge/provider_test.go @@ -542,7 +542,18 @@ func testIgnoreChangesV2(t *testing.T, prov shim.Provider) { testIgnoreChanges(t, provider) } -func testProviderPreview(t *testing.T, provider *Provider) { +func TestProviderPreview(t *testing.T) { + provider := &Provider{ + tf: shimv1.NewProvider(testTFProvider), + config: shimv1.NewSchemaMap(testTFProvider.Schema), + } + provider.resources = map[tokens.Type]Resource{ + "ExampleResource": { + TF: shimv1.NewResource(testTFProvider.ResourcesMap["example_resource"]), + TFName: "example_resource", + Schema: &ResourceInfo{Tok: "ExampleResource"}, + }, + } urn := resource.NewURN("stack", "project", "", "ExampleResource", "name") unknown := resource.MakeComputed(resource.NewStringProperty("")) @@ -651,34 +662,138 @@ func testProviderPreview(t *testing.T, provider *Provider) { }).DeepEquals(outs["nestedResources"])) } -func TestProviderPreview(t *testing.T) { - provider := &Provider{ - tf: shimv1.NewProvider(testTFProvider), - config: shimv1.NewSchemaMap(testTFProvider.Schema), - } - provider.resources = map[tokens.Type]Resource{ - "ExampleResource": { - TF: shimv1.NewResource(testTFProvider.ResourcesMap["example_resource"]), - TFName: "example_resource", - Schema: &ResourceInfo{Tok: "ExampleResource"}, - }, - } - testProviderPreview(t, provider) -} - func TestProviderPreviewV2(t *testing.T) { + shimProvider := shimv2.NewProvider(testTFProviderV2) provider := &Provider{ - tf: shimv2.NewProvider(testTFProviderV2), + tf: shimProvider, config: shimv2.NewSchemaMap(testTFProviderV2.Schema), } provider.resources = map[tokens.Type]Resource{ "ExampleResource": { - TF: shimv2.NewResource(testTFProviderV2.ResourcesMap["example_resource"]), + TF: shimProvider.ResourcesMap().Get("example_resource"), TFName: "example_resource", Schema: &ResourceInfo{Tok: "ExampleResource"}, }, } - testProviderPreview(t, provider) + urn := resource.NewURN("stack", "project", "", "ExampleResource", "name") + + unknown := resource.MakeComputed(resource.NewStringProperty("")) + + // Step 1: create and check an input bag. + pulumiIns, err := plugin.MarshalProperties(resource.PropertyMap{ + "stringPropertyValue": resource.NewStringProperty("foo"), + "setPropertyValues": resource.NewArrayProperty([]resource.PropertyValue{resource.NewStringProperty("foo")}), + "nestedResources": resource.NewObjectProperty(resource.PropertyMap{ + "kind": unknown, + "configuration": resource.NewObjectProperty(resource.PropertyMap{ + "name": resource.NewStringProperty("foo"), + }), + }), + }, plugin.MarshalOptions{KeepUnknowns: true}) + assert.NoError(t, err) + checkResp, err := provider.Check(context.Background(), &pulumirpc.CheckRequest{ + Urn: string(urn), + News: pulumiIns, + }) + assert.NoError(t, err) + + // Step 2a: preview the creation of a resource using the checked input bag. + createResp, err := provider.Create(context.Background(), &pulumirpc.CreateRequest{ + Urn: string(urn), + Properties: checkResp.GetInputs(), + Preview: true, + }) + assert.NoError(t, err) + + outs, err := plugin.UnmarshalProperties(createResp.GetProperties(), plugin.MarshalOptions{KeepUnknowns: true}) + assert.NoError(t, err) + //nolint:lll + autogold.Expect(resource.PropertyMap{ + resource.PropertyKey("__meta"): resource.PropertyValue{ + V: `{"_new_extra_shim":{},"e2bfb730-ecaa-11e6-8f88-34363bc7c4c0":{"create":120000000000}}`, + }, + resource.PropertyKey("arrayPropertyValues"): resource.PropertyValue{}, + resource.PropertyKey("boolPropertyValue"): resource.PropertyValue{}, + resource.PropertyKey("floatPropertyValue"): resource.PropertyValue{}, + resource.PropertyKey("id"): resource.PropertyValue{V: resource.Computed{Element: resource.PropertyValue{ + V: "", + }}}, + resource.PropertyKey("nestedResources"): resource.PropertyValue{V: resource.PropertyMap{ + resource.PropertyKey("configuration"): resource.PropertyValue{V: resource.PropertyMap{resource.PropertyKey("name"): resource.PropertyValue{ + V: "foo", + }}}, + resource.PropertyKey("kind"): resource.PropertyValue{V: resource.Computed{Element: resource.PropertyValue{V: ""}}}, + resource.PropertyKey("optBool"): resource.PropertyValue{}, + }}, + resource.PropertyKey("nilPropertyValue"): resource.PropertyValue{}, + resource.PropertyKey("numberPropertyValue"): resource.PropertyValue{}, + resource.PropertyKey("objectPropertyValue"): resource.PropertyValue{}, + resource.PropertyKey("setPropertyValues"): resource.PropertyValue{V: []resource.PropertyValue{{V: "foo"}}}, + resource.PropertyKey("stringPropertyValue"): resource.PropertyValue{V: "foo"}, + resource.PropertyKey("stringWithBadInterpolation"): resource.PropertyValue{}, + }).Equal(t, outs) + + // Step 2b: actually create the resource. + pulumiIns, err = plugin.MarshalProperties(resource.NewPropertyMapFromMap(map[string]interface{}{ + "stringPropertyValue": "foo", + "setPropertyValues": []interface{}{"foo"}, + "nestedResources": map[string]interface{}{ + "kind": "foo", + "configuration": map[string]interface{}{ + "name": "foo", + }, + }, + }), plugin.MarshalOptions{}) + assert.NoError(t, err) + checkResp, err = provider.Check(context.Background(), &pulumirpc.CheckRequest{ + Urn: string(urn), + News: pulumiIns, + }) + assert.NoError(t, err) + createResp, err = provider.Create(context.Background(), &pulumirpc.CreateRequest{ + Urn: string(urn), + Properties: checkResp.GetInputs(), + }) + assert.NoError(t, err) + + // Step 3: preview an update to the resource we just created. + pulumiIns, err = plugin.MarshalProperties(resource.PropertyMap{ + "stringPropertyValue": resource.NewStringProperty("bar"), + "setPropertyValues": resource.NewArrayProperty([]resource.PropertyValue{resource.NewStringProperty("foo")}), + "nestedResources": resource.NewObjectProperty(resource.PropertyMap{ + "kind": unknown, + "configuration": resource.NewObjectProperty(resource.PropertyMap{ + "name": resource.NewStringProperty("foo"), + }), + }), + }, plugin.MarshalOptions{KeepUnknowns: true}) + assert.NoError(t, err) + checkResp, err = provider.Check(context.Background(), &pulumirpc.CheckRequest{ + Urn: string(urn), + News: pulumiIns, + Olds: createResp.GetProperties(), + }) + assert.NoError(t, err) + + updateResp, err := provider.Update(context.Background(), &pulumirpc.UpdateRequest{ + Id: "MyID", + Urn: string(urn), + Olds: createResp.GetProperties(), + News: checkResp.GetInputs(), + Preview: true, + }) + assert.NoError(t, err) + + outs, err = plugin.UnmarshalProperties(updateResp.GetProperties(), plugin.MarshalOptions{KeepUnknowns: true}) + assert.NoError(t, err) + assert.Equal(t, resource.NewStringProperty("bar"), outs["stringPropertyValue"]) + assert.True(t, resource.NewObjectProperty(resource.PropertyMap{ + "kind": unknown, + "configuration": resource.NewObjectProperty(resource.PropertyMap{ + "name": resource.NewStringProperty("foo"), + }), + "optBool": resource.NewBoolProperty(false), + }).DeepEquals(outs["nestedResources"])) } func testCheckFailures(t *testing.T, provider *Provider, typeName tokens.Type) []*pulumirpc.CheckFailure { @@ -875,11 +990,9 @@ func testProviderRead(t *testing.T, provider *Provider, typeName tokens.Type, ch "configurationValue": resource.NewStringProperty("true"), }), }), ins["nestedResources"]) - assert.Equal(t, resource.NewArrayProperty( - []resource.PropertyValue{ - resource.NewStringProperty("set member 2"), - resource.NewStringProperty("set member 1"), - }), ins["setPropertyValues"]) + assert.Len(t, ins["setPropertyValues"].ArrayValue(), 2) + assert.Contains(t, ins["setPropertyValues"].ArrayValue(), resource.NewStringProperty("set member 1")) + assert.Contains(t, ins["setPropertyValues"].ArrayValue(), resource.NewStringProperty("set member 2")) assert.Equal(t, resource.NewStringProperty("some ${interpolated:value} with syntax errors"), ins["stringWithBadInterpolation"]) @@ -940,13 +1053,14 @@ func TestProviderReadV1(t *testing.T) { } func TestProviderReadV2(t *testing.T) { + shimProvider := shimv2.NewProvider(testTFProviderV2) provider := &Provider{ - tf: shimv2.NewProvider(testTFProviderV2), + tf: shimProvider, config: shimv2.NewSchemaMap(testTFProviderV2.Schema), } provider.resources = map[tokens.Type]Resource{ "ExampleResource": { - TF: shimv2.NewResource(testTFProviderV2.ResourcesMap["example_resource"]), + TF: shimProvider.ResourcesMap().Get("example_resource"), TFName: "example_resource", Schema: &ResourceInfo{Tok: "ExampleResource"}, }, @@ -1020,13 +1134,14 @@ func TestProviderReadNestedSecretV1(t *testing.T) { } func TestProviderReadNestedSecretV2(t *testing.T) { + shimProvider := shimv2.NewProvider(testTFProviderV2) provider := &Provider{ - tf: shimv2.NewProvider(testTFProviderV2), + tf: shimProvider, config: shimv2.NewSchemaMap(testTFProviderV2.Schema), } provider.resources = map[tokens.Type]Resource{ "NestedSecretResource": { - TF: shimv2.NewResource(testTFProviderV2.ResourcesMap["nested_secret_resource"]), + TF: shimProvider.ResourcesMap().Get("nested_secret_resource"), TFName: "nested_secret_resource", Schema: &ResourceInfo{Tok: "NestedSecretResource"}, }, @@ -1037,8 +1152,9 @@ func TestProviderReadNestedSecretV2(t *testing.T) { func TestCheck(t *testing.T) { t.Run("Default application can consult prior state in Check", func(t *testing.T) { + shimProvider := shimv2.NewProvider(testTFProviderV2) provider := &Provider{ - tf: shimv2.NewProvider(testTFProviderV2), + tf: shimProvider, config: shimv2.NewSchemaMap(testTFProviderV2.Schema), } computeStringDefault := func(ctx context.Context, opts ComputeDefaultOptions) (interface{}, error) { @@ -1053,7 +1169,7 @@ func TestCheck(t *testing.T) { } provider.resources = map[tokens.Type]Resource{ "ExampleResource": { - TF: shimv2.NewResource(testTFProviderV2.ResourcesMap["example_resource"]), + TF: shimProvider.ResourcesMap().Get("example_resource"), TFName: "example_resource", Schema: &ResourceInfo{ Tok: "ExampleResource", @@ -1118,14 +1234,15 @@ func TestCheck(t *testing.T) { p2 := testprovider.ProviderV2() p2.ResourcesMap["example_resource"].Schema["string_property_value"].Sensitive = true + shimProvider := shimv2.NewProvider(p2) provider := &Provider{ - tf: shimv2.NewProvider(p2), + tf: shimProvider, config: shimv2.NewSchemaMap(p2.Schema), } provider.resources = map[tokens.Type]Resource{ "ExampleResource": { - TF: shimv2.NewResource(p2.ResourcesMap["example_resource"]), + TF: shimProvider.ResourcesMap().Get("example_resource"), TFName: "example_resource", Schema: &ResourceInfo{ Tok: "ExampleResource", @@ -1258,9 +1375,10 @@ func TestCheckWarnings(t *testing.T) { }, }, } + shimProvider := shimv2.NewProvider(p) provider := &Provider{ - tf: shimv2.NewProvider(p, shimv2.WithDiffStrategy(shimv2.PlanState)), + tf: shimProvider, module: "testprov", config: shimv2.NewSchemaMap(p.Schema), pulumiSchema: []byte("hello"), // we only check whether this is nil in type checking @@ -1268,7 +1386,7 @@ func TestCheckWarnings(t *testing.T) { hasTypeErrors: make(map[resource.URN]struct{}), resources: map[tokens.Type]Resource{ "ExampleResource": { - TF: shimv2.NewResource(p.ResourcesMap["example_resource"]), + TF: shimProvider.ResourcesMap().Get("example_resource"), TFName: "example_resource", Schema: &ResourceInfo{ Tok: "ExampleResource", @@ -2105,13 +2223,14 @@ func TestInvoke(t *testing.T) { prop.Computed = true prop.Optional = true + shimProvider := shimv2.NewProvider(p) provider := &Provider{ - tf: shimv2.NewProvider(testTFProviderV2), + tf: shimProvider, config: shimv2.NewSchemaMap(testTFProviderV2.Schema), dataSources: map[tokens.ModuleMember]DataSource{ "tprov:index/ExampleFn:ExampleFn": { - TF: shimv2.NewResource(ds), + TF: shimProvider.DataSourcesMap().Get(dsName), TFName: dsName, Schema: &DataSourceInfo{ Tok: "tprov:index/ExampleFn:ExampleFn", @@ -2154,12 +2273,13 @@ func TestInvoke(t *testing.T) { } func TestTransformOutputs(t *testing.T) { + shimProvider := shimv2.NewProvider(testTFProviderV2) provider := &Provider{ - tf: shimv2.NewProvider(testTFProviderV2), + tf: shimProvider, config: shimv2.NewSchemaMap(testTFProviderV2.Schema), resources: map[tokens.Type]Resource{ "ExampleResource": { - TF: shimv2.NewResource(testTFProviderV2.ResourcesMap["example_resource"]), + TF: shimProvider.ResourcesMap().Get("example_resource"), TFName: "example_resource", Schema: &ResourceInfo{ Tok: "ExampleResource", @@ -2190,8 +2310,18 @@ func TestTransformOutputs(t *testing.T) { }, "response": { "properties": { - "id": "", - "stringPropertyValue": "TRANSFORMED" + "id": "04da6b54-80e4-46f7-96ec-b56ff0331ba9", + "stringPropertyValue": "TRANSFORMED", + "__meta": "*", + "arrayPropertyValues": "*", + "boolPropertyValue": "*", + "floatPropertyValue": "*", + "nestedResources": "*", + "nilPropertyValue": "*", + "numberPropertyValue": "*", + "objectPropertyValue": "*", + "setPropertyValues": "*", + "stringWithBadInterpolation": "*" } } }`) @@ -2222,7 +2352,10 @@ func TestTransformOutputs(t *testing.T) { "nestedResources": "*", "numberPropertyValue": "*", "setPropertyValues": "*", - "stringWithBadInterpolation": "*" + "stringWithBadInterpolation": "*", + "nilPropertyValue": "*", + "boolPropertyValue": "*", + "floatPropertyValue": "*" } } }`) @@ -2248,9 +2381,17 @@ func TestTransformOutputs(t *testing.T) { "id": "*", "stringPropertyValue": "TRANSFORMED", "__meta": "*", + "objectPropertyValue": "*", + "floatPropertyValue": "*", + "stringPropertyValue": "*", "arrayPropertyValues": "*", "nestedResources": "*", - "setPropertyValues": "*" + "numberPropertyValue": "*", + "setPropertyValues": "*", + "stringWithBadInterpolation": "*", + "nilPropertyValue": "*", + "boolPropertyValue": "*", + "floatPropertyValue": "*" } } }`) @@ -2282,7 +2423,8 @@ func TestTransformOutputs(t *testing.T) { "nestedResources": "*", "numberPropertyValue": "*", "setPropertyValues": "*", - "stringWithBadInterpolation": "*" + "stringWithBadInterpolation": "*", + "nilPropertyValue": "*" } } }`) @@ -2311,7 +2453,8 @@ func TestTransformOutputs(t *testing.T) { "nestedResources": "*", "numberPropertyValue": "*", "setPropertyValues": "*", - "stringWithBadInterpolation": "*" + "stringWithBadInterpolation": "*", + "nilPropertyValue": "*" } } }`) @@ -2321,17 +2464,18 @@ func TestTransformOutputs(t *testing.T) { func TestSkipDetailedDiff(t *testing.T) { provider := func(t *testing.T, skipDetailedDiffForChanges bool) *Provider { p := testprovider.CustomizedDiffProvider(func(data *schema.ResourceData) {}) + shimProvider := shimv2.NewProvider(p) return &Provider{ - tf: shimv2.NewProvider(p), + tf: shimProvider, config: shimv2.NewSchemaMap(p.Schema), resources: map[tokens.Type]Resource{ "Resource": { - TF: shimv2.NewResource(p.ResourcesMap["test_resource"]), + TF: shimProvider.ResourcesMap().Get("test_resource"), TFName: "test_resource", Schema: &ResourceInfo{Tok: "Resource"}, }, "Replace": { - TF: shimv2.NewResource(p.ResourcesMap["test_replace"]), + TF: shimProvider.ResourcesMap().Get("test_replace"), TFName: "test_replace", Schema: &ResourceInfo{Tok: "Replace"}, }, @@ -2425,12 +2569,13 @@ func TestTransformFromState(t *testing.T) { }) var called bool t.Cleanup(func() { assert.True(t, called, "Transform was not called") }) + shimProvider := shimv2.NewProvider(p) return &Provider{ - tf: shimv2.NewProvider(p), + tf: shimProvider, config: shimv2.NewSchemaMap(p.Schema), resources: map[tokens.Type]Resource{ "Echo": { - TF: shimv2.NewResource(p.ResourcesMap["echo"]), + TF: shimProvider.ResourcesMap().Get("echo"), TFName: "echo", Schema: &ResourceInfo{ Tok: "Echo", @@ -2587,12 +2732,13 @@ func TestTransformFromState(t *testing.T) { // https://github.com/pulumi/pulumi-aws/issues/3092 func TestMaxItemOneWrongStateDiff(t *testing.T) { p := testprovider.MaxItemsOneProvider() + shimProvider := shimv2.NewProvider(p) provider := &Provider{ - tf: shimv2.NewProvider(p), + tf: shimProvider, config: shimv2.NewSchemaMap(p.Schema), resources: map[tokens.Type]Resource{ "NestedStrRes": { - TF: shimv2.NewResource(p.ResourcesMap["nested_str_res"]), + TF: shimProvider.ResourcesMap().Get("nested_str_res"), TFName: "nested_str_res", Schema: &ResourceInfo{ Tok: "NestedStrRes", @@ -2716,12 +2862,13 @@ func TestMaxItemOneWrongStateDiff(t *testing.T) { // https://github.com/pulumi/pulumi-terraform-bridge/issues/1546 func TestDefaultsAndConflictsWithValidationInteraction(t *testing.T) { p := testprovider.ConflictsWithValidationProvider() + shimProvider := shimv2.NewProvider(p) provider := &Provider{ - tf: shimv2.NewProvider(p), + tf: shimProvider, config: shimv2.NewSchemaMap(p.Schema), resources: map[tokens.Type]Resource{ "DefaultValueRes": { - TF: shimv2.NewResource(p.ResourcesMap["default_value_res"]), + TF: shimProvider.ResourcesMap().Get("default_value_res"), TFName: "default_value_res", Schema: &ResourceInfo{}, }, @@ -2776,12 +2923,13 @@ func TestDefaultsAndConflictsWithValidationInteraction(t *testing.T) { // https://github.com/pulumi/pulumi-terraform-bridge/issues/1546 func TestDefaultsAndExactlyOneOfValidationInteraction(t *testing.T) { p := testprovider.ExactlyOneOfValidationProvider() + shimProvider := shimv2.NewProvider(p) provider := &Provider{ - tf: shimv2.NewProvider(p), + tf: shimProvider, config: shimv2.NewSchemaMap(p.Schema), resources: map[tokens.Type]Resource{ "DefaultValueRes": { - TF: shimv2.NewResource(p.ResourcesMap["default_value_res"]), + TF: shimProvider.ResourcesMap().Get("default_value_res"), TFName: "default_value_res", Schema: &ResourceInfo{}, }, @@ -2840,12 +2988,13 @@ func TestDefaultsAndExactlyOneOfValidationInteraction(t *testing.T) { // https://github.com/pulumi/pulumi-terraform-bridge/issues/1546 func TestDefaultsAndRequiredWithValidationInteraction(t *testing.T) { p := testprovider.RequiredWithValidationProvider() + shimProvider := shimv2.NewProvider(p) provider := &Provider{ - tf: shimv2.NewProvider(p), + tf: shimProvider, config: shimv2.NewSchemaMap(p.Schema), resources: map[tokens.Type]Resource{ "DefaultValueRes": { - TF: shimv2.NewResource(p.ResourcesMap["default_value_res"]), + TF: shimProvider.ResourcesMap().Get("default_value_res"), TFName: "default_value_res", Schema: &ResourceInfo{}, }, @@ -3498,7 +3647,7 @@ func TestMaxItemsOneConflictsWith(t *testing.T) { }, resources: map[tokens.Type]Resource{ "Res": { - TF: shimv2.NewResource(p.ResourcesMap["res"]), + TF: shimProv.ResourcesMap().Get("res"), TFName: "res", Schema: &ResourceInfo{}, }, @@ -3596,7 +3745,7 @@ func TestMinMaxItemsOneOptional(t *testing.T) { }, resources: map[tokens.Type]Resource{ "Res": { - TF: shimv2.NewResource(p.ResourcesMap["res"]), + TF: shimProv.ResourcesMap().Get("res"), TFName: "res", Schema: &ResourceInfo{}, }, @@ -3700,7 +3849,7 @@ func TestComputedMaxItemsOneNotSpecified(t *testing.T) { }, resources: map[tokens.Type]Resource{ "Res": { - TF: shimv2.NewResource(p.ResourcesMap["res"]), + TF: shimProv.ResourcesMap().Get("res"), TFName: "res", Schema: &ResourceInfo{}, }, @@ -3855,7 +4004,7 @@ func TestMaxItemsOnePropCheckResponseNoNulls(t *testing.T) { info: ProviderInfo{P: shimProv}, resources: map[tokens.Type]Resource{ "Res": { - TF: shimv2.NewResource(p.ResourcesMap["res"]), + TF: shimProv.ResourcesMap().Get("res"), TFName: "res", Schema: &ResourceInfo{}, }, @@ -3886,6 +4035,8 @@ func TestMaxItemsOnePropCheckResponseNoNulls(t *testing.T) { // TODO[pulumi/pulumi#15636] if/when Pulumi supports customizing Read timeouts these could be added here. func TestCustomTimeouts(t *testing.T) { + // TODO[pulumi/pulumi-terraform-bridge#2386] + t.Skipf("Skipping test until pulumi/pulumi-terraform-bridge#2386 is resolved") t.Parallel() type testCase struct { @@ -4262,7 +4413,7 @@ func TestStringValForOtherProperty(t *testing.T) { info: ProviderInfo{P: shimProv}, resources: map[tokens.Type]Resource{ "Res": { - TF: shimv2.NewResource(p.ResourcesMap["res"]), + TF: shimProv.ResourcesMap().Get("res"), TFName: "res", Schema: &ResourceInfo{}, }, diff --git a/pkg/tfbridge/schema_test.go b/pkg/tfbridge/schema_test.go index 10795d7b5..9cc780c2f 100644 --- a/pkg/tfbridge/schema_test.go +++ b/pkg/tfbridge/schema_test.go @@ -644,30 +644,6 @@ func TestTerraformOutputsWithSecretsUnsupported(t *testing.T) { } } -func clearMeta(state shim.InstanceState) bool { - if tf, ok := shimv1.IsInstanceState(state); ok { - tf.Meta = map[string]interface{}{} - return true - } - if tf, ok := shimv2.IsInstanceState(state); ok { - tf.Meta = map[string]interface{}{} - return true - } - return false -} - -func clearID(state shim.InstanceState) bool { - if tf, ok := shimv1.IsInstanceState(state); ok { - tf.ID = "" - return true - } - if tf, ok := shimv2.IsInstanceState(state); ok { - tf.ID = "" - return true - } - return false -} - // Test that meta-properties are correctly produced. func TestMetaProperties(t *testing.T) { for _, f := range factories { @@ -678,7 +654,8 @@ func TestMetaProperties(t *testing.T) { const resName = "example_resource" res := prov.ResourcesMap().Get(resName) - state := f.NewInstanceState("0") + state, err := res.InstanceState("0", map[string]interface{}{}, map[string]interface{}{}) + assert.NoError(t, err) read, err := prov.Refresh(ctx, resName, state, nil) assert.NoError(t, err) assert.NotNil(t, read) @@ -690,7 +667,8 @@ func TestMetaProperties(t *testing.T) { state, err = makeTerraformStateWithOpts( ctx, Resource{TF: res, Schema: &ResourceInfo{}}, state.ID(), props, makeTerraformStateOptions{ - defaultZeroSchemaVersion: true, unknownCollectionsSupported: prov.SupportsUnknownCollections()}) + defaultZeroSchemaVersion: true, unknownCollectionsSupported: prov.SupportsUnknownCollections(), + }) assert.NoError(t, err) assert.NotNil(t, state) @@ -707,23 +685,14 @@ func TestMetaProperties(t *testing.T) { state, err = makeTerraformStateWithOpts( ctx, Resource{TF: res, Schema: &ResourceInfo{}}, state.ID(), props, makeTerraformStateOptions{ - defaultZeroSchemaVersion: true, unknownCollectionsSupported: prov.SupportsUnknownCollections()}) + defaultZeroSchemaVersion: true, unknownCollectionsSupported: prov.SupportsUnknownCollections(), + }) assert.NoError(t, err) assert.NotNil(t, state) assert.Equal(t, "0", state.Meta()["schema_version"]) - // Remove the resource's meta-attributes and ensure that we do not include them in the result. - ok := clearMeta(read2) - assert.True(t, ok) - props, err = MakeTerraformResult(ctx, prov, read2, res.Schema(), nil, nil, true) - assert.NoError(t, err) - assert.NotNil(t, props) - assert.NotContains(t, props, metaKey) - // Ensure that timeouts are populated and preserved. - ok = clearID(state) - assert.True(t, ok) cfg := prov.NewResourceConfig(ctx, map[string]interface{}{}) // To populate default timeouts, we take the timeouts from the resource schema and insert them into the diff @@ -748,7 +717,8 @@ func TestMetaProperties(t *testing.T) { state, err = makeTerraformStateWithOpts( ctx, Resource{TF: res, Schema: &ResourceInfo{}}, state.ID(), props, makeTerraformStateOptions{ - defaultZeroSchemaVersion: true, unknownCollectionsSupported: prov.SupportsUnknownCollections()}) + defaultZeroSchemaVersion: true, unknownCollectionsSupported: prov.SupportsUnknownCollections(), + }) assert.NoError(t, err) assert.NotNil(t, state) @@ -766,7 +736,8 @@ func TestInjectingCustomTimeouts(t *testing.T) { const resName = "second_resource" res := prov.ResourcesMap().Get(resName) - state := f.NewInstanceState("0") + state, err := res.InstanceState("0", map[string]interface{}{}, map[string]interface{}{}) + assert.NoError(t, err) read, err := prov.Refresh(ctx, resName, state, nil) assert.NoError(t, err) assert.NotNil(t, read) @@ -778,7 +749,8 @@ func TestInjectingCustomTimeouts(t *testing.T) { state, err = makeTerraformStateWithOpts( ctx, Resource{TF: res, Schema: &ResourceInfo{}}, state.ID(), props, makeTerraformStateOptions{ - defaultZeroSchemaVersion: true, unknownCollectionsSupported: prov.SupportsUnknownCollections()}) + defaultZeroSchemaVersion: true, unknownCollectionsSupported: prov.SupportsUnknownCollections(), + }) assert.NoError(t, err) assert.NotNil(t, state) @@ -795,23 +767,14 @@ func TestInjectingCustomTimeouts(t *testing.T) { state, err = makeTerraformStateWithOpts( ctx, Resource{TF: res, Schema: &ResourceInfo{}}, state.ID(), props, makeTerraformStateOptions{ - defaultZeroSchemaVersion: true, unknownCollectionsSupported: prov.SupportsUnknownCollections()}) + defaultZeroSchemaVersion: true, unknownCollectionsSupported: prov.SupportsUnknownCollections(), + }) assert.NoError(t, err) assert.NotNil(t, state) assert.Equal(t, "0", state.Meta()["schema_version"]) - // Remove the resource's meta-attributes and ensure that we do not include them in the result. - ok := clearMeta(read2) - assert.True(t, ok) - props, err = MakeTerraformResult(ctx, prov, read2, res.Schema(), nil, nil, true) - assert.NoError(t, err) - assert.NotNil(t, props) - assert.NotContains(t, props, metaKey) - // Ensure that timeouts are populated and preserved. - ok = clearID(state) - assert.True(t, ok) cfg := prov.NewResourceConfig(ctx, map[string]interface{}{}) // To populate default timeouts, we take the timeouts from the resource schema and insert them into the diff @@ -839,7 +802,8 @@ func TestInjectingCustomTimeouts(t *testing.T) { state, err = makeTerraformStateWithOpts( ctx, Resource{TF: res, Schema: &ResourceInfo{}}, state.ID(), props, makeTerraformStateOptions{ - defaultZeroSchemaVersion: true, unknownCollectionsSupported: prov.SupportsUnknownCollections()}) + defaultZeroSchemaVersion: true, unknownCollectionsSupported: prov.SupportsUnknownCollections(), + }) assert.NoError(t, err) assert.NotNil(t, state) @@ -865,59 +829,82 @@ func TestInjectingCustomTimeouts(t *testing.T) { } } -func getStateAttributes(state shim.InstanceState) (map[string]string, bool) { - if tf, ok := shimv1.IsInstanceState(state); ok { - return tf.Attributes, true - } - if tf, ok := shimv2.IsInstanceState(state); ok { - return tf.Attributes, true - } - return nil, false -} - // Test that MakeTerraformResult reads property values appropriately. func TestResultAttributesRoundTrip(t *testing.T) { - for _, f := range factories { - t.Run(f.SDKVersion(), func(t *testing.T) { - prov := f.NewTestProvider() - ctx := context.Background() + setup := func(f shimFactory) (shim.Resource, shim.InstanceState, shim.InstanceState) { + prov := f.NewTestProvider() + ctx := context.Background() - const resName = "example_resource" - res := prov.ResourcesMap().Get("example_resource") + const resName = "example_resource" + res := prov.ResourcesMap().Get("example_resource") - state := f.NewInstanceState("0") - read, err := prov.Refresh(ctx, resName, state, nil) - assert.NoError(t, err) - assert.NotNil(t, read) + state, err := res.InstanceState("0", map[string]interface{}{}, map[string]interface{}{}) + assert.NoError(t, err) + read, err := prov.Refresh(ctx, resName, state, nil) + assert.NoError(t, err) + assert.NotNil(t, read) - props, err := MakeTerraformResult(ctx, prov, read, res.Schema(), nil, nil, true) - assert.NoError(t, err) - assert.NotNil(t, props) + props, err := MakeTerraformResult(ctx, prov, read, res.Schema(), nil, nil, true) + assert.NoError(t, err) + assert.NotNil(t, props) - state, err = makeTerraformStateWithOpts( - ctx, Resource{TF: res, Schema: &ResourceInfo{}}, state.ID(), props, - makeTerraformStateOptions{ - defaultZeroSchemaVersion: true, unknownCollectionsSupported: prov.SupportsUnknownCollections()}) - assert.NoError(t, err) - assert.NotNil(t, state) + state, err = makeTerraformStateWithOpts( + ctx, Resource{TF: res, Schema: &ResourceInfo{}}, state.ID(), props, + makeTerraformStateOptions{ + defaultZeroSchemaVersion: true, unknownCollectionsSupported: prov.SupportsUnknownCollections(), + }) + assert.NoError(t, err) + assert.NotNil(t, state) - readAttributes, ok := getStateAttributes(read) - assert.True(t, ok) - stateAttributes, ok := getStateAttributes(state) - assert.True(t, ok) - - // We may add extra "%" fields to represent map counts. These diffs are innocuous. If we only see them in the - // attributes produced by MakeTerraformResult, ignore them. - for k, v := range stateAttributes { - expected, ok := readAttributes[k] - if !ok { - assert.True(t, strings.HasSuffix(k, ".%")) - } else { - assert.Equal(t, expected, v) - } - } - }) + return res, read, state } + t.Run("v1", func(t *testing.T) { + _, read, state := setup(factories[0]) + + getStateAttributes := func(state shim.InstanceState) (map[string]string, bool) { + if tf, ok := shimv1.IsInstanceState(state); ok { + return tf.Attributes, true + } + return nil, false + } + + readAttributes, ok := getStateAttributes(read) + assert.True(t, ok) + + stateAttributes, ok := getStateAttributes(state) + assert.True(t, ok) + + // We may add extra "%" fields to represent map counts. These diffs are innocuous. If we only see them in the + // attributes produced by MakeTerraformResult, ignore them. + for k, v := range stateAttributes { + expected, ok := readAttributes[k] + if !ok { + assert.True(t, strings.HasSuffix(k, ".%")) + } else { + assert.Equal(t, expected, v) + } + } + }) + + t.Run("v2", func(t *testing.T) { + res, read, state := setup(factories[1]) + readAttributes, err := read.Object(res.Schema()) + assert.NoError(t, err) + + stateAttributes, err := state.Object(res.Schema()) + assert.NoError(t, err) + + // We may add extra "%" fields to represent map counts. These diffs are innocuous. If we only see them in the + // attributes produced by MakeTerraformResult, ignore them. + for k, v := range stateAttributes { + expected, ok := readAttributes[k] + if !ok { + assert.True(t, strings.HasSuffix(k, ".%")) + } else { + assert.Equal(t, expected, v) + } + } + }) } func sortDefaultsList(m resource.PropertyMap) { @@ -2737,11 +2724,12 @@ func TestExtractSchemaInputsNestedMaxItemsOne(t *testing.T) { return nil } + shimProvider := shimv2.NewProvider(tfProvider) return &Provider{ - tf: shimv2.NewProvider(tfProvider), + tf: shimProvider, resources: map[tokens.Type]Resource{ "importableResource": { - TF: shimv2.NewResource(tfProvider.ResourcesMap["importable_resource"]), + TF: shimProvider.ResourcesMap().Get("importable_resource"), TFName: "importable_resource", Schema: info, }, @@ -2760,7 +2748,8 @@ func TestExtractSchemaInputsNestedMaxItemsOne(t *testing.T) { { name: "no overrides", expectedOutputs: resource.PropertyMap{ - "id": resource.NewProperty("MyID"), + "id": resource.NewProperty("MyID"), + "__meta": resource.NewStringProperty("{\"schema_version\":\"0\"}"), "listObjectMaxitems": resource.NewProperty(resource.PropertyMap{ "field1": resource.NewProperty(true), "listScalar": resource.NewProperty(2.0), @@ -2808,7 +2797,8 @@ func TestExtractSchemaInputsNestedMaxItemsOne(t *testing.T) { }, }, expectedOutputs: resource.PropertyMap{ - "id": resource.NewProperty("MyID"), + "id": resource.NewProperty("MyID"), + "__meta": resource.NewStringProperty("{\"schema_version\":\"0\"}"), "listObject": resource.NewProperty(resource.PropertyMap{ "field1": resource.NewProperty(false), "listScalars": resource.NewProperty([]resource.PropertyValue{ diff --git a/pkg/tfbridge/tests/provider_test.go b/pkg/tfbridge/tests/provider_test.go index 6cd1deac1..9990d8597 100644 --- a/pkg/tfbridge/tests/provider_test.go +++ b/pkg/tfbridge/tests/provider_test.go @@ -13,7 +13,6 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go" - "github.com/stretchr/testify/assert" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge/info" @@ -285,444 +284,6 @@ func TestReproMinimalDiffCycle(t *testing.T) { }`) } -func TestValidateInputsPanic(t *testing.T) { - ctx := context.Background() - p := newTestProvider(ctx, tfbridge.ProviderInfo{ - P: shimv2.NewProvider(&schema.Provider{ - Schema: map[string]*schema.Schema{}, - ResourcesMap: map[string]*schema.Resource{ - "example_resource": { - Schema: map[string]*schema.Schema{ - "tags": { - Type: schema.TypeMap, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - }, - "network_configuration": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "assign_public_ip": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "security_groups": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "subnets": { - Type: schema.TypeSet, - Required: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - }, - }, - }, - }, - }, shimv2.WithDiffStrategy(shimv2.PlanState)), - Name: "testprov", - ResourcePrefix: "example", - Resources: map[string]*tfbridge.ResourceInfo{ - "example_resource": {Tok: "testprov:index:ExampleResource"}, - }, - }, newTestProviderOptions{}) - - t.Run("diff_panic", func(t *testing.T) { - assert.Panics(t, func() { - replay.ReplaySequence(t, p, ` - [ - { - "method": "/pulumirpc.ResourceProvider/Diff", - "request": { - "urn": "urn:pulumi:dev::teststack::testprov:index:ExampleResource::exres", - "olds": { - "networkConfiguration": { - "__defaults": [ - "assignPublicIp" - ], - "assignPublicIp": false, - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - }, - "news": { - "networkConfiguration": { - "__defaults": [ - "assignPublicIp" - ], - "assignPublicIp": false, - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - } - }, - "response": { - } - } - ] - `) - }) - }) - - t.Run("diff_no_panic", func(t *testing.T) { - replay.ReplaySequence(t, p, fmt.Sprintf(` - [ - { - "method": "/pulumirpc.ResourceProvider/Check", - "request": { - "urn": "urn:pulumi:dev::teststack::testprov:index:ExampleResource::exres", - "randomSeed": "ZCiVOcvG/CT5jx4XriguWgj2iMpQEb8P3ZLqU/AS2yg=", - "olds": { - "__defaults": [] - }, - "news": { - "networkConfiguration": { - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - } - }, - "response": { - "inputs": { - "__defaults": [], - "networkConfiguration": { - "__defaults": [ - "assignPublicIp" - ], - "assignPublicIp": false, - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - } - } - }, - { - "method": "/pulumirpc.ResourceProvider/Diff", - "request": { - "urn": "urn:pulumi:dev::teststack::testprov:index:ExampleResource::exres", - "olds": { - "networkConfiguration": { - "__defaults": [ - "assignPublicIp" - ], - "assignPublicIp": false, - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - }, - "news": { - "networkConfiguration": { - "__defaults": [ - "assignPublicIp" - ], - "assignPublicIp": false, - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - } - }, - "response": { - }, - "errors": [ - "%s" - ] - } - ] - `, "diffing urn:pulumi:dev::teststack::testprov:index:ExampleResource::exres: "+ - `panicked: \"value has no attribute of that name\"`)) - }) - - t.Run("update_panic", func(t *testing.T) { - assert.Panics(t, func() { - replay.ReplaySequence(t, p, ` - [ - { - "method": "/pulumirpc.ResourceProvider/Update", - "request": { - "urn": "urn:pulumi:dev::teststack::testprov:index:ExampleResource::exres1", - "olds": { - "networkConfiguration": { - "__defaults": [ - "assignPublicIp" - ], - "assignPublicIp": false, - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - }, - "news": { - "networkConfiguration": { - "__defaults": [ - "assignPublicIp" - ], - "assignPublicIp": false, - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - }, - "preview": true - }, - "response": { - } - } - ] - `) - - }) - }) - - t.Run("update_no_panic", func(t *testing.T) { - replay.ReplaySequence(t, p, fmt.Sprintf(` - [ - { - "method": "/pulumirpc.ResourceProvider/Check", - "request": { - "urn": "urn:pulumi:dev::teststack::testprov:index:ExampleResource::exres2", - "randomSeed": "ZCiVOcvG/CT5jx4XriguWgj2iMpQEb8P3ZLqU/AS2yg=", - "olds": { - "__defaults": [] - }, - "news": { - "networkConfiguration": { - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - } - }, - "response": { - "inputs": { - "__defaults": [], - "networkConfiguration": { - "__defaults": [ - "assignPublicIp" - ], - "assignPublicIp": false, - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - } - } - }, - { - "method": "/pulumirpc.ResourceProvider/Update", - "request": { - "urn": "urn:pulumi:dev::teststack::testprov:index:ExampleResource::exres2", - "olds": { - "networkConfiguration": { - "__defaults": [ - "assignPublicIp" - ], - "assignPublicIp": false, - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - }, - "news": { - "networkConfiguration": { - "__defaults": [ - "assignPublicIp" - ], - "assignPublicIp": false, - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - }, - "preview": true - }, - "response": { - }, - "errors": [ - "%s" - ] - } - ] - `, "diffing urn:pulumi:dev::teststack::testprov:index:ExampleResource::exres2: "+ - `panicked: \"value has no attribute of that name\"`)) - - }) - - // don't validate properties with "__*" names - t.Run("check_with_defaults_no_panic", func(t *testing.T) { - t.Setenv("PULUMI_ERROR_TYPE_CHECKER", "true") - replay.ReplaySequence(t, p, ` - [ - { - "method": "/pulumirpc.ResourceProvider/Check", - "request": { - "urn": "urn:pulumi:dev::teststack::testprov:index:ExampleResource::exres3", - "randomSeed": "ZCiVOcvG/CT5jx4XriguWgj2iMpQEb8P3ZLqU/AS2yg=", - "olds": { - "__defaults": [], - "tags": { - "LocalTag": "foo", - "__defaults": [] - } - }, - "news": { - "tags": { - "LocalTag": "foo", - "__defaults": [] - }, - "networkConfiguration": { - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": ["first","second"] - } - } - }, - "response": { - "inputs": { - "tags": { - "LocalTag": "foo", - "__defaults": [] - }, - "__defaults": [], - "networkConfiguration": { - "__defaults": [ - "assignPublicIp" - ], - "assignPublicIp": false, - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": ["first","second"] - } - } - } - } - ] - `) - - }) - - t.Run("create_no_panic", func(t *testing.T) { - replay.ReplaySequence(t, p, fmt.Sprintf(` - [ - { - "method": "/pulumirpc.ResourceProvider/Check", - "request": { - "urn": "urn:pulumi:dev::teststack::testprov:index:ExampleResource::exres3", - "randomSeed": "ZCiVOcvG/CT5jx4XriguWgj2iMpQEb8P3ZLqU/AS2yg=", - "olds": { - "__defaults": [] - }, - "news": { - "networkConfiguration": { - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - } - }, - "response": { - "inputs": { - "__defaults": [], - "networkConfiguration": { - "__defaults": [ - "assignPublicIp" - ], - "assignPublicIp": false, - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - } - } - }, - { - "method": "/pulumirpc.ResourceProvider/Create", - "request": { - "urn": "urn:pulumi:dev::teststack::testprov:index:ExampleResource::exres3", - "properties": { - "networkConfiguration": { - "__defaults": [ - "assignPublicIp" - ], - "assignPublicIp": false, - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - }, - "preview": true - }, - "response": { - }, - "errors": [ - "%s" - ] - } - ] - `, "diffing urn:pulumi:dev::teststack::testprov:index:ExampleResource::exres3: "+ - `panicked: \"value has no attribute of that name\"`)) - }) - - t.Run("create_panic", func(t *testing.T) { - assert.Panics(t, func() { - - replay.ReplaySequence(t, p, ` - [ - { - "method": "/pulumirpc.ResourceProvider/Create", - "request": { - "urn": "urn:pulumi:dev::teststack::testprov:index:ExampleResource::exres4", - "properties": { - "networkConfiguration": { - "__defaults": [ - "assignPublicIp" - ], - "assignPublicIp": false, - "securityGroups": [ - "04da6b54-80e4-46f7-96ec-b56ff0331ba9" - ], - "subnets": "[\"first\",\"second\"]" - } - }, - "preview": true - }, - "response": { - } - } - ] - `) - }) - }) -} - func TestValidateConfig(t *testing.T) { ctx := context.Background() p := newTestProvider(ctx, tfbridge.ProviderInfo{ diff --git a/pkg/tfshim/sdk-v2/provider.go b/pkg/tfshim/sdk-v2/provider.go index d8236885c..5d2d41a59 100644 --- a/pkg/tfshim/sdk-v2/provider.go +++ b/pkg/tfshim/sdk-v2/provider.go @@ -3,14 +3,12 @@ package sdkv2 import ( "context" "fmt" - "os" "github.com/golang/glog" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" testing "github.com/mitchellh/go-testing-interface" - "github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" shim "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim" @@ -69,13 +67,11 @@ func NewProvider(p *schema.Provider, opts ...providerOption) shim.Provider { o, err := getProviderOptions(opts) contract.AssertNoErrorf(err, "provider options failed to apply") - if cmdutil.IsTruthy(os.Getenv("PULUMI_ENABLE_PLAN_RESOURCE_CHANGE")) { - return newProviderWithPlanResourceChange(p, prov, func(s string) bool { return true }, o.planStateEdit) - } if o.planResourceChangeFilter != nil { return newProviderWithPlanResourceChange(p, prov, o.planResourceChangeFilter, o.planStateEdit) } - return prov + + return newProviderWithPlanResourceChange(p, prov, func(s string) bool { return true }, o.planStateEdit) } func (p v2Provider) Schema() shim.SchemaMap { diff --git a/pkg/tfshim/sdk-v2/provider_diff_test.go b/pkg/tfshim/sdk-v2/provider_diff_test.go index d943ba950..f32d48a4f 100644 --- a/pkg/tfshim/sdk-v2/provider_diff_test.go +++ b/pkg/tfshim/sdk-v2/provider_diff_test.go @@ -57,15 +57,15 @@ func TestRawPlanSet(t *testing.T) { instanceState.Meta = map[string]interface{}{} // ignore schema versions for this test resourceConfig := terraform.NewResourceConfigShimmed(config, r.CoreConfigSchema()) - ss := v2InstanceState{ - resource: r, - tf: instanceState, + ss := v2InstanceState2{ + resourceType: "myres", + stateValue: state, } - id, err := wp.Diff(ctx, "myres", ss, v2ResourceConfig{ + id, err := wp.Diff(ctx, "myres", &ss, v2ResourceConfig{ tf: resourceConfig, }, shim.DiffOptions{}) require.NoError(t, err) - assert.False(t, id.(v2InstanceDiff).tf.RawPlan.IsNull(), "RawPlan should not be Null") + assert.False(t, id.(*v2InstanceDiff2).tf.RawPlan.IsNull(), "RawPlan should not be Null") }