Repository: pulumi/pulumi-terraform-bridge
Pull request #308 (2020-12-12 00:04:43)

Preserve unknowns during provider preview.
Just what it says on the tin.

pgavlin: Fixes https://github.com/pulumi/pulumi/issues/5864.

I'm working on tests to verify that we don't regress this.
github-actions[bot]: Diff for [pulumi-azuread](https://github.com/pulumi-bot/pulumi-azuread/compare/b9196b2..bf83735) with merge commit 132ff296814e28488e4a62658f6483fee1bb627b
github-actions[bot]: Diff for [pulumi-random](https://github.com/pulumi-bot/pulumi-random/compare/d53c624..0dca5ba) with merge commit 132ff296814e28488e4a62658f6483fee1bb627b
github-actions[bot]: Diff for [pulumi-gcp](https://github.com/pulumi-bot/pulumi-gcp/compare/561c84a6..b5cecc2d) with merge commit 132ff296814e28488e4a62658f6483fee1bb627b
github-actions[bot]: Diff for [pulumi-azure](https://github.com/pulumi-bot/pulumi-azure/compare/a050fd0d6..7b4df51e2) with merge commit 132ff296814e28488e4a62658f6483fee1bb627b
github-actions[bot]: Diff for [pulumi-aws](https://github.com/pulumi-bot/pulumi-aws/compare/123815704..3cbc3c163) with merge commit 132ff296814e28488e4a62658f6483fee1bb627b
github-actions[bot]: Diff for [pulumi-random](https://github.com/pulumi-bot/pulumi-random/compare/c802dd5..9b5a261) with merge commit ad30094744adb1fbe6ebd18e7ef303006def57bd
github-actions[bot]: Diff for [pulumi-azuread](https://github.com/pulumi-bot/pulumi-azuread/compare/f07a7e9..978183d) with merge commit ad30094744adb1fbe6ebd18e7ef303006def57bd
github-actions[bot]: Diff for [pulumi-gcp](https://github.com/pulumi-bot/pulumi-gcp/compare/172a2830..c78db2fa) with merge commit ad30094744adb1fbe6ebd18e7ef303006def57bd
github-actions[bot]: Diff for [pulumi-azure](https://github.com/pulumi-bot/pulumi-azure/compare/f6ae1d2cd..198d816fd) with merge commit ad30094744adb1fbe6ebd18e7ef303006def57bd
github-actions[bot]: Diff for [pulumi-aws](https://github.com/pulumi-bot/pulumi-aws/compare/5e7234ff9..acc6903bb) with merge commit ad30094744adb1fbe6ebd18e7ef303006def57bd
github-actions[bot]: Diff for [pulumi-random](https://github.com/pulumi-bot/pulumi-random/compare/f381d19..7174d43) with merge commit c07be0e6df9e89d2a8d8d91b8bc94de7c69202c6
github-actions[bot]: Diff for [pulumi-azuread](https://github.com/pulumi-bot/pulumi-azuread/compare/3b50b76..7ddee7d) with merge commit c07be0e6df9e89d2a8d8d91b8bc94de7c69202c6
github-actions[bot]: Diff for [pulumi-gcp](https://github.com/pulumi-bot/pulumi-gcp/compare/5787da48..2477f002) with merge commit c07be0e6df9e89d2a8d8d91b8bc94de7c69202c6
github-actions[bot]: Diff for [pulumi-azure](https://github.com/pulumi-bot/pulumi-azure/compare/6e6ab92ae..cf22d9121) with merge commit c07be0e6df9e89d2a8d8d91b8bc94de7c69202c6
github-actions[bot]: Diff for [pulumi-aws](https://github.com/pulumi-bot/pulumi-aws/compare/80dfe022d..df68cdc25) with merge commit c07be0e6df9e89d2a8d8d91b8bc94de7c69202c6
github-actions[bot]: Diff for [pulumi-random](https://github.com/pulumi-bot/pulumi-random/compare/202103a..c07d344) with merge commit 9281cc2d1527897f9bf834a3d8c7d07d3bb3d809
github-actions[bot]: Diff for [pulumi-azuread](https://github.com/pulumi-bot/pulumi-azuread/compare/d5a319b..9406cc3) with merge commit 9281cc2d1527897f9bf834a3d8c7d07d3bb3d809
lukehoban: > Just what it says on the tin.

Would love more details on what the issue was, what the design is, why these changes are the right delta to achieve the design, and what follow-ups (if any) we should consider in this area. 
github-actions[bot]: Diff for [pulumi-gcp](https://github.com/pulumi-bot/pulumi-gcp/compare/f079d6b2..50c48486) with merge commit 9281cc2d1527897f9bf834a3d8c7d07d3bb3d809
github-actions[bot]: Diff for [pulumi-azure](https://github.com/pulumi-bot/pulumi-azure/compare/40273474d..7153e8f33) with merge commit 9281cc2d1527897f9bf834a3d8c7d07d3bb3d809
github-actions[bot]: Diff for [pulumi-aws](https://github.com/pulumi-bot/pulumi-aws/compare/a8c6bd44e..7c630f778) with merge commit 9281cc2d1527897f9bf834a3d8c7d07d3bb3d809

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b94b5279..e6d84b49 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,9 @@ CHANGELOG
 
 ## HEAD (Unreleased)
 
+- Ensure that nested unknowns are preserved during previews of `Create` and `Update` operations.
+  [#308](https://github.com/pulumi/pulumi-terraform-bridge/pull/308)
+
 - Fix the diff reader s.t. reading sets with partially-known elements does not panic.
   [#305](https://github.com/pulumi/pulumi-terraform-bridge/pull/305)
 
diff --git a/pkg/tfbridge/provider.go b/pkg/tfbridge/provider.go
index 30fe480f..954fe888 100644
--- a/pkg/tfbridge/provider.go
+++ b/pkg/tfbridge/provider.go
@@ -821,7 +821,11 @@ func (p *Provider) Create(ctx context.Context, req *pulumirpc.CreateRequest) (*p
 		reasons = append(reasons, errors.Wrapf(err, "converting result for %s", urn).Error())
 	}
 
-	mprops, err := plugin.MarshalProperties(props, plugin.MarshalOptions{Label: fmt.Sprintf("%s.outs", label)})
+	mprops, err := plugin.MarshalProperties(props, plugin.MarshalOptions{
+		Label:        fmt.Sprintf("%s.outs", label),
+		KeepUnknowns: req.GetPreview(),
+		KeepSecrets:  p.supportsSecrets,
+	})
 	if err != nil {
 		reasons = append(reasons, errors.Wrapf(err, "marshalling %s", urn).Error())
 	}
@@ -897,7 +901,10 @@ func (p *Provider) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*pulum
 		if err != nil {
 			return nil, err
 		}
-		minputs, err := plugin.MarshalProperties(inputs, plugin.MarshalOptions{Label: label + ".inputs"})
+		minputs, err := plugin.MarshalProperties(inputs, plugin.MarshalOptions{
+			Label:       label + ".inputs",
+			KeepSecrets: p.supportsSecrets,
+		})
 		if err != nil {
 			return nil, err
 		}
@@ -995,7 +1002,10 @@ func (p *Provider) Update(ctx context.Context, req *pulumirpc.UpdateRequest) (*p
 		reasons = append(reasons, errors.Wrapf(err, "converting result for %s", urn).Error())
 	}
 	mprops, err := plugin.MarshalProperties(props, plugin.MarshalOptions{
-		Label: fmt.Sprintf("%s.outs", label)})
+		Label:        fmt.Sprintf("%s.outs", label),
+		KeepUnknowns: req.GetPreview(),
+		KeepSecrets:  p.supportsSecrets,
+	})
 	if err != nil {
 		reasons = append(reasons, errors.Wrapf(err, "marshalling %s", urn).Error())
 	}


Test patch:
diff --git a/internal/testprovider/schema.go b/internal/testprovider/schema.go
index 965dfae5..a3fe403b 100644
--- a/internal/testprovider/schema.go
+++ b/internal/testprovider/schema.go
@@ -64,6 +64,7 @@ func ProviderV1() *schemav1.Provider {
 						// as Schemas.
 						Elem: &schemav1.Resource{
 							Schema: map[string]*schemav1.Schema{
+								"kind":          {Type: schemav1.TypeString, Optional: true},
 								"configuration": {Type: schemav1.TypeMap, Required: true},
 							},
 						},
@@ -368,6 +369,7 @@ func ProviderV2() *schemav2.Provider {
 						// as Schemas.
 						Elem: &schemav2.Resource{
 							Schema: map[string]*schemav2.Schema{
+								"kind":          {Type: schemav2.TypeString, Optional: true},
 								"configuration": {Type: schemav2.TypeMap, Required: true},
 							},
 						},
diff --git a/pkg/tfbridge/provider_test.go b/pkg/tfbridge/provider_test.go
index 57286c45..d79c3b47 100644
--- a/pkg/tfbridge/provider_test.go
+++ b/pkg/tfbridge/provider_test.go
@@ -12,6 +12,7 @@ import (
 
 	shim "github.com/pulumi/pulumi-terraform-bridge/v2/pkg/tfshim"
 	shimv1 "github.com/pulumi/pulumi-terraform-bridge/v2/pkg/tfshim/sdk-v1"
+	shimv2 "github.com/pulumi/pulumi-terraform-bridge/v2/pkg/tfshim/sdk-v2"
 )
 
 func TestConvertStringToPropertyValue(t *testing.T) {
@@ -196,26 +197,22 @@ func TestBuildConfig(t *testing.T) {
 	assert.Equal(t, expected, configOut)
 }
 
-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"},
-		},
-	}
-
+func testProviderPreview(t *testing.T, provider *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.NewPropertyMapFromMap(map[string]interface{}{
-		"stringPropertyValue": "foo",
-		"setPropertyValue":    []interface{}{"foo"},
-	}), plugin.MarshalOptions{})
+	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),
@@ -231,15 +228,37 @@ func TestProviderPreview(t *testing.T) {
 	})
 	assert.NoError(t, err)
 
-	outs, err := plugin.UnmarshalProperties(createResp.GetProperties(), plugin.MarshalOptions{})
+	outs, err := plugin.UnmarshalProperties(createResp.GetProperties(), plugin.MarshalOptions{KeepUnknowns: true})
 	assert.NoError(t, err)
-	assert.True(t, resource.NewPropertyMapFromMap(map[string]interface{}{
-		"id":                  "",
-		"stringPropertyValue": "foo",
-		"setPropertyValues":   []interface{}{"foo"},
-	}).DeepEquals(outs))
+	assert.True(t, resource.PropertyMap{
+		"id":                  resource.NewStringProperty(""),
+		"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"),
+			}),
+		}),
+	}.DeepEquals(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(),
@@ -247,10 +266,16 @@ func TestProviderPreview(t *testing.T) {
 	assert.NoError(t, err)
 
 	// Step 3: preview an update to the resource we just created.
-	pulumiIns, err = plugin.MarshalProperties(resource.NewPropertyMapFromMap(map[string]interface{}{
-		"stringPropertyValue": "bar",
-		"setPropertyValue":    []interface{}{"foo"},
-	}), plugin.MarshalOptions{})
+	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),
@@ -268,7 +293,43 @@ func TestProviderPreview(t *testing.T) {
 	})
 	assert.NoError(t, err)
 
-	outs, err = plugin.UnmarshalProperties(updateResp.GetProperties(), plugin.MarshalOptions{})
+	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"),
+		}),
+	}).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) {
+	provider := &Provider{
+		tf:     shimv2.NewProvider(testTFProviderV2),
+		config: shimv2.NewSchemaMap(testTFProviderV2.Schema),
+	}
+	provider.resources = map[tokens.Type]Resource{
+		"ExampleResource": {
+			TF:     shimv2.NewResource(testTFProviderV2.ResourcesMap["example_resource"]),
+			TFName: "example_resource",
+			Schema: &ResourceInfo{Tok: "ExampleResource"},
+		},
+	}
+	testProviderPreview(t, provider)
 }
diff --git a/pkg/tfshim/tfplugin5/provider_test.go b/pkg/tfshim/tfplugin5/provider_test.go
index d2cff68a..57bab36b 100644
--- a/pkg/tfshim/tfplugin5/provider_test.go
+++ b/pkg/tfshim/tfplugin5/provider_test.go
@@ -229,6 +229,7 @@ func TestProviderResourcesMap(t *testing.T) {
 				"array_property_value":  cty.List(cty.String),
 				"object_property_value": cty.Map(cty.String),
 				"nested_resources": cty.List(cty.Object(map[string]cty.Type{
+					"kind":          cty.String,
 					"configuration": cty.Map(cty.String),
 				})),
 				"set_property_value":            cty.Set(cty.String),
@@ -308,14 +309,21 @@ func TestProviderResourcesMap(t *testing.T) {
 				},
 				"nested_resources": &attributeSchema{
 					ctyType: cty.List(cty.Object(map[string]cty.Type{
+						"kind":          cty.String,
 						"configuration": cty.Map(cty.String),
 					})),
 					valueType: shim.TypeList,
 					elem: &resource{
 						ctyType: cty.Object(map[string]cty.Type{
+							"kind":          cty.String,
 							"configuration": cty.Map(cty.String),
 						}),
 						schema: schema.SchemaMap{
+							"kind": &attributeSchema{
+								ctyType:   cty.String,
+								valueType: shim.TypeString,
+								optional:  true,
+							},
 							"configuration": &attributeSchema{
 								ctyType:   cty.Map(cty.String),
 								valueType: shim.TypeMap,
@@ -883,6 +891,7 @@ func TestDiff(t *testing.T) {
 				"array_property_value":  cty.ListValEmpty(cty.String),
 				"object_property_value": cty.NullVal(cty.Map(cty.String)),
 				"nested_resources": cty.ListValEmpty(cty.Object(map[string]cty.Type{
+					"kind":          cty.String,
 					"configuration": cty.Map(cty.String),
 				})),
 				"set_property_value":            cty.NullVal(cty.Set(cty.String)),
@@ -1000,6 +1009,7 @@ func TestApply(t *testing.T) {
 				"array_property_value": []interface{}{"foo"},
 				"nested_resources": []interface{}{
 					map[string]interface{}{
+						"kind": cty.StringVal(""),
 						"configuration": map[string]interface{}{
 							"baz": "qux",
 						},
@@ -1027,6 +1037,7 @@ func TestApply(t *testing.T) {
 					"property.c": cty.StringVal("some.value"),
 				}),
 				"nested_resources": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
+					"kind": cty.StringVal(""),
 					"configuration": cty.MapVal(map[string]cty.Value{
 						"configurationValue": cty.StringVal("true"),
 					}),
@@ -1086,6 +1097,7 @@ func TestApply(t *testing.T) {
 						"property.c": cty.StringVal("some.value"),
 					}),
 					"nested_resources": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
+						"kind": cty.StringVal(""),
 						"configuration": cty.MapVal(map[string]cty.Value{
 							"configurationValue": cty.StringVal("true"),
 						}),
@@ -1187,6 +1199,7 @@ func TestRefresh(t *testing.T) {
 			"property.c": cty.StringVal("some.value"),
 		}),
 		"nested_resources": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
+			"kind": cty.StringVal(""),
 			"configuration": cty.MapVal(map[string]cty.Value{
 				"configurationValue": cty.StringVal("true"),
 			}),