Repository: GoogleCloudPlatform/terraform-validator (go)

Issue:
Error: converting tfplan to CAI assets: .. Error resolving image name ''"
**What do I see?**
When running a policy validate on our terraform, we get the following error.

`Error: converting tfplan to CAI assets: converting resource google_compute_disk: converting asset: Error resolving image name '': Error checking if image exists: json: invalid use of ,string struct tag, trying to unmarshal "projects/acmck-02-sandbox/global/images/" into uint64`

**What do I expect**
The policy validation  to output a violation(s) or report "No Violations".

**Reproduce**
Attach a disk to GCE instance with no image defined

```
resource "google_compute_disk" "bastion-disk" {
  name = "${var.name}-disk"
  type = "pd-ssd"
  zone = "us-central1-a"
  size = 100
}
```

and attach inside the instance resource:

```
    attached_disk {
      source      = google_compute_disk.bastion-disk.self_link
  }
```

**versions**

```
bash-5.0# terraform version
Terraform v0.12.16
+ provider.google v2.17.0
+ provider.google-beta v2.20.0
+ provider.kubernetes v1.10.0
+ provider.null v2.1.2
+ provider.random v2.2.1

Your version of Terraform is out of date! The latest version
is 0.12.17. You can update by downloading from https://www.terraform.io/downloads.html

bash-5.0# terraform-validator version
Build version: 2019-11-07
```
Happens on alpine or debain base image based containers.

```
bash-5.0# uname -a
Linux 9a1661522e53 4.9.184-linuxkit #1 SMP Tue Jul 2 22:58:16 UTC 2019 x86_64 Linux
```




Pull request description:
Fixed GetOk / GetOkExists behavior
Resolved #113. Made FakeResourceData behave more consistently with Terraform core in terms of the values it produces. This ensures that expanders written with Terraform [ResourceData](https://github.com/hashicorp/terraform-plugin-sdk/blob/main/helper/schema/resource_data.go) structs in mind behave properly in TFV as well.

Some test data had to be updated due to the code now properly excluding empty data.

I'm sorry for the long PR; most of it is new tests.

Patch:
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/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/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
+}


Test patch:
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..89777e19 100644
--- a/test/cli_test.go
+++ b/test/cli_test.go
@@ -63,6 +63,7 @@ func TestCLI(t *testing.T) {
 		{name: "example_bigquery_dataset"},
 		{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..7e350f76 100644
--- a/test/read_test.go
+++ b/test/read_test.go
@@ -24,6 +24,7 @@ func TestReadPlannedAssetsCoverage(t *testing.T) {
 		{name: "example_bigquery_dataset"},
 		{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 +105,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_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": [
           {