Repository: GoogleCloudPlatform/terraform-validator
Pull request #360 (2021-11-01 19:23:34)

Add Tests for Bigquery dataset IAM 
Add Tests for Bigquery dataset IAM 

palani-ram-google-partner: @melinath Please review this PR

Here is the Corresponding MM PR for this TFV
https://github.com/GoogleCloudPlatform/magic-modules/pull/5408


Patch:
diff --git a/ancestrymanager/ancestrymanager.go b/ancestrymanager/ancestrymanager.go
index 37a2319c..ee8695d8 100644
--- a/ancestrymanager/ancestrymanager.go
+++ b/ancestrymanager/ancestrymanager.go
@@ -101,22 +101,27 @@ func (m *resourceAncestryManager) getAncestryFromResource(tfData resources.Terra
 	}
 }
 
-type onlineAncestryManager struct {
+type manager struct {
 	resourceAncestryManager
 	// Talk to GCP resource manager. This field would be nil in offline mode.
 	resourceManager *cloudresourcemanager.Service
 	// Cache to prevent multiple network calls for looking up the same project's ancestry
 	// map[project]ancestryPath
 	ancestryCache map[string]string
+	offline       bool
 }
 
 // GetAncestry uses the resource manager API to get ancestry paths for
 // projects. It implements a cache because many resources share the same
 // project.
-func (m *onlineAncestryManager) GetAncestry(project string) (string, error) {
+func (m *manager) GetAncestry(project string) (string, error) {
 	if path, ok := m.ancestryCache[project]; ok {
 		return path, nil
 	}
+
+	if m.offline {
+		return "", fmt.Errorf("cannot fetch ancestry in offline mode")
+	}
 	ancestry, err := m.resourceManager.Projects.GetAncestry(project, &cloudresourcemanager.GetAncestryRequest{}).Do()
 	if err != nil {
 		return "", err
@@ -126,52 +131,20 @@ func (m *onlineAncestryManager) GetAncestry(project string) (string, error) {
 	return path, nil
 }
 
-// GetAncestryWithResource first attempts to get Ancestry from the API
-// If that fails, it falls back to inspecting the resource.
-func (m *onlineAncestryManager) GetAncestryWithResource(project string, tfData resources.TerraformResourceData, cai resources.Asset) (string, error) {
-	path, err := m.GetAncestry(project)
-	if path != "" {
-		return path, nil
-	}
-
-	ancestry, ok := m.getAncestryFromResource(tfData, cai)
-	if !ok {
-		return "", err
-	}
-	path = ancestryPath(ancestry)
-	m.errorLogger.Info(fmt.Sprintf("[Online] Retrieved ancestry for %s: %s", project, path))
-	m.store(project, path)
-	return path, nil
-}
-
-func (m *onlineAncestryManager) store(project, ancestry string) {
+func (m *manager) store(project, ancestry string) {
 	if project != "" && ancestry != "" {
 		m.ancestryCache[project] = ancestry
 	}
 }
 
-type offlineAncestryManager struct {
-	resourceAncestryManager
-	project  string
-	ancestry string
-}
-
-// GetAncestry returns the ancestry for the project. It returns an error if
-// the project does not equal to the one provided during initialization.
-func (m *offlineAncestryManager) GetAncestry(project string) (string, error) {
-	if project != m.project {
-		return "", fmt.Errorf("cannot fetch ancestry in offline mode")
-	}
-	return m.ancestry, nil
-}
-
 // GetAncestryWithResource first attempts to get Ancestry from the resource
 // If that fails, it falls back to the offline cache.
-func (m *offlineAncestryManager) GetAncestryWithResource(project string, tfData resources.TerraformResourceData, cai resources.Asset) (string, error) {
+func (m *manager) GetAncestryWithResource(project string, tfData resources.TerraformResourceData, cai resources.Asset) (string, error) {
 	ancestry, ok := m.getAncestryFromResource(tfData, cai)
 	if ok {
 		path := ancestryPath(ancestry)
-		m.errorLogger.Info(fmt.Sprintf("[Offline] Retrieved ancestry for %s: %s", project, path))
+		m.store(project, path)
+		m.errorLogger.Info(fmt.Sprintf("Retrieved ancestry for %s: %s", project, path))
 		return path, nil
 	}
 
@@ -184,17 +157,25 @@ func (m *offlineAncestryManager) GetAncestryWithResource(project string, tfData
 }
 
 // New returns AncestryManager that can be used to fetch ancestry information for a project.
-func New(retriever ClientRetriever, project, ancestry, userAgent string, offline bool, errorLogger *zap.Logger) (AncestryManager, error) {
-	if ancestry != "" {
-		ancestry = fmt.Sprintf("%s/project/%s", ancestry, project)
+// entries takes project as key and ancestry as value
+func New(retriever ClientRetriever, entries map[string]string, userAgent string, offline bool, errorLogger *zap.Logger) (AncestryManager, error) {
+	am := &manager{
+		ancestryCache: map[string]string{},
+		resourceAncestryManager: resourceAncestryManager{
+			errorLogger: errorLogger,
+		},
+		offline: offline,
+	}
+	if !offline {
+		rm := retriever.NewResourceManagerClient(userAgent)
+		am.resourceManager = rm
 	}
-	if offline {
-		return &offlineAncestryManager{project: project, ancestry: ancestry, resourceAncestryManager: resourceAncestryManager{errorLogger: errorLogger}}, nil
+	for project, ancestry := range entries {
+		if ancestry != "" {
+			ancestry = fmt.Sprintf("%s/project/%s", ancestry, project)
+		}
+		am.store(project, ancestry)
 	}
-	am := &onlineAncestryManager{ancestryCache: map[string]string{}, resourceAncestryManager: resourceAncestryManager{errorLogger: errorLogger}}
-	am.store(project, ancestry)
-	rm := retriever.NewResourceManagerClient(userAgent)
-	am.resourceManager = rm
 	return am, nil
 }
 
diff --git a/converters/google/fake_resource_data.go b/converters/google/fake_resource_data.go
index 9821ecb6..e1b43752 100644
--- a/converters/google/fake_resource_data.go
+++ b/converters/google/fake_resource_data.go
@@ -19,6 +19,7 @@ package google
 
 import (
 	"fmt"
+	"reflect"
 	"strconv"
 	"strings"
 	"time"
@@ -26,6 +27,20 @@ import (
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
 )
 
+// Must be set to the same value as the internal typeObject const
+const typeObject schema.ValueType = 8
+
+
+// This is more or less equivalent to the internal getResult struct
+// used by schema.ResourceData
+type getResult struct {
+	Value interface{}
+	Computed bool
+	Exists bool
+	Schema *schema.Schema
+}
+
+
 // Compare to https://github.com/hashicorp/terraform-plugin-sdk/blob/97b4465/helper/schema/resource_data.go#L15
 type FakeResourceData struct {
 	reader schema.FieldReader
@@ -43,6 +58,36 @@ func (d *FakeResourceData) Id() string {
 	return ""
 }
 
+func (d *FakeResourceData) getRaw(key string) getResult {
+	var parts []string
+	if key != "" {
+		parts = strings.Split(key, ".")
+	}
+	return d.get(parts)
+}
+
+func (d *FakeResourceData) get(addr []string) getResult {
+	r, err := d.reader.ReadField(addr)
+	if err != nil {
+		panic(err)
+	}
+
+	var s *schema.Schema
+	if schemaPath := addrToSchema(addr, d.schema); len(schemaPath) > 0 {
+		s = schemaPath[len(schemaPath)-1]
+	}
+	if r.Value == nil && s != nil {
+		r.Value = r.ValueOrZero(s)
+	}
+
+	return getResult{
+		Value: r.Value,
+		Computed: r.Computed,
+		Exists: r.Exists,
+		Schema: s,
+	}
+}
+
 // Get reads a single field by key.
 func (d *FakeResourceData) Get(name string) interface{} {
 	val, _ := d.GetOk(name)
@@ -52,24 +97,28 @@ func (d *FakeResourceData) Get(name string) interface{} {
 // Get reads a single field by key and returns a boolean indicating
 // whether the field exists.
 func (d *FakeResourceData) GetOk(name string) (interface{}, bool) {
-	res, err := d.reader.ReadField(strings.Split(name, "."))
-	if err != nil {
-		return nil, false
-	}
+	r := d.getRaw(name)
+	exists := r.Exists && !r.Computed
+
+	if exists {
+		// Verify that it's not the zero-value
+		value := r.Value
+		zero := r.Schema.Type.Zero()
 
-	addr := strings.Split(name, ".")
-	schemaPath := addrToSchema(addr, d.schema)
-	if len(schemaPath) == 0 {
-		return nil, false
+		if eq, ok := value.(schema.Equal); ok {
+			exists = !eq.Equal(zero)
+		} else {
+			exists = !reflect.DeepEqual(value, zero)
+		}
 	}
 
-	return res.ValueOrZero(schemaPath[len(schemaPath)-1]), res.Exists && !res.Computed
+	return r.Value, exists
 }
 
-// GetOkExists currently just calls GetOk but should be updated to support
-// schema.ResourceData.GetOkExists functionality.
-func (d *FakeResourceData) GetOkExists(name string) (interface{}, bool) {
-	return d.GetOk(name)
+func (d *FakeResourceData) GetOkExists(key string) (interface{}, bool) {
+	r := d.getRaw(key)
+	exists := r.Exists && !r.Computed
+	return r.Value, exists
 }
 
 // These methods are required by some mappers but we don't actually have (or need)
@@ -101,8 +150,6 @@ func NewFakeResourceData(kind string, resourceSchema map[string]*schema.Schema,
 // NOTE: This function was copied from the terraform library:
 // github.com/hashicorp/terraform/helper/schema/field_reader.go
 func addrToSchema(addr []string, schemaMap map[string]*schema.Schema) []*schema.Schema {
-	const typeObject = 999
-
 	current := &schema.Schema{
 		Type: typeObject,
 		Elem: schemaMap,
diff --git a/converters/google/resources/bigquery_dataset_iam.go b/converters/google/resources/bigquery_dataset_iam.go
new file mode 100644
index 00000000..a1152665
--- /dev/null
+++ b/converters/google/resources/bigquery_dataset_iam.go
@@ -0,0 +1,103 @@
+package google
+
+import "fmt"
+
+func resourceConverterBigqueryDatasetIamPolicy() ResourceConverter {
+	return ResourceConverter{
+		AssetType:         "bigquery.googleapis.com/Dataset",
+		Convert:           GetBigqueryDatasetIamPolicyCaiObject,
+		MergeCreateUpdate: MergeBigqueryDatasetIamPolicy,
+	}
+}
+
+func resourceConverterBigqueryDatasetIamBinding() ResourceConverter {
+	return ResourceConverter{
+		AssetType:         "bigquery.googleapis.com/Dataset",
+		Convert:           GetBigqueryDatasetIamBindingCaiObject,
+		FetchFullResource: FetchBigqueryDatasetIamPolicy,
+		MergeCreateUpdate: MergeBigqueryDatasetIamBinding,
+		MergeDelete:       MergeBigqueryDatasetIamBindingDelete,
+	}
+}
+
+func resourceConverterBigqueryDatasetIamMember() ResourceConverter {
+	return ResourceConverter{
+		AssetType:         "bigquery.googleapis.com/Dataset",
+		Convert:           GetBigqueryDatasetIamMemberCaiObject,
+		FetchFullResource: FetchBigqueryDatasetIamPolicy,
+		MergeCreateUpdate: MergeBigqueryDatasetIamMember,
+		MergeDelete:       MergeBigqueryDatasetIamMemberDelete,
+	}
+}
+
+func GetBigqueryDatasetIamPolicyCaiObject(d TerraformResourceData, config *Config) ([]Asset, error) {
+	return newBigqueryDatasetIamAsset(d, config, expandIamPolicyBindings)
+}
+
+func GetBigqueryDatasetIamBindingCaiObject(d TerraformResourceData, config *Config) ([]Asset, error) {
+	return newBigqueryDatasetIamAsset(d, config, expandIamRoleBindings)
+}
+
+func GetBigqueryDatasetIamMemberCaiObject(d TerraformResourceData, config *Config) ([]Asset, error) {
+	return newBigqueryDatasetIamAsset(d, config, expandIamMemberBindings)
+}
+
+func MergeBigqueryDatasetIamPolicy(existing, incoming Asset) Asset {
+	existing.IAMPolicy = incoming.IAMPolicy
+	return existing
+}
+
+func MergeBigqueryDatasetIamBinding(existing, incoming Asset) Asset {
+	return mergeIamAssets(existing, incoming, mergeAuthoritativeBindings)
+}
+
+func MergeBigqueryDatasetIamBindingDelete(existing, incoming Asset) Asset {
+	return mergeDeleteIamAssets(existing, incoming, mergeDeleteAuthoritativeBindings)
+}
+
+func MergeBigqueryDatasetIamMember(existing, incoming Asset) Asset {
+	return mergeIamAssets(existing, incoming, mergeAdditiveBindings)
+}
+
+func MergeBigqueryDatasetIamMemberDelete(existing, incoming Asset) Asset {
+	return mergeDeleteIamAssets(existing, incoming, mergeDeleteAdditiveBindings)
+}
+
+func newBigqueryDatasetIamAsset(
+	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, "//bigquery.googleapis.com/{{dataset_id}}")
+	if err != nil {
+		return []Asset{}, err
+	}
+
+	return []Asset{{
+		Name: name,
+		Type: "bigquery.googleapis.com/Dataset",
+		IAMPolicy: &IAMPolicy{
+			Bindings: bindings,
+		},
+	}}, nil
+}
+
+func FetchBigqueryDatasetIamPolicy(d TerraformResourceData, config *Config) (Asset, error) {
+	// Check if the identity field returns a value
+	if _, ok := d.GetOk("{{dataset_id}}"); !ok {
+		return Asset{}, ErrEmptyIdentityField
+	}
+
+	return fetchIamPolicy(
+		NewBigqueryDatasetIamUpdater,
+		d,
+		config,
+		"//bigquery.googleapis.com/{{dataset_id}}",
+		"bigquery.googleapis.com/Dataset",
+	)
+}
diff --git a/converters/google/resources/billing_budget.go b/converters/google/resources/billing_budget.go
index 0041abd7..49af9803 100644
--- a/converters/google/resources/billing_budget.go
+++ b/converters/google/resources/billing_budget.go
@@ -165,13 +165,13 @@ func expandBillingBudgetBudgetFilterSubaccounts(v interface{}, d TerraformResour
 	return v, nil
 }
 
-func expandBillingBudgetBudgetFilterLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) {
+func expandBillingBudgetBudgetFilterLabels(v interface{}, d TerraformResourceData, config *Config) (map[string][]string, error) {
 	if v == nil {
-		return map[string]string{}, nil
+		return map[string][]string{}, nil
 	}
-	m := make(map[string]string)
+	m := make(map[string][]string)
 	for k, val := range v.(map[string]interface{}) {
-		m[k] = val.(string)
+		m[k] = []string{val.(string)}
 	}
 	return m, nil
 }
diff --git a/converters/google/resources/compute_firewall.go b/converters/google/resources/compute_firewall.go
index 7e51118c..7b927bf2 100644
--- a/converters/google/resources/compute_firewall.go
+++ b/converters/google/resources/compute_firewall.go
@@ -101,7 +101,8 @@ func diffSuppressSourceRanges(k, old, new string, d *schema.ResourceData) bool {
 			// this allows for diff suppress on ["0.0.0.0/0"] -> []
 			return true
 		}
-		return old == new
+		// For any other source_ranges.# diff, don't suppress
+		return false
 	}
 	kLength := "source_ranges.#"
 	oldLength, newLength := d.GetChange(kLength)
@@ -125,7 +126,8 @@ func diffSuppressSourceRanges(k, old, new string, d *schema.ResourceData) bool {
 			return true
 		}
 	}
-	return old == new
+	// For any other source_ranges value diff, don't suppress
+	return false
 }
 
 const ComputeFirewallAssetType string = "compute.googleapis.com/Firewall"
diff --git a/converters/google/resources/error_retry_predicates.go b/converters/google/resources/error_retry_predicates.go
index 8f4c9333..63907c53 100644
--- a/converters/google/resources/error_retry_predicates.go
+++ b/converters/google/resources/error_retry_predicates.go
@@ -31,6 +31,12 @@ var defaultErrorRetryPredicates = []RetryErrorPredicateFunc{
 	// we had this in our global default error retries.
 	// Keeping it as a default for now.
 	is409OperationInProgressError,
+
+	// GCE Subnetworks are considered unready for a brief period when certain
+	// operations are performed on them, and the scope is likely too broad to
+	// apply a mutex. If we attempt an operation w/ an unready subnetwork, retry
+	// it.
+	isSubnetworkUnreadyError,
 }
 
 /** END GLOBAL ERROR RETRY PREDICATES HERE **/
@@ -97,6 +103,19 @@ func is409OperationInProgressError(err error) (bool, string) {
 	return false, ""
 }
 
+func isSubnetworkUnreadyError(err error) (bool, string) {
+	gerr, ok := err.(*googleapi.Error)
+	if !ok {
+		return false, ""
+	}
+
+	if gerr.Code == 400 && strings.Contains(gerr.Body, "resourceNotReady") && strings.Contains(gerr.Body, "subnetworks") {
+		log.Printf("[DEBUG] Dismissed an error as retryable based on error code 400 and error reason 'resourceNotReady' w/ `subnetwork`: %s", err)
+		return true, "Subnetwork not ready"
+	}
+	return false, ""
+}
+
 // Retry on comon googleapi error codes for retryable errors.
 // TODO(#5609): This may not need to be applied globally - figure out
 // what retryable error codes apply to which API.
diff --git a/converters/google/resources/iam_bigquery_dataset.go b/converters/google/resources/iam_bigquery_dataset.go
new file mode 100644
index 00000000..4443e1a4
--- /dev/null
+++ b/converters/google/resources/iam_bigquery_dataset.go
@@ -0,0 +1,262 @@
+package google
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/hashicorp/errwrap"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"google.golang.org/api/cloudresourcemanager/v1"
+)
+
+var IamBigqueryDatasetSchema = map[string]*schema.Schema{
+	"dataset_id": {
+		Type:     schema.TypeString,
+		Required: true,
+		ForceNew: true,
+	},
+	"project": {
+		Type:     schema.TypeString,
+		Optional: true,
+		Computed: true,
+		ForceNew: true,
+	},
+}
+
+var bigqueryAccessPrimitiveToRoleMap = map[string]string{
+	"OWNER":  "roles/bigquery.dataOwner",
+	"WRITER": "roles/bigquery.dataEditor",
+	"READER": "roles/bigquery.dataViewer",
+}
+
+type BigqueryDatasetIamUpdater struct {
+	project   string
+	datasetId string
+	d         TerraformResourceData
+	Config    *Config
+}
+
+func NewBigqueryDatasetIamUpdater(d TerraformResourceData, config *Config) (ResourceIamUpdater, error) {
+	project, err := getProject(d, config)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := d.Set("project", project); err != nil {
+		return nil, fmt.Errorf("Error setting project: %s", err)
+	}
+
+	return &BigqueryDatasetIamUpdater{
+		project:   project,
+		datasetId: d.Get("dataset_id").(string),
+		d:         d,
+		Config:    config,
+	}, nil
+}
+
+func BigqueryDatasetIdParseFunc(d *schema.ResourceData, config *Config) error {
+	fv, err := parseProjectFieldValue("datasets", d.Id(), "project", d, config, false)
+	if err != nil {
+		return err
+	}
+
+	if err := d.Set("project", fv.Project); err != nil {
+		return fmt.Errorf("Error setting project: %s", err)
+	}
+	if err := d.Set("dataset_id", fv.Name); err != nil {
+		return fmt.Errorf("Error setting dataset_id: %s", err)
+	}
+
+	// Explicitly set the id so imported resources have the same ID format as non-imported ones.
+	d.SetId(fv.RelativeLink())
+	return nil
+}
+
+func (u *BigqueryDatasetIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) {
+	url := fmt.Sprintf("%s%s", u.Config.BigQueryBasePath, u.GetResourceId())
+
+	userAgent, err := generateUserAgentString(u.d, u.Config.userAgent)
+	if err != nil {
+		return nil, err
+	}
+
+	res, err := sendRequest(u.Config, "GET", u.project, url, userAgent, nil)
+	if err != nil {
+		return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err)
+	}
+
+	policy, err := accessToPolicy(res["access"])
+	if err != nil {
+		return nil, err
+	}
+	return policy, nil
+}
+
+func (u *BigqueryDatasetIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error {
+	url := fmt.Sprintf("%s%s", u.Config.BigQueryBasePath, u.GetResourceId())
+
+	access, err := policyToAccess(policy)
+	if err != nil {
+		return err
+	}
+	obj := map[string]interface{}{
+		"access": access,
+	}
+
+	userAgent, err := generateUserAgentString(u.d, u.Config.userAgent)
+	if err != nil {
+		return err
+	}
+
+	_, err = sendRequest(u.Config, "PATCH", u.project, url, userAgent, obj)
+	if err != nil {
+		return fmt.Errorf("Error creating DatasetAccess: %s", err)
+	}
+
+	return nil
+}
+
+func accessToPolicy(access interface{}) (*cloudresourcemanager.Policy, error) {
+	if access == nil {
+		return nil, nil
+	}
+	roleToBinding := make(map[string]*cloudresourcemanager.Binding)
+
+	accessArr := access.([]interface{})
+	for _, v := range accessArr {
+		memberRole := v.(map[string]interface{})
+		rawRole, ok := memberRole["role"]
+		if !ok {
+			// "view" allows role to not be defined. It is a special dataset access construct, so ignore
+			// If a user wants to manage "view" access they should use the `bigquery_dataset_access` resource
+			continue
+		}
+		role := rawRole.(string)
+		if iamRole, ok := bigqueryAccessPrimitiveToRoleMap[role]; ok {
+			// API changes certain IAM roles to legacy roles. Revert these changes
+			role = iamRole
+		}
+		member, err := accessToIamMember(memberRole)
+		if err != nil {
+			return nil, err
+		}
+		// We have to combine bindings manually
+		binding, ok := roleToBinding[role]
+		if !ok {
+			binding = &cloudresourcemanager.Binding{Role: role, Members: []string{}}
+		}
+		binding.Members = append(binding.Members, member)
+
+		roleToBinding[role] = binding
+	}
+	bindings := make([]*cloudresourcemanager.Binding, 0)
+	for _, v := range roleToBinding {
+		bindings = append(bindings, v)
+	}
+
+	return &cloudresourcemanager.Policy{Bindings: bindings}, nil
+}
+
+func policyToAccess(policy *cloudresourcemanager.Policy) ([]map[string]interface{}, error) {
+	res := make([]map[string]interface{}, 0)
+	if len(policy.AuditConfigs) != 0 {
+		return nil, errors.New("Access policies not allowed on BigQuery Dataset IAM policies")
+	}
+	for _, binding := range policy.Bindings {
+		if binding.Condition != nil {
+			return nil, errors.New("IAM conditions not allowed on BigQuery Dataset IAM")
+		}
+		if fullRole, ok := bigqueryAccessPrimitiveToRoleMap[binding.Role]; ok {
+			return nil, fmt.Errorf("BigQuery Dataset legacy role %s is not allowed when using google_bigquery_dataset_iam resources. Please use the full form: %s", binding.Role, fullRole)
+		}
+		for _, member := range binding.Members {
+			// Do not append any deleted members
+			if strings.HasPrefix(member, "deleted:") {
+				continue
+			}
+			access := map[string]interface{}{
+				"role": binding.Role,
+			}
+			memberType, member, err := iamMemberToAccess(member)
+			if err != nil {
+				return nil, err
+			}
+			access[memberType] = member
+			res = append(res, access)
+		}
+	}
+
+	return res, nil
+}
+
+// Returns the member access type and member for an IAM member.
+// Dataset access uses different member types to identify groups, domains, etc.
+// these types are used as keys in the access JSON payload
+func iamMemberToAccess(member string) (string, string, error) {
+	if strings.HasPrefix(member, "deleted:") {
+		return "", "", fmt.Errorf("BigQuery Dataset IAM member is deleted: %s", member)
+	}
+
+	pieces := strings.SplitN(member, ":", 2)
+	if len(pieces) > 1 {
+		switch pieces[0] {
+		case "group":
+			return "groupByEmail", pieces[1], nil
+		case "domain":
+			return "domain", pieces[1], nil
+		case "user":
+			return "userByEmail", pieces[1], nil
+		case "serviceAccount":
+			return "userByEmail", pieces[1], nil
+		default:
+			return "", "", fmt.Errorf("Failed to parse BigQuery Dataset IAM member type: %s", member)
+		}
+	}
+	if member == "projectOwners" || member == "projectReaders" || member == "projectWriters" || member == "allAuthenticatedUsers" {
+		// These are special BigQuery Dataset permissions
+		return "specialGroup", member, nil
+	}
+	return "iamMember", member, nil
+}
+
+func accessToIamMember(access map[string]interface{}) (string, error) {
+	// One of the fields must be set, we have to find which IAM member type this maps to
+	if member, ok := access["groupByEmail"]; ok {
+		return fmt.Sprintf("group:%s", member.(string)), nil
+	}
+	if member, ok := access["domain"]; ok {
+		return fmt.Sprintf("domain:%s", member.(string)), nil
+	}
+	if member, ok := access["specialGroup"]; ok {
+		return member.(string), nil
+	}
+	if member, ok := access["iamMember"]; ok {
+		return member.(string), nil
+	}
+	if _, ok := access["view"]; ok {
+		// view does not map to an IAM member, use access instead
+		return "", fmt.Errorf("Failed to convert BigQuery Dataset access to IAM member. To use views with a dataset, please use dataset_access")
+	}
+	if member, ok := access["userByEmail"]; ok {
+		// service accounts have "gservice" in their email. This is best guess due to lost information
+		if strings.Contains(member.(string), "gserviceaccount") {
+			return fmt.Sprintf("serviceAccount:%s", member.(string)), nil
+		}
+		return fmt.Sprintf("user:%s", member.(string)), nil
+	}
+	return "", fmt.Errorf("Failed to identify IAM member from BigQuery Dataset access: %v", access)
+}
+
+func (u *BigqueryDatasetIamUpdater) GetResourceId() string {
+	return fmt.Sprintf("projects/%s/datasets/%s", u.project, u.datasetId)
+}
+
+// Matches the mutex of google_big_query_dataset_access
+func (u *BigqueryDatasetIamUpdater) GetMutexKey() string {
+	return fmt.Sprintf("%s", u.datasetId)
+}
+
+func (u *BigqueryDatasetIamUpdater) DescribeResource() string {
+	return fmt.Sprintf("Bigquery Dataset %s/%s", u.project, u.datasetId)
+}
diff --git a/converters/google/resources/pubsub_topic.go b/converters/google/resources/pubsub_topic.go
index c009483d..679a3180 100644
--- a/converters/google/resources/pubsub_topic.go
+++ b/converters/google/resources/pubsub_topic.go
@@ -78,6 +78,12 @@ func GetPubsubTopicApiObject(d TerraformResourceData, config *Config) (map[strin
 	} else if v, ok := d.GetOkExists("schema_settings"); !isEmptyValue(reflect.ValueOf(schemaSettingsProp)) && (ok || !reflect.DeepEqual(v, schemaSettingsProp)) {
 		obj["schemaSettings"] = schemaSettingsProp
 	}
+	messageRetentionDurationProp, err := expandPubsubTopicMessageRetentionDuration(d.Get("message_retention_duration"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("message_retention_duration"); !isEmptyValue(reflect.ValueOf(messageRetentionDurationProp)) && (ok || !reflect.DeepEqual(v, messageRetentionDurationProp)) {
+		obj["messageRetentionDuration"] = messageRetentionDurationProp
+	}
 
 	return resourcePubsubTopicEncoder(d, config, obj)
 }
@@ -162,3 +168,7 @@ func expandPubsubTopicSchemaSettingsSchema(v interface{}, d TerraformResourceDat
 func expandPubsubTopicSchemaSettingsEncoding(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
 	return v, nil
 }
+
+func expandPubsubTopicMessageRetentionDuration(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 f991744e..fece0de8 100644
--- a/converters/google/resources/resource_converters.go
+++ b/converters/google/resources/resource_converters.go
@@ -35,6 +35,9 @@ func ResourceConverters() map[string][]ResourceConverter {
 		"google_container_cluster":                         {resourceConverterContainerCluster()},
 		"google_container_node_pool":                       {resourceConverterContainerNodePool()},
 		"google_bigquery_dataset":                          {resourceConverterBigQueryDataset()},
+		"google_bigquery_dataset_iam_policy":               {resourceConverterBigqueryDatasetIamPolicy()},
+		"google_bigquery_dataset_iam_binding":              {resourceConverterBigqueryDatasetIamBinding()},
+		"google_bigquery_dataset_iam_member":               {resourceConverterBigqueryDatasetIamMember()},
 		"google_spanner_instance":                          {resourceConverterSpannerInstance()},
 		"google_project_service":                           {resourceConverterServiceUsage()},
 		"google_pubsub_subscription":                       {resourceConverterPubsubSubscription()},
diff --git a/tfgcv/planned_assets.go b/tfgcv/planned_assets.go
index 5a2b84cc..a38c3a12 100644
--- a/tfgcv/planned_assets.go
+++ b/tfgcv/planned_assets.go
@@ -67,7 +67,10 @@ func newConverter(ctx context.Context, path, project, ancestry string, offline,
 		return nil, errors.Wrap(err, "building google configuration")
 	}
 	ua := fmt.Sprintf("config-validator-tf/%s", BuildVersion())
-	ancestryManager, err := ancestrymanager.New(cfg, project, ancestry, ua, offline, errorLogger)
+	entries := map[string]string{
+		project: ancestry,
+	}
+	ancestryManager, err := ancestrymanager.New(cfg, entries, ua, offline, errorLogger)
 	if err != nil {
 		return nil, errors.Wrap(err, "building google ancestry manager")
 	}


Test patch:
diff --git a/ancestrymanager/ancestrymanager_test.go b/ancestrymanager/ancestrymanager_test.go
index 5022a696..a2e3c351 100644
--- a/ancestrymanager/ancestrymanager_test.go
+++ b/ancestrymanager/ancestrymanager_test.go
@@ -13,9 +13,8 @@ import (
 )
 
 type testRetriever struct {
-	online      bool
-	opts        []option.ClientOption
-	errorLogger *zap.Logger
+	online bool
+	opts   []option.ClientOption
 }
 
 func (tr *testRetriever) NewResourceManagerClient(userAgent string) *cloudresourcemanager.Service {
@@ -96,13 +95,17 @@ func TestGetAncestry(t *testing.T) {
 
 	// option.WithEndpoint(ts.URL), option.WithoutAuthentication()
 	trOnline := &testRetriever{online: true, opts: []option.ClientOption{option.WithEndpoint(ts.URL), option.WithoutAuthentication()}}
-	amOnline, err := New(trOnline, ownerProject, ownerAncestry, "", false, zap.NewExample())
+
+	entries := map[string]string{
+		ownerProject: ownerAncestry,
+	}
+	amOnline, err := New(trOnline, entries, "", false, zap.NewExample())
 	if err != nil {
 		t.Fatalf("failed to create online ancestry manager: %s", err)
 	}
 
 	trOffline := &testRetriever{online: false}
-	amOffline, err := New(trOffline, ownerProject, ownerAncestry, "", true, zap.NewExample())
+	amOffline, err := New(trOffline, entries, "", true, zap.NewExample())
 	if err != nil {
 		t.Fatalf("failed to create offline ancestry manager: %s", err)
 	}
diff --git a/converters/google/convert_test.go b/converters/google/convert_test.go
index 57e5bba7..c12927b2 100644
--- a/converters/google/convert_test.go
+++ b/converters/google/convert_test.go
@@ -32,7 +32,7 @@ const testProject = "test-project"
 
 func newTestConverter(convertUnchanged bool) (*Converter, error) {
 	ctx := context.Background()
-	ancestry := ""
+	ancestry := "default-ancestry"
 	ua := ""
 	project := testProject
 	offline := true
@@ -41,7 +41,10 @@ func newTestConverter(convertUnchanged bool) (*Converter, error) {
 		return nil, errors.Wrap(err, "constructing configuration")
 	}
 	errorLogger := zap.NewExample()
-	ancestryManager, err := ancestrymanager.New(cfg, project, ancestry, ua, offline, errorLogger)
+	entries := map[string]string{
+		project: ancestry,
+	}
+	ancestryManager, err := ancestrymanager.New(cfg, entries, ua, offline, errorLogger)
 	if err != nil {
 		return nil, errors.Wrap(err, "constructing resource ancestryManager")
 	}
diff --git a/converters/google/fake_resource_data_test.go b/converters/google/fake_resource_data_test.go
index db83cf2c..7cf86ec8 100644
--- a/converters/google/fake_resource_data_test.go
+++ b/converters/google/fake_resource_data_test.go
@@ -35,10 +35,9 @@ func TestFakeResourceData_kind(t *testing.T) {
 		p.ResourcesMap["google_compute_disk"].Schema,
 		values,
 	)
-	assert.Equal(t, d.Kind(), "google_compute_disk")
+	assert.Equal(t, "google_compute_disk", d.Kind())
 }
 
-
 func TestFakeResourceData_id(t *testing.T) {
 	p := provider.Provider()
 
@@ -57,7 +56,6 @@ func TestFakeResourceData_id(t *testing.T) {
 	assert.Equal(t, d.Id(), "")
 }
 
-
 func TestFakeResourceData_get(t *testing.T) {
 	p := provider.Provider()
 
@@ -76,7 +74,6 @@ func TestFakeResourceData_get(t *testing.T) {
 	assert.Equal(t, d.Get("name"), "test-disk")
 }
 
-
 func TestFakeResourceData_getOkOk(t *testing.T) {
 	p := provider.Provider()
 
@@ -93,12 +90,11 @@ func TestFakeResourceData_getOkOk(t *testing.T) {
 		values,
 	)
 	res, ok := d.GetOk("name")
-	assert.Equal(t, res, "test-disk")
+	assert.Equal(t, "test-disk", res)
 	assert.True(t, ok)
 }
 
-
-func TestFakeResourceData_getOkNotOk(t *testing.T) {
+func TestFakeResourceData_getOkNonexistentField(t *testing.T) {
 	p := provider.Provider()
 
 	values := map[string]interface{}{
@@ -117,3 +113,214 @@ func TestFakeResourceData_getOkNotOk(t *testing.T) {
 	assert.Nil(t, res)
 	assert.False(t, ok)
 }
+
+func TestFakeResourceData_getOkEmptyString(t *testing.T) {
+	p := provider.Provider()
+
+	values := map[string]interface{}{
+		"name":                      "test-disk",
+		"type":                      "pd-ssd",
+		"zone":                      "us-central1-a",
+		"image":                     "",
+		"physical_block_size_bytes": 4096,
+	}
+	d := NewFakeResourceData(
+		"google_compute_disk",
+		p.ResourcesMap["google_compute_disk"].Schema,
+		values,
+	)
+	res, ok := d.GetOk("image")
+	assert.Equal(t, "", res)
+	assert.False(t, ok)
+}
+
+func TestFakeResourceData_getOkUnsetString(t *testing.T) {
+	p := provider.Provider()
+
+	values := map[string]interface{}{
+		"name":     "my-node-pool",
+		"location": "us-central1",
+		"cluster":  "projects/my-project-id/global/clusters/my-gke-cluster",
+		"config": map[string]interface{}{
+			"machineType": "n1-standard-1",
+			"metadata": map[string]string{
+				"disable-legacy-endpoints": "true",
+			},
+			"oauthScopes": []string{
+				"https://www.googleapis.com/auth/cloud-platform",
+			},
+			"preemptible": true,
+		},
+	}
+	d := NewFakeResourceData(
+		"google_container_cluster",
+		p.ResourcesMap["google_container_cluster"].Schema,
+		values,
+	)
+	res, ok := d.GetOk("subnetwork")
+	assert.Equal(t, "", res)
+	assert.False(t, ok)
+}
+
+func TestFakeResourceData_getOkTypeObject(t *testing.T) {
+	p := provider.Provider()
+
+	values := map[string]interface{}{
+		"advanced_machine_features": []interface{}{},
+		"allow_stopping_for_update": nil,
+		"attached_disk": []interface{}{
+			map[string]interface{}{
+				"device_name":             "test-device_name",
+				"disk_encryption_key_raw": nil,
+				"kms_key_self_link":       "test-kms_key_self_link",
+				"mode":                    "READ_ONLY",
+				"source":                  "test-source",
+			},
+			map[string]interface{}{
+				"disk_encryption_key_raw": nil,
+				"mode":                    "READ_WRITE",
+				"source":                  "test-source2",
+			},
+		},
+		"boot_disk": []interface{}{
+			map[string]interface{}{
+				"auto_delete":             true,
+				"disk_encryption_key_raw": nil,
+				"initialize_params": []interface{}{
+					map[string]interface{}{
+						"image": "debian-cloud/debian-9",
+					},
+				},
+				"mode": "READ_WRITE",
+			},
+		},
+		"can_ip_forward":          false,
+		"deletion_protection":     false,
+		"description":             nil,
+		"desired_status":          nil,
+		"enable_display":          nil,
+		"hostname":                nil,
+		"labels":                  nil,
+		"machine_type":            "n1-standard-1",
+		"metadata":                nil,
+		"metadata_startup_script": nil,
+		"name":                    "test",
+		"network_interface": []interface{}{
+			map[string]interface{}{
+				"access_config": []interface{}{
+					map[string]interface{}{
+						"public_ptr_domain_name": nil,
+					},
+				},
+				"alias_ip_range":     []interface{}{},
+				"ipv6_access_config": []interface{}{},
+				"network":            "default",
+				"nic_type":           nil,
+			},
+		},
+		"resource_policies": nil,
+		"scratch_disk": []interface{}{
+			map[string]interface{}{
+				"interface": "SCSI",
+			},
+		},
+		"service_account":          []interface{}{},
+		"shielded_instance_config": []interface{}{},
+		"tags": []interface{}{
+			"bar",
+			"foo",
+		},
+		"timeouts": nil,
+		"zone":     "us-central1-a",
+	}
+	d := NewFakeResourceData(
+		"google_compute_instance",
+		p.ResourcesMap["google_compute_instance"].Schema,
+		values,
+	)
+	res, ok := d.GetOk("attached_disk.0")
+	assert.Equal(t, map[string]interface{}{
+		"device_name":                "test-device_name",
+		"disk_encryption_key_raw":    "",
+		"disk_encryption_key_sha256": "",
+		"kms_key_self_link":          "test-kms_key_self_link",
+		"mode":                       "READ_ONLY",
+		"source":                     "test-source",
+	}, res)
+	assert.True(t, ok)
+}
+
+func TestFakeResourceData_getOknsetTypeObject(t *testing.T) {
+	p := provider.Provider()
+
+	values := map[string]interface{}{
+		"advanced_machine_features": []interface{}{},
+		"allow_stopping_for_update": nil,
+		"attached_disk":             []interface{}{},
+		"boot_disk": []interface{}{
+			map[string]interface{}{
+				"auto_delete":             true,
+				"disk_encryption_key_raw": nil,
+				"initialize_params": []interface{}{
+					map[string]interface{}{
+						"image": "debian-cloud/debian-9",
+					},
+				},
+				"mode": "READ_WRITE",
+			},
+		},
+		"can_ip_forward":          false,
+		"deletion_protection":     false,
+		"description":             nil,
+		"desired_status":          nil,
+		"enable_display":          nil,
+		"hostname":                nil,
+		"labels":                  nil,
+		"machine_type":            "n1-standard-1",
+		"metadata":                nil,
+		"metadata_startup_script": nil,
+		"name":                    "test",
+		"network_interface": []interface{}{
+			map[string]interface{}{
+				"access_config": []interface{}{
+					map[string]interface{}{
+						"public_ptr_domain_name": nil,
+					},
+				},
+				"alias_ip_range":     []interface{}{},
+				"ipv6_access_config": []interface{}{},
+				"network":            "default",
+				"nic_type":           nil,
+			},
+		},
+		"resource_policies": nil,
+		"scratch_disk": []interface{}{
+			map[string]interface{}{
+				"interface": "SCSI",
+			},
+		},
+		"service_account":          []interface{}{},
+		"shielded_instance_config": []interface{}{},
+		"tags": []interface{}{
+			"bar",
+			"foo",
+		},
+		"timeouts": nil,
+		"zone":     "us-central1-a",
+	}
+	d := NewFakeResourceData(
+		"google_compute_instance",
+		p.ResourcesMap["google_compute_instance"].Schema,
+		values,
+	)
+	res, ok := d.GetOk("attached_disk.0")
+	assert.Equal(t, map[string]interface{}{
+		"device_name":                "",
+		"disk_encryption_key_raw":    "",
+		"disk_encryption_key_sha256": "",
+		"kms_key_self_link":          "",
+		"mode":                       "",
+		"source":                     "",
+	}, res)
+	assert.False(t, ok)
+}
diff --git a/test/cli_test.go b/test/cli_test.go
index f39ed517..579de7d9 100644
--- a/test/cli_test.go
+++ b/test/cli_test.go
@@ -61,8 +61,12 @@ func TestCLI(t *testing.T) {
 		{name: "sql"},
 		{name: "example_access_context_manager_service_perimeter"},
 		{name: "example_bigquery_dataset"},
+		{name: "example_bigquery_dataset_iam_policy"},
+		{name: "example_bigquery_dataset_iam_binding"},
+		{name: "example_bigquery_dataset_iam_member"},
 		{name: "example_bigtable_instance"},
 		{name: "example_compute_disk"},
+		{name: "example_compute_disk_empty_image"},
 		{name: "example_compute_firewall"},
 		{name: "example_compute_forwarding_rule"},
 		{name: "example_compute_instance"},
diff --git a/test/read_test.go b/test/read_test.go
index a576c72d..8eb6fd9d 100644
--- a/test/read_test.go
+++ b/test/read_test.go
@@ -22,8 +22,12 @@ func TestReadPlannedAssetsCoverage(t *testing.T) {
 	}{
 		{name: "example_access_context_manager_service_perimeter"},
 		{name: "example_bigquery_dataset"},
+		{name: "example_bigquery_dataset_iam_policy"},
+		{name: "example_bigquery_dataset_iam_binding"},
+		{name: "example_bigquery_dataset_iam_member"},
 		{name: "example_bigtable_instance"},
 		{name: "example_compute_disk"},
+		{name: "example_compute_disk_empty_image"},
 		{name: "example_compute_firewall"},
 		// This test can't run in offline mode
 		// {name: "example_compute_instance"},
@@ -104,8 +108,8 @@ func TestReadPlannedAssetsCoverage(t *testing.T) {
 				t.Fatalf("ReadPlannedAssets(%s, %s, %s, %t): %v", planfile, data.Provider["project"], data.Ancestry, true, err)
 			}
 
-			expectedAssets := normalizeAssets(t, got, true)
-			actualAssets := normalizeAssets(t, want, true)
+			expectedAssets := normalizeAssets(t, want, true)
+			actualAssets := normalizeAssets(t, got, true)
 			require.ElementsMatch(t, actualAssets, expectedAssets)
 		})
 	}
diff --git a/testdata/templates/bucket.json b/testdata/templates/bucket.json
index 9b6f2919..9ec82929 100644
--- a/testdata/templates/bucket.json
+++ b/testdata/templates/bucket.json
@@ -9,8 +9,9 @@
       "discovery_name": "Bucket",
       "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}",
       "data": {
-        "billing": {},
-        "lifecycle": {},
+        "lifecycle": {
+          "rule": []
+        },
         "iamConfiguration": {
           "uniformBucketLevelAccess": {
             "enabled": false
diff --git a/testdata/templates/example_bigquery_dataset_iam_binding.json b/testdata/templates/example_bigquery_dataset_iam_binding.json
new file mode 100644
index 00000000..e779657d
--- /dev/null
+++ b/testdata/templates/example_bigquery_dataset_iam_binding.json
@@ -0,0 +1,31 @@
+[{
+	"name": "//bigquery.googleapis.com/projects/{{.Provider.project}}/datasets/test-dataset",
+	"asset_type": "bigquery.googleapis.com/Dataset",
+	"ancestry_path": "{{.Ancestry}}/project/{{.Provider.project}}",
+	"resource": {
+		"version": "v2",
+		"discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/bigquery/v2/rest",
+		"discovery_name": "Dataset",
+		"parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}",
+		"data": {
+			"datasetReference": {
+				"datasetId": "test-dataset"
+			},
+			"defaultTableExpirationMs": 3600000,
+			"labels": {
+				"env": "dev"
+			},
+			"location": "EU"
+		}
+	}
+}, {
+	"name": "//bigquery.googleapis.com/test-dataset",
+	"asset_type": "bigquery.googleapis.com/Dataset",
+	"ancestry_path": "{{.Ancestry}}/project/{{.Provider.project}}",
+	"iam_policy": {
+		"bindings": [{
+			"role": "roles/bigquery.dataViewer",
+			"members": ["allUsers", "allAuthenticatedUsers"]
+		}]
+	}
+}]
\ No newline at end of file
diff --git a/testdata/templates/example_bigquery_dataset_iam_binding.tf b/testdata/templates/example_bigquery_dataset_iam_binding.tf
new file mode 100644
index 00000000..df14ceb2
--- /dev/null
+++ b/testdata/templates/example_bigquery_dataset_iam_binding.tf
@@ -0,0 +1,52 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+terraform {
+  required_providers {
+    google = {
+      source = "hashicorp/google"
+      version = "~> {{.Provider.version}}"
+    }
+  }
+}
+
+provider "google" {
+  {{if .Provider.credentials }}credentials = "{{.Provider.credentials}}"{{end}}
+}
+
+resource "google_bigquery_dataset" "example_dataset" {
+  dataset_id                  = "test-dataset"
+  location                    = "EU"
+  project                     = "{{.Provider.project}}"
+  default_table_expiration_ms = 3600000
+
+  labels = {
+    env = "dev"
+  }
+  
+}
+
+resource "google_bigquery_dataset_iam_binding" "dataset" {
+  dataset_id = google_bigquery_dataset.example_dataset.dataset_id
+  role       = "roles/bigquery.dataViewer"
+
+  members = [
+    "allUsers","allAuthenticatedUsers"
+  ]
+}
+
+
+
diff --git a/testdata/templates/example_bigquery_dataset_iam_binding.tfplan.json b/testdata/templates/example_bigquery_dataset_iam_binding.tfplan.json
new file mode 100644
index 00000000..d3a1e114
--- /dev/null
+++ b/testdata/templates/example_bigquery_dataset_iam_binding.tfplan.json
@@ -0,0 +1,162 @@
+{
+	"format_version": "0.1",
+	"terraform_version": "0.15.3",
+	"planned_values": {
+		"root_module": {
+			"resources": [{
+				"address": "google_bigquery_dataset.example_dataset",
+				"mode": "managed",
+				"type": "google_bigquery_dataset",
+				"name": "example_dataset",
+				"provider_name": "google",
+				"schema_version": 0,
+				"values": {
+					"dataset_id": "test-dataset",
+					"default_encryption_configuration": [],
+					"default_partition_expiration_ms": null,
+					"default_table_expiration_ms": 3600000,
+					"delete_contents_on_destroy": false,
+					"description": null,
+					"friendly_name": null,
+					"labels": {
+						"env": "dev"
+					},
+					"location": "EU",
+					"project": "{{.Provider.project}}",
+					"timeouts": null
+				}
+			}, {
+				"address": "google_bigquery_dataset_iam_binding.reader",
+				"mode": "managed",
+				"type": "google_bigquery_dataset_iam_binding",
+				"name": "reader",
+				"provider_name": "google",
+				"schema_version": 0,
+				"values": {
+					"condition": [],
+					"dataset_id": "test-dataset",
+					"members": ["allAuthenticatedUsers", "allUsers"],
+					"role": "roles/bigquery.dataViewer"
+				}
+			}]
+		}
+	},
+	"resource_changes": [{
+		"address": "google_bigquery_dataset.example_dataset",
+		"mode": "managed",
+		"type": "google_bigquery_dataset",
+		"name": "example_dataset",
+		"provider_name": "google",
+		"change": {
+			"actions": ["create"],
+			"before": null,
+			"after": {
+				"dataset_id": "test-dataset",
+				"default_encryption_configuration": [],
+				"default_partition_expiration_ms": null,
+				"default_table_expiration_ms": 3600000,
+				"delete_contents_on_destroy": false,
+				"description": null,
+				"friendly_name": null,
+				"labels": {
+					"env": "dev"
+				},
+				"location": "EU",
+				"project": "{{.Provider.project}}",
+				"timeouts": null
+			},
+			"after_unknown": {
+				"access": true,
+				"creation_time": true,
+				"default_encryption_configuration": [],
+				"etag": true,
+				"id": true,
+				"labels": {},
+				"last_modified_time": true,
+				"self_link": true
+			}
+		}
+	}, {
+		"address": "google_bigquery_dataset_iam_binding.reader",
+		"mode": "managed",
+		"type": "google_bigquery_dataset_iam_binding",
+		"name": "reader",
+		"provider_name": "google",
+		"change": {
+			"actions": ["create"],
+			"before": null,
+			"after": {
+				"condition": [],
+				"dataset_id": "test-dataset",
+				"members": ["allAuthenticatedUsers", "allUsers"],
+				"role": "roles/bigquery.dataViewer"
+			},
+			"after_unknown": {
+				"condition": [],
+				"etag": true,
+				"id": true,
+				"members": [false, false],
+				"project": true
+			}
+		}
+	}],
+	"configuration": {
+		"provider_config": {
+			"google": {
+				"name": "google",
+				"expressions": {
+					"project": {
+						"constant_value": "{{.Provider.project}}"
+					}
+				}
+			}
+		},
+		"root_module": {
+			"resources": [{
+				"address": "google_bigquery_dataset.example_dataset",
+				"mode": "managed",
+				"type": "google_bigquery_dataset",
+				"name": "example_dataset",
+				"provider_config_key": "google",
+				"expressions": {
+					"dataset_id": {
+						"constant_value": "test-dataset"
+					},
+					"default_table_expiration_ms": {
+						"constant_value": 3600000
+					},
+					"labels": {
+						"constant_value": {
+							"env": "dev"
+						}
+					},
+					"location": {
+						"constant_value": "EU"
+					},
+					"project": {
+						"constant_value": "{{.Provider.project}}"
+					}
+				},
+				"schema_version": 0
+			}, {
+				"address": "google_bigquery_dataset_iam_binding.reader",
+				"mode": "managed",
+				"type": "google_bigquery_dataset_iam_binding",
+				"name": "reader",
+				"provider_config_key": "google",
+				"expressions": {
+					"dataset_id": {
+						"references": ["google_bigquery_dataset.example_dataset"]
+					},
+					"members": {
+						"constant_value": ["allUsers", "allAuthenticatedUsers"]
+					},
+					"role": {
+						"constant_value": "roles/bigquery.dataViewer"
+					}
+				},
+				"schema_version": 0
+			}]
+		}
+	}
+}
\ No newline at end of file
diff --git a/testdata/templates/example_bigquery_dataset_iam_member.json b/testdata/templates/example_bigquery_dataset_iam_member.json
new file mode 100644
index 00000000..5dce14fe
--- /dev/null
+++ b/testdata/templates/example_bigquery_dataset_iam_member.json
@@ -0,0 +1,31 @@
+[{
+	"name": "//bigquery.googleapis.com/projects/{{.Provider.project}}/datasets/test-dataset",
+	"asset_type": "bigquery.googleapis.com/Dataset",
+	"ancestry_path": "{{.Ancestry}}/project/{{.Provider.project}}",
+	"resource": {
+		"version": "v2",
+		"discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/bigquery/v2/rest",
+		"discovery_name": "Dataset",
+		"parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}",
+		"data": {
+			"datasetReference": {
+				"datasetId": "test-dataset"
+			},
+			"defaultTableExpirationMs": 3600000,
+			"labels": {
+				"env": "dev"
+			},
+			"location": "EU"
+		}
+	}
+}, {
+	"name": "//bigquery.googleapis.com/test-dataset",
+	"asset_type": "bigquery.googleapis.com/Dataset",
+	"ancestry_path": "{{.Ancestry}}/project/{{.Provider.project}}",
+	"iam_policy": {
+		"bindings": [{
+			"role": "roles/bigquery.dataEditor",
+			"members": ["allAuthenticatedUsers"]
+		}]
+	}
+}]
\ No newline at end of file
diff --git a/testdata/templates/example_bigquery_dataset_iam_member.tf b/testdata/templates/example_bigquery_dataset_iam_member.tf
new file mode 100644
index 00000000..a3626628
--- /dev/null
+++ b/testdata/templates/example_bigquery_dataset_iam_member.tf
@@ -0,0 +1,50 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+terraform {
+  required_providers {
+    google = {
+      source = "hashicorp/google"
+      version = "~> {{.Provider.version}}"
+    }
+  }
+}
+
+provider "google" {
+  {{if .Provider.credentials }}credentials = "{{.Provider.credentials}}"{{end}}
+}
+
+resource "google_bigquery_dataset" "example_dataset" {
+  dataset_id                  = "test-dataset"
+  location                    = "EU"
+  project                     = "{{.Provider.project}}"
+  default_table_expiration_ms = 3600000
+
+  labels = {
+    env = "dev"
+  }
+  
+}
+
+resource "google_bigquery_dataset_iam_member" "dataset" {
+  dataset_id = google_bigquery_dataset.example_dataset.dataset_id
+  role       = "roles/bigquery.dataEditor"
+  member     = "allAuthenticatedUsers"
+    
+}
+
+
+
diff --git a/testdata/templates/example_bigquery_dataset_iam_member.tfplan.json b/testdata/templates/example_bigquery_dataset_iam_member.tfplan.json
new file mode 100644
index 00000000..5848f267
--- /dev/null
+++ b/testdata/templates/example_bigquery_dataset_iam_member.tfplan.json
@@ -0,0 +1,161 @@
+{
+	"format_version": "0.1",
+	"terraform_version": "0.15.3",
+	"planned_values": {
+		"root_module": {
+			"resources": [{
+				"address": "google_bigquery_dataset.example_dataset",
+				"mode": "managed",
+				"type": "google_bigquery_dataset",
+				"name": "example_dataset",
+				"provider_name": "google",
+				"schema_version": 0,
+				"values": {
+					"dataset_id": "test-dataset",
+					"default_encryption_configuration": [],
+					"default_partition_expiration_ms": null,
+					"default_table_expiration_ms": 3600000,
+					"delete_contents_on_destroy": false,
+					"description": null,
+					"friendly_name": null,
+					"labels": {
+						"env": "dev"
+					},
+					"location": "EU",
+					"project": "{{.Provider.project}}",
+					"timeouts": null
+				}
+			}, {
+				"address": "google_bigquery_dataset_iam_member.dataset",
+				"mode": "managed",
+				"type": "google_bigquery_dataset_iam_member",
+				"name": "dataset",
+				"provider_name": "google",
+				"schema_version": 0,
+				"values": {
+					"condition": [],
+					"dataset_id": "test-dataset",
+					"member": "allAuthenticatedUsers",
+					"role": "roles/bigquery.dataEditor"
+				}
+			}]
+		}
+	},
+	"resource_changes": [{
+		"address": "google_bigquery_dataset.example_dataset",
+		"mode": "managed",
+		"type": "google_bigquery_dataset",
+		"name": "example_dataset",
+		"provider_name": "google",
+		"change": {
+			"actions": ["create"],
+			"before": null,
+			"after": {
+				"dataset_id": "test-dataset",
+				"default_encryption_configuration": [],
+				"default_partition_expiration_ms": null,
+				"default_table_expiration_ms": 3600000,
+				"delete_contents_on_destroy": false,
+				"description": null,
+				"friendly_name": null,
+				"labels": {
+					"env": "dev"
+				},
+				"location": "EU",
+				"project": "{{.Provider.project}}",
+				"timeouts": null
+			},
+			"after_unknown": {
+				"access": true,
+				"creation_time": true,
+				"default_encryption_configuration": [],
+				"etag": true,
+				"id": true,
+				"labels": {},
+				"last_modified_time": true,
+				"self_link": true
+			}
+		}
+	}, {
+		"address": "google_bigquery_dataset_iam_member.dataset",
+		"mode": "managed",
+		"type": "google_bigquery_dataset_iam_member",
+		"name": "dataset",
+		"provider_name": "google",
+		"change": {
+			"actions": ["create"],
+			"before": null,
+			"after": {
+				"condition": [],
+				"dataset_id": "test-dataset",
+				"member": "allAuthenticatedUsers",
+				"role": "roles/bigquery.dataEditor"
+			},
+			"after_unknown": {
+				"condition": [],
+				"etag": true,
+				"id": true,
+				"project": true
+			}
+		}
+	}],
+	"configuration": {
+		"provider_config": {
+			"google": {
+				"name": "google",
+				"expressions": {
+					"project": {
+						"constant_value": "{{.Provider.project}}"
+					}
+				}
+			}
+		},
+		"root_module": {
+			"resources": [{
+				"address": "google_bigquery_dataset.example_dataset",
+				"mode": "managed",
+				"type": "google_bigquery_dataset",
+				"name": "example_dataset",
+				"provider_config_key": "google",
+				"expressions": {
+					"dataset_id": {
+						"constant_value": "test-dataset"
+					},
+					"default_table_expiration_ms": {
+						"constant_value": 3600000
+					},
+					"labels": {
+						"constant_value": {
+							"env": "dev"
+						}
+					},
+					"location": {
+						"constant_value": "EU"
+					},
+					"project": {
+						"constant_value": "{{.Provider.project}}"
+					}
+				},
+				"schema_version": 0
+			}, {
+				"address": "google_bigquery_dataset_iam_member.dataset",
+				"mode": "managed",
+				"type": "google_bigquery_dataset_iam_member",
+				"name": "dataset",
+				"provider_config_key": "google",
+				"expressions": {
+					"dataset_id": {
+						"references": ["google_bigquery_dataset.example_dataset"]
+					},
+					"member": {
+						"constant_value": "allAuthenticatedUsers"
+					},
+					"role": {
+						"constant_value": "roles/bigquery.dataEditor"
+					}
+				},
+				"schema_version": 0
+			}]
+		}
+	}
+}
\ No newline at end of file
diff --git a/testdata/templates/example_bigquery_dataset_iam_policy.json b/testdata/templates/example_bigquery_dataset_iam_policy.json
new file mode 100644
index 00000000..0f4f658a
--- /dev/null
+++ b/testdata/templates/example_bigquery_dataset_iam_policy.json
@@ -0,0 +1,31 @@
+[{
+	"name": "//bigquery.googleapis.com/projects/{{.Provider.project}}/datasets/test-dataset",
+	"asset_type": "bigquery.googleapis.com/Dataset",
+	"ancestry_path": "{{.Ancestry}}/project/{{.Provider.project}}",
+	"resource": {
+		"version": "v2",
+		"discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/bigquery/v2/rest",
+		"discovery_name": "Dataset",
+		"parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}",
+		"data": {
+			"datasetReference": {
+				"datasetId": "test-dataset"
+			},
+			"defaultTableExpirationMs": 3600000,
+			"labels": {
+				"env": "dev"
+			},
+			"location": "EU"
+		}
+	}
+}, {
+	"name": "//bigquery.googleapis.com/test-dataset",
+	"asset_type": "bigquery.googleapis.com/Dataset",
+	"ancestry_path": "{{.Ancestry}}/project/{{.Provider.project}}",
+	"iam_policy": {
+		"bindings": [{
+			"role": "roles/editor",
+			"members": ["serviceAccount:998476993360@cloudservices.gserviceaccount.com", "allAuthenticatedUsers"]
+		}]
+	}
+}]
\ No newline at end of file
diff --git a/testdata/templates/example_bigquery_dataset_iam_policy.tf b/testdata/templates/example_bigquery_dataset_iam_policy.tf
new file mode 100644
index 00000000..73e61c45
--- /dev/null
+++ b/testdata/templates/example_bigquery_dataset_iam_policy.tf
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+terraform {
+  required_providers {
+    google = {
+      source = "hashicorp/google"
+      version = "~> {{.Provider.version}}"
+    }
+  }
+}
+
+provider "google" {
+  {{if .Provider.credentials }}credentials = "{{.Provider.credentials}}"{{end}}
+}
+
+resource "google_bigquery_dataset" "example-dataset" {
+  dataset_id                  = "test-dataset"
+  location                    = "EU"
+  project                     = "{{.Provider.project}}"
+  default_table_expiration_ms = 3600000
+
+  labels = {
+    env = "dev"
+  }
+  
+}
+
+resource "google_bigquery_dataset_iam_policy" "dataset" {
+  dataset_id  = google_bigquery_dataset.example-dataset.dataset_id
+  policy_data = "{\"bindings\":[{\"members\":[\"serviceAccount:998476993360@cloudservices.gserviceaccount.com\",\"allAuthenticatedUsers\"],\"role\":\"roles/editor\"}]}"
+}
diff --git a/testdata/templates/example_bigquery_dataset_iam_policy.tfplan.json b/testdata/templates/example_bigquery_dataset_iam_policy.tfplan.json
new file mode 100644
index 00000000..6a19eeda
--- /dev/null
+++ b/testdata/templates/example_bigquery_dataset_iam_policy.tfplan.json
@@ -0,0 +1,153 @@
+{
+	"format_version": "0.1",
+	"terraform_version": "0.15.3",
+	"planned_values": {
+		"root_module": {
+			"resources": [{
+				"address": "google_bigquery_dataset.example-dataset",
+				"mode": "managed",
+				"type": "google_bigquery_dataset",
+				"name": "example-dataset",
+				"provider_name": "google",
+				"schema_version": 0,
+				"values": {
+					"dataset_id": "test-dataset",
+					"default_encryption_configuration": [],
+					"default_partition_expiration_ms": null,
+					"default_table_expiration_ms": 3600000,
+					"delete_contents_on_destroy": false,
+					"description": null,
+					"friendly_name": null,
+					"labels": {
+						"env": "dev"
+					},
+					"location": "EU",
+					"project": "{{.Provider.project}}",
+					"timeouts": null
+				}
+			}, {
+				"address": "google_bigquery_dataset_iam_policy.dataset",
+				"mode": "managed",
+				"type": "google_bigquery_dataset_iam_policy",
+				"name": "dataset",
+				"provider_name": "google",
+				"schema_version": 0,
+				"values": {
+					"dataset_id": "test-dataset",
+					"policy_data": "{\"bindings\":[{\"members\":[\"serviceAccount:998476993360@cloudservices.gserviceaccount.com\",\"allAuthenticatedUsers\"],\"role\":\"roles/editor\"}]}"
+				}
+			}]
+		}
+	},
+	"resource_changes": [{
+		"address": "google_bigquery_dataset.example-dataset",
+		"mode": "managed",
+		"type": "google_bigquery_dataset",
+		"name": "example-dataset",
+		"provider_name": "google",
+		"change": {
+			"actions": ["create"],
+			"before": null,
+			"after": {
+				"dataset_id": "test-dataset",
+				"default_encryption_configuration": [],
+				"default_partition_expiration_ms": null,
+				"default_table_expiration_ms": 3600000,
+				"delete_contents_on_destroy": false,
+				"description": null,
+				"friendly_name": null,
+				"labels": {
+					"env": "dev"
+				},
+				"location": "EU",
+				"project": "{{.Provider.project}}",
+				"timeouts": null
+			},
+			"after_unknown": {
+				"access": true,
+				"creation_time": true,
+				"default_encryption_configuration": [],
+				"etag": true,
+				"id": true,
+				"labels": {},
+				"last_modified_time": true,
+				"self_link": true
+			}
+		}
+	}, {
+		"address": "google_bigquery_dataset_iam_policy.dataset",
+		"mode": "managed",
+		"type": "google_bigquery_dataset_iam_policy",
+		"name": "dataset",
+		"provider_name": "google",
+		"change": {
+			"actions": ["create"],
+			"before": null,
+			"after": {
+				"dataset_id": "test-dataset",
+				"policy_data": "{\"bindings\":[{\"members\":[\"serviceAccount:998476993360@cloudservices.gserviceaccount.com\",\"allAuthenticatedUsers\"],\"role\":\"roles/editor\"}]}"
+			},
+			"after_unknown": {
+				"etag": true,
+				"id": true,
+				"project": true
+			}
+		}
+	}],
+	"configuration": {
+		"provider_config": {
+			"google": {
+				"name": "google",
+				"expressions": {
+					"project": {
+						"constant_value": "{{.Provider.project}}"
+					}
+				}
+			}
+		},
+		"root_module": {
+			"resources": [{
+				"address": "google_bigquery_dataset.example-dataset",
+				"mode": "managed",
+				"type": "google_bigquery_dataset",
+				"name": "example-dataset",
+				"provider_config_key": "google",
+				"expressions": {
+					"dataset_id": {
+						"constant_value": "test-dataset"
+					},
+					"default_table_expiration_ms": {
+						"constant_value": 3600000
+					},
+					"labels": {
+						"constant_value": {
+							"env": "dev"
+						}
+					},
+					"location": {
+						"constant_value": "EU"
+					},
+					"project": {
+						"constant_value": "{{.Provider.project}}"
+					}
+				},
+				"schema_version": 0
+			}, {
+				"address": "google_bigquery_dataset_iam_policy.dataset",
+				"mode": "managed",
+				"type": "google_bigquery_dataset_iam_policy",
+				"name": "dataset",
+				"provider_config_key": "google",
+				"expressions": {
+					"dataset_id": {
+						"references": ["google_bigquery_dataset.example-dataset"]
+					},
+					"policy_data": {
+						"constant_value": "{\"bindings\":[{\"members\":[\"serviceAccount:998476993360@cloudservices.gserviceaccount.com\",\"allAuthenticatedUsers\"],\"role\":\"roles/editor\"}]}"
+					}
+				},
+				"schema_version": 0
+			}]
+		}
+	}
+}
\ No newline at end of file
diff --git a/testdata/templates/example_compute_disk_empty_image.json b/testdata/templates/example_compute_disk_empty_image.json
new file mode 100644
index 00000000..b5c8a9ab
--- /dev/null
+++ b/testdata/templates/example_compute_disk_empty_image.json
@@ -0,0 +1,22 @@
+[
+  {
+    "name": "//compute.googleapis.com/projects/{{.Provider.project}}/zones/us-central1-a/disks/test-disk",
+    "asset_type": "compute.googleapis.com/Disk",
+    "ancestry_path": "{{.Ancestry}}/project/{{.Provider.project}}",
+    "resource": {
+      "version": "v1",
+      "discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/compute/v1/rest",
+      "discovery_name": "Disk",
+      "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}",
+      "data": {
+        "labels": {
+          "environment": "dev"
+        },
+        "name": "test-disk",
+        "physicalBlockSizeBytes": 4096,
+        "type": "projects/{{.Provider.project}}/zones/us-central1-a/diskTypes/pd-ssd",
+        "zone": "projects/{{.Provider.project}}/global/zones/us-central1-a"
+      }
+    }
+  }
+]
diff --git a/testdata/templates/example_compute_disk_empty_image.tf b/testdata/templates/example_compute_disk_empty_image.tf
new file mode 100644
index 00000000..3168f98a
--- /dev/null
+++ b/testdata/templates/example_compute_disk_empty_image.tf
@@ -0,0 +1,39 @@
+/**
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+terraform {
+  required_providers {
+    google = {
+      source = "hashicorp/google"
+      version = "~> {{.Provider.version}}"
+    }
+  }
+}
+
+provider "google" {
+  {{if .Provider.credentials }}credentials = "{{.Provider.credentials}}"{{end}}
+}
+
+resource "google_compute_disk" "default" {
+  name  = "test-disk"
+  type  = "pd-ssd"
+  zone  = "us-central1-a"
+  image = ""
+  labels = {
+    environment = "dev"
+  }
+  physical_block_size_bytes = 4096
+}
diff --git a/testdata/templates/example_compute_disk_empty_image.tfplan.json b/testdata/templates/example_compute_disk_empty_image.tfplan.json
new file mode 100644
index 00000000..e44bdfa8
--- /dev/null
+++ b/testdata/templates/example_compute_disk_empty_image.tfplan.json
@@ -0,0 +1,131 @@
+{
+  "format_version": "0.1",
+  "terraform_version": "0.12.10",
+  "planned_values": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_compute_disk.default",
+          "mode": "managed",
+          "type": "google_compute_disk",
+          "name": "default",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "description": null,
+            "disk_encryption_key": [],
+            "disk_encryption_key_raw": null,
+            "image": "",
+            "labels": {
+              "environment": "dev"
+            },
+            "name": "test-disk",
+            "physical_block_size_bytes": 4096,
+            "snapshot": null,
+            "source_image_encryption_key": [],
+            "source_snapshot_encryption_key": [],
+            "timeouts": null,
+            "type": "pd-ssd",
+            "zone": "us-central1-a"
+          }
+        }
+      ]
+    }
+  },
+  "resource_changes": [
+    {
+      "address": "google_compute_disk.default",
+      "mode": "managed",
+      "type": "google_compute_disk",
+      "name": "default",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "description": null,
+          "disk_encryption_key": [],
+          "disk_encryption_key_raw": null,
+          "image": "",
+          "labels": {
+            "environment": "dev"
+          },
+          "name": "test-disk",
+          "physical_block_size_bytes": 4096,
+          "snapshot": null,
+          "source_image_encryption_key": [],
+          "source_snapshot_encryption_key": [],
+          "timeouts": null,
+          "type": "pd-ssd",
+          "zone": "us-central1-a"
+        },
+        "after_unknown": {
+          "creation_timestamp": true,
+          "disk_encryption_key": [],
+          "disk_encryption_key_sha256": true,
+          "id": true,
+          "label_fingerprint": true,
+          "labels": {},
+          "last_attach_timestamp": true,
+          "last_detach_timestamp": true,
+          "project": true,
+          "self_link": true,
+          "size": true,
+          "source_image_encryption_key": [],
+          "source_image_id": true,
+          "source_snapshot_encryption_key": [],
+          "source_snapshot_id": true,
+          "users": true
+        }
+      }
+    }
+  ],
+  "configuration": {
+    "provider_config": {
+      "google": {
+        "name": "google",
+        "expressions": {
+          "project": {
+            "constant_value": "{{.Provider.project}}"
+          }
+        }
+      }
+    },
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_compute_disk.default",
+          "mode": "managed",
+          "type": "google_compute_disk",
+          "name": "default",
+          "provider_config_key": "google",
+          "expressions": {
+            "image": {
+              "constant_value": ""
+            },
+            "labels": {
+              "constant_value": {
+                "environment": "dev"
+              }
+            },
+            "name": {
+              "constant_value": "test-disk"
+            },
+            "physical_block_size_bytes": {
+              "constant_value": 4096
+            },
+            "type": {
+              "constant_value": "pd-ssd"
+            },
+            "zone": {
+              "constant_value": "us-central1-a"
+            }
+          },
+          "schema_version": 0
+        }
+      ]
+    }
+  }
+}
diff --git a/testdata/templates/example_compute_instance.json b/testdata/templates/example_compute_instance.json
index 5d6f4af7..c110196e 100644
--- a/testdata/templates/example_compute_instance.json
+++ b/testdata/templates/example_compute_instance.json
@@ -10,14 +10,6 @@
       "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}",
       "data": {
         "canIpForward": false,
-        "displayDevice": {
-          "enableDisplay": false
-        },
-        "shieldedInstanceConfig": {
-          "enableIntegrityMonitoring": false,
-          "enableSecureBoot": false,
-          "enableVtpm": false
-        },
         "deletionProtection": false,
         "disks": [
           {
diff --git a/testdata/templates/example_project_in_folder.json b/testdata/templates/example_project_in_folder.json
index 83ef3dc4..1dc2440e 100644
--- a/testdata/templates/example_project_in_folder.json
+++ b/testdata/templates/example_project_in_folder.json
@@ -1,20 +1,4 @@
 [
-    {
-      "name": "//cloudbilling.googleapis.com/projects/foobat/billingInfo",
-      "asset_type": "cloudbilling.googleapis.com/ProjectBillingInfo",
-      "ancestry_path": "organization/unknown/folder/{{.FolderID}}/project/foobat",
-      "resource": {
-        "version": "v1",
-        "discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/cloudbilling/v1/rest",
-        "discovery_name": "ProjectBillingInfo",
-        "parent": "//cloudresourcemanager.googleapis.com/projects/foobat",
-        "data": {
-          "billingAccountName": "billingAccounts/",
-          "name": "projects/foobat/billingInfo",
-          "projectId": "foobat"
-        }
-      }
-    },
     {
       "name": "//cloudresourcemanager.googleapis.com/projects/foobat",
       "asset_type": "cloudresourcemanager.googleapis.com/Project",
diff --git a/testdata/templates/example_project_in_org.json b/testdata/templates/example_project_in_org.json
index ceadcc00..39a72793 100644
--- a/testdata/templates/example_project_in_org.json
+++ b/testdata/templates/example_project_in_org.json
@@ -1,20 +1,4 @@
 [
-    {
-      "name": "//cloudbilling.googleapis.com/projects/foobat/billingInfo",
-      "asset_type": "cloudbilling.googleapis.com/ProjectBillingInfo",
-      "ancestry_path": "organization/{{.OrgID}}/project/foobat",
-      "resource": {
-        "version": "v1",
-        "discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/cloudbilling/v1/rest",
-        "discovery_name": "ProjectBillingInfo",
-        "parent": "//cloudresourcemanager.googleapis.com/projects/foobat",
-        "data": {
-          "billingAccountName": "billingAccounts/",
-          "name": "projects/foobat/billingInfo",
-          "projectId": "foobat"
-        }
-      }
-    },
     {
       "name": "//cloudresourcemanager.googleapis.com/projects/foobat",
       "asset_type": "cloudresourcemanager.googleapis.com/Project",
diff --git a/testdata/templates/example_storage_bucket.json b/testdata/templates/example_storage_bucket.json
index db11b993..6086117f 100644
--- a/testdata/templates/example_storage_bucket.json
+++ b/testdata/templates/example_storage_bucket.json
@@ -9,13 +9,14 @@
       "discovery_name": "Bucket",
       "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}",
       "data": {
-        "billing": {},
         "iamConfiguration": {
           "uniformBucketLevelAccess": {
             "enabled": false
           }
         },
-        "lifecycle": {},
+        "lifecycle": {
+          "rule": []
+        },
         "location": "EU",
         "name": "image-store-bucket",
         "project": "{{.Provider.project}}",
diff --git a/testdata/templates/example_storage_bucket_iam_binding.json b/testdata/templates/example_storage_bucket_iam_binding.json
index 4f948393..0ff50ed7 100644
--- a/testdata/templates/example_storage_bucket_iam_binding.json
+++ b/testdata/templates/example_storage_bucket_iam_binding.json
@@ -9,13 +9,14 @@
       "discovery_name": "Bucket",
       "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}",
       "data": {
-        "billing": {},
         "iamConfiguration": {
           "uniformBucketLevelAccess": {
             "enabled": true
           }
         },
-        "lifecycle": {},
+        "lifecycle": {
+          "rule": []
+        },
         "location": "EU",
         "name": "fake-bucket-123456",
         "project": "{{.Provider.project}}",
diff --git a/testdata/templates/example_storage_bucket_iam_member.json b/testdata/templates/example_storage_bucket_iam_member.json
index 4f948393..0ff50ed7 100644
--- a/testdata/templates/example_storage_bucket_iam_member.json
+++ b/testdata/templates/example_storage_bucket_iam_member.json
@@ -9,13 +9,14 @@
       "discovery_name": "Bucket",
       "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}",
       "data": {
-        "billing": {},
         "iamConfiguration": {
           "uniformBucketLevelAccess": {
             "enabled": true
           }
         },
-        "lifecycle": {},
+        "lifecycle": {
+          "rule": []
+        },
         "location": "EU",
         "name": "fake-bucket-123456",
         "project": "{{.Provider.project}}",
diff --git a/testdata/templates/example_storage_bucket_iam_member_random_suffix.json b/testdata/templates/example_storage_bucket_iam_member_random_suffix.json
index 2038511e..26979b0e 100644
--- a/testdata/templates/example_storage_bucket_iam_member_random_suffix.json
+++ b/testdata/templates/example_storage_bucket_iam_member_random_suffix.json
@@ -9,13 +9,14 @@
       "discovery_name": "Bucket",
       "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}",
       "data": {
-        "billing": {},
         "iamConfiguration": {
           "uniformBucketLevelAccess": {
             "enabled": true
           }
         },
-        "lifecycle": {},
+        "lifecycle": {
+          "rule": []
+        },
         "location": "EU",
         "project": "{{.Provider.project}}",
         "storageClass": "STANDARD"
diff --git a/testdata/templates/example_storage_bucket_iam_policy.json b/testdata/templates/example_storage_bucket_iam_policy.json
index 4f948393..0ff50ed7 100644
--- a/testdata/templates/example_storage_bucket_iam_policy.json
+++ b/testdata/templates/example_storage_bucket_iam_policy.json
@@ -9,13 +9,14 @@
       "discovery_name": "Bucket",
       "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}",
       "data": {
-        "billing": {},
         "iamConfiguration": {
           "uniformBucketLevelAccess": {
             "enabled": true
           }
         },
-        "lifecycle": {},
+        "lifecycle": {
+          "rule": []
+        },
         "location": "EU",
         "name": "fake-bucket-123456",
         "project": "{{.Provider.project}}",
diff --git a/testdata/templates/full_compute_instance.json b/testdata/templates/full_compute_instance.json
index 82343f72..a2a8ea85 100644
--- a/testdata/templates/full_compute_instance.json
+++ b/testdata/templates/full_compute_instance.json
@@ -10,9 +10,6 @@
       "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}",
       "data": {
         "canIpForward": true,
-        "displayDevice": {
-          "enableDisplay": false
-        },
         "shieldedInstanceConfig": {
           "enableIntegrityMonitoring": true,
           "enableSecureBoot": true,
@@ -153,14 +150,6 @@
       "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}",
       "data": {
         "canIpForward": false,
-        "displayDevice": {
-          "enableDisplay": false
-        },
-        "shieldedInstanceConfig": {
-          "enableIntegrityMonitoring": false,
-          "enableSecureBoot": false,
-          "enableVtpm": false
-        },
         "deletionProtection": false,
         "disks": [
           {
diff --git a/testdata/templates/instance.json b/testdata/templates/instance.json
index b1d0a75e..a5f3f22c 100644
--- a/testdata/templates/instance.json
+++ b/testdata/templates/instance.json
@@ -10,14 +10,6 @@
       "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}",
       "data": {
         "canIpForward": false,
-        "displayDevice": {
-          "enableDisplay": false
-        },
-        "shieldedInstanceConfig": {
-          "enableIntegrityMonitoring": false,
-          "enableSecureBoot": false,
-          "enableVtpm": false
-        },
         "deletionProtection": false,
         "disks": [
           {