diff --git a/pkg/tests/schema_pulumi_test.go b/pkg/tests/schema_pulumi_test.go index aeeb70f32..3b17a109d 100644 --- a/pkg/tests/schema_pulumi_test.go +++ b/pkg/tests/schema_pulumi_test.go @@ -3,10 +3,13 @@ package tests import ( "context" "fmt" + "os" + "path/filepath" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hexops/autogold/v2" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tests/internal/pulcheck" "github.com/pulumi/pulumi/sdk/v3/go/auto/optpreview" "github.com/pulumi/pulumi/sdk/v3/go/auto/optrefresh" @@ -684,3 +687,726 @@ outputs: }) } } + +func TestUnknownBlocks(t *testing.T) { + resMap := map[string]*schema.Resource{ + "prov_test": { + Schema: map[string]*schema.Schema{ + "test": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "test_prop": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + "prov_nested_test": { + Schema: map[string]*schema.Schema{ + "test": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "nested_prop": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "test_prop": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "prov_aux": { + Schema: map[string]*schema.Schema{ + "aux": { + Type: schema.TypeList, + Computed: true, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "test_prop": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + }, + }, + }, + "nested_aux": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "nested_prop": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "test_prop": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + }, + }, + CreateContext: func(_ context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics { + d.SetId("aux") + if d.Get("aux") == nil { + err := d.Set("aux", []map[string]interface{}{{"test_prop": "aux"}}) + require.NoError(t, err) + } + if d.Get("nested_aux") == nil { + err := d.Set("nested_aux", []map[string]interface{}{ + { + "nested_prop": []map[string]interface{}{ + {"test_prop": []string{"aux"}}, + }, + }, + }) + require.NoError(t, err) + } + return nil + }, + }, + } + bridgedProvider := pulcheck.BridgedProvider(t, "prov", resMap) + + provTestKnownProgram := ` +name: test +runtime: yaml +resources: + mainRes: + type: prov:index:Test + properties: + tests: + - testProp: "known_val" +` + nestedProvTestKnownProgram := ` +name: test +runtime: yaml +resources: + mainRes: + type: prov:index:NestedTest + properties: + tests: + - nestedProps: + - testProps: + - "known_val" +` + + for _, tc := range []struct { + name string + program string + initialKnownProgram string + expectedInitial autogold.Value + expectedUpdate autogold.Value + }{ + { + "list of objects", + ` +name: test +runtime: yaml +resources: + auxRes: + type: prov:index:Aux + properties: + auxes: %s + nestedAuxes: %s + mainRes: + type: prov:index:Test + properties: + tests: ${auxRes.auxes} +`, + provTestKnownProgram, + autogold.Expect(`Previewing update (test): ++ pulumi:pulumi:Stack: (create) + [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: (create) + [urn=urn:pulumi:test::test::prov:index/test:Test::mainRes] + tests : [ + [0]: {} + ] +Resources: + + 3 to create +`), + 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]: { + - testProp: "known_val" + } + ] +Resources: + + 1 to create + ~ 1 to update + 2 changes. 1 unchanged +`), + }, + { + "unknown object", + ` +name: test +runtime: yaml +resources: + auxRes: + type: prov:index:Aux + properties: + auxes: %s + nestedAuxes: %s + mainRes: + type: prov:index:Test + properties: + tests: + - ${auxRes.auxes[0]} +`, + provTestKnownProgram, + + autogold.Expect(`Previewing update (test): ++ pulumi:pulumi:Stack: (create) + [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: (create) + [urn=urn:pulumi:test::test::prov:index/test:Test::mainRes] + tests : [ + [0]: output + ] +Resources: + + 3 to create +`), + 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]: { + - testProp: "known_val" + } + + [0]: output + ] +Resources: + + 1 to create + ~ 1 to update + 2 changes. 1 unchanged +`), + }, + { + "unknown object with others", + ` +name: test +runtime: yaml +resources: + auxRes: + type: prov:index:Aux + properties: + auxes: %s + nestedAuxes: %s + mainRes: + type: prov:index:Test + properties: + tests: + - ${auxRes.auxes[0]} + - {"testProp": "val"} +`, + provTestKnownProgram, + + autogold.Expect(`Previewing update (test): ++ pulumi:pulumi:Stack: (create) + [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: (create) + [urn=urn:pulumi:test::test::prov:index/test:Test::mainRes] + tests : [ + [0]: output + [1]: { + testProp : "val" + } + ] +Resources: + + 3 to create +`), + 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]: { + - testProp: "known_val" + } + + [0]: output + + [1]: { + + testProp : "val" + } + ] +Resources: + + 1 to create + ~ 1 to update + 2 changes. 1 unchanged +`), + }, + { + "unknown nested", + ` +name: test +runtime: yaml +resources: + auxRes: + type: prov:index:Aux + properties: + auxes: %s + nestedAuxes: %s + mainRes: + type: prov:index:NestedTest + properties: + tests: ${auxRes.nestedAuxes} +`, + nestedProvTestKnownProgram, + autogold.Expect(`Previewing update (test): ++ pulumi:pulumi:Stack: (create) + [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/nestedTest:NestedTest: (create) + [urn=urn:pulumi:test::test::prov:index/nestedTest:NestedTest::mainRes] + tests : [ + [0]: {} + ] +Resources: + + 3 to create +`), + 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/nestedTest:NestedTest: (update) + [id=newid] + [urn=urn:pulumi:test::test::prov:index/nestedTest:NestedTest::mainRes] + ~ tests: [ + ~ [0]: { + - nestedProps: [ + - [0]: { + - testProps: [ + - [0]: "known_val" + ] + } + ] + - nestedProps: [ + - [0]: { + - testProps: [ + - [0]: "known_val" + ] + } + ] + } + ] +Resources: + + 1 to create + ~ 1 to update + 2 changes. 1 unchanged +`), + }, + { + "unknown nested level 1", + ` +name: test +runtime: yaml +resources: + auxRes: + type: prov:index:Aux + properties: + auxes: %s + nestedAuxes: %s + mainRes: + type: prov:index:NestedTest + properties: + tests: + - ${auxRes.nestedAuxes[0]} +`, + nestedProvTestKnownProgram, + autogold.Expect(`Previewing update (test): ++ pulumi:pulumi:Stack: (create) + [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/nestedTest:NestedTest: (create) + [urn=urn:pulumi:test::test::prov:index/nestedTest:NestedTest::mainRes] + tests : [ + [0]: output + ] +Resources: + + 3 to create +`), + 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/nestedTest:NestedTest: (update) + [id=newid] + [urn=urn:pulumi:test::test::prov:index/nestedTest:NestedTest::mainRes] + ~ tests: [ + - [0]: { + - nestedProps: [ + - [0]: { + - testProps: [ + - [0]: "known_val" + ] + } + ] + } + + [0]: output + ] +Resources: + + 1 to create + ~ 1 to update + 2 changes. 1 unchanged +`), + }, + { + "unknown nested level 2", + ` +name: test +runtime: yaml +resources: + auxRes: + type: prov:index:Aux + properties: + auxes: %s + nestedAuxes: %s + mainRes: + type: prov:index:NestedTest + properties: + tests: + - nestedProps: ${auxRes.nestedAuxes[0].nestedProps} +`, + nestedProvTestKnownProgram, + autogold.Expect(`Previewing update (test): ++ pulumi:pulumi:Stack: (create) + [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/nestedTest:NestedTest: (create) + [urn=urn:pulumi:test::test::prov:index/nestedTest:NestedTest::mainRes] + tests : [ + [0]: { + nestedProps: [ + [0]: {} + ] + } + ] +Resources: + + 3 to create +`), + 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/nestedTest:NestedTest: (update) + [id=newid] + [urn=urn:pulumi:test::test::prov:index/nestedTest:NestedTest::mainRes] + ~ tests: [ + ~ [0]: { + ~ nestedProps: [ + ~ [0]: { + - testProps: [ + - [0]: "known_val" + ] + - testProps: [ + - [0]: "known_val" + ] + } + ] + } + ] +Resources: + + 1 to create + ~ 1 to update + 2 changes. 1 unchanged +`), + }, + { + "unknown nested level 3", + ` +name: test +runtime: yaml +resources: + auxRes: + type: prov:index:Aux + properties: + auxes: %s + nestedAuxes: %s + mainRes: + type: prov:index:NestedTest + properties: + tests: + - nestedProps: + - ${auxRes.nestedAuxes[0].nestedProps[0]} +`, + nestedProvTestKnownProgram, + autogold.Expect(`Previewing update (test): ++ pulumi:pulumi:Stack: (create) + [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/nestedTest:NestedTest: (create) + [urn=urn:pulumi:test::test::prov:index/nestedTest:NestedTest::mainRes] + tests : [ + [0]: { + nestedProps: [ + [0]: output + ] + } + ] +Resources: + + 3 to create +`), + 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/nestedTest:NestedTest: (update) + [id=newid] + [urn=urn:pulumi:test::test::prov:index/nestedTest:NestedTest::mainRes] + ~ tests: [ + ~ [0]: { + ~ nestedProps: [ + - [0]: { + - testProps: [ + - [0]: "known_val" + ] + } + + [0]: output + ] + } + ] +Resources: + + 1 to create + ~ 1 to update + 2 changes. 1 unchanged +`), + }, + { + "unknown nested level 4", + ` +name: test +runtime: yaml +resources: + auxRes: + type: prov:index:Aux + properties: + auxes: %s + nestedAuxes: %s + mainRes: + type: prov:index:NestedTest + properties: + tests: + - nestedProps: + - testProps: ${auxRes.nestedAuxes[0].nestedProps[0].testProps} +`, + nestedProvTestKnownProgram, + autogold.Expect(`Previewing update (test): ++ pulumi:pulumi:Stack: (create) + [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/nestedTest:NestedTest: (create) + [urn=urn:pulumi:test::test::prov:index/nestedTest:NestedTest::mainRes] + tests : [ + [0]: { + nestedProps: [ + [0]: { + testProps : [ + [0]: output + ] + } + ] + } + ] +Resources: + + 3 to create +`), + 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/nestedTest:NestedTest: (update) + [id=newid] + [urn=urn:pulumi:test::test::prov:index/nestedTest:NestedTest::mainRes] + ~ tests: [ + ~ [0]: { + ~ nestedProps: [ + ~ [0]: { + ~ testProps: [ + ~ [0]: "known_val" => output + ] + } + ] + } + ] +Resources: + + 1 to create + ~ 1 to update + 2 changes. 1 unchanged +`), + }, + { + "unknown nested level 5", + ` +name: test +runtime: yaml +resources: + auxRes: + type: prov:index:Aux + properties: + auxes: %s + nestedAuxes: %s + mainRes: + type: prov:index:NestedTest + properties: + tests: + - nestedProps: + - testProps: + - ${auxRes.nestedAuxes[0].nestedProps[0].testProps[0]} +`, + nestedProvTestKnownProgram, + autogold.Expect(`Previewing update (test): ++ pulumi:pulumi:Stack: (create) + [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/nestedTest:NestedTest: (create) + [urn=urn:pulumi:test::test::prov:index/nestedTest:NestedTest::mainRes] + tests : [ + [0]: { + nestedProps: [ + [0]: { + testProps : [ + [0]: output + ] + } + ] + } + ] +Resources: + + 3 to create +`), + 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/nestedTest:NestedTest: (update) + [id=newid] + [urn=urn:pulumi:test::test::prov:index/nestedTest:NestedTest::mainRes] + ~ tests: [ + ~ [0]: { + ~ nestedProps: [ + ~ [0]: { + ~ testProps: [ + ~ [0]: "known_val" => output + ] + } + ] + } + ] +Resources: + + 1 to create + ~ 1 to update + 2 changes. 1 unchanged +`), + }, + } { + t.Run(tc.name, func(t *testing.T) { + computedProgram := fmt.Sprintf(tc.program, "null", "null") + + t.Run("initial preview", func(t *testing.T) { + pt := pulcheck.PulCheck(t, bridgedProvider, computedProgram) + res := pt.Preview(optpreview.Diff()) + t.Logf(res.StdOut) + + tc.expectedInitial.Equal(t, res.StdOut) + }) + + t.Run("update preview", func(t *testing.T) { + t.Skipf("Skipping this test as it this case is not handled by the TF plugin sdk") + // The TF plugin SDK does not handle removing an input for a computed value, even if the provider implements it. + // The plugin SDK always fills an empty Computed property with the value from the state. + // Diff in these cases always returns no diff and the old state value is used. + nonComputedProgram := fmt.Sprintf(tc.program, "[{testProp: \"val1\"}]", "[{nestedProps: [{testProps: [\"val1\"]}]}]") + pt := pulcheck.PulCheck(t, bridgedProvider, nonComputedProgram) + pt.Up() + + pulumiYamlPath := filepath.Join(pt.CurrentStack().Workspace().WorkDir(), "Pulumi.yaml") + + err := os.WriteFile(pulumiYamlPath, []byte(computedProgram), 0o600) + require.NoError(t, err) + + res := pt.Preview(optpreview.Diff()) + t.Logf(res.StdOut) + tc.expectedUpdate.Equal(t, res.StdOut) + }) + + t.Run("update preview with computed", func(t *testing.T) { + pt := pulcheck.PulCheck(t, bridgedProvider, tc.initialKnownProgram) + pt.Up() + + pulumiYamlPath := filepath.Join(pt.CurrentStack().Workspace().WorkDir(), "Pulumi.yaml") + + err := os.WriteFile(pulumiYamlPath, []byte(computedProgram), 0o600) + require.NoError(t, err) + + res := pt.Preview(optpreview.Diff()) + t.Logf(res.StdOut) + tc.expectedUpdate.Equal(t, res.StdOut) + }) + }) + } +}