Repository: GoogleCloudPlatform/terraform-validator
Pull request #88 (2019-11-04 07:37:24)

add unit tests for ReadPlannedAssets based on examples from Terraform Google Provider
The purpose of the the test is to uncover potential issues on the supported Terraform configuration, and to allow adding new configuration comes with coverage. This can be also used as a way to create regression tests on runtime error bugs like https://github.com/GoogleCloudPlatform/terraform-validator/issues/81 or feature request validation like https://github.com/GoogleCloudPlatform/terraform-validator/issues/53.  

In this pull requests, 12 test cases are constructed using examples from Terraform Google Provider:

```
google_compute_disk
google_compute_firewall
google_compute_instance
google_organization_iam_binding
google_organization_iam_member
google_organization_iam_policy
google_project
google_project_iam_binding
google_project_iam_member
google_project_iam_policy
google_sql_database_instance
google_storage_bucket
```

I haven't added the google_folder_* configuration as test cases because the folder expansion logic is not working (https://github.com/GoogleCloudPlatform/terraform-validator/issues/86).

The tests also unveiled that the disk image path resolving logic is not working correctly unless the image is provided in the form of `projects/<project>/global/images/<image>` or `global/images/<image>`. I rewrite the example configuration to use this image format in `google_compute_disk` and `google_compute_instance`.

I have also written a few more test cases with more comprehensive coverage. Those may need more time from reviewers to validate if the resultant assets is in the expected form, thus they will be provided in other pull requests after this one get through.



Patch:
diff --git a/go.mod b/go.mod
index 86514b3f..3d64a796 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ require (
 	github.com/forseti-security/config-validator v0.0.0-20190619160113-b8fae8e864e3
 	github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
 	github.com/golang/protobuf v1.3.2
+	github.com/google/go-cmp v0.3.0
 	github.com/hashicorp/hil v0.0.0-20190212132231-97b3a9cdfa93 // indirect
 	github.com/hashicorp/terraform-plugin-sdk v1.2.0
 	github.com/hashicorp/vault v1.0.2 // indirect


Test patch:
diff --git a/tfgcv/planned_assets_test.go b/tfgcv/planned_assets_test.go
index f841ecc9..47dd52ea 100644
--- a/tfgcv/planned_assets_test.go
+++ b/tfgcv/planned_assets_test.go
@@ -1,10 +1,16 @@
 package tfgcv
 
 import (
+	"encoding/json"
+	"io/ioutil"
 	"path/filepath"
 	"testing"
 
+	"github.com/GoogleCloudPlatform/terraform-validator/converters/google"
 	"github.com/GoogleCloudPlatform/terraform-validator/version"
+	"github.com/golang/protobuf/proto"
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
 )
 
 const (
@@ -91,3 +97,73 @@ func TestReadPlannedAssets(t *testing.T) {
 		})
 	}
 }
