Repository: pulumi/pulumi-terraform-bridge
Pull request #502 (2022-04-06 14:31:51)

Fix Read not returning Secrets
Fixs https://github.com/pulumi/pulumi-terraform-bridge/issues/501

github-actions[bot]: Diff for [pulumi-random](https://github.com/pulumi-bot/pulumi-random/compare/e7f2028..9929f30) with merge commit c212bcb3c56bb1bf572d410a6477252544a7a14c
github-actions[bot]: Diff for [pulumi-azuread](https://github.com/pulumi-bot/pulumi-azuread/compare/11386e0..a6922f2) with merge commit c212bcb3c56bb1bf572d410a6477252544a7a14c
github-actions[bot]: Diff for [pulumi-gcp](https://github.com/pulumi-bot/pulumi-gcp/compare/0b6d9d5ad..7f90bf128) with merge commit c212bcb3c56bb1bf572d410a6477252544a7a14c
github-actions[bot]: Diff for [pulumi-azuread](https://github.com/pulumi-bot/pulumi-azuread/compare/5c7ef81..f7cb6f4) with merge commit 9f3e79ac844f2081b98238104aba6afdda1c7af7
github-actions[bot]: Diff for [pulumi-random](https://github.com/pulumi-bot/pulumi-random/compare/f95dc38..f79fd61) with merge commit 9f3e79ac844f2081b98238104aba6afdda1c7af7
github-actions[bot]: Diff for [pulumi-azure](https://github.com/pulumi-bot/pulumi-azure/compare/858d72b88..302a3a210) with merge commit 9f3e79ac844f2081b98238104aba6afdda1c7af7
github-actions[bot]: Diff for [pulumi-aws](https://github.com/pulumi-bot/pulumi-aws/compare/351990564..8bdf7f30c) with merge commit 9f3e79ac844f2081b98238104aba6afdda1c7af7
github-actions[bot]: Diff for [pulumi-random](https://github.com/pulumi-bot/pulumi-random/compare/bee56f9..4b1a56b) with merge commit c6ac009303db17888d7e15636efca278980175a6
github-actions[bot]: Diff for [pulumi-azuread](https://github.com/pulumi-bot/pulumi-azuread/compare/c3074da..961b089) with merge commit c6ac009303db17888d7e15636efca278980175a6
github-actions[bot]: Diff for [pulumi-azure](https://github.com/pulumi-bot/pulumi-azure/compare/7238609cc..d7bdbb387) with merge commit c6ac009303db17888d7e15636efca278980175a6

Patch:
diff --git a/pkg/tfbridge/provider.go b/pkg/tfbridge/provider.go
index a038f778..5812a9c1 100644
--- a/pkg/tfbridge/provider.go
+++ b/pkg/tfbridge/provider.go
@@ -951,7 +951,10 @@ func (p *Provider) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*pulum
 			return nil, err
 		}
 
-		mprops, err := plugin.MarshalProperties(props, plugin.MarshalOptions{Label: label + ".state"})
+		mprops, err := plugin.MarshalProperties(props, plugin.MarshalOptions{
+			Label:       label + ".state",
+			KeepSecrets: p.supportsSecrets,
+		})
 		if err != nil {
 			return nil, err
 		}


Test patch:
diff --git a/internal/testprovider/schema.go b/internal/testprovider/schema.go
index 95892eeb..d006821f 100644
--- a/internal/testprovider/schema.go
+++ b/internal/testprovider/schema.go
@@ -43,6 +43,65 @@ func ProviderV1() *schemav1.Provider {
 			"config_value": {Type: schemav1.TypeString, Optional: true},
 		},
 		ResourcesMap: map[string]*schemav1.Resource{
+			"nested_secret_resource": {
+				Schema: map[string]*schemav1.Schema{
+					"nested": {
+						Type:     schemav1.TypeList,
+						MaxItems: 1,
+						Computed: true,
+						Optional: true,
+						Elem: &schemav1.Resource{
+							Schema: map[string]*schemav1.Schema{
+								"a_secret": {
+									Type:      schemav1.TypeString,
+									Computed:  true,
+									Sensitive: true,
+								},
+							},
+						},
+					},
+				},
+				SchemaVersion: 1,
+				MigrateState: func(v int, is *terraformv1.InstanceState, p interface{}) (*terraformv1.InstanceState, error) {
+					return is, nil
+				},
+				Create: func(data *schemav1.ResourceData, p interface{}) error {
+					data.SetId("0")
+					MustSetIfUnset(data, "nested", []interface{}{
+						map[string]interface{}{
+							"a_secret": "password",
+						},
+					})
+					return nil
+				},
+				Read: func(data *schemav1.ResourceData, p interface{}) error {
+					MustSetIfUnset(data, "nested", []interface{}{
+						map[string]interface{}{
+							"a_secret": "password",
+						},
+					})
+					return nil
+				},
+				Update: func(data *schemav1.ResourceData, p interface{}) error {
+					MustSetIfUnset(data, "nested", []interface{}{
+						map[string]interface{}{
+							"a_secret": "password",
+						},
+					})
+					return nil
+				},
+				Delete: func(data *schemav1.ResourceData, p interface{}) error {
+					return nil
+				},
+				Timeouts: &schemav1.ResourceTimeout{
+					Create: Timeout(time.Second * 120),
+				},
+				Importer: &schemav1.ResourceImporter{
+					State: func(state *schemav1.ResourceData, _ interface{}) ([]*schemav1.ResourceData, error) {
+						return []*schemav1.ResourceData{state}, nil
+					},
+				},
+			},
 			"example_resource": {
 				Schema: map[string]*schemav1.Schema{
 					"nil_property_value":    {Type: schemav1.TypeMap, Optional: true},
@@ -364,6 +423,65 @@ func ProviderV2() *schemav2.Provider {
 			"config_value": {Type: schemav2.TypeString, Optional: true},
 		},
 		ResourcesMap: map[string]*schemav2.Resource{
+			"nested_secret_resource": {
+				Schema: map[string]*schemav2.Schema{
+					"nested": {
+						Type:     schemav2.TypeList,
+						MaxItems: 1,
+						Computed: true,
+						Optional: true,
+						Elem: &schemav2.Resource{
+							Schema: map[string]*schemav2.Schema{
+								"a_secret": {
+									Type:      schemav2.TypeString,
+									Computed:  true,
+									Sensitive: true,
+								},
+							},
+						},
+					},
+				},
+				SchemaVersion: 1,
+				MigrateState: func(v int, is *terraformv2.InstanceState, p interface{}) (*terraformv2.InstanceState, error) {
+					return is, nil
+				},
+				Create: func(data *schemav2.ResourceData, p interface{}) error {
+					data.SetId("0")
+					MustSetIfUnset(data, "nested", []interface{}{
+						map[string]interface{}{
+							"a_secret": "password",
+						},
+					})
+					return nil
+				},
+				Read: func(data *schemav2.ResourceData, p interface{}) error {
+					MustSetIfUnset(data, "nested", []interface{}{
+						map[string]interface{}{
+							"a_secret": "password",
+						},
+					})
+					return nil
+				},
+				Update: func(data *schemav2.ResourceData, p interface{}) error {
+					MustSetIfUnset(data, "nested", []interface{}{
+						map[string]interface{}{
+							"a_secret": "password",
+						},
+					})
+					return nil
+				},
+				Delete: func(data *schemav2.ResourceData, p interface{}) error {
+					return nil
+				},
+				Timeouts: &schemav2.ResourceTimeout{
+					Create: Timeout(time.Second * 120),
+				},
+				Importer: &schemav2.ResourceImporter{
+					State: func(state *schemav2.ResourceData, _ interface{}) ([]*schemav2.ResourceData, error) {
+						return []*schemav2.ResourceData{state}, nil
+					},
+				},
+			},
 			"example_resource": {
 				Schema: map[string]*schemav2.Schema{
 					"nil_property_value":    {Type: schemav2.TypeMap, Optional: true},
diff --git a/pkg/tfbridge/provider_test.go b/pkg/tfbridge/provider_test.go
index 81c87cac..28ca44a6 100644
--- a/pkg/tfbridge/provider_test.go
+++ b/pkg/tfbridge/provider_test.go
@@ -656,3 +656,77 @@ func TestProviderReadV2(t *testing.T) {
 
 	testProviderRead(t, provider, "ExampleResource")
 }
+
+func testProviderReadNestedSecret(t *testing.T, provider *Provider, typeName tokens.Type) {
+	urn := resource.NewURN("stack", "project", "", typeName, "name")
+
+	// Configure that we support secrets
+	provider.Configure(context.Background(), &pulumirpc.ConfigureRequest{
+		AcceptSecrets:   true,
+		AcceptResources: true,
+	})
+
+	// Check that if we create the resource the secret property comes back as a secret
+	createResp, err := provider.Create(context.Background(), &pulumirpc.CreateRequest{
+		Urn:        string(urn),
+		Properties: nil,
+	})
+	assert.NoError(t, err)
+
+	assert.NotNil(t, createResp.GetProperties())
+	props, err := plugin.UnmarshalProperties(createResp.GetProperties(), plugin.MarshalOptions{KeepUnknowns: true, KeepSecrets: true})
+	assert.NoError(t, err)
+
+	assert.Equal(t, resource.NewObjectProperty(resource.PropertyMap{
+		"aSecret": resource.MakeSecret(resource.NewStringProperty("password")),
+	}), props["nested"])
+
+	// Check that read is also a secret
+	readResp, err := provider.Read(context.Background(), &pulumirpc.ReadRequest{
+		Id:         string("0"),
+		Urn:        string(urn),
+		Properties: nil,
+	})
+	assert.NoError(t, err)
+
+	assert.NotNil(t, readResp.GetProperties())
+	props, err = plugin.UnmarshalProperties(readResp.GetProperties(), plugin.MarshalOptions{KeepUnknowns: true, KeepSecrets: true})
+	assert.NoError(t, err)
+
+	assert.Equal(t, resource.NewObjectProperty(resource.PropertyMap{
+		"aSecret": resource.MakeSecret(resource.NewStringProperty("password")),
+	}), props["nested"])
+}
+
+func TestProviderReadNestedSecretV1(t *testing.T) {
+	provider := &Provider{
+		tf:     shimv1.NewProvider(testTFProvider),
+		config: shimv1.NewSchemaMap(testTFProvider.Schema),
+	}
+
+	provider.resources = map[tokens.Type]Resource{
+		"NestedSecretResource": {
+			TF:     shimv1.NewResource(testTFProvider.ResourcesMap["nested_secret_resource"]),
+			TFName: "nested_secret_resource",
+			Schema: &ResourceInfo{Tok: "NestedSecretResource"},
+		},
+	}
+
+	testProviderReadNestedSecret(t, provider, "NestedSecretResource")
+}
+
+func TestProviderReadNestedSecretV2(t *testing.T) {
+	provider := &Provider{
+		tf:     shimv2.NewProvider(testTFProviderV2),
+		config: shimv2.NewSchemaMap(testTFProviderV2.Schema),
+	}
+	provider.resources = map[tokens.Type]Resource{
+		"NestedSecretResource": {
+			TF:     shimv2.NewResource(testTFProviderV2.ResourcesMap["nested_secret_resource"]),
+			TFName: "nested_secret_resource",
+			Schema: &ResourceInfo{Tok: "NestedSecretResource"},
+		},
+	}
+
+	testProviderReadNestedSecret(t, provider, "NestedSecretResource")
+}
diff --git a/pkg/tfshim/tfplugin5/provider_test.go b/pkg/tfshim/tfplugin5/provider_test.go
index 3ad66dc2..f9595fab 100644
--- a/pkg/tfshim/tfplugin5/provider_test.go
+++ b/pkg/tfshim/tfplugin5/provider_test.go
@@ -215,6 +215,66 @@ func TestProviderResourcesMap(t *testing.T) {
 	}
 
 	expected := map[string]*resource{
+		"nested_secret_resource": {
+			resourceType:  "nested_secret_resource",
+			schemaVersion: 1,
+			ctyType: cty.Object(map[string]cty.Type{
+				"id": cty.String,
+				"timeouts": cty.Object(map[string]cty.Type{
+					"create": cty.String,
+				}),
+				"nested": cty.List(cty.Object(map[string]cty.Type{
+					"a_secret": cty.String,
+				})),
+			}),
+			schema: schema.SchemaMap{
+				"id": &attributeSchema{
+					ctyType:   cty.String,
+					valueType: shim.TypeString,
+					computed:  true,
+				},
+				"timeouts": &attributeSchema{
+					ctyType: cty.Object(map[string]cty.Type{
+						"create": cty.String,
+					}),
+					valueType: shim.TypeMap,
+					elem: &resource{
+						ctyType: cty.Object(map[string]cty.Type{
+							"create": cty.String,
+						}),
+						schema: schema.SchemaMap{
+							"create": &attributeSchema{
+								ctyType:   cty.String,
+								valueType: shim.TypeString,
+								optional:  true,
+							},
+						},
+					},
+					required: true,
+				},
+				"nested": &attributeSchema{
+					ctyType: cty.List(cty.Object(map[string]cty.Type{
+						"a_secret": cty.String,
+					})),
+					valueType: shim.TypeList,
+					elem: &resource{
+						ctyType: cty.Object(map[string]cty.Type{
+							"a_secret": cty.String,
+						}),
+						schema: schema.SchemaMap{
+							"a_secret": &attributeSchema{
+								ctyType:   cty.String,
+								valueType: shim.TypeString,
+								sensitive: true,
+								computed:  true,
+							},
+						},
+					},
+					maxItems: 1,
+					computed: true,
+				},
+			},
+		},
 		"example_resource": {
 			resourceType:  "example_resource",
 			schemaVersion: 1,