Repository: GoogleCloudPlatform/terraform-validator
Pull request #73 (2019-09-05 22:26:07)

Offline flag for v0.12
This is based from #72 to add `--offline` flag. It also makes unit test possible without connecting to GCP API. 

This pull request would invalidate #38 as #38 has no version switch logic.


Patch:
diff --git a/cmd/convert.go b/cmd/convert.go
index 133b833d..378e6bee 100644
--- a/cmd/convert.go
+++ b/cmd/convert.go
@@ -46,7 +46,7 @@ Example:
 		return nil
 	},
 	RunE: func(c *cobra.Command, args []string) error {
-		assets, err := tfgcv.ReadPlannedAssets(args[0], flags.convert.project, flags.convert.ancestry)
+		assets, err := tfgcv.ReadPlannedAssets(args[0], flags.convert.project, flags.convert.ancestry, false)
 		if err != nil {
 			if errors.Cause(err) == tfgcv.ErrParsingProviderProject {
 				return errors.New("unable to parse provider project, please use --project flag")
diff --git a/cmd/root.go b/cmd/root.go
index 60a4b04b..3672fc8e 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -35,6 +35,9 @@ func init() {
 	validateCmd.MarkFlagRequired("policy-path")
 	validateCmd.Flags().StringVar(&flags.validate.project, "project", "", "Provider project override (override the default project configuration assigned to the google terraform provider when validating resources)")
 	validateCmd.Flags().StringVar(&flags.validate.ancestry, "ancestry", "", "Override the ancestry location of the project when validating resources")
+	if version.Supported(version.TF12) {
+		validateCmd.Flags().BoolVar(&flags.validate.offline, "offline", false, "Do not connect to GCP API")
+	}
 
 	convertCmd.Flags().StringVar(&flags.convert.project, "project", "", "Provider project override (override the default project configuration assigned to the google terraform provider when converting resources)")
 	convertCmd.Flags().StringVar(&flags.convert.ancestry, "ancestry", "", "Override the ancestry location of the project when validating resources")
@@ -62,6 +65,7 @@ var flags struct {
 	validate struct {
 		project    string
 		ancestry   string
+		offline    bool
 		policyPath string
 		outputJSON bool
 	}
diff --git a/cmd/validate.go b/cmd/validate.go
index a6835cf5..ca7377f1 100644
--- a/cmd/validate.go
+++ b/cmd/validate.go
@@ -43,10 +43,13 @@ Example:
 		if len(args) != 1 {
 			return errors.New("missing required argument <tfplan>")
 		}
+		if flags.validate.offline && flags.validate.ancestry == "" {
+			return errors.New("please set ancestry via --ancestry in offline mode")
+		}
 		return nil
 	},
 	RunE: func(c *cobra.Command, args []string) error {
-		assets, err := tfgcv.ReadPlannedAssets(args[0], flags.validate.project, flags.validate.ancestry)
+		assets, err := tfgcv.ReadPlannedAssets(args[0], flags.validate.project, flags.validate.ancestry, flags.validate.offline)
 		if err != nil {
 			if errors.Cause(err) == tfgcv.ErrParsingProviderProject {
 				return errors.New("unable to parse provider project, please use --project flag")
diff --git a/converters/google/convert.go b/converters/google/convert.go
index e92004f6..f6000ef9 100644
--- a/converters/google/convert.go
+++ b/converters/google/convert.go
@@ -86,13 +86,15 @@ type AssetResource struct {
 }
 
 // NewConverter is a factory function for Converter.
-func NewConverter(resourceManager *cloudresourcemanager.Service, project, ancestry, credentials string) (*Converter, error) {
+func NewConverter(resourceManager *cloudresourcemanager.Service, project, ancestry, credentials string, offline bool) (*Converter, error) {
 	cfg := &converter.Config{
 		Project:     project,
 		Credentials: credentials,
 	}
-	if err := cfg.LoadAndValidate(); err != nil {
-		return nil, errors.Wrap(err, "configuring")
+	if !offline {
+		if err := cfg.LoadAndValidate(); err != nil {
+			return nil, errors.Wrap(err, "configuring")
+		}
 	}
 
 	ancestryCache := make(map[string]string)
@@ -122,6 +124,7 @@ type Converter struct {
 
 	cfg *converter.Config
 
+	// Talk to GCP resource manager. This field would be nil in offline mode.
 	resourceManager *cloudresourcemanager.Service
 
 	// Cache to prevent multiple network calls for looking up the same project's ancestry
@@ -252,6 +255,9 @@ func (c *Converter) getAncestry(project string) (string, error) {
 	if path, ok := c.ancestryCache[project]; ok {
 		return path, nil
 	}
+	if c.resourceManager == nil {
+		return "", fmt.Errorf("cannot fetch ancestry in offline mode for project %s", project)
+	}
 
 	ancestry, err := c.resourceManager.Projects.GetAncestry(project, &cloudresourcemanager.GetAncestryRequest{}).Do()
 	if err != nil {
diff --git a/tfgcv/planned_assets.go b/tfgcv/planned_assets.go
index ee28cf27..c7b05b17 100644
--- a/tfgcv/planned_assets.go
+++ b/tfgcv/planned_assets.go
@@ -36,17 +36,21 @@ import (
 // If ancestry path is provided, it assumes the project is in that path rather
 // than fetching the ancestry information using Google API.
 // It ignores non-supported resources.
-func ReadPlannedAssets(path, project, ancestry string) ([]google.Asset, error) {
+func ReadPlannedAssets(path, project, ancestry string, offline bool) ([]google.Asset, error) {
 
-	// Add User Agent string to indicate Terraform Validator usage.
-	// Do *NOT* change the "config-validator-tf/" prefix, or else it will
-	// break usage tracking.
-	ua := option.WithUserAgent(fmt.Sprintf("config-validator-tf/%s", BuildVersion()))
-	resourceManager, err := cloudresourcemanager.NewService(context.Background(), ua)
-	if err != nil {
-		return nil, errors.Wrap(err, "constructing resource manager client")
+	var resourceManager *cloudresourcemanager.Service
+	if !offline {
+		// Add User Agent string to indicate Terraform Validator usage.
+		// Do *NOT* change the "config-validator-tf/" prefix, or else it will
+		// break usage tracking.
+		ua := option.WithUserAgent(fmt.Sprintf("config-validator-tf/%s", BuildVersion()))
+		var err error 
+		resourceManager, err = cloudresourcemanager.NewService(context.Background(), ua)
+		if err != nil {
+			return nil, errors.Wrap(err, "constructing resource manager client")
+		}
 	}
-	converter, err := google.NewConverter(resourceManager, project, ancestry, "")
+	converter, err := google.NewConverter(resourceManager, project, ancestry, "", offline)
 	if err != nil {
 		return nil, errors.Wrap(err, "building google converter")
 	}


Test patch:
diff --git a/test/json_templates/instance.json b/test/json_templates/instance.json
index 00699535..2746f660 100644
--- a/test/json_templates/instance.json
+++ b/test/json_templates/instance.json
@@ -30,10 +30,6 @@
 {{end}}
 	"metadata": {
 		"items": [
-			{
-				"key": "foo",
-				"value": "bar"
-			},
 			{
 				"key": "startup-script",
 				"value": "echo hi > /test.txt"
diff --git a/test/read_planned_assets/tf12plan.allcoverage.json b/test/read_planned_assets/tf12plan.allcoverage.json
new file mode 100644
index 00000000..bbfa4653
--- /dev/null
+++ b/test/read_planned_assets/tf12plan.allcoverage.json
@@ -0,0 +1,1519 @@
+{
+  "format_version": "0.1",
+  "terraform_version": "0.12.6",
+  "planned_values": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_compute_disk.my-disk-resource",
+          "mode": "managed",
+          "type": "google_compute_disk",
+          "name": "my-disk-resource",
+          "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": {
+              "disk-label-key-a": "disk-label-val-a"
+            },
+            "name": "my-disk",
+            "project": "foobar",
+            "snapshot": null,
+            "source_image_encryption_key": [
+
+            ],
+            "source_snapshot_encryption_key": [
+
+            ],
+            "timeouts": null,
+            "type": "pd-ssd",
+            "zone": "projects/foobar/global/zones/us-central1-a"
+          }
+        },
+        {
+          "address": "google_compute_firewall.my-test-firewall",
+          "mode": "managed",
+          "type": "google_compute_firewall",
+          "name": "my-test-firewall",
+          "provider_name": "google",
+          "schema_version": 1,
+          "values": {
+            "allow": [
+              {
+                "ports": [
+                  "80",
+                  "8080",
+                  "1000-2000"
+                ],
+                "protocol": "tcp"
+              },
+              {
+                "ports": [
+
+                ],
+                "protocol": "icmp"
+              }
+            ],
+            "deny": [
+
+            ],
+            "description": null,
+            "disabled": null,
+            "name": "my-test-firewall",
+            "network": "default",
+            "priority": 1000,
+            "source_service_accounts": null,
+            "source_tags": [
+              "web"
+            ],
+            "target_service_accounts": null,
+            "target_tags": null,
+            "timeouts": null
+          }
+        },
+        {
+          "address": "google_compute_instance.my-test-instance",
+          "mode": "managed",
+          "type": "google_compute_instance",
+          "name": "my-test-instance",
+          "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-8-jessie-v20170523"
+                  }
+                ]
+              }
+            ],
+            "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": "echo hi > /test.txt",
+            "min_cpu_platform": null,
+            "name": "my-instance",
+            "network_interface": [
+              {
+                "access_config": [
+                  {
+                    "public_ptr_domain_name": null
+                  }
+                ],
+                "alias_ip_range": [
+
+                ],
+                "network": "default"
+              }
+            ],
+            "project": "foobar",
+            "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": "projects/foobar/global/zones/us-central1-a"
+          }
+        },
+        {
+          "address": "google_folder_iam_binding.my-test-folder-binding",
+          "mode": "managed",
+          "type": "google_folder_iam_binding",
+          "name": "my-test-folder-binding",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "folder": "my-folder",
+            "members": [
+              "user:alice@example.com"
+            ],
+            "role": "roles/browser"
+          }
+        },
+        {
+          "address": "google_folder_iam_member.my-test-folder-member",
+          "mode": "managed",
+          "type": "google_folder_iam_member",
+          "name": "my-test-folder-member",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "folder": "my-folder",
+            "member": "user:jane@example.com",
+            "role": "roles/viewer"
+          }
+        },
+        {
+          "address": "google_folder_iam_policy.my-test-folder-policy",
+          "mode": "managed",
+          "type": "google_folder_iam_policy",
+          "name": "my-test-folder-policy",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "folder": "my-folder",
+            "policy_data": "{\"bindings\":[{\"members\":[\"user:bob@example.com\"],\"role\":\"roles/editor\"}]}"
+          }
+        },
+        {
+          "address": "google_organization_iam_binding.my-test-org-binding",
+          "mode": "managed",
+          "type": "google_organization_iam_binding",
+          "name": "my-test-org-binding",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "members": [
+              "user:alice@example.com"
+            ],
+            "org_id": "123456789",
+            "role": "roles/browser"
+          }
+        },
+        {
+          "address": "google_organization_iam_member.my-test-org-member",
+          "mode": "managed",
+          "type": "google_organization_iam_member",
+          "name": "my-test-org-member",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "member": "user:jane@example.com",
+            "org_id": "123456789",
+            "role": "roles/viewer"
+          }
+        },
+        {
+          "address": "google_organization_iam_policy.my-test-org-policy",
+          "mode": "managed",
+          "type": "google_organization_iam_policy",
+          "name": "my-test-org-policy",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "org_id": "123456789",
+            "policy_data": "{\"bindings\":[{\"members\":[\"user:bob@example.com\"],\"role\":\"roles/editor\"}]}"
+          }
+        },
+        {
+          "address": "google_project.my-project-resource",
+          "mode": "managed",
+          "type": "google_project",
+          "name": "my-project-resource",
+          "provider_name": "google",
+          "schema_version": 1,
+          "values": {
+            "auto_create_network": true,
+            "billing_account": "012345-567890-ABCDEF",
+            "labels": {
+              "project-label-key-a": "project-label-val-a"
+            },
+            "name": "My Project Name",
+            "org_id": "my-org",
+            "project_id": "my-project-id",
+            "timeouts": null
+          }
+        },
+        {
+          "address": "google_project_iam_binding.my-test-project-binding",
+          "mode": "managed",
+          "type": "google_project_iam_binding",
+          "name": "my-test-project-binding",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "members": [
+              "user:alice@example.com"
+            ],
+            "project": "foobar",
+            "role": "roles/browser"
+          }
+        },
+        {
+          "address": "google_project_iam_member.my-test-project-member",
+          "mode": "managed",
+          "type": "google_project_iam_member",
+          "name": "my-test-project-member",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "member": "user:jane@example.com",
+            "project": "foobar",
+            "role": "roles/viewer"
+          }
+        },
+        {
+          "address": "google_project_iam_policy.my-test-project-policy",
+          "mode": "managed",
+          "type": "google_project_iam_policy",
+          "name": "my-test-project-policy",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "authoritative": null,
+            "disable_project": null,
+            "policy_data": "{\"bindings\":[{\"members\":[\"user:bob@example.com\"],\"role\":\"roles/editor\"}]}",
+            "project": "foobar"
+          }
+        },
+        {
+          "address": "google_sql_database_instance.my-test-sql",
+          "mode": "managed",
+          "type": "google_sql_database_instance",
+          "name": "my-test-sql",
+          "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
+          }
+        },
+        {
+          "address": "google_storage_bucket.my-test-bucket",
+          "mode": "managed",
+          "type": "google_storage_bucket",
+          "name": "my-test-bucket",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "cors": [
+
+            ],
+            "encryption": [
+
+            ],
+            "force_destroy": false,
+            "labels": null,
+            "lifecycle_rule": [
+
+            ],
+            "location": "EU",
+            "logging": [
+
+            ],
+            "name": "test-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_compute_disk.my-disk-resource",
+      "mode": "managed",
+      "type": "google_compute_disk",
+      "name": "my-disk-resource",
+      "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": {
+            "disk-label-key-a": "disk-label-val-a"
+          },
+          "name": "my-disk",
+          "project": "foobar",
+          "snapshot": null,
+          "source_image_encryption_key": [
+
+          ],
+          "source_snapshot_encryption_key": [
+
+          ],
+          "timeouts": null,
+          "type": "pd-ssd",
+          "zone": "projects/foobar/global/zones/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,
+          "physical_block_size_bytes": true,
+          "self_link": true,
+          "size": true,
+          "source_image_encryption_key": [
+
+          ],
+          "source_image_id": true,
+          "source_snapshot_encryption_key": [
+
+          ],
+          "source_snapshot_id": true,
+          "users": true
+        }
+      }
+    },
+    {
+      "address": "google_compute_firewall.my-test-firewall",
+      "mode": "managed",
+      "type": "google_compute_firewall",
+      "name": "my-test-firewall",
+      "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": "my-test-firewall",
+          "network": "default",
+          "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_instance.my-test-instance",
+      "mode": "managed",
+      "type": "google_compute_instance",
+      "name": "my-test-instance",
+      "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-8-jessie-v20170523"
+                }
+              ]
+            }
+          ],
+          "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": "echo hi > /test.txt",
+          "min_cpu_platform": null,
+          "name": "my-instance",
+          "network_interface": [
+            {
+              "access_config": [
+                {
+                  "public_ptr_domain_name": null
+                }
+              ],
+              "alias_ip_range": [
+
+              ],
+              "network": "default"
+            }
+          ],
+          "project": "foobar",
+          "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": "projects/foobar/global/zones/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
+            }
+          ],
+          "scheduling": true,
+          "scratch_disk": [
+            {
+            }
+          ],
+          "self_link": true,
+          "service_account": [
+            {
+              "email": true,
+              "scopes": [
+                false,
+                false,
+                false
+              ]
+            }
+          ],
+          "shielded_instance_config": [
+
+          ],
+          "tags": [
+            false,
+            false
+          ],
+          "tags_fingerprint": true
+        }
+      }
+    },
+    {
+      "address": "google_folder_iam_binding.my-test-folder-binding",
+      "mode": "managed",
+      "type": "google_folder_iam_binding",
+      "name": "my-test-folder-binding",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "folder": "my-folder",
+          "members": [
+            "user:alice@example.com"
+          ],
+          "role": "roles/browser"
+        },
+        "after_unknown": {
+          "etag": true,
+          "id": true,
+          "members": [
+            false
+          ]
+        }
+      }
+    },
+    {
+      "address": "google_folder_iam_member.my-test-folder-member",
+      "mode": "managed",
+      "type": "google_folder_iam_member",
+      "name": "my-test-folder-member",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "folder": "my-folder",
+          "member": "user:jane@example.com",
+          "role": "roles/viewer"
+        },
+        "after_unknown": {
+          "etag": true,
+          "id": true
+        }
+      }
+    },
+    {
+      "address": "google_folder_iam_policy.my-test-folder-policy",
+      "mode": "managed",
+      "type": "google_folder_iam_policy",
+      "name": "my-test-folder-policy",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "folder": "my-folder",
+          "policy_data": "{\"bindings\":[{\"members\":[\"user:bob@example.com\"],\"role\":\"roles/editor\"}]}"
+        },
+        "after_unknown": {
+          "etag": true,
+          "id": true
+        }
+      }
+    },
+    {
+      "address": "google_organization_iam_binding.my-test-org-binding",
+      "mode": "managed",
+      "type": "google_organization_iam_binding",
+      "name": "my-test-org-binding",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "members": [
+            "user:alice@example.com"
+          ],
+          "org_id": "123456789",
+          "role": "roles/browser"
+        },
+        "after_unknown": {
+          "etag": true,
+          "id": true,
+          "members": [
+            false
+          ]
+        }
+      }
+    },
+    {
+      "address": "google_organization_iam_member.my-test-org-member",
+      "mode": "managed",
+      "type": "google_organization_iam_member",
+      "name": "my-test-org-member",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "member": "user:jane@example.com",
+          "org_id": "123456789",
+          "role": "roles/viewer"
+        },
+        "after_unknown": {
+          "etag": true,
+          "id": true
+        }
+      }
+    },
+    {
+      "address": "google_organization_iam_policy.my-test-org-policy",
+      "mode": "managed",
+      "type": "google_organization_iam_policy",
+      "name": "my-test-org-policy",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "org_id": "123456789",
+          "policy_data": "{\"bindings\":[{\"members\":[\"user:bob@example.com\"],\"role\":\"roles/editor\"}]}"
+        },
+        "after_unknown": {
+          "etag": true,
+          "id": true
+        }
+      }
+    },
+    {
+      "address": "google_project.my-project-resource",
+      "mode": "managed",
+      "type": "google_project",
+      "name": "my-project-resource",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "auto_create_network": true,
+          "billing_account": "012345-567890-ABCDEF",
+          "labels": {
+            "project-label-key-a": "project-label-val-a"
+          },
+          "name": "My Project Name",
+          "org_id": "my-org",
+          "project_id": "my-project-id",
+          "timeouts": null
+        },
+        "after_unknown": {
+          "app_engine": true,
+          "folder_id": true,
+          "id": true,
+          "labels": {
+          },
+          "number": true,
+          "policy_data": true,
+          "policy_etag": true,
+          "skip_delete": true
+        }
+      }
+    },
+    {
+      "address": "google_project_iam_binding.my-test-project-binding",
+      "mode": "managed",
+      "type": "google_project_iam_binding",
+      "name": "my-test-project-binding",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "members": [
+            "user:alice@example.com"
+          ],
+          "project": "foobar",
+          "role": "roles/browser"
+        },
+        "after_unknown": {
+          "etag": true,
+          "id": true,
+          "members": [
+            false
+          ]
+        }
+      }
+    },
+    {
+      "address": "google_project_iam_member.my-test-project-member",
+      "mode": "managed",
+      "type": "google_project_iam_member",
+      "name": "my-test-project-member",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "member": "user:jane@example.com",
+          "project": "foobar",
+          "role": "roles/viewer"
+        },
+        "after_unknown": {
+          "etag": true,
+          "id": true
+        }
+      }
+    },
+    {
+      "address": "google_project_iam_policy.my-test-project-policy",
+      "mode": "managed",
+      "type": "google_project_iam_policy",
+      "name": "my-test-project-policy",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "authoritative": null,
+          "disable_project": null,
+          "policy_data": "{\"bindings\":[{\"members\":[\"user:bob@example.com\"],\"role\":\"roles/editor\"}]}",
+          "project": "foobar"
+        },
+        "after_unknown": {
+          "etag": true,
+          "id": true,
+          "restore_policy": true
+        }
+      }
+    },
+    {
+      "address": "google_sql_database_instance.my-test-sql",
+      "mode": "managed",
+      "type": "google_sql_database_instance",
+      "name": "my-test-sql",
+      "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
+            }
+          ]
+        }
+      }
+    },
+    {
+      "address": "google_storage_bucket.my-test-bucket",
+      "mode": "managed",
+      "type": "google_storage_bucket",
+      "name": "my-test-bucket",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "cors": [
+
+          ],
+          "encryption": [
+
+          ],
+          "force_destroy": false,
+          "labels": null,
+          "lifecycle_rule": [
+
+          ],
+          "location": "EU",
+          "logging": [
+
+          ],
+          "name": "test-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": [
+            {
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "prior_state": {
+    "format_version": "0.1",
+    "terraform_version": "0.12.6",
+    "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:bob@example.com"
+                  ],
+                  "role": "roles/editor"
+                }
+              ],
+              "id": "2315450596",
+              "policy_data": "{\"bindings\":[{\"members\":[\"user:bob@example.com\"],\"role\":\"roles/editor\"}]}"
+            }
+          }
+        ]
+      }
+    }
+  },
+  "configuration": {
+    "provider_config": {
+      "google": {
+        "name": "google",
+        "version_constraint": "~> 2.5",
+        "expressions": {
+        }
+      }
+    },
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_compute_disk.my-disk-resource",
+          "mode": "managed",
+          "type": "google_compute_disk",
+          "name": "my-disk-resource",
+          "provider_config_key": "google",
+          "expressions": {
+            "image": {
+              "constant_value": "projects/debian-cloud/global/images/debian-8-jessie-v20170523"
+            },
+            "labels": {
+              "constant_value": {
+                "disk-label-key-a": "disk-label-val-a"
+              }
+            },
+            "name": {
+              "constant_value": "my-disk"
+            },
+            "project": {
+              "constant_value": "foobar"
+            },
+            "type": {
+              "constant_value": "pd-ssd"
+            },
+            "zone": {
+              "constant_value": "projects/foobar/global/zones/us-central1-a"
+            }
+          },
+          "schema_version": 0
+        },
+        {
+          "address": "google_compute_firewall.my-test-firewall",
+          "mode": "managed",
+          "type": "google_compute_firewall",
+          "name": "my-test-firewall",
+          "provider_config_key": "google",
+          "expressions": {
+            "allow": [
+              {
+                "protocol": {
+                  "constant_value": "icmp"
+                }
+              },
+              {
+                "ports": {
+                  "constant_value": [
+                    "80",
+                    "8080",
+                    "1000-2000"
+                  ]
+                },
+                "protocol": {
+                  "constant_value": "tcp"
+                }
+              }
+            ],
+            "name": {
+              "constant_value": "my-test-firewall"
+            },
+            "network": {
+              "constant_value": "default"
+            },
+            "source_tags": {
+              "constant_value": [
+                "web"
+              ]
+            }
+          },
+          "schema_version": 1
+        },
+        {
+          "address": "google_compute_instance.my-test-instance",
+          "mode": "managed",
+          "type": "google_compute_instance",
+          "name": "my-test-instance",
+          "provider_config_key": "google",
+          "expressions": {
+            "boot_disk": [
+              {
+                "initialize_params": [
+                  {
+                    "image": {
+                      "constant_value": "projects/debian-cloud/global/images/debian-8-jessie-v20170523"
+                    }
+                  }
+                ]
+              }
+            ],
+            "machine_type": {
+              "constant_value": "n1-standard-1"
+            },
+            "metadata": {
+              "constant_value": {
+                "foo": "bar"
+              }
+            },
+            "metadata_startup_script": {
+              "constant_value": "echo hi > /test.txt"
+            },
+            "name": {
+              "constant_value": "my-instance"
+            },
+            "network_interface": [
+              {
+                "access_config": [
+                  {
+                  }
+                ],
+                "network": {
+                  "constant_value": "default"
+                }
+              }
+            ],
+            "project": {
+              "constant_value": "foobar"
+            },
+            "scratch_disk": [
+              {
+              }
+            ],
+            "service_account": [
+              {
+                "scopes": {
+                  "constant_value": [
+                    "userinfo-email",
+                    "compute-ro",
+                    "storage-ro"
+                  ]
+                }
+              }
+            ],
+            "tags": {
+              "constant_value": [
+                "foo",
+                "bar"
+              ]
+            },
+            "zone": {
+              "constant_value": "projects/foobar/global/zones/us-central1-a"
+            }
+          },
+          "schema_version": 6
+        },
+        {
+          "address": "google_folder_iam_binding.my-test-folder-binding",
+          "mode": "managed",
+          "type": "google_folder_iam_binding",
+          "name": "my-test-folder-binding",
+          "provider_config_key": "google",
+          "expressions": {
+            "folder": {
+              "constant_value": "my-folder"
+            },
+            "members": {
+              "constant_value": [
+                "user:alice@example.com"
+              ]
+            },
+            "role": {
+              "constant_value": "roles/browser"
+            }
+          },
+          "schema_version": 0
+        },
+        {
+          "address": "google_folder_iam_member.my-test-folder-member",
+          "mode": "managed",
+          "type": "google_folder_iam_member",
+          "name": "my-test-folder-member",
+          "provider_config_key": "google",
+          "expressions": {
+            "folder": {
+              "constant_value": "my-folder"
+            },
+            "member": {
+              "constant_value": "user:jane@example.com"
+            },
+            "role": {
+              "constant_value": "roles/viewer"
+            }
+          },
+          "schema_version": 0
+        },
+        {
+          "address": "google_folder_iam_policy.my-test-folder-policy",
+          "mode": "managed",
+          "type": "google_folder_iam_policy",
+          "name": "my-test-folder-policy",
+          "provider_config_key": "google",
+          "expressions": {
+            "folder": {
+              "constant_value": "my-folder"
+            },
+            "policy_data": {
+              "references": [
+                "data.google_iam_policy.admin"
+              ]
+            }
+          },
+          "schema_version": 0
+        },
+        {
+          "address": "google_organization_iam_binding.my-test-org-binding",
+          "mode": "managed",
+          "type": "google_organization_iam_binding",
+          "name": "my-test-org-binding",
+          "provider_config_key": "google",
+          "expressions": {
+            "members": {
+              "constant_value": [
+                "user:alice@example.com"
+              ]
+            },
+            "org_id": {
+              "constant_value": "123456789"
+            },
+            "role": {
+              "constant_value": "roles/browser"
+            }
+          },
+          "schema_version": 0
+        },
+        {
+          "address": "google_organization_iam_member.my-test-org-member",
+          "mode": "managed",
+          "type": "google_organization_iam_member",
+          "name": "my-test-org-member",
+          "provider_config_key": "google",
+          "expressions": {
+            "member": {
+              "constant_value": "user:jane@example.com"
+            },
+            "org_id": {
+              "constant_value": "123456789"
+            },
+            "role": {
+              "constant_value": "roles/viewer"
+            }
+          },
+          "schema_version": 0
+        },
+        {
+          "address": "google_organization_iam_policy.my-test-org-policy",
+          "mode": "managed",
+          "type": "google_organization_iam_policy",
+          "name": "my-test-org-policy",
+          "provider_config_key": "google",
+          "expressions": {
+            "org_id": {
+              "constant_value": "123456789"
+            },
+            "policy_data": {
+              "references": [
+                "data.google_iam_policy.admin"
+              ]
+            }
+          },
+          "schema_version": 0
+        },
+        {
+          "address": "google_project.my-project-resource",
+          "mode": "managed",
+          "type": "google_project",
+          "name": "my-project-resource",
+          "provider_config_key": "google",
+          "expressions": {
+            "billing_account": {
+              "constant_value": "012345-567890-ABCDEF"
+            },
+            "labels": {
+              "constant_value": {
+                "project-label-key-a": "project-label-val-a"
+              }
+            },
+            "name": {
+              "constant_value": "My Project Name"
+            },
+            "org_id": {
+              "constant_value": "my-org"
+            },
+            "project_id": {
+              "constant_value": "my-project-id"
+            }
+          },
+          "schema_version": 1
+        },
+        {
+          "address": "google_project_iam_binding.my-test-project-binding",
+          "mode": "managed",
+          "type": "google_project_iam_binding",
+          "name": "my-test-project-binding",
+          "provider_config_key": "google",
+          "expressions": {
+            "members": {
+              "constant_value": [
+                "user:alice@example.com"
+              ]
+            },
+            "project": {
+              "constant_value": "foobar"
+            },
+            "role": {
+              "constant_value": "roles/browser"
+            }
+          },
+          "schema_version": 0
+        },
+        {
+          "address": "google_project_iam_member.my-test-project-member",
+          "mode": "managed",
+          "type": "google_project_iam_member",
+          "name": "my-test-project-member",
+          "provider_config_key": "google",
+          "expressions": {
+            "member": {
+              "constant_value": "user:jane@example.com"
+            },
+            "project": {
+              "constant_value": "foobar"
+            },
+            "role": {
+              "constant_value": "roles/viewer"
+            }
+          },
+          "schema_version": 0
+        },
+        {
+          "address": "google_project_iam_policy.my-test-project-policy",
+          "mode": "managed",
+          "type": "google_project_iam_policy",
+          "name": "my-test-project-policy",
+          "provider_config_key": "google",
+          "expressions": {
+            "policy_data": {
+              "references": [
+                "data.google_iam_policy.admin"
+              ]
+            },
+            "project": {
+              "constant_value": "foobar"
+            }
+          },
+          "schema_version": 0
+        },
+        {
+          "address": "google_sql_database_instance.my-test-sql",
+          "mode": "managed",
+          "type": "google_sql_database_instance",
+          "name": "my-test-sql",
+          "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
+        },
+        {
+          "address": "google_storage_bucket.my-test-bucket",
+          "mode": "managed",
+          "type": "google_storage_bucket",
+          "name": "my-test-bucket",
+          "provider_config_key": "google",
+          "expressions": {
+            "location": {
+              "constant_value": "EU"
+            },
+            "name": {
+              "constant_value": "test-bucket"
+            },
+            "website": [
+              {
+                "main_page_suffix": {
+                  "constant_value": "index.html"
+                },
+                "not_found_page": {
+                  "constant_value": "404.html"
+                }
+              }
+            ]
+          },
+          "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:bob@example.com"
+                  ]
+                },
+                "role": {
+                  "constant_value": "roles/editor"
+                }
+              }
+            ]
+          },
+          "schema_version": 0
+        }
+      ]
+    }
+  }
+}
diff --git a/test/tf_templates/instance.tf b/test/tf_templates/instance.tf
index e442a700..7797dc35 100644
--- a/test/tf_templates/instance.tf
+++ b/test/tf_templates/instance.tf
@@ -24,10 +24,6 @@ resource "google_compute_instance" "my-test-instance" {
     }
   }
 