+
+func TestReadPlannedAssetsCoverage(t *testing.T) {
+	if !version.Supported(version.TF12) {
+		t.Skipf("TestReadPlannedAssetsCoverage runs on terraform v0.12 or above.")
+	}
+
+	testdata := "testdata"
+	defaultProject := "foobar"
+	defaultAncestry := "organization/12345/folder/67890"
+	cases := []struct {
+		name     string
+	}{
+		{name: "example_compute_disk"},
+		{name: "example_compute_firewall"},
+		{name: "example_compute_instance"},
+		{name: "example_organization_iam_binding"},
+		{name: "example_organization_iam_member"},
+		{name: "example_organization_iam_policy"},
+		{name: "example_project"},
+		{name: "example_project_iam_binding"},
+		{name: "example_project_iam_member"},
+		{name: "example_project_iam_policy"},
+		{name: "example_sql_database_instance"},
+		{name: "example_storage_bucket"},
+	}
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			ancestry := defaultAncestry
+			project := defaultProject
+			testPlanFile := filepath.Join(testdata, c.name+"_tfplan.json")
+			wantAssetPath := filepath.Join(testdata, c.name+"_assets.json")
+			wantJSON, err := ioutil.ReadFile(wantAssetPath)
+			if err != nil {
+				t.Fatalf("cannot open %s, got: %s", wantAssetPath, err)
+			}
+			var want []google.Asset
+			if err := json.Unmarshal(wantJSON, &want); err != nil {
+				t.Fatalf("cannot unmarshal JSON into assets: %s", err)
+			}
+			assets, err := ReadPlannedAssets(testPlanFile, project, ancestry, true)
+			if err != nil {
+				t.Fatalf("ReadPlannedAssets(%s, %s, %s, %t): %v", testPlanFile, project, ancestry, true, err)
+			}
+			got := remarshal(t, assets)
+			if diff := cmp.Diff(want, got, cmp.Comparer(proto.Equal), cmpopts.IgnoreUnexported(google.Asset{})); diff != "" {
+				t.Errorf("ReadPlannedAssets(%s, %s, %s, %t) returned diff (-want +got):\n%s", testPlanFile, project, ancestry, true, diff)
+			}
+		})
+	}
+}
+
+// remarshal runs Marshal and unmarshal to set type correctly.
+//
+// This is necessary as some of the types are interface and
+// the result returned from ShowAssets of a different primitive (float64 vs int).
+// For example, there are cases like float64(1000) is returned but the
+// test fixture in JSON would decode that as int(1000).
+func remarshal(t *testing.T, assets []google.Asset) []google.Asset {
+	t.Helper()
+	var got []google.Asset
+	payload, err := json.Marshal(assets)
+	if err != nil {
+		t.Fatalf("cannot marshal returned assets: %v", err)
+	}
+	if err := json.Unmarshal(payload, &got); err != nil {
+		t.Fatalf("cannot unmarshal returned assets: %v", err)
+	}
+	return got
+}
+
diff --git a/tfgcv/testdata/example_compute_disk.tf b/tfgcv/testdata/example_compute_disk.tf
new file mode 100644
index 00000000..c6028f30
--- /dev/null
+++ b/tfgcv/testdata/example_compute_disk.tf
@@ -0,0 +1,10 @@
+resource "google_compute_disk" "default" {
+  name  = "test-disk"
+  type  = "pd-ssd"
+  zone  = "us-central1-a"
+  image = "projects/debian-cloud/global/images/debian-8-jessie-v20170523"
+  labels = {
+    environment = "dev"
+  }
+  physical_block_size_bytes = 4096
+}
diff --git a/tfgcv/testdata/example_compute_disk_assets.json b/tfgcv/testdata/example_compute_disk_assets.json
new file mode 100644
index 00000000..dae7deb3
--- /dev/null
+++ b/tfgcv/testdata/example_compute_disk_assets.json
@@ -0,0 +1,26 @@
+[
+  {
+    "name": "//compute.googleapis.com/projects/foobar/zones/us-central1-a/disks/test-disk",
+    "asset_type": "compute.googleapis.com/Disk",
+    "ancestry_path": "organization/12345/folder/67890/project/foobar",
+    "resource": {
+      "version": "v1",
+      "discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/compute/v1/rest",
+      "discovery_name": "Disk",
+      "parent": "//cloudresourcemanager.googleapis.com/projects/foobar",
+      "data": {
+        "diskEncryptionKey": null,
+        "labels": {
+          "environment": "dev"
+        },
+        "name": "test-disk",
+        "physicalBlockSizeBytes": 4096,
+        "sourceImage": "projects/debian-cloud/global/images/debian-8-jessie-v20170523",
+        "sourceImageEncryptionKey": null,
+        "sourceSnapshotEncryptionKey": null,
+        "type": "projects/foobar/zones/us-central1-a/diskTypes/pd-ssd",
+        "zone": "projects/foobar/global/zones/us-central1-a"
+      }
+    }
+  }
+]
diff --git a/tfgcv/testdata/example_compute_disk_tfplan.json b/tfgcv/testdata/example_compute_disk_tfplan.json
new file mode 100644
index 00000000..6b10af73
--- /dev/null
+++ b/tfgcv/testdata/example_compute_disk_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": "projects/debian-cloud/global/images/debian-8-jessie-v20170523",
+            "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": "projects/debian-cloud/global/images/debian-8-jessie-v20170523",
+          "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": "foobar"
+          }
+        }
+      }
+    },
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_compute_disk.default",
+          "mode": "managed",
+          "type": "google_compute_disk",
+          "name": "default",
+          "provider_config_key": "google",
+          "expressions": {
+            "image": {
+              "constant_value": "projects/debian-cloud/global/images/debian-8-jessie-v20170523"
+            },
+            "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/tfgcv/testdata/example_compute_firewall.tf b/tfgcv/testdata/example_compute_firewall.tf
new file mode 100644
index 00000000..286c5b6d
--- /dev/null
+++ b/tfgcv/testdata/example_compute_firewall.tf
@@ -0,0 +1,19 @@
+resource "google_compute_firewall" "default" {
+  name    = "test-firewall"
+  network = "${google_compute_network.default.name}"
+
+  allow {
+    protocol = "icmp"
+  }
+
+  allow {
+    protocol = "tcp"
+    ports    = ["80", "8080", "1000-2000"]
+  }
+
+  source_tags = ["web"]
+}
+
+resource "google_compute_network" "default" {
+  name = "test-network"
+}
diff --git a/tfgcv/testdata/example_compute_firewall_assets.json b/tfgcv/testdata/example_compute_firewall_assets.json
new file mode 100644
index 00000000..b1cb00ba
--- /dev/null
+++ b/tfgcv/testdata/example_compute_firewall_assets.json
@@ -0,0 +1,35 @@
+[
+  {
+    "name": "//compute.googleapis.com/projects/foobar/global/firewalls/test-firewall",
+    "asset_type": "compute.googleapis.com/Firewall",
+    "ancestry_path": "organization/12345/folder/67890/project/foobar",
+    "resource": {
+      "version": "v1",
+      "discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/compute/v1/rest",
+      "discovery_name": "Firewall",
+      "parent": "//cloudresourcemanager.googleapis.com/projects/foobar",
+      "data": {
+        "allowed": [
+          {
+            "IPProtocol": "icmp"
+          },
+          {
+            "IPProtocol": "tcp",
+            "ports": [
+              "80",
+              "8080",
+              "1000-2000"
+            ]
+          }
+        ],
+        "disabled": false,
+        "name": "test-firewall",
+        "network": "projects/foobar/global/networks/test-network",
+        "priority": 1000,
+        "sourceTags": [
+          "web"
+        ]
+      }
+    }
+  }
+]
diff --git a/tfgcv/testdata/example_compute_firewall_tfplan.json b/tfgcv/testdata/example_compute_firewall_tfplan.json
new file mode 100644
index 00000000..4aaf726a
--- /dev/null
+++ b/tfgcv/testdata/example_compute_firewall_tfplan.json
@@ -0,0 +1,231 @@
+{
+  "format_version": "0.1",
+  "terraform_version": "0.12.10",
+  "planned_values": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_compute_firewall.default",
+          "mode": "managed",
+          "type": "google_compute_firewall",
+          "name": "default",
+          "provider_name": "google",
+          "schema_version": 1,
+          "values": {
+            "allow": [
+              {
+                "ports": [
+                  "80",
+                  "8080",
+                  "1000-2000"
+                ],
+                "protocol": "tcp"
+              },
+              {
+                "ports": [],
+                "protocol": "icmp"
+              }
+            ],
+            "deny": [],
+            "description": null,
+            "disabled": null,
+            "name": "test-firewall",
+            "network": "test-network",
+            "priority": 1000,
+            "source_service_accounts": null,
+            "source_tags": [
+              "web"
+            ],
+            "target_service_accounts": null,
+            "target_tags": null,
+            "timeouts": null
+          }
+        },
+        {
+          "address": "google_compute_network.default",
+          "mode": "managed",
+          "type": "google_compute_network",
+          "name": "default",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "auto_create_subnetworks": true,
+            "delete_default_routes_on_create": false,
+            "description": null,
+            "ipv4_range": null,
+            "name": "test-network",
+            "timeouts": null
+          }
+        }
+      ]
+    }
+  },
+  "resource_changes": [
+    {
+      "address": "google_compute_firewall.default",
+      "mode": "managed",
+      "type": "google_compute_firewall",
+      "name": "default",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "allow": [
+            {
+              "ports": [
+                "80",
+                "8080",
+                "1000-2000"
+              ],
+              "protocol": "tcp"
+            },
+            {
+              "ports": [],
+              "protocol": "icmp"
+            }
+          ],
+          "deny": [],
+          "description": null,
+          "disabled": null,
+          "name": "test-firewall",
+          "network": "test-network",
+          "priority": 1000,
+          "source_service_accounts": null,
+          "source_tags": [
+            "web"
+          ],
+          "target_service_accounts": null,
+          "target_tags": null,
+          "timeouts": null
+        },
+        "after_unknown": {
+          "allow": [
+            {
+              "ports": [
+                false,
+                false,
+                false
+              ]
+            },
+            {
+              "ports": []
+            }
+          ],
+          "creation_timestamp": true,
+          "deny": [],
+          "destination_ranges": true,
+          "direction": true,
+          "id": true,
+          "project": true,
+          "self_link": true,
+          "source_ranges": true,
+          "source_tags": [
+            false
+          ]
+        }
+      }
+    },
+    {
+      "address": "google_compute_network.default",
+      "mode": "managed",
+      "type": "google_compute_network",
+      "name": "default",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "auto_create_subnetworks": true,
+          "delete_default_routes_on_create": false,
+          "description": null,
+          "ipv4_range": null,
+          "name": "test-network",
+          "timeouts": null
+        },
+        "after_unknown": {
+          "gateway_ipv4": true,
+          "id": true,
+          "project": true,
+          "routing_mode": true,
+          "self_link": true
+        }
+      }
+    }
+  ],
+  "configuration": {
+    "provider_config": {
+      "google": {
+        "name": "google",
+        "expressions": {
+          "project": {
+            "constant_value": "foobar"
+          }
+        }
+      }
+    },
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_compute_firewall.default",
+          "mode": "managed",
+          "type": "google_compute_firewall",
+          "name": "default",
+          "provider_config_key": "google",
+          "expressions": {
+            "allow": [
+              {
+                "protocol": {
+                  "constant_value": "icmp"
+                }
+              },
+              {
+                "ports": {
+                  "constant_value": [
+                    "80",
+                    "8080",
+                    "1000-2000"
+                  ]
+                },
+                "protocol": {
+                  "constant_value": "tcp"
+                }
+              }
+            ],
+            "name": {
+              "constant_value": "test-firewall"
+            },
+            "network": {
+              "references": [
+                "google_compute_network.default"
+              ]
+            },
+            "source_tags": {
+              "constant_value": [
+                "web"
+              ]
+            }
+          },
+          "schema_version": 1
+        },
+        {
+          "address": "google_compute_network.default",
+          "mode": "managed",
+          "type": "google_compute_network",
+          "name": "default",
+          "provider_config_key": "google",
+          "expressions": {
+            "name": {
+              "constant_value": "test-network"
+            }
+          },
+          "schema_version": 0
+        }
+      ]
+    }
+  }
+}
diff --git a/tfgcv/testdata/example_compute_instance.tf b/tfgcv/testdata/example_compute_instance.tf
new file mode 100644
index 00000000..75aceb5c
--- /dev/null
+++ b/tfgcv/testdata/example_compute_instance.tf
@@ -0,0 +1,39 @@
+resource "google_compute_instance" "default" {
+  name         = "test"
+  machine_type = "n1-standard-1"
+  zone         = "us-central1-a"
+
+  tags = ["foo", "bar"]
+
+  boot_disk {
+    initialize_params {
+      image = "projects/debian-cloud/global/images/debian-9"
+    }
+  }
+
+  // Local SSD disk
+  scratch_disk {
+  }
+
+  network_interface {
+    network = "default"
+
+    access_config {
+      // Ephemeral IP
+    }
+  }
+
+  metadata = {
+    foo = "bar"
+  }
+
+  # metadata_startup_script is equivalent to metadata{ startup-script } and
+  # the result is not deterministic as there will be two metadata block inserted
+  # at different time. Thus commenting out.
+  #
+  # metadata_startup_script = "echo hi > /test.txt"
+
+  service_account {
+    scopes = ["userinfo-email", "compute-ro", "storage-ro"]
+  }
+}
diff --git a/tfgcv/testdata/example_compute_instance_assets.json b/tfgcv/testdata/example_compute_instance_assets.json
new file mode 100644
index 00000000..46b15579
--- /dev/null
+++ b/tfgcv/testdata/example_compute_instance_assets.json
@@ -0,0 +1,73 @@
+[
+  {
+    "name": "//compute.googleapis.com/projects/foobar/zones/us-central1-a/instances/test",
+    "asset_type": "compute.googleapis.com/Instance",
+    "ancestry_path": "organization/12345/folder/67890/project/foobar",
+    "resource": {
+      "version": "v1",
+      "discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/compute/v1/rest",
+      "discovery_name": "Instance",
+      "parent": "//cloudresourcemanager.googleapis.com/projects/foobar",
+      "data": {
+        "canIpForward": false,
+        "deletionProtection": false,
+        "disks": [
+          {
+            "autoDelete": true,
+            "boot": true,
+            "initializeParams": {
+              "sourceImage": "projects/debian-cloud/global/images/debian-9"
+            }
+          },
+          {
+            "autoDelete": true,
+            "initializeParams": {
+              "diskType": "projects/foobar/zones/us-central1-a/diskTypes/local-ssd"
+            },
+            "interface": "SCSI",
+            "type": "SCRATCH"
+          }
+        ],
+        "machineType": "projects/foobar/zones/us-central1-a/machineTypes/n1-standard-1",
+        "metadata": {
+          "items": [
+            {
+              "key": "foo",
+              "value": "bar"
+            }
+          ]
+        },
+        "name": "test",
+        "networkInterfaces": [
+          {
+            "accessConfigs": [
+              {
+                "type": "ONE_TO_ONE_NAT"
+              }
+            ],
+            "network": "projects/foobar/global/networks/default"
+          }
+        ],
+        "scheduling": {
+          "automaticRestart": true
+        },
+        "serviceAccounts": [
+          {
+            "email": "default",
+            "scopes": [
+              "https://www.googleapis.com/auth/devstorage.read_only",
+              "https://www.googleapis.com/auth/userinfo.email",
+              "https://www.googleapis.com/auth/compute.readonly"
+            ]
+          }
+        ],
+        "tags": {
+          "items": [
+            "bar",
+            "foo"
+          ]
+        }
+      }
+    }
+  }
+]
diff --git a/tfgcv/testdata/example_compute_instance_tfplan.json b/tfgcv/testdata/example_compute_instance_tfplan.json
new file mode 100644
index 00000000..f539cc62
--- /dev/null
+++ b/tfgcv/testdata/example_compute_instance_tfplan.json
@@ -0,0 +1,299 @@
+{
+  "format_version": "0.1",
+  "terraform_version": "0.12.10",
+  "planned_values": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_compute_instance.default",
+          "mode": "managed",
+          "type": "google_compute_instance",
+          "name": "default",
+          "provider_name": "google",
+          "schema_version": 6,
+          "values": {
+            "allow_stopping_for_update": null,
+            "attached_disk": [],
+            "boot_disk": [
+              {
+                "auto_delete": true,
+                "disk_encryption_key_raw": null,
+                "initialize_params": [
+                  {
+                    "image": "projects/debian-cloud/global/images/debian-9"
+                  }
+                ]
+              }
+            ],
+            "can_ip_forward": false,
+            "deletion_protection": false,
+            "description": null,
+            "disk": [],
+            "hostname": null,
+            "labels": null,
+            "machine_type": "n1-standard-1",
+            "metadata": {
+              "foo": "bar"
+            },
+            "metadata_startup_script": null,
+            "min_cpu_platform": null,
+            "name": "test",
+            "network_interface": [
+              {
+                "access_config": [
+                  {
+                    "public_ptr_domain_name": null
+                  }
+                ],
+                "alias_ip_range": [],
+                "network": "default"
+              }
+            ],
+            "scratch_disk": [
+              {
+                "interface": "SCSI"
+              }
+            ],
+            "service_account": [
+              {
+                "scopes": [
+                  "https://www.googleapis.com/auth/compute.readonly",
+                  "https://www.googleapis.com/auth/devstorage.read_only",
+                  "https://www.googleapis.com/auth/userinfo.email"
+                ]
+              }
+            ],
+            "shielded_instance_config": [],
+            "tags": [
+              "bar",
+              "foo"
+            ],
+            "timeouts": null,
+            "zone": "us-central1-a"
+          }
+        }
+      ]
+    }
+  },
+  "resource_changes": [
+    {
+      "address": "google_compute_instance.default",
+      "mode": "managed",
+      "type": "google_compute_instance",
+      "name": "default",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "allow_stopping_for_update": null,
+          "attached_disk": [],
+          "boot_disk": [
+            {
+              "auto_delete": true,
+              "disk_encryption_key_raw": null,
+              "initialize_params": [
+                {
+                  "image": "projects/debian-cloud/global/images/debian-9"
+                }
+              ]
+            }
+          ],
+          "can_ip_forward": false,
+          "deletion_protection": false,
+          "description": null,
+          "disk": [],
+          "hostname": null,
+          "labels": null,
+          "machine_type": "n1-standard-1",
+          "metadata": {
+            "foo": "bar"
+          },
+          "metadata_startup_script": null,
+          "min_cpu_platform": null,
+          "name": "test",
+          "network_interface": [
+            {
+              "access_config": [
+                {
+                  "public_ptr_domain_name": null
+                }
+              ],
+              "alias_ip_range": [],
+              "network": "default"
+            }
+          ],
+          "scratch_disk": [
+            {
+              "interface": "SCSI"
+            }
+          ],
+          "service_account": [
+            {
+              "scopes": [
+                "https://www.googleapis.com/auth/compute.readonly",
+                "https://www.googleapis.com/auth/devstorage.read_only",
+                "https://www.googleapis.com/auth/userinfo.email"
+              ]
+            }
+          ],
+          "shielded_instance_config": [],
+          "tags": [
+            "bar",
+            "foo"
+          ],
+          "timeouts": null,
+          "zone": "us-central1-a"
+        },
+        "after_unknown": {
+          "attached_disk": [],
+          "boot_disk": [
+            {
+              "device_name": true,
+              "disk_encryption_key_sha256": true,
+              "initialize_params": [
+                {
+                  "labels": true,
+                  "size": true,
+                  "type": true
+                }
+              ],
+              "kms_key_self_link": true,
+              "source": true
+            }
+          ],
+          "cpu_platform": true,
+          "disk": [],
+          "guest_accelerator": true,
+          "id": true,
+          "instance_id": true,
+          "label_fingerprint": true,
+          "metadata": {},
+          "metadata_fingerprint": true,
+          "network_interface": [
+            {
+              "access_config": [
+                {
+                  "assigned_nat_ip": true,
+                  "nat_ip": true,
+                  "network_tier": true
+                }
+              ],
+              "address": true,
+              "alias_ip_range": [],
+              "name": true,
+              "network_ip": true,
+              "subnetwork": true,
+              "subnetwork_project": true
+            }
+          ],
+          "project": true,
+          "scheduling": true,
+          "scratch_disk": [
+            {}
+          ],
+          "self_link": true,
+          "service_account": [
+            {
+              "email": true,
+              "scopes": [
+                false,
+                false,
+                false
+              ]
+            }
+          ],
+          "shielded_instance_config": [],
+          "tags": [
+            false,
+            false
+          ],
+          "tags_fingerprint": true
+        }
+      }
+    }
+  ],
+  "configuration": {
+    "provider_config": {
+      "google": {
+        "name": "google",
+        "expressions": {
+          "project": {
+            "constant_value": "foobar"
+          }
+        }
+      }
+    },
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_compute_instance.default",
+          "mode": "managed",
+          "type": "google_compute_instance",
+          "name": "default",
+          "provider_config_key": "google",
+          "expressions": {
+            "boot_disk": [
+              {
+                "initialize_params": [
+                  {
+                    "image": {
+                      "constant_value": "projects/debian-cloud/global/images/debian-9"
+                    }
+                  }
+                ]
+              }
+            ],
+            "machine_type": {
+              "constant_value": "n1-standard-1"
+            },
+            "metadata": {
+              "constant_value": {
+                "foo": "bar"
+              }
+            },
+            "name": {
+              "constant_value": "test"
+            },
+            "network_interface": [
+              {
+                "access_config": [
+                  {}
+                ],
+                "network": {
+                  "constant_value": "default"
+                }
+              }
+            ],
+            "scratch_disk": [
+              {}
+            ],
+            "service_account": [
+              {
+                "scopes": {
+                  "constant_value": [
+                    "userinfo-email",
+                    "compute-ro",
+                    "storage-ro"
+                  ]
+                }
+              }
+            ],
+            "tags": {
+              "constant_value": [
+                "foo",
+                "bar"
+              ]
+            },
+            "zone": {
+              "constant_value": "us-central1-a"
+            }
+          },
+          "schema_version": 6
+        }
+      ]
+    }
+  }
+}
diff --git a/tfgcv/testdata/example_organization_iam_binding.tf b/tfgcv/testdata/example_organization_iam_binding.tf
new file mode 100644
index 00000000..d16fd633
--- /dev/null
+++ b/tfgcv/testdata/example_organization_iam_binding.tf
@@ -0,0 +1,8 @@
+resource "google_organization_iam_binding" "binding" {
+  org_id = "123456789"
+  role    = "roles/browser"
+
+  members = [
+    "user:alice@gmail.com",
+  ]
+}
diff --git a/tfgcv/testdata/example_organization_iam_binding_assets.json b/tfgcv/testdata/example_organization_iam_binding_assets.json
new file mode 100644
index 00000000..4367ef77
--- /dev/null
+++ b/tfgcv/testdata/example_organization_iam_binding_assets.json
@@ -0,0 +1,17 @@
+[
+  {
+    "name": "//cloudresourcemanager.googleapis.com/organizations/123456789",
+    "asset_type": "cloudresourcemanager.googleapis.com/Organization",
+    "ancestry_path": "organization/12345/folder/67890/project/foobar",
+    "iam_policy": {
+      "bindings": [
+        {
+          "role": "roles/browser",
+          "members": [
+            "user:alice@gmail.com"
+          ]
+        }
+      ]
+    }
+  }
+]
diff --git a/tfgcv/testdata/example_organization_iam_binding_tfplan.json b/tfgcv/testdata/example_organization_iam_binding_tfplan.json
new file mode 100644
index 00000000..4e53c567
--- /dev/null
+++ b/tfgcv/testdata/example_organization_iam_binding_tfplan.json
@@ -0,0 +1,91 @@
+{
+  "format_version": "0.1",
+  "terraform_version": "0.12.10",
+  "planned_values": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_organization_iam_binding.binding",
+          "mode": "managed",
+          "type": "google_organization_iam_binding",
+          "name": "binding",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "members": [
+              "user:alice@gmail.com"
+            ],
+            "org_id": "123456789",
+            "role": "roles/browser"
+          }
+        }
+      ]
+    }
+  },
+  "resource_changes": [
+    {
+      "address": "google_organization_iam_binding.binding",
+      "mode": "managed",
+      "type": "google_organization_iam_binding",
+      "name": "binding",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "members": [
+            "user:alice@gmail.com"
+          ],
+          "org_id": "123456789",
+          "role": "roles/browser"
+        },
+        "after_unknown": {
+          "etag": true,
+          "id": true,
+          "members": [
+            false
+          ]
+        }
+      }
+    }
+  ],
+  "configuration": {
+    "provider_config": {
+      "google": {
+        "name": "google",
+        "expressions": {
+          "project": {
+            "constant_value": "foobar"
+          }
+        }
+      }
+    },
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_organization_iam_binding.binding",
+          "mode": "managed",
+          "type": "google_organization_iam_binding",
+          "name": "binding",
+          "provider_config_key": "google",
+          "expressions": {
+            "members": {
+              "constant_value": [
+                "user:alice@gmail.com"
+              ]
+            },
+            "org_id": {
+              "constant_value": "123456789"
+            },
+            "role": {
+              "constant_value": "roles/browser"
+            }
+          },
+          "schema_version": 0
+        }
+      ]
+    }
+  }
+}
diff --git a/tfgcv/testdata/example_organization_iam_member.tf b/tfgcv/testdata/example_organization_iam_member.tf
new file mode 100644
index 00000000..970d9e3b
--- /dev/null
+++ b/tfgcv/testdata/example_organization_iam_member.tf
@@ -0,0 +1,5 @@
+resource "google_organization_iam_member" "binding" {
+  org_id = "0123456789"
+  role    = "roles/editor"
+  member  = "user:alice@gmail.com"
+}
diff --git a/tfgcv/testdata/example_organization_iam_member_assets.json b/tfgcv/testdata/example_organization_iam_member_assets.json
new file mode 100644
index 00000000..a4cf2388
--- /dev/null
+++ b/tfgcv/testdata/example_organization_iam_member_assets.json
@@ -0,0 +1,17 @@
+[
+  {
+    "name": "//cloudresourcemanager.googleapis.com/organizations/0123456789",
+    "asset_type": "cloudresourcemanager.googleapis.com/Organization",
+    "ancestry_path": "organization/12345/folder/67890/project/foobar",
+    "iam_policy": {
+      "bindings": [
+        {
+          "role": "roles/editor",
+          "members": [
+            "user:alice@gmail.com"
+          ]
+        }
+      ]
+    }
+  }
+]
diff --git a/tfgcv/testdata/example_organization_iam_member_tfplan.json b/tfgcv/testdata/example_organization_iam_member_tfplan.json
new file mode 100644
index 00000000..f226a34b
--- /dev/null
+++ b/tfgcv/testdata/example_organization_iam_member_tfplan.json
@@ -0,0 +1,82 @@
+{
+  "format_version": "0.1",
+  "terraform_version": "0.12.10",
+  "planned_values": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_organization_iam_member.binding",
+          "mode": "managed",
+          "type": "google_organization_iam_member",
+          "name": "binding",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "member": "user:alice@gmail.com",
+            "org_id": "0123456789",
+            "role": "roles/editor"
+          }
+        }
+      ]
+    }
+  },
+  "resource_changes": [
+    {
+      "address": "google_organization_iam_member.binding",
+      "mode": "managed",
+      "type": "google_organization_iam_member",
+      "name": "binding",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "member": "user:alice@gmail.com",
+          "org_id": "0123456789",
+          "role": "roles/editor"
+        },
+        "after_unknown": {
+          "etag": true,
+          "id": true
+        }
+      }
+    }
+  ],
+  "configuration": {
+    "provider_config": {
+      "google": {
+        "name": "google",
+        "expressions": {
+          "project": {
+            "constant_value": "foobar"
+          }
+        }
+      }
+    },
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_organization_iam_member.binding",
+          "mode": "managed",
+          "type": "google_organization_iam_member",
+          "name": "binding",
+          "provider_config_key": "google",
+          "expressions": {
+            "member": {
+              "constant_value": "user:alice@gmail.com"
+            },
+            "org_id": {
+              "constant_value": "0123456789"
+            },
+            "role": {
+              "constant_value": "roles/editor"
+            }
+          },
+          "schema_version": 0
+        }
+      ]
+    }
+  }
+}
diff --git a/tfgcv/testdata/example_organization_iam_policy.tf b/tfgcv/testdata/example_organization_iam_policy.tf
new file mode 100644
index 00000000..0a85f5ad
--- /dev/null
+++ b/tfgcv/testdata/example_organization_iam_policy.tf
@@ -0,0 +1,14 @@
+resource "google_organization_iam_policy" "policy" {
+  org_id = "123456789"
+  policy_data = "${data.google_iam_policy.admin.policy_data}"
+}
+
+data "google_iam_policy" "admin" {
+  binding {
+    role = "roles/editor"
+
+    members = [
+      "user:jane@example.com",
+    ]
+  }
+}
diff --git a/tfgcv/testdata/example_organization_iam_policy_assets.json b/tfgcv/testdata/example_organization_iam_policy_assets.json
new file mode 100644
index 00000000..196021f6
--- /dev/null
+++ b/tfgcv/testdata/example_organization_iam_policy_assets.json
@@ -0,0 +1,17 @@
+[
+  {
+    "name": "//cloudresourcemanager.googleapis.com/organizations/123456789",
+    "asset_type": "cloudresourcemanager.googleapis.com/Organization",
+    "ancestry_path": "organization/12345/folder/67890/project/foobar",
+    "iam_policy": {
+      "bindings": [
+        {
+          "role": "roles/editor",
+          "members": [
+            "user:jane@example.com"
+          ]
+        }
+      ]
+    }
+  }
+]
diff --git a/tfgcv/testdata/example_organization_iam_policy_tfplan.json b/tfgcv/testdata/example_organization_iam_policy_tfplan.json
new file mode 100644
index 00000000..ea8eec17
--- /dev/null
+++ b/tfgcv/testdata/example_organization_iam_policy_tfplan.json
@@ -0,0 +1,132 @@
+{
+  "format_version": "0.1",
+  "terraform_version": "0.12.10",
+  "planned_values": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_organization_iam_policy.policy",
+          "mode": "managed",
+          "type": "google_organization_iam_policy",
+          "name": "policy",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "org_id": "123456789",
+            "policy_data": "{\"bindings\":[{\"members\":[\"user:jane@example.com\"],\"role\":\"roles/editor\"}]}"
+          }
+        }
+      ]
+    }
+  },
+  "resource_changes": [
+    {
+      "address": "google_organization_iam_policy.policy",
+      "mode": "managed",
+      "type": "google_organization_iam_policy",
+      "name": "policy",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "org_id": "123456789",
+          "policy_data": "{\"bindings\":[{\"members\":[\"user:jane@example.com\"],\"role\":\"roles/editor\"}]}"
+        },
+        "after_unknown": {
+          "etag": true,
+          "id": true
+        }
+      }
+    }
+  ],
+  "prior_state": {
+    "format_version": "0.1",
+    "terraform_version": "0.12.10",
+    "values": {
+      "root_module": {
+        "resources": [
+          {
+            "address": "data.google_iam_policy.admin",
+            "mode": "data",
+            "type": "google_iam_policy",
+            "name": "admin",
+            "provider_name": "google",
+            "schema_version": 0,
+            "values": {
+              "audit_config": null,
+              "binding": [
+                {
+                  "members": [
+                    "user:jane@example.com"
+                  ],
+                  "role": "roles/editor"
+                }
+              ],
+              "id": "1120114865",
+              "policy_data": "{\"bindings\":[{\"members\":[\"user:jane@example.com\"],\"role\":\"roles/editor\"}]}"
+            }
+          }
+        ]
+      }
+    }
+  },
+  "configuration": {
+    "provider_config": {
+      "google": {
+        "name": "google",
+        "expressions": {
+          "project": {
+            "constant_value": "foobar"
+          }
+        }
+      }
+    },
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_organization_iam_policy.policy",
+          "mode": "managed",
+          "type": "google_organization_iam_policy",
+          "name": "policy",
+          "provider_config_key": "google",
+          "expressions": {
+            "org_id": {
+              "constant_value": "123456789"
+            },
+            "policy_data": {
+              "references": [
+                "data.google_iam_policy.admin"
+              ]
+            }
+          },
+          "schema_version": 0
+        },
+        {
+          "address": "data.google_iam_policy.admin",
+          "mode": "data",
+          "type": "google_iam_policy",
+          "name": "admin",
+          "provider_config_key": "google",
+          "expressions": {
+            "binding": [
+              {
+                "members": {
+                  "constant_value": [
+                    "user:jane@example.com"
+                  ]
+                },
+                "role": {
+                  "constant_value": "roles/editor"
+                }
+              }
+            ]
+          },
+          "schema_version": 0
+        }
+      ]
+    }
+  }
+}
diff --git a/tfgcv/testdata/example_project.tf b/tfgcv/testdata/example_project.tf
new file mode 100644
index 00000000..91b05795
--- /dev/null
+++ b/tfgcv/testdata/example_project.tf
@@ -0,0 +1,10 @@
+resource "google_project" "my_project-in-a-folder" {
+  name = "My Project"
+  project_id = "foobar"
+  folder_id  = "${google_folder.department1.name}"
+}
+
+resource "google_folder" "department1" {
+  display_name = "Department 1"
+  parent     = "organizations/1234567"
+}
diff --git a/tfgcv/testdata/example_project_assets.json b/tfgcv/testdata/example_project_assets.json
new file mode 100644
index 00000000..03581520
--- /dev/null
+++ b/tfgcv/testdata/example_project_assets.json
@@ -0,0 +1,33 @@
+[
+  {
+    "name": "//cloudbilling.googleapis.com/projects/foobar/billingInfo",
+    "asset_type": "cloudbilling.googleapis.com/ProjectBillingInfo",
+    "ancestry_path": "organization/12345/folder/67890/project/foobar",
+    "resource": {
+      "version": "v1",
+      "discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/cloudbilling/v1/rest",
+      "discovery_name": "ProjectBillingInfo",
+      "parent": "//cloudresourcemanager.googleapis.com/projects/foobar",
+      "data": {
+        "billingAccountName": "billingAccounts/",
+        "name": "projects/foobar/billingInfo",
+        "projectId": "foobar"
+      }
+    }
+  },
+  {
+    "name": "//cloudresourcemanager.googleapis.com/projects/foobar",
+    "asset_type": "cloudresourcemanager.googleapis.com/Project",
+    "ancestry_path": "organization/12345/folder/67890/project/foobar",
+    "resource": {
+      "version": "v1",
+      "discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/compute/v1/rest",
+      "discovery_name": "Project",
+      "parent": "//cloudresourcemanager.googleapis.com/projects/foobar",
+      "data": {
+        "name": "My Project",
+        "projectId": "foobar"
+      }
+    }
+  }
+]
diff --git a/tfgcv/testdata/example_project_iam_binding.tf b/tfgcv/testdata/example_project_iam_binding.tf
new file mode 100644
index 00000000..8367fa73
--- /dev/null
+++ b/tfgcv/testdata/example_project_iam_binding.tf
@@ -0,0 +1,8 @@
+resource "google_project_iam_binding" "project" {
+  project = "foobar"
+  role    = "roles/editor"
+
+  members = [
+    "user:jane@example.com",
+  ]
+}
diff --git a/tfgcv/testdata/example_project_iam_binding_assets.json b/tfgcv/testdata/example_project_iam_binding_assets.json
new file mode 100644
index 00000000..9a337d6a
--- /dev/null
+++ b/tfgcv/testdata/example_project_iam_binding_assets.json
@@ -0,0 +1,17 @@
+[
+  {
+    "name": "//cloudresourcemanager.googleapis.com/projects/foobar",
+    "asset_type": "cloudresourcemanager.googleapis.com/Project",
+    "ancestry_path": "organization/12345/folder/67890/project/foobar",
+    "iam_policy": {
+      "bindings": [
+        {
+          "role": "roles/editor",
+          "members": [
+            "user:jane@example.com"
+          ]
+        }
+      ]
+    }
+  }
+]
diff --git a/tfgcv/testdata/example_project_iam_binding_tfplan.json b/tfgcv/testdata/example_project_iam_binding_tfplan.json
new file mode 100644
index 00000000..c7cd76b3
--- /dev/null
+++ b/tfgcv/testdata/example_project_iam_binding_tfplan.json
@@ -0,0 +1,91 @@
+{
+  "format_version": "0.1",
+  "terraform_version": "0.12.10",
+  "planned_values": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_project_iam_binding.project",
+          "mode": "managed",
+          "type": "google_project_iam_binding",
+          "name": "project",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "members": [
+              "user:jane@example.com"
+            ],
+            "project": "foobar",
+            "role": "roles/editor"
+          }
+        }
+      ]
+    }
+  },
+  "resource_changes": [
+    {
+      "address": "google_project_iam_binding.project",
+      "mode": "managed",
+      "type": "google_project_iam_binding",
+      "name": "project",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "members": [
+            "user:jane@example.com"
+          ],
+          "project": "foobar",
+          "role": "roles/editor"
+        },
+        "after_unknown": {
+          "etag": true,
+          "id": true,
+          "members": [
+            false
+          ]
+        }
+      }
+    }
+  ],
+  "configuration": {
+    "provider_config": {
+      "google": {
+        "name": "google",
+        "expressions": {
+          "project": {
+            "constant_value": "foobar"
+          }
+        }
+      }
+    },
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_project_iam_binding.project",
+          "mode": "managed",
+          "type": "google_project_iam_binding",
+          "name": "project",
+          "provider_config_key": "google",
+          "expressions": {
+            "members": {
+              "constant_value": [
+                "user:jane@example.com"
+              ]
+            },
+            "project": {
+              "constant_value": "foobar"
+            },
+            "role": {
+              "constant_value": "roles/editor"
+            }
+          },
+          "schema_version": 0
+        }
+      ]
+    }
+  }
+}
diff --git a/tfgcv/testdata/example_project_iam_member.tf b/tfgcv/testdata/example_project_iam_member.tf
new file mode 100644
index 00000000..c607b84e
--- /dev/null
+++ b/tfgcv/testdata/example_project_iam_member.tf
@@ -0,0 +1,5 @@
+resource "google_project_iam_member" "project" {
+  project = "foobar"
+  role    = "roles/editor"
+  member  = "user:jane@example.com"
+}
diff --git a/tfgcv/testdata/example_project_iam_member_assets.json b/tfgcv/testdata/example_project_iam_member_assets.json
new file mode 100644
index 00000000..9a337d6a
--- /dev/null
+++ b/tfgcv/testdata/example_project_iam_member_assets.json
@@ -0,0 +1,17 @@
+[
+  {
+    "name": "//cloudresourcemanager.googleapis.com/projects/foobar",
+    "asset_type": "cloudresourcemanager.googleapis.com/Project",
+    "ancestry_path": "organization/12345/folder/67890/project/foobar",
+    "iam_policy": {
+      "bindings": [
+        {
+          "role": "roles/editor",
+          "members": [
+            "user:jane@example.com"
+          ]
+        }
+      ]
+    }
+  }
+]
diff --git a/tfgcv/testdata/example_project_iam_member_tfplan.json b/tfgcv/testdata/example_project_iam_member_tfplan.json
new file mode 100644
index 00000000..be790f1f
--- /dev/null
+++ b/tfgcv/testdata/example_project_iam_member_tfplan.json
@@ -0,0 +1,82 @@
+{
+  "format_version": "0.1",
+  "terraform_version": "0.12.10",
+  "planned_values": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_project_iam_member.project",
+          "mode": "managed",
+          "type": "google_project_iam_member",
+          "name": "project",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "member": "user:jane@example.com",
+            "project": "foobar",
+            "role": "roles/editor"
+          }
+        }
+      ]
+    }
+  },
+  "resource_changes": [
+    {
+      "address": "google_project_iam_member.project",
+      "mode": "managed",
+      "type": "google_project_iam_member",
+      "name": "project",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "member": "user:jane@example.com",
+          "project": "foobar",
+          "role": "roles/editor"
+        },
+        "after_unknown": {
+          "etag": true,
+          "id": true
+        }
+      }
+    }
+  ],
+  "configuration": {
+    "provider_config": {
+      "google": {
+        "name": "google",
+        "expressions": {
+          "project": {
+            "constant_value": "foobar"
+          }
+        }
+      }
+    },
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_project_iam_member.project",
+          "mode": "managed",
+          "type": "google_project_iam_member",
+          "name": "project",
+          "provider_config_key": "google",
+          "expressions": {
+            "member": {
+              "constant_value": "user:jane@example.com"
+            },
+            "project": {
+              "constant_value": "foobar"
+            },
+            "role": {
+              "constant_value": "roles/editor"
+            }
+          },
+          "schema_version": 0
+        }
+      ]
+    }
+  }
+}
diff --git a/tfgcv/testdata/example_project_iam_policy.tf b/tfgcv/testdata/example_project_iam_policy.tf
new file mode 100644
index 00000000..e3c91e4f
--- /dev/null
+++ b/tfgcv/testdata/example_project_iam_policy.tf
@@ -0,0 +1,14 @@
+resource "google_project_iam_policy" "project" {
+  project     = "foobar"
+  policy_data = "${data.google_iam_policy.admin.policy_data}"
+}
+
+data "google_iam_policy" "admin" {
+  binding {
+    role = "roles/editor"
+
+    members = [
+      "user:jane@example.com",
+    ]
+  }
+}
diff --git a/tfgcv/testdata/example_project_iam_policy_assets.json b/tfgcv/testdata/example_project_iam_policy_assets.json
new file mode 100644
index 00000000..9a337d6a
--- /dev/null
+++ b/tfgcv/testdata/example_project_iam_policy_assets.json
@@ -0,0 +1,17 @@
+[
+  {
+    "name": "//cloudresourcemanager.googleapis.com/projects/foobar",
+    "asset_type": "cloudresourcemanager.googleapis.com/Project",
+    "ancestry_path": "organization/12345/folder/67890/project/foobar",
+    "iam_policy": {
+      "bindings": [
+        {
+          "role": "roles/editor",
+          "members": [
+            "user:jane@example.com"
+          ]
+        }
+      ]
+    }
+  }
+]
diff --git a/tfgcv/testdata/example_project_iam_policy_tfplan.json b/tfgcv/testdata/example_project_iam_policy_tfplan.json
new file mode 100644
index 00000000..3d45a749
--- /dev/null
+++ b/tfgcv/testdata/example_project_iam_policy_tfplan.json
@@ -0,0 +1,137 @@
+{
+  "format_version": "0.1",
+  "terraform_version": "0.12.10",
+  "planned_values": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_project_iam_policy.project",
+          "mode": "managed",
+          "type": "google_project_iam_policy",
+          "name": "project",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "authoritative": null,
+            "disable_project": null,
+            "policy_data": "{\"bindings\":[{\"members\":[\"user:jane@example.com\"],\"role\":\"roles/editor\"}]}",
+            "project": "foobar"
+          }
+        }
+      ]
+    }
+  },
+  "resource_changes": [
+    {
+      "address": "google_project_iam_policy.project",
+      "mode": "managed",
+      "type": "google_project_iam_policy",
+      "name": "project",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "authoritative": null,
+          "disable_project": null,
+          "policy_data": "{\"bindings\":[{\"members\":[\"user:jane@example.com\"],\"role\":\"roles/editor\"}]}",
+          "project": "foobar"
+        },
+        "after_unknown": {
+          "etag": true,
+          "id": true,
+          "restore_policy": true
+        }
+      }
+    }
+  ],
+  "prior_state": {
+    "format_version": "0.1",
+    "terraform_version": "0.12.10",
+    "values": {
+      "root_module": {
+        "resources": [
+          {
+            "address": "data.google_iam_policy.admin",
+            "mode": "data",
+            "type": "google_iam_policy",
+            "name": "admin",
+            "provider_name": "google",
+            "schema_version": 0,
+            "values": {
+              "audit_config": null,
+              "binding": [
+                {
+                  "members": [
+                    "user:jane@example.com"
+                  ],
+                  "role": "roles/editor"
+                }
+              ],
+              "id": "1120114865",
+              "policy_data": "{\"bindings\":[{\"members\":[\"user:jane@example.com\"],\"role\":\"roles/editor\"}]}"
+            }
+          }
+        ]
+      }
+    }
+  },
+  "configuration": {
+    "provider_config": {
+      "google": {
+        "name": "google",
+        "expressions": {
+          "project": {
+            "constant_value": "foobar"
+          }
+        }
+      }
+    },
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_project_iam_policy.project",
+          "mode": "managed",
+          "type": "google_project_iam_policy",
+          "name": "project",
+          "provider_config_key": "google",
+          "expressions": {
+            "policy_data": {
+              "references": [
+                "data.google_iam_policy.admin"
+              ]
+            },
+            "project": {
+              "constant_value": "foobar"
+            }
+          },
+          "schema_version": 0
+        },
+        {
+          "address": "data.google_iam_policy.admin",
+          "mode": "data",
+          "type": "google_iam_policy",
+          "name": "admin",
+          "provider_config_key": "google",
+          "expressions": {
+            "binding": [
+              {
+                "members": {
+                  "constant_value": [
+                    "user:jane@example.com"
+                  ]
+                },
+                "role": {
+                  "constant_value": "roles/editor"
+                }
+              }
+            ]
+          },
+          "schema_version": 0
+        }
+      ]
+    }
+  }
+}
diff --git a/tfgcv/testdata/example_project_tfplan.json b/tfgcv/testdata/example_project_tfplan.json
new file mode 100644
index 00000000..a6cb2228
--- /dev/null
+++ b/tfgcv/testdata/example_project_tfplan.json
@@ -0,0 +1,149 @@
+{
+  "format_version": "0.1",
+  "terraform_version": "0.12.10",
+  "planned_values": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_folder.department1",
+          "mode": "managed",
+          "type": "google_folder",
+          "name": "department1",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "display_name": "Department 1",
+            "parent": "organizations/1234567",
+            "timeouts": null
+          }
+        },
+        {
+          "address": "google_project.my_project-in-a-folder",
+          "mode": "managed",
+          "type": "google_project",
+          "name": "my_project-in-a-folder",
+          "provider_name": "google",
+          "schema_version": 1,
+          "values": {
+            "auto_create_network": true,
+            "billing_account": null,
+            "labels": null,
+            "name": "My Project",
+            "project_id": "foobar",
+            "timeouts": null
+          }
+        }
+      ]
+    }
+  },
+  "resource_changes": [
+    {
+      "address": "google_folder.department1",
+      "mode": "managed",
+      "type": "google_folder",
+      "name": "department1",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "display_name": "Department 1",
+          "parent": "organizations/1234567",
+          "timeouts": null
+        },
+        "after_unknown": {
+          "create_time": true,
+          "id": true,
+          "lifecycle_state": true,
+          "name": true
+        }
+      }
+    },
+    {
+      "address": "google_project.my_project-in-a-folder",
+      "mode": "managed",
+      "type": "google_project",
+      "name": "my_project-in-a-folder",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "auto_create_network": true,
+          "billing_account": null,
+          "labels": null,
+          "name": "My Project",
+          "project_id": "foobar",
+          "timeouts": null
+        },
+        "after_unknown": {
+          "app_engine": true,
+          "folder_id": true,
+          "id": true,
+          "number": true,
+          "org_id": true,
+          "policy_data": true,
+          "policy_etag": true,
+          "skip_delete": true
+        }
+      }
+    }
+  ],
+  "configuration": {
+    "provider_config": {
+      "google": {
+        "name": "google",
+        "expressions": {
+          "project": {
+            "constant_value": "foobar"
+          }
+        }
+      }
+    },
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_folder.department1",
+          "mode": "managed",
+          "type": "google_folder",
+          "name": "department1",
+          "provider_config_key": "google",
+          "expressions": {
+            "display_name": {
+              "constant_value": "Department 1"
+            },
+            "parent": {
+              "constant_value": "organizations/1234567"
+            }
+          },
+          "schema_version": 0
+        },
+        {
+          "address": "google_project.my_project-in-a-folder",
+          "mode": "managed",
+          "type": "google_project",
+          "name": "my_project-in-a-folder",
+          "provider_config_key": "google",
+          "expressions": {
+            "folder_id": {
+              "references": [
+                "google_folder.department1"
+              ]
+            },
+            "name": {
+              "constant_value": "My Project"
+            },
+            "project_id": {
+              "constant_value": "foobar"
+            }
+          },
+          "schema_version": 1
+        }
+      ]
+    }
+  }
+}
diff --git a/tfgcv/testdata/example_sql_database_instance.tf b/tfgcv/testdata/example_sql_database_instance.tf
new file mode 100644
index 00000000..ec2c95ab
--- /dev/null
+++ b/tfgcv/testdata/example_sql_database_instance.tf
@@ -0,0 +1,11 @@
+resource "google_sql_database_instance" "master" {
+  name = "master-instance"
+  database_version = "POSTGRES_9_6"
+  region = "us-central1"
+
+  settings {
+    # Second-generation instance tiers are based on the machine
+    # type. See argument reference below.
+    tier = "db-f1-micro"
+  }
+}
diff --git a/tfgcv/testdata/example_sql_database_instance_assets.json b/tfgcv/testdata/example_sql_database_instance_assets.json
new file mode 100644
index 00000000..c55a5155
--- /dev/null
+++ b/tfgcv/testdata/example_sql_database_instance_assets.json
@@ -0,0 +1,25 @@
+[
+  {
+    "name": "//cloudsql.googleapis.com/projects/foobar/instances/master-instance",
+    "asset_type": "sqladmin.googleapis.com/Instance",
+    "ancestry_path": "organization/12345/folder/67890/project/foobar",
+    "resource": {
+      "version": "v1beta4",
+      "discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/sqladmin/v1beta4/rest",
+      "discovery_name": "DatabaseInstance",
+      "parent": "//cloudresourcemanager.googleapis.com/projects/foobar",
+      "data": {
+        "databaseVersion": "POSTGRES_9_6",
+        "name": "master-instance",
+        "project": "foobar",
+        "region": "us-central1",
+        "settings": {
+          "pricingPlan": "PER_USE",
+          "replicationType": "SYNCHRONOUS",
+          "storageAutoResize": true,
+          "tier": "db-f1-micro"
+        }
+      }
+    }
+  }
+]
diff --git a/tfgcv/testdata/example_sql_database_instance_tfplan.json b/tfgcv/testdata/example_sql_database_instance_tfplan.json
new file mode 100644
index 00000000..91fe0bab
--- /dev/null
+++ b/tfgcv/testdata/example_sql_database_instance_tfplan.json
@@ -0,0 +1,140 @@
+{
+  "format_version": "0.1",
+  "terraform_version": "0.12.10",
+  "planned_values": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_sql_database_instance.master",
+          "mode": "managed",
+          "type": "google_sql_database_instance",
+          "name": "master",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "database_version": "POSTGRES_9_6",
+            "name": "master-instance",
+            "region": "us-central1",
+            "settings": [
+              {
+                "authorized_gae_applications": null,
+                "database_flags": [],
+                "disk_autoresize": true,
+                "maintenance_window": [],
+                "pricing_plan": "PER_USE",
+                "replication_type": "SYNCHRONOUS",
+                "tier": "db-f1-micro",
+                "user_labels": null
+              }
+            ],
+            "timeouts": null
+          }
+        }
+      ]
+    }
+  },
+  "resource_changes": [
+    {
+      "address": "google_sql_database_instance.master",
+      "mode": "managed",
+      "type": "google_sql_database_instance",
+      "name": "master",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "database_version": "POSTGRES_9_6",
+          "name": "master-instance",
+          "region": "us-central1",
+          "settings": [
+            {
+              "authorized_gae_applications": null,
+              "database_flags": [],
+              "disk_autoresize": true,
+              "maintenance_window": [],
+              "pricing_plan": "PER_USE",
+              "replication_type": "SYNCHRONOUS",
+              "tier": "db-f1-micro",
+              "user_labels": null
+            }
+          ],
+          "timeouts": null
+        },
+        "after_unknown": {
+          "connection_name": true,
+          "first_ip_address": true,
+          "id": true,
+          "ip_address": true,
+          "master_instance_name": true,
+          "private_ip_address": true,
+          "project": true,
+          "public_ip_address": true,
+          "replica_configuration": true,
+          "self_link": true,
+          "server_ca_cert": true,
+          "service_account_email_address": true,
+          "settings": [
+            {
+              "activation_policy": true,
+              "availability_type": true,
+              "backup_configuration": true,
+              "crash_safe_replication": true,
+              "database_flags": [],
+              "disk_size": true,
+              "disk_type": true,
+              "ip_configuration": true,
+              "location_preference": true,
+              "maintenance_window": [],
+              "version": true
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "configuration": {
+    "provider_config": {
+      "google": {
+        "name": "google",
+        "expressions": {
+          "project": {
+            "constant_value": "foobar"
+          }
+        }
+      }
+    },
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_sql_database_instance.master",
+          "mode": "managed",
+          "type": "google_sql_database_instance",
+          "name": "master",
+          "provider_config_key": "google",
+          "expressions": {
+            "database_version": {
+              "constant_value": "POSTGRES_9_6"
+            },
+            "name": {
+              "constant_value": "master-instance"
+            },
+            "region": {
+              "constant_value": "us-central1"
+            },
+            "settings": [
+              {
+                "tier": {
+                  "constant_value": "db-f1-micro"
+                }
+              }
+            ]
+          },
+          "schema_version": 0
+        }
+      ]
+    }
+  }
+}
diff --git a/tfgcv/testdata/example_storage_bucket.tf b/tfgcv/testdata/example_storage_bucket.tf
new file mode 100644
index 00000000..b63dc666
--- /dev/null
+++ b/tfgcv/testdata/example_storage_bucket.tf
@@ -0,0 +1,9 @@
+resource "google_storage_bucket" "image-store" {
+  name     = "image-store-bucket"
+  location = "EU"
+
+  website {
+    main_page_suffix = "index.html"
+    not_found_page   = "404.html"
+  }
+}
diff --git a/tfgcv/testdata/example_storage_bucket_assets.json b/tfgcv/testdata/example_storage_bucket_assets.json
new file mode 100644
index 00000000..700aaa27
--- /dev/null
+++ b/tfgcv/testdata/example_storage_bucket_assets.json
@@ -0,0 +1,30 @@
+[
+  {
+    "name": "//storage.googleapis.com/image-store-bucket",
+    "asset_type": "storage.googleapis.com/Bucket",
+    "ancestry_path": "organization/12345/folder/67890/project/foobar",
+    "resource": {
+      "version": "v1",
+      "discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/storage/v1/rest",
+      "discovery_name": "Bucket",
+      "parent": "//cloudresourcemanager.googleapis.com/projects/foobar",
+      "data": {
+        "billing": {},
+        "iamConfiguration": {
+          "bucketPolicyOnly": {
+            "enabled": false
+          }
+        },
+        "lifecycle": {},
+        "location": "EU",
+        "name": "image-store-bucket",
+        "project": "foobar",
+        "storageClass": "STANDARD",
+        "website": {
+          "mainPageSuffix": "index.html",
+          "notFoundPage": "404.html"
+        }
+      }
+    }
+  }
+]
diff --git a/tfgcv/testdata/example_storage_bucket_tfplan.json b/tfgcv/testdata/example_storage_bucket_tfplan.json
new file mode 100644
index 00000000..bb9909fa
--- /dev/null
+++ b/tfgcv/testdata/example_storage_bucket_tfplan.json
@@ -0,0 +1,133 @@
+{
+  "format_version": "0.1",
+  "terraform_version": "0.12.10",
+  "planned_values": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_storage_bucket.image-store",
+          "mode": "managed",
+          "type": "google_storage_bucket",
+          "name": "image-store",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "cors": [],
+            "encryption": [],
+            "force_destroy": false,
+            "labels": null,
+            "lifecycle_rule": [],
+            "location": "EU",
+            "logging": [],
+            "name": "image-store-bucket",
+            "predefined_acl": null,
+            "requester_pays": null,
+            "retention_policy": [],
+            "storage_class": "STANDARD",
+            "versioning": [],
+            "website": [
+              {
+                "main_page_suffix": "index.html",
+                "not_found_page": "404.html"
+              }
+            ]
+          }
+        }
+      ]
+    }
+  },
+  "resource_changes": [
+    {
+      "address": "google_storage_bucket.image-store",
+      "mode": "managed",
+      "type": "google_storage_bucket",
+      "name": "image-store",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "cors": [],
+          "encryption": [],
+          "force_destroy": false,
+          "labels": null,
+          "lifecycle_rule": [],
+          "location": "EU",
+          "logging": [],
+          "name": "image-store-bucket",
+          "predefined_acl": null,
+          "requester_pays": null,
+          "retention_policy": [],
+          "storage_class": "STANDARD",
+          "versioning": [],
+          "website": [
+            {
+              "main_page_suffix": "index.html",
+              "not_found_page": "404.html"
+            }
+          ]
+        },
+        "after_unknown": {
+          "bucket_policy_only": true,
+          "cors": [],
+          "encryption": [],
+          "id": true,
+          "lifecycle_rule": [],
+          "logging": [],
+          "project": true,
+          "retention_policy": [],
+          "self_link": true,
+          "url": true,
+          "versioning": [],
+          "website": [
+            {}
+          ]
+        }
+      }
+    }
+  ],
+  "configuration": {
+    "provider_config": {
+      "google": {
+        "name": "google",
+        "expressions": {
+          "project": {
+            "constant_value": "foobar"
+          }
+        }
+      }
+    },
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_storage_bucket.image-store",
+          "mode": "managed",
+          "type": "google_storage_bucket",
+          "name": "image-store",
+          "provider_config_key": "google",
+          "expressions": {
+            "location": {
+              "constant_value": "EU"
+            },
+            "name": {
+              "constant_value": "image-store-bucket"
+            },
+            "website": [
+              {
+                "main_page_suffix": {
+                  "constant_value": "index.html"
+                },
+                "not_found_page": {
+                  "constant_value": "404.html"
+                }
+              }
+            ]
+          },
+          "schema_version": 0
+        }
+      ]
+    }
+  }
+}