diff --git a/pkg/tests/cross-tests/diff_check.go b/pkg/tests/cross-tests/diff_check.go index 62dd84610..472fd8e80 100644 --- a/pkg/tests/cross-tests/diff_check.go +++ b/pkg/tests/cross-tests/diff_check.go @@ -63,9 +63,11 @@ func runDiffCheck(t T, tc diffTestCase) diffResult { lifecycleArgs := lifecycleArgs{CreateBeforeDestroy: !tc.DeleteBeforeReplace} + tfConfig1 := coalesceInputs(t, tc.Resource.Schema, tc.Config1) + tfConfig2 := coalesceInputs(t, tc.Resource.Schema, tc.Config2) tfd := newTFResDriver(t, tfwd, defProviderShortName, defRtype, tc.Resource) - _ = tfd.writePlanApply(t, tc.Resource.Schema, defRtype, "example", tc.Config1, lifecycleArgs) - tfDiffPlan := tfd.writePlanApply(t, tc.Resource.Schema, defRtype, "example", tc.Config2, lifecycleArgs) + _ = tfd.writePlanApply(t, tc.Resource.Schema, defRtype, "example", tfConfig1, lifecycleArgs) + tfDiffPlan := tfd.writePlanApply(t, tc.Resource.Schema, defRtype, "example", tfConfig2, lifecycleArgs) resMap := map[string]*schema.Resource{defRtype: tc.Resource} tfp := &schema.Provider{ResourcesMap: resMap} @@ -78,16 +80,16 @@ func runDiffCheck(t T, tc diffTestCase) diffResult { name: defProviderShortName, pulumiResourceToken: defRtoken, tfResourceName: defRtype, - objectType: nil, } - yamlProgram := pd.generateYAML(t, bridgedProvider.P.ResourcesMap(), tc.Config1) - pt := pulcheck.PulCheck(t, bridgedProvider, string(yamlProgram)) + yamlProgram := pd.generateYAML(t, convertConfigValueForYamlProperties(t, + bridgedProvider.P.ResourcesMap().Get(defRtype).Schema(), tc.ObjectType, tc.Config1)) + pt := pulcheck.PulCheck(t, bridgedProvider, string(yamlProgram)) pt.Up(t) - yamlProgram = pd.generateYAML(t, bridgedProvider.P.ResourcesMap(), tc.Config2) - p := filepath.Join(pt.CurrentStack().Workspace().WorkDir(), "Pulumi.yaml") - err := os.WriteFile(p, yamlProgram, 0o600) + yamlProgram = pd.generateYAML(t, convertConfigValueForYamlProperties(t, + bridgedProvider.P.ResourcesMap().Get(defRtype).Schema(), tc.ObjectType, tc.Config2)) + err := os.WriteFile(filepath.Join(pt.CurrentStack().Workspace().WorkDir(), "Pulumi.yaml"), yamlProgram, 0o600) require.NoErrorf(t, err, "writing Pulumi.yaml") x := pt.Up(t) diff --git a/pkg/tests/cross-tests/input_check.go b/pkg/tests/cross-tests/input_check.go index 5fa2b5aee..d932e4b0d 100644 --- a/pkg/tests/cross-tests/input_check.go +++ b/pkg/tests/cross-tests/input_check.go @@ -53,7 +53,7 @@ func runCreateInputCheck(t T, tc inputTestCase) { tfwd := t.TempDir() tfd := newTFResDriver(t, tfwd, defProviderShortName, defRtype, tc.Resource) - tfd.writePlanApply(t, tc.Resource.Schema, defRtype, "example", tc.Config, lifecycleArgs{}) + tfd.writePlanApply(t, tc.Resource.Schema, defRtype, "example", coalesceInputs(t, tc.Resource.Schema, tc.Config), lifecycleArgs{}) resMap := map[string]*schema.Resource{defRtype: tc.Resource} tfp := &schema.Provider{ResourcesMap: resMap} @@ -62,9 +62,10 @@ func runCreateInputCheck(t T, tc inputTestCase) { name: defProviderShortName, pulumiResourceToken: defRtoken, tfResourceName: defRtype, - objectType: tc.ObjectType, } - yamlProgram := pd.generateYAML(t, bridgedProvider.P.ResourcesMap(), tc.Config) + + yamlProgram := pd.generateYAML(t, convertConfigValueForYamlProperties(t, + bridgedProvider.P.ResourcesMap().Get(pd.tfResourceName).Schema(), tc.ObjectType, tc.Config)) pt := pulcheck.PulCheck(t, bridgedProvider, string(yamlProgram)) diff --git a/pkg/tests/cross-tests/pu_driver.go b/pkg/tests/cross-tests/pu_driver.go index 493e9fac9..0ae7f2fa8 100644 --- a/pkg/tests/cross-tests/pu_driver.go +++ b/pkg/tests/cross-tests/pu_driver.go @@ -15,8 +15,7 @@ package crosstests import ( - "github.com/hashicorp/terraform-plugin-go/tftypes" - shim "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim" + "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" ) @@ -25,14 +24,10 @@ type pulumiDriver struct { name string pulumiResourceToken string tfResourceName string - objectType *tftypes.Object } -func (pd *pulumiDriver) generateYAML(t T, resMap shim.ResourceMap, tfConfig any) []byte { - res := resMap.Get(pd.tfResourceName) - schema := res.Schema() - - data, err := generateYaml(schema, pd.pulumiResourceToken, pd.objectType, tfConfig) +func (pd *pulumiDriver) generateYAML(t T, puConfig resource.PropertyMap) []byte { + data, err := generateYaml(pd.pulumiResourceToken, puConfig) require.NoErrorf(t, err, "generateYaml") b, err := yaml.Marshal(data) diff --git a/pkg/tests/cross-tests/puwrite.go b/pkg/tests/cross-tests/puwrite.go index 7ed6e9a30..a256b834f 100644 --- a/pkg/tests/cross-tests/puwrite.go +++ b/pkg/tests/cross-tests/puwrite.go @@ -6,14 +6,25 @@ import ( "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/convert" - "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge" shim "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim" "github.com/pulumi/pulumi-terraform-bridge/v3/unstable/logging" "github.com/pulumi/pulumi-terraform-bridge/v3/unstable/propertyvalue" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" + "github.com/stretchr/testify/require" ) -func generateYaml(schema shim.SchemaMap, resourceToken string, objectType *tftypes.Object, tfConfig any) (map[string]any, error) { +func convertConfigValueForYamlProperties(t T, schema shim.SchemaMap, objectType *tftypes.Object, tfConfig any) resource.PropertyMap { + if tfConfig == nil { + return nil + } + pConfig, err := convertConfigToPulumi(schema, objectType, tfConfig) + require.NoError(t, err) + + // TODO[pulumi/pulumi-terraform-bridge#1864]: schema secrets may be set by convertConfigToPulumi. + return propertyvalue.RemoveSecrets(resource.NewObjectProperty(pConfig)).ObjectValue() +} + +func generateYaml(resourceToken string, puConfig resource.PropertyMap) (map[string]any, error) { data := map[string]any{ "name": "project", "runtime": "yaml", @@ -21,25 +32,16 @@ func generateYaml(schema shim.SchemaMap, resourceToken string, objectType *tftyp "url": "file://./data", }, } - if tfConfig == nil { + if puConfig == nil { return data, nil } - pConfig, err := convertConfigToPulumi(schema, nil, objectType, tfConfig) - if err != nil { - return nil, err - } - - // TODO[pulumi/pulumi-terraform-bridge#1864]: schema secrets may be set by convertConfigToPulumi. - pConfig = propertyvalue.RemoveSecrets(resource.NewObjectProperty(pConfig)).ObjectValue() - - // This is a bit of a leap of faith that serializing PropertyMap to YAML in this way will yield valid Pulumi - // YAML. This probably needs refinement. - yamlProperties := pConfig.Mappable() data["resources"] = map[string]any{ "example": map[string]any{ - "type": resourceToken, - "properties": yamlProperties, + "type": resourceToken, + // This is a bit of a leap of faith that serializing PropertyMap to YAML in this way will yield valid Pulumi + // YAML. This probably needs refinement. + "properties": puConfig.Mappable(), }, } return data, nil @@ -47,7 +49,6 @@ func generateYaml(schema shim.SchemaMap, resourceToken string, objectType *tftyp func convertConfigToPulumi( schemaMap shim.SchemaMap, - schemaInfos map[string]*tfbridge.SchemaInfo, objectType *tftypes.Object, tfConfig any, ) (resource.PropertyMap, error) { @@ -87,9 +88,8 @@ func convertConfigToPulumi( } decoder, err := convert.NewObjectDecoder(convert.ObjectSchema{ - SchemaMap: schemaMap, - SchemaInfos: schemaInfos, - Object: objectType, + SchemaMap: schemaMap, + Object: objectType, }) if err != nil { return nil, err diff --git a/pkg/tests/cross-tests/puwrite_test.go b/pkg/tests/cross-tests/puwrite_test.go index bb726153a..935685e0f 100644 --- a/pkg/tests/cross-tests/puwrite_test.go +++ b/pkg/tests/cross-tests/puwrite_test.go @@ -121,7 +121,8 @@ runtime: yaml func(tfResourceType string) bool { return true }, )) schema := shimProvider.ResourcesMap().Get(rtype).Schema() - out, err := generateYaml(schema, rtoken, nil, tc.tfConfig) + out, err := generateYaml(rtoken, + convertConfigValueForYamlProperties(t, schema, nil, tc.tfConfig)) require.NoError(t, err) b, err := yaml.Marshal(out) require.NoError(t, err) diff --git a/pkg/tests/cross-tests/tf_driver.go b/pkg/tests/cross-tests/tf_driver.go index 99c351416..573f646d9 100644 --- a/pkg/tests/cross-tests/tf_driver.go +++ b/pkg/tests/cross-tests/tf_driver.go @@ -47,17 +47,25 @@ func newTFResDriver(t T, dir, providerName, resName string, res *schema.Resource } } -func (d *TfResDriver) coalesce(t T, x any) *tftypes.Value { - if x == nil { - return nil - } - objectType := convert.InferObjectType(sdkv2.NewSchemaMap(d.res.Schema), nil) - for k := range objectType.AttributeTypes { - objectType.OptionalAttributes[k] = struct{}{} +func coalesceInputs(t T, schema map[string]*schema.Schema, config any) cty.Value { + switch config := ((any)(config)).(type) { + case nil: + return cty.NullVal(cty.DynamicPseudoType) + case cty.Value: + return config + case map[string]any: + objectType := convert.InferObjectType(sdkv2.NewSchemaMap(schema), nil) + for k := range objectType.AttributeTypes { + objectType.OptionalAttributes[k] = struct{}{} + } + v := fromType(objectType).NewValue(config) + return fromValue(v).ToCty() + case tftypes.Value: + return fromValue(config).ToCty() + default: + require.Failf(t, "unknown type", "unable to convert config type %T to %T", config, cty.Value{}) + return cty.Value{} } - t.Logf("infer object type: %v", objectType) - v := fromType(objectType).NewValue(x) - return &v } type lifecycleArgs struct { @@ -68,16 +76,16 @@ func (d *TfResDriver) writePlanApply( t T, resourceSchema map[string]*schema.Schema, resourceType, resourceName string, - rawConfig any, + config cty.Value, lifecycle lifecycleArgs, ) *tfcheck.TfPlan { - config := d.coalesce(t, rawConfig) - if config != nil { - d.write(t, resourceSchema, resourceType, resourceName, *config, lifecycle) + if !config.IsNull() { + d.write(t, resourceSchema, resourceType, resourceName, config, lifecycle) } else { t.Logf("empty config file") d.driver.Write(t, "") } + plan, err := d.driver.Plan(t) require.NoError(t, err) err = d.driver.Apply(t, plan) @@ -89,24 +97,21 @@ func (d *TfResDriver) write( t T, resourceSchema map[string]*schema.Schema, resourceType, resourceName string, - config tftypes.Value, + config cty.Value, lifecycle lifecycleArgs, ) { var buf bytes.Buffer - ctyConfig := fromValue(config).ToCty() if lifecycle.CreateBeforeDestroy { - ctyMap := ctyConfig.AsValueMap() + ctyMap := config.AsValueMap() if ctyMap == nil { - ctyMap = make(map[string]cty.Value) + ctyMap = map[string]cty.Value{} } - ctyMap["lifecycle"] = cty.ObjectVal( - map[string]cty.Value{ - "create_before_destroy": cty.True, - }, - ) - ctyConfig = cty.ObjectVal(ctyMap) + ctyMap["lifecycle"] = cty.ObjectVal(map[string]cty.Value{ + "create_before_destroy": cty.True, + }) + config = cty.ObjectVal(ctyMap) } - err := WriteHCL(&buf, resourceSchema, resourceType, resourceName, ctyConfig) + err := WriteHCL(&buf, resourceSchema, resourceType, resourceName, config) require.NoError(t, err) t.Logf("HCL: \n%s\n", buf.String()) d.driver.Write(t, buf.String()) diff --git a/pkg/tests/cross-tests/upgrade_state_check.go b/pkg/tests/cross-tests/upgrade_state_check.go index ba0942a2b..d0e91d78f 100644 --- a/pkg/tests/cross-tests/upgrade_state_check.go +++ b/pkg/tests/cross-tests/upgrade_state_check.go @@ -69,16 +69,17 @@ func runPulumiUpgrade(t T, res1, res2 *schema.Resource, config1, config2 any, di name: defProviderShortName, pulumiResourceToken: defRtoken, tfResourceName: defRtype, - objectType: nil, } - yamlProgram := pd.generateYAML(t, prov1.P.ResourcesMap(), config1) + yamlProgram := pd.generateYAML(t, convertConfigValueForYamlProperties(t, + prov1.P.ResourcesMap().Get(pd.tfResourceName).Schema(), nil, config1)) pt := pulcheck.PulCheck(t, prov1, string(yamlProgram)) pt.Up(t) stack := pt.ExportStack(t) schemaVersion1 := getVersionInState(t, stack) - yamlProgram = pd.generateYAML(t, prov2.P.ResourcesMap(), config2) + yamlProgram = pd.generateYAML(t, convertConfigValueForYamlProperties(t, + prov1.P.ResourcesMap().Get(pd.tfResourceName).Schema(), nil, config2)) p := filepath.Join(pt.CurrentStack().Workspace().WorkDir(), "Pulumi.yaml") err := os.WriteFile(p, yamlProgram, 0o600) require.NoErrorf(t, err, "writing Pulumi.yaml") @@ -134,10 +135,10 @@ func runUpgradeStateInputCheck(t T, tc upgradeStateTestCase) { tfwd := t.TempDir() tfd := newTFResDriver(t, tfwd, defProviderShortName, defRtype, tc.Resource) - _ = tfd.writePlanApply(t, tc.Resource.Schema, defRtype, "example", tc.Config1, lifecycleArgs{}) + _ = tfd.writePlanApply(t, tc.Resource.Schema, defRtype, "example", coalesceInputs(t, tc.Resource.Schema, tc.Config1), lifecycleArgs{}) tfd2 := newTFResDriver(t, tfwd, defProviderShortName, defRtype, &upgradeRes) - _ = tfd2.writePlanApply(t, tc.Resource.Schema, defRtype, "example", tc.Config2, lifecycleArgs{}) + _ = tfd2.writePlanApply(t, tc.Resource.Schema, defRtype, "example", coalesceInputs(t, tc.Resource.Schema, tc.Config2), lifecycleArgs{}) schemaVersion1, schemaVersion2 := runPulumiUpgrade(t, tc.Resource, &upgradeRes, tc.Config1, tc.Config2, tc.DisablePlanResourceChange)