-  metadata = {
-    foo = "bar"
-  }
-
   metadata_startup_script = "echo hi > /test.txt"
 
   service_account {
diff --git a/tfgcv/planned_assets_test.go b/tfgcv/planned_assets_test.go
index 6182b74b..f841ecc9 100644
--- a/tfgcv/planned_assets_test.go
+++ b/tfgcv/planned_assets_test.go
@@ -1,17 +1,22 @@
 package tfgcv
 
 import (
+	"path/filepath"
 	"testing"
 
 	"github.com/GoogleCloudPlatform/terraform-validator/version"
 )
 
-const TF11PLAN string = "../test/read_planned_assets/tf11plan.tfplan"
-const TF12PLAN string = "../test/read_planned_assets/tf12plan.json"
+const (
+	testDataDir = "../test/read_planned_assets"
+	testProjectName = "gl-akopachevskyy-sql-db"
+	testAncestryName = "ancestry"
+)
+
 
 func TestReadPlannedAssets(t *testing.T) {
 	type args struct {
-		path     string
+		file     string
 		project  string
 		ancestry string
 	}
@@ -28,15 +33,23 @@ func TestReadPlannedAssets(t *testing.T) {
 		tests = append(tests, []testcase{
 			{
 				"Test TF12 and JSON plan",
-				args{TF12PLAN, "prj", "ancsetry"},
+				args{"tf12plan.json", testProjectName, testAncestryName},
 				2,
 				false,
 			},
+			// TODO: Add tf11plan.tfplan to the repository.
+			// See https://github.com/GoogleCloudPlatform/terraform-validator/issues/74
+			// {
+			// 	"Test TF12 and binary plan should error out",
+			// 	args{"tf11plan.tfplan", testProjectName, testAncestryName},
+			// 	0,
+			// 	true,
+			// },
 			{
-				"Test TF12 and binary plan",
-				args{TF12PLAN, "prj", "ancsetry"},
-				0,
-				true,
+				"Test TF12 with all coverage",
+				args{"tf12plan.allcoverage.json", "foobar", testAncestryName},
+				9,
+				false,
 			},
 		}...)
 	}
@@ -44,23 +57,30 @@ func TestReadPlannedAssets(t *testing.T) {
 	if version.Supported(version.TF11) {
 		tests = append(tests, []testcase{
 			{
-				"Test TF11 and JSON plan",
-				args{TF11PLAN, "prj", "ancsetry"},
+				"Test TF11 and json plan should error out",
+				args{"tf12plan.json", testProjectName, testAncestryName},
 				0,
 				true,
 			},
-			{
-				"Test TF11 and binary plan",
-				args{TF12PLAN, "prj", "ancsetry"},
-				2,
-				false,
-			},
+			// TODO: Add tf11plan.tfplan to the repository.
+			// See https://github.com/GoogleCloudPlatform/terraform-validator/issues/74
+			// {
+			// 	"Test TF11 and binary plan",
+			// 	args{"tf11plan.tfplan", testProjectName, testAncestryName},
+			// 	2,
+			// 	false,
+			// },
 		}...)
 	}
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := ReadPlannedAssets(tt.args.path, tt.args.project, tt.args.ancestry)
+			testFile := filepath.Join(testDataDir, tt.args.file)
+			var offline bool
+			if version.Supported(version.TF12) {
+				offline = true
+			}
+			got, err := ReadPlannedAssets(testFile, tt.args.project, tt.args.ancestry, offline)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("ReadPlannedAssets() error = %v, wantErr %v", err, tt.wantErr)
 				return