Repository: GoogleCloudPlatform/terraform-validator
Pull request #607 (2022-03-28 14:06:24)

Change ancestry from string to map[string]string
b/211908063
Change function signatures to use map[string]string instead of a pure string for ancestry cache. This is to support folder ID as a key in the ancestry cache, rather than only project ID. 
This PR is to support a larger ancestry manager refactoring. 


Patch:
diff --git a/cmd/convert.go b/cmd/convert.go
index f0c6b521..a7b04b39 100644
--- a/cmd/convert.go
+++ b/cmd/convert.go
@@ -93,7 +93,10 @@ func (o *convertOptions) validateArgs(args []string) error {
 
 func (o *convertOptions) run(plan string) error {
 	ctx := context.Background()
-	assets, err := o.readPlannedAssets(ctx, plan, o.project, o.ancestry, o.offline, false, o.rootOptions.errorLogger)
+	ancestryCache := map[string]string{
+		o.project: o.ancestry,
+	}
+	assets, err := o.readPlannedAssets(ctx, plan, o.project, ancestryCache, o.offline, false, o.rootOptions.errorLogger)
 	if err != nil {
 		if errors.Cause(err) == tfgcv.ErrParsingProviderProject {
 			return errors.New("unable to parse provider project, please use --project flag")
diff --git a/cmd/validate.go b/cmd/validate.go
index bf04ea96..cb48fb75 100644
--- a/cmd/validate.go
+++ b/cmd/validate.go
@@ -112,7 +112,10 @@ func (o *validateOptions) run(plan string) error {
 	var assets []google.Asset
 	if err := json.Unmarshal(content, &assets); err != nil {
 		var err error
-		assets, err = o.readPlannedAssets(ctx, plan, o.project, o.ancestry, o.offline, false, o.rootOptions.errorLogger)
+		ancestryCache := map[string]string{
+			o.project: o.ancestry,
+		}
+		assets, err = o.readPlannedAssets(ctx, plan, o.project, ancestryCache, o.offline, false, o.rootOptions.errorLogger)
 		if err != nil {
 			if errors.Cause(err) == tfgcv.ErrParsingProviderProject {
 				return errors.New("unable to parse provider project, please use --project flag")
diff --git a/converters/google/resources/bigtable_client_factory.go b/converters/google/resources/bigtable_client_factory.go
index 9a735017..19c7047e 100644
--- a/converters/google/resources/bigtable_client_factory.go
+++ b/converters/google/resources/bigtable_client_factory.go
@@ -48,3 +48,19 @@ func (s BigtableClientFactory) NewAdminClient(project, instance string) (*bigtab
 
 	return bigtable.NewAdminClient(context.Background(), project, instance, opts...)
 }
+
+func (s BigtableClientFactory) NewClient(project, instance string) (*bigtable.Client, error) {
+	var opts []option.ClientOption
+	if requestReason := os.Getenv("CLOUDSDK_CORE_REQUEST_REASON"); requestReason != "" {
+		opts = append(opts, option.WithRequestReason(requestReason))
+	}
+
+	if s.UserProjectOverride && s.BillingProject != "" {
+		opts = append(opts, option.WithQuotaProject(s.BillingProject))
+	}
+
+	opts = append(opts, option.WithTokenSource(s.TokenSource), option.WithUserAgent(s.UserAgent))
+	opts = append(opts, s.gRPCLoggingOptions...)
+
+	return bigtable.NewClient(context.Background(), project, instance, opts...)
+}
diff --git a/converters/google/resources/cloudbuild_trigger.go b/converters/google/resources/cloudbuild_trigger.go
index 8f6a441c..114f2f1f 100644
--- a/converters/google/resources/cloudbuild_trigger.go
+++ b/converters/google/resources/cloudbuild_trigger.go
@@ -188,6 +188,12 @@ func GetCloudBuildTriggerApiObject(d TerraformResourceData, config *Config) (map
 	} else if v, ok := d.GetOkExists("webhook_config"); !isEmptyValue(reflect.ValueOf(webhookConfigProp)) && (ok || !reflect.DeepEqual(v, webhookConfigProp)) {
 		obj["webhookConfig"] = webhookConfigProp
 	}
+	approvalConfigProp, err := expandCloudBuildTriggerApprovalConfig(d.Get("approval_config"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("approval_config"); !isEmptyValue(reflect.ValueOf(approvalConfigProp)) && (ok || !reflect.DeepEqual(v, approvalConfigProp)) {
+		obj["approvalConfig"] = approvalConfigProp
+	}
 	buildProp, err := expandCloudBuildTriggerBuild(d.Get("build"), d, config)
 	if err != nil {
 		return nil, err
@@ -663,6 +669,29 @@ func expandCloudBuildTriggerWebhookConfigState(v interface{}, d TerraformResourc
 	return v, nil
 }
 
+func expandCloudBuildTriggerApprovalConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	l := v.([]interface{})
+	if len(l) == 0 || l[0] == nil {
+		return nil, nil
+	}
+	raw := l[0]
+	original := raw.(map[string]interface{})
+	transformed := make(map[string]interface{})
+
+	transformedApprovalRequired, err := expandCloudBuildTriggerApprovalConfigApprovalRequired(original["approval_required"], d, config)
+	if err != nil {
+		return nil, err
+	} else if val := reflect.ValueOf(transformedApprovalRequired); val.IsValid() && !isEmptyValue(val) {
+		transformed["approvalRequired"] = transformedApprovalRequired
+	}
+
+	return transformed, nil
+}
+
+func expandCloudBuildTriggerApprovalConfigApprovalRequired(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
 func expandCloudBuildTriggerBuild(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
 	l := v.([]interface{})
 	if len(l) == 0 || l[0] == nil {
diff --git a/converters/google/resources/iam.go b/converters/google/resources/iam.go
index 7f953bf9..b11f99b4 100644
--- a/converters/google/resources/iam.go
+++ b/converters/google/resources/iam.go
@@ -244,7 +244,11 @@ func subtractFromBindings(bindings []*cloudresourcemanager.Binding, toRemove ...
 }
 
 func iamMemberIsCaseSensitive(member string) bool {
-	return strings.HasPrefix(member, "principalSet:") || strings.HasPrefix(member, "principal:") || strings.HasPrefix(member, "principalHierarchy:")
+	// allAuthenticatedUsers and allUsers are special identifiers that are case sensitive. See:
+	// https://cloud.google.com/iam/docs/overview#all-authenticated-users
+	return strings.Contains(member, "allAuthenticatedUsers") || strings.Contains(member, "allUsers") ||
+		strings.HasPrefix(member, "principalSet:") || strings.HasPrefix(member, "principal:") ||
+		strings.HasPrefix(member, "principalHierarchy:")
 }
 
 // normalizeIamMemberCasing returns the case adjusted value of an iamMember
diff --git a/converters/google/resources/iam_privateca_ca_pool.go b/converters/google/resources/iam_privateca_ca_pool.go
index 4f35ac68..86e9afec 100644
--- a/converters/google/resources/iam_privateca_ca_pool.go
+++ b/converters/google/resources/iam_privateca_ca_pool.go
@@ -149,6 +149,10 @@ func (u *PrivatecaCaPoolIamUpdater) GetResourceIamPolicy() (*cloudresourcemanage
 		return nil, err
 	}
 	var obj map[string]interface{}
+	url, err = addQueryParams(url, map[string]string{"options.requestedPolicyVersion": fmt.Sprintf("%d", iamPolicyVersion)})
+	if err != nil {
+		return nil, err
+	}
 
 	userAgent, err := generateUserAgentString(u.d, u.Config.userAgent)
 	if err != nil {
diff --git a/converters/google/resources/iam_service_management_service_consumers.go b/converters/google/resources/iam_service_management_service_consumers.go
new file mode 100644
index 00000000..e95202a8
--- /dev/null
+++ b/converters/google/resources/iam_service_management_service_consumers.go
@@ -0,0 +1,181 @@
+// ----------------------------------------------------------------------------
+//
+//     ***     AUTO GENERATED CODE    ***    Type: MMv1     ***
+//
+// ----------------------------------------------------------------------------
+//
+//     This file is automatically generated by Magic Modules and manual
+//     changes will be clobbered when the file is regenerated.
+//
+//     Please read more about how to change this file in
+//     .github/CONTRIBUTING.md.
+//
+// ----------------------------------------------------------------------------
+package google
+
+import (
+	"fmt"
+
+	"github.com/hashicorp/errwrap"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"google.golang.org/api/cloudresourcemanager/v1"
+)
+
+var ServiceManagementServiceConsumersIamSchema = map[string]*schema.Schema{
+	"service_name": {
+		Type:     schema.TypeString,
+		Required: true,
+		ForceNew: true,
+	},
+	"consumer_project": {
+		Type:             schema.TypeString,
+		Required:         true,
+		ForceNew:         true,
+		DiffSuppressFunc: compareSelfLinkOrResourceName,
+	},
+}
+
+type ServiceManagementServiceConsumersIamUpdater struct {
+	serviceName     string
+	consumerProject string
+	d               TerraformResourceData
+	Config          *Config
+}
+
+func ServiceManagementServiceConsumersIamUpdaterProducer(d TerraformResourceData, config *Config) (ResourceIamUpdater, error) {
+	values := make(map[string]string)
+
+	if v, ok := d.GetOk("service_name"); ok {
+		values["service_name"] = v.(string)
+	}
+
+	if v, ok := d.GetOk("consumer_project"); ok {
+		values["consumer_project"] = v.(string)
+	}
+
+	// We may have gotten either a long or short name, so attempt to parse long name if possible
+	m, err := getImportIdQualifiers([]string{"services/(?P<service_name>[^/]+)/consumers/(?P<consumer_project>[^/]+)", "(?P<service_name>[^/]+)/(?P<consumer_project>[^/]+)", "(?P<consumer_project>[^/]+)"}, d, config, d.Get("consumer_project").(string))
+	if err != nil {
+		return nil, err
+	}
+
+	for k, v := range m {
+		values[k] = v
+	}
+
+	u := &ServiceManagementServiceConsumersIamUpdater{
+		serviceName:     values["service_name"],
+		consumerProject: values["consumer_project"],
+		d:               d,
+		Config:          config,
+	}
+
+	if err := d.Set("service_name", u.serviceName); err != nil {
+		return nil, fmt.Errorf("Error setting service_name: %s", err)
+	}
+	if err := d.Set("consumer_project", u.GetResourceId()); err != nil {
+		return nil, fmt.Errorf("Error setting consumer_project: %s", err)
+	}
+
+	return u, nil
+}
+
+func ServiceManagementServiceConsumersIdParseFunc(d *schema.ResourceData, config *Config) error {
+	values := make(map[string]string)
+
+	m, err := getImportIdQualifiers([]string{"services/(?P<service_name>[^/]+)/consumers/(?P<consumer_project>[^/]+)", "(?P<service_name>[^/]+)/(?P<consumer_project>[^/]+)", "(?P<consumer_project>[^/]+)"}, d, config, d.Id())
+	if err != nil {
+		return err
+	}
+
+	for k, v := range m {
+		values[k] = v
+	}
+
+	u := &ServiceManagementServiceConsumersIamUpdater{
+		serviceName:     values["service_name"],
+		consumerProject: values["consumer_project"],
+		d:               d,
+		Config:          config,
+	}
+	if err := d.Set("consumer_project", u.GetResourceId()); err != nil {
+		return fmt.Errorf("Error setting consumer_project: %s", err)
+	}
+	d.SetId(u.GetResourceId())
+	return nil
+}
+
+func (u *ServiceManagementServiceConsumersIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) {
+	url, err := u.qualifyServiceConsumersUrl("getIamPolicy")
+	if err != nil {
+		return nil, err
+	}
+
+	var obj map[string]interface{}
+
+	userAgent, err := generateUserAgentString(u.d, u.Config.userAgent)
+	if err != nil {
+		return nil, err
+	}
+
+	policy, err := sendRequest(u.Config, "POST", "", url, userAgent, obj)
+	if err != nil {
+		return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err)
+	}
+
+	out := &cloudresourcemanager.Policy{}
+	err = Convert(policy, out)
+	if err != nil {
+		return nil, errwrap.Wrapf("Cannot convert a policy to a resource manager policy: {{err}}", err)
+	}
+
+	return out, nil
+}
+
+func (u *ServiceManagementServiceConsumersIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error {
+	json, err := ConvertToMap(policy)
+	if err != nil {
+		return err
+	}
+
+	obj := make(map[string]interface{})
+	obj["policy"] = json
+
+	url, err := u.qualifyServiceConsumersUrl("setIamPolicy")
+	if err != nil {
+		return err
+	}
+
+	userAgent, err := generateUserAgentString(u.d, u.Config.userAgent)
+	if err != nil {
+		return err
+	}
+
+	_, err = sendRequestWithTimeout(u.Config, "POST", "", url, userAgent, obj, u.d.Timeout(schema.TimeoutCreate))
+	if err != nil {
+		return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{err}}", u.DescribeResource()), err)
+	}
+
+	return nil
+}
+
+func (u *ServiceManagementServiceConsumersIamUpdater) qualifyServiceConsumersUrl(methodIdentifier string) (string, error) {
+	urlTemplate := fmt.Sprintf("{{ServiceManagementBasePath}}%s:%s", fmt.Sprintf("services/%s/consumers/%s", u.serviceName, u.consumerProject), methodIdentifier)
+	url, err := replaceVars(u.d, u.Config, urlTemplate)
+	if err != nil {
+		return "", err
+	}
+	return url, nil
+}
+
+func (u *ServiceManagementServiceConsumersIamUpdater) GetResourceId() string {
+	return fmt.Sprintf("services/%s/consumers/%s", u.serviceName, u.consumerProject)
+}
+
+func (u *ServiceManagementServiceConsumersIamUpdater) GetMutexKey() string {
+	return fmt.Sprintf("iam-servicemanagement-serviceconsumers-%s", u.GetResourceId())
+}
+
+func (u *ServiceManagementServiceConsumersIamUpdater) DescribeResource() string {
+	return fmt.Sprintf("servicemanagement serviceconsumers %q", u.GetResourceId())
+}
diff --git a/converters/google/resources/pubsub_subscription.go b/converters/google/resources/pubsub_subscription.go
index 5c1967f6..f5c3d7d1 100644
--- a/converters/google/resources/pubsub_subscription.go
+++ b/converters/google/resources/pubsub_subscription.go
@@ -139,6 +139,12 @@ func GetPubsubSubscriptionApiObject(d TerraformResourceData, config *Config) (ma
 	} else if v, ok := d.GetOkExists("enable_message_ordering"); !isEmptyValue(reflect.ValueOf(enableMessageOrderingProp)) && (ok || !reflect.DeepEqual(v, enableMessageOrderingProp)) {
 		obj["enableMessageOrdering"] = enableMessageOrderingProp
 	}
+	enableExactlyOnceDeliveryProp, err := expandPubsubSubscriptionEnableExactlyOnceDelivery(d.Get("enable_exactly_once_delivery"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("enable_exactly_once_delivery"); !isEmptyValue(reflect.ValueOf(enableExactlyOnceDeliveryProp)) && (ok || !reflect.DeepEqual(v, enableExactlyOnceDeliveryProp)) {
+		obj["enableExactlyOnceDelivery"] = enableExactlyOnceDeliveryProp
+	}
 
 	return resourcePubsubSubscriptionEncoder(d, config, obj)
 }
@@ -382,3 +388,7 @@ func expandPubsubSubscriptionRetryPolicyMaximumBackoff(v interface{}, d Terrafor
 func expandPubsubSubscriptionEnableMessageOrdering(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
 	return v, nil
 }
+
+func expandPubsubSubscriptionEnableExactlyOnceDelivery(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
diff --git a/converters/google/resources/resource_converters.go b/converters/google/resources/resource_converters.go
index 919f3e11..ed814950 100644
--- a/converters/google/resources/resource_converters.go
+++ b/converters/google/resources/resource_converters.go
@@ -150,6 +150,9 @@ func ResourceConverters() map[string][]ResourceConverter {
 		"google_endpoints_service_iam_policy":               {resourceConverterServiceManagementServiceIamPolicy()},
 		"google_endpoints_service_iam_binding":              {resourceConverterServiceManagementServiceIamBinding()},
 		"google_endpoints_service_iam_member":               {resourceConverterServiceManagementServiceIamMember()},
+		"google_endpoints_service_consumers_iam_policy":     {resourceConverterServiceManagementServiceConsumersIamPolicy()},
+		"google_endpoints_service_consumers_iam_binding":    {resourceConverterServiceManagementServiceConsumersIamBinding()},
+		"google_endpoints_service_consumers_iam_member":     {resourceConverterServiceManagementServiceConsumersIamMember()},
 		"google_project": {
 			resourceConverterProject(),
 			resourceConverterProjectBillingInfo(),
diff --git a/converters/google/resources/service_management_service_consumers_iam.go b/converters/google/resources/service_management_service_consumers_iam.go
new file mode 100644
index 00000000..a79e1bb1
--- /dev/null
+++ b/converters/google/resources/service_management_service_consumers_iam.go
@@ -0,0 +1,123 @@
+// ----------------------------------------------------------------------------
+//
+//     ***     AUTO GENERATED CODE    ***    Type: MMv1     ***
+//
+// ----------------------------------------------------------------------------
+//
+//     This file is automatically generated by Magic Modules and manual
+//     changes will be clobbered when the file is regenerated.
+//
+//     Please read more about how to change this file in
+//     .github/CONTRIBUTING.md.
+//
+// ----------------------------------------------------------------------------
+
+package google
+
+import "fmt"
+
+// Provide a separate asset type constant so we don't have to worry about name conflicts between IAM and non-IAM converter files
+const ServiceManagementServiceConsumersIAMAssetType string = "servicemanagement.googleapis.com/ServiceConsumers"
+
+func resourceConverterServiceManagementServiceConsumersIamPolicy() ResourceConverter {
+	return ResourceConverter{
+		AssetType:         ServiceManagementServiceConsumersIAMAssetType,
+		Convert:           GetServiceManagementServiceConsumersIamPolicyCaiObject,
+		MergeCreateUpdate: MergeServiceManagementServiceConsumersIamPolicy,
+	}
+}
+
+func resourceConverterServiceManagementServiceConsumersIamBinding() ResourceConverter {
+	return ResourceConverter{
+		AssetType:         ServiceManagementServiceConsumersIAMAssetType,
+		Convert:           GetServiceManagementServiceConsumersIamBindingCaiObject,
+		FetchFullResource: FetchServiceManagementServiceConsumersIamPolicy,
+		MergeCreateUpdate: MergeServiceManagementServiceConsumersIamBinding,
+		MergeDelete:       MergeServiceManagementServiceConsumersIamBindingDelete,
+	}
+}
+
+func resourceConverterServiceManagementServiceConsumersIamMember() ResourceConverter {
+	return ResourceConverter{
+		AssetType:         ServiceManagementServiceConsumersIAMAssetType,
+		Convert:           GetServiceManagementServiceConsumersIamMemberCaiObject,
+		FetchFullResource: FetchServiceManagementServiceConsumersIamPolicy,
+		MergeCreateUpdate: MergeServiceManagementServiceConsumersIamMember,
+		MergeDelete:       MergeServiceManagementServiceConsumersIamMemberDelete,
+	}
+}
+
+func GetServiceManagementServiceConsumersIamPolicyCaiObject(d TerraformResourceData, config *Config) ([]Asset, error) {
+	return newServiceManagementServiceConsumersIamAsset(d, config, expandIamPolicyBindings)
+}
+
+func GetServiceManagementServiceConsumersIamBindingCaiObject(d TerraformResourceData, config *Config) ([]Asset, error) {
+	return newServiceManagementServiceConsumersIamAsset(d, config, expandIamRoleBindings)
+}
+
+func GetServiceManagementServiceConsumersIamMemberCaiObject(d TerraformResourceData, config *Config) ([]Asset, error) {
+	return newServiceManagementServiceConsumersIamAsset(d, config, expandIamMemberBindings)
+}
+
+func MergeServiceManagementServiceConsumersIamPolicy(existing, incoming Asset) Asset {
+	existing.IAMPolicy = incoming.IAMPolicy
+	return existing
+}
+
+func MergeServiceManagementServiceConsumersIamBinding(existing, incoming Asset) Asset {
+	return mergeIamAssets(existing, incoming, mergeAuthoritativeBindings)
+}
+
+func MergeServiceManagementServiceConsumersIamBindingDelete(existing, incoming Asset) Asset {
+	return mergeDeleteIamAssets(existing, incoming, mergeDeleteAuthoritativeBindings)
+}
+
+func MergeServiceManagementServiceConsumersIamMember(existing, incoming Asset) Asset {
+	return mergeIamAssets(existing, incoming, mergeAdditiveBindings)
+}
+
+func MergeServiceManagementServiceConsumersIamMemberDelete(existing, incoming Asset) Asset {
+	return mergeDeleteIamAssets(existing, incoming, mergeDeleteAdditiveBindings)
+}
+
+func newServiceManagementServiceConsumersIamAsset(
+	d TerraformResourceData,
+	config *Config,
+	expandBindings func(d TerraformResourceData) ([]IAMBinding, error),
+) ([]Asset, error) {
+	bindings, err := expandBindings(d)
+	if err != nil {
+		return []Asset{}, fmt.Errorf("expanding bindings: %v", err)
+	}
+
+	name, err := assetName(d, config, "//servicemanagement.googleapis.com/services/{{service_name}}/consumers/{{consumer_project}}")
+	if err != nil {
+		return []Asset{}, err
+	}
+
+	return []Asset{{
+		Name: name,
+		Type: ServiceManagementServiceConsumersIAMAssetType,
+		IAMPolicy: &IAMPolicy{
+			Bindings: bindings,
+		},
+	}}, nil
+}
+
+func FetchServiceManagementServiceConsumersIamPolicy(d TerraformResourceData, config *Config) (Asset, error) {
+	// Check if the identity field returns a value
+	if _, ok := d.GetOk("service_name"); !ok {
+		return Asset{}, ErrEmptyIdentityField
+	}
+	if _, ok := d.GetOk("consumer_project"); !ok {
+		return Asset{}, ErrEmptyIdentityField
+	}
+
+	return fetchIamPolicy(
+		ServiceManagementServiceConsumersIamUpdaterProducer,
+		d,
+		config,
+		"//servicemanagement.googleapis.com/services/{{service_name}}/consumers/{{consumer_project}}",
+		ServiceManagementServiceConsumersIAMAssetType,
+	)
+}
diff --git a/converters/google/resources/spanner_database.go b/converters/google/resources/spanner_database.go
index f9db8e7c..6da3a206 100644
--- a/converters/google/resources/spanner_database.go
+++ b/converters/google/resources/spanner_database.go
@@ -104,6 +104,12 @@ func GetSpannerDatabaseApiObject(d TerraformResourceData, config *Config) (map[s
 	} else if v, ok := d.GetOkExists("encryption_config"); !isEmptyValue(reflect.ValueOf(encryptionConfigProp)) && (ok || !reflect.DeepEqual(v, encryptionConfigProp)) {
 		obj["encryptionConfig"] = encryptionConfigProp
 	}
+	databaseDialectProp, err := expandSpannerDatabaseDatabaseDialect(d.Get("database_dialect"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("database_dialect"); !isEmptyValue(reflect.ValueOf(databaseDialectProp)) && (ok || !reflect.DeepEqual(v, databaseDialectProp)) {
+		obj["databaseDialect"] = databaseDialectProp
+	}
 	instanceProp, err := expandSpannerDatabaseInstance(d.Get("instance"), d, config)
 	if err != nil {
 		return nil, err
@@ -116,6 +122,9 @@ func GetSpannerDatabaseApiObject(d TerraformResourceData, config *Config) (map[s
 
 func resourceSpannerDatabaseEncoder(d TerraformResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) {
 	obj["createStatement"] = fmt.Sprintf("CREATE DATABASE `%s`", obj["name"])
+	if dialect, ok := obj["databaseDialect"]; ok && dialect == "POSTGRESQL" {
+		obj["createStatement"] = fmt.Sprintf("CREATE DATABASE %s", obj["name"])
+	}
 	delete(obj, "name")
 	delete(obj, "instance")
 	return obj, nil
@@ -152,6 +161,10 @@ func expandSpannerDatabaseEncryptionConfigKmsKeyName(v interface{}, d TerraformR
 	return v, nil
 }
 
+func expandSpannerDatabaseDatabaseDialect(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
 func expandSpannerDatabaseInstance(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
 	f, err := parseGlobalFieldValue("instances", v.(string), "project", d, config, true)
 	if err != nil {
diff --git a/go.mod b/go.mod
index cf4643ab..0ec55ab2 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
 	github.com/hashicorp/go-cleanhttp v0.5.2
 	github.com/hashicorp/terraform-json v0.13.0
 	github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1
-	github.com/hashicorp/terraform-provider-google v1.20.1-0.20220324184940-33916265211a
+	github.com/hashicorp/terraform-provider-google v1.20.1-0.20220401195508-e86f490c464d
 	github.com/kr/pretty v0.3.0 // indirect
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/onsi/gomega v1.17.0 // indirect
diff --git a/go.sum b/go.sum
index 42808f34..2011931e 100644
--- a/go.sum
+++ b/go.sum
@@ -696,8 +696,8 @@ github.com/hashicorp/terraform-plugin-log v0.2.0 h1:rjflRuBqCnSk3UHOR25MP1G5BDLK
 github.com/hashicorp/terraform-plugin-log v0.2.0/go.mod h1:E1kJmapEHzqu1x6M++gjvhzM2yMQNXPVWZRCB8sgYjg=
 github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1 h1:B9AocC+dxrCqcf4vVhztIkSkt3gpRjUkEka8AmZWGlQ=
 github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1/go.mod h1:FjM9DXWfP0w/AeOtJoSKHBZ01LqmaO6uP4bXhv3fekw=
-github.com/hashicorp/terraform-provider-google v1.20.1-0.20220324184940-33916265211a h1:/yvlZCO06ueA765YFDu+vDwbCXGW/a7OISVcyRj0eK0=
-github.com/hashicorp/terraform-provider-google v1.20.1-0.20220324184940-33916265211a/go.mod h1:zuersTi7DrjFzHJ9B6noGikoBdHD6EJj6RlQ2YMQNb4=
+github.com/hashicorp/terraform-provider-google v1.20.1-0.20220401195508-e86f490c464d h1:DGT+Smyrke23S2oyAwHltCdx1U8Heutir/dmLwbadJ8=
+github.com/hashicorp/terraform-provider-google v1.20.1-0.20220401195508-e86f490c464d/go.mod h1:zuersTi7DrjFzHJ9B6noGikoBdHD6EJj6RlQ2YMQNb4=
 github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 h1:1FGtlkJw87UsTMg5s8jrekrHmUPUJaMcu6ELiVhQrNw=
 github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896/go.mod h1:bzBPnUIkI0RxauU8Dqo+2KrZZ28Cf48s8V6IHt3p4co=
 github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0=
diff --git a/tfgcv/planned_assets.go b/tfgcv/planned_assets.go
index 2c54ed01..3060830e 100644
--- a/tfgcv/planned_assets.go
+++ b/tfgcv/planned_assets.go
@@ -30,7 +30,7 @@ import (
 	"go.uber.org/zap"
 )
 
-type ReadPlannedAssetsFunc func(ctx context.Context, path, project, ancestry string, offline, convertUnchanged bool, errorLogger *zap.Logger) ([]google.Asset, error)
+type ReadPlannedAssetsFunc func(ctx context.Context, path, project string, ancestryCache map[string]string, offline, convertUnchanged bool, errorLogger *zap.Logger) ([]google.Asset, error)
 
 // ReadPlannedAssets extracts CAI assets from a terraform plan file.
 // If ancestry path is provided, it assumes the project is in that path rather
@@ -39,8 +39,8 @@ type ReadPlannedAssetsFunc func(ctx context.Context, path, project, ancestry str
 // are also reported in the output, otherwise only resources that are going to
 // be changed are reported.
 // It ignores non-supported resources.
-func ReadPlannedAssets(ctx context.Context, path, project, ancestry string, offline, convertUnchanged bool, errorLogger *zap.Logger) ([]google.Asset, error) {
-	converter, err := newConverter(ctx, path, project, ancestry, offline, convertUnchanged, errorLogger)
+func ReadPlannedAssets(ctx context.Context, path, project string, ancestryCache map[string]string, offline, convertUnchanged bool, errorLogger *zap.Logger) ([]google.Asset, error) {
+	converter, err := newConverter(ctx, path, project, ancestryCache, offline, convertUnchanged, errorLogger)
 	if err != nil {
 		return nil, err
 	}
@@ -63,19 +63,16 @@ func ReadPlannedAssets(ctx context.Context, path, project, ancestry string, offl
 	return converter.Assets(), nil
 }
 
-func newConverter(ctx context.Context, path, project, ancestry string, offline, convertUnchanged bool, errorLogger *zap.Logger) (*google.Converter, error) {
+func newConverter(ctx context.Context, path, project string, ancestryCache map[string]string, offline, convertUnchanged bool, errorLogger *zap.Logger) (*google.Converter, error) {
 	cfg, err := resources.GetConfig(ctx, project, offline)
 	if err != nil {
 		return nil, errors.Wrap(err, "building google configuration")
 	}
-	entries := map[string]string{
-		project: ancestry,
-	}
 	var resourceManager *cloudresourcemanager.Service
 	if !offline {
 		resourceManager = cfg.NewResourceManagerClient(cfg.UserAgent())
 	}
-	ancestryManager, err := ancestrymanager.New(resourceManager, entries, errorLogger)
+	ancestryManager, err := ancestrymanager.New(resourceManager, ancestryCache, errorLogger)
 	if err != nil {
 		return nil, errors.Wrap(err, "building google ancestry manager")
 	}


Test patch:
diff --git a/cmd/convert_test.go b/cmd/convert_test.go
index e1f3c75d..f1ae5925 100644
--- a/cmd/convert_test.go
+++ b/cmd/convert_test.go
@@ -36,7 +36,7 @@ func testAssets() []google.Asset {
 	}
 }
 
-func MockReadPlannedAssets(ctx context.Context, path, project, ancestry string, offline, convertUnchanged bool, errorLogger *zap.Logger) ([]google.Asset, error) {
+func MockReadPlannedAssets(ctx context.Context, path, project string, ancestryCache map[string]string, offline, convertUnchanged bool, errorLogger *zap.Logger) ([]google.Asset, error) {
 	return testAssets(), nil
 }
 
diff --git a/test/read_test.go b/test/read_test.go
index dd4d9719..d5a6405c 100644
--- a/test/read_test.go
+++ b/test/read_test.go
@@ -138,9 +138,12 @@ func TestReadPlannedAssetsCoverage(t *testing.T) {
 
 			planfile := filepath.Join(dir, c.name+".tfplan.json")
 			ctx := context.Background()
-			got, err := tfgcv.ReadPlannedAssets(ctx, planfile, data.Provider["project"], data.Ancestry, true, false, zaptest.NewLogger(t))
+			ancestryCache := map[string]string{
+				data.Provider["project"]: data.Ancestry,
+			}
+			got, err := tfgcv.ReadPlannedAssets(ctx, planfile, data.Provider["project"], ancestryCache, true, false, zaptest.NewLogger(t))
 			if err != nil {
-				t.Fatalf("ReadPlannedAssets(%s, %s, %s, %t): %v", planfile, data.Provider["project"], data.Ancestry, true, err)
+				t.Fatalf("ReadPlannedAssets(%s, %s, %s, %t): %v", planfile, data.Provider["project"], ancestryCache, true, err)
 			}
 
 			expectedAssets := normalizeAssets(t, want, true)
diff --git a/tfgcv/planned_assets_test.go b/tfgcv/planned_assets_test.go
index 2d385816..425bc841 100644
--- a/tfgcv/planned_assets_test.go
+++ b/tfgcv/planned_assets_test.go
@@ -2,9 +2,10 @@ package tfgcv
 
 import (
 	"context"
-	"go.uber.org/zap"
 	"path/filepath"
 	"testing"
+
+	"go.uber.org/zap"
 )
 
 const (
@@ -77,7 +78,10 @@ func TestReadPlannedAssets(t *testing.T) {
 			var offline bool
 			offline = true
 			ctx := context.Background()
-			got, err := ReadPlannedAssets(ctx, testFile, tt.args.project, tt.args.ancestry, offline, tt.args.convertUnchanged, zap.NewExample())
+			ancestryCache := map[string]string{
+				tt.args.project: tt.args.ancestry,
+			}
+			got, err := ReadPlannedAssets(ctx, testFile, tt.args.project, ancestryCache, offline, tt.args.convertUnchanged, zap.NewExample())
 			if (err != nil) != tt.wantErr {
 				t.Errorf("ReadPlannedAssets() error = %v, wantErr %v", err, tt.wantErr)
 				return