Skip to content

Commit

Permalink
Refactor cross-tests to use (cty.Value, resource.PropertyMap) pairs
Browse files Browse the repository at this point in the history
Passing `(cty.Value, resource.PropertyMap)` pairs (instead of just passing
`map[string]any` or `tftypes.Value`) is necessary for testing scenarios where the
Terraform value doesn't neatly line up with the Pulumi value, such as #2048.

This commit pushes pkg/tests/cross-tests to work directly with cty.Value and
resource.PropertyMap instead of converting at the last minute.

All tests pass after this refactor. It only effects test code. Existing tests do not need
to be rewritten.

stack-info: PR: #2500, branch: iwahbe/stack/1
  • Loading branch information
iwahbe committed Oct 18, 2024
1 parent 49aaf75 commit 23d65d6
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 71 deletions.
18 changes: 10 additions & 8 deletions pkg/internal/tests/cross-tests/diff_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,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}
Expand All @@ -81,16 +83,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)

Expand Down
8 changes: 5 additions & 3 deletions pkg/internal/tests/cross-tests/input_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ 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}
Expand All @@ -63,9 +64,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))

Expand Down
12 changes: 3 additions & 9 deletions pkg/internal/tests/cross-tests/pu_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,19 @@
package crosstests

import (
"github.com/hashicorp/terraform-plugin-go/tftypes"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"

shim "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim"
)

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)
Expand Down
40 changes: 20 additions & 20 deletions pkg/internal/tests/cross-tests/puwrite.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,49 +20,50 @@ import (

"github.com/hashicorp/terraform-plugin-go/tftypes"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/stretchr/testify/require"

"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"
)

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",
"backend": map[string]any{
"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
}

func convertConfigToPulumi(
schemaMap shim.SchemaMap,
schemaInfos map[string]*tfbridge.SchemaInfo,
objectType *tftypes.Object,
tfConfig any,
) (resource.PropertyMap, error) {
Expand Down Expand Up @@ -102,9 +103,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
Expand Down
3 changes: 2 additions & 1 deletion pkg/internal/tests/cross-tests/puwrite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,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)
Expand Down
55 changes: 30 additions & 25 deletions pkg/internal/tests/cross-tests/tf_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,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 := 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 {
Expand All @@ -69,16 +77,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)
Expand All @@ -90,24 +98,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())
Expand Down
11 changes: 6 additions & 5 deletions pkg/internal/tests/cross-tests/upgrade_state_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,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")
Expand Down Expand Up @@ -149,10 +150,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)

Expand Down

0 comments on commit 23d65d6

Please sign in to comment.