Repository: cloud-custodian/cloud-custodian

Issue:
c7n-left - terraform -  data is mixed into resources namespace

currently data resources are showing up in the same namespace as resources to be provisioned, they should be separated out.

ie.
```terraform
data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["925514488322"] # Canonical
}
```

will currently be targeted by a policy on resource: aws_ami

we should change this to instead target resource: data.aws_ami ideally or perhaps data_aws_ami (the later introduces provider ambiguity though, albeit both need special handling 🤷 ).



Discussion:
This appears to be a change in behavior due to a recent dependency upgrade.

Resolving patch:
diff --git a/tools/c7n_left/c7n_left/providers/terraform/graph.py b/tools/c7n_left/c7n_left/providers/terraform/graph.py
--- a/tools/c7n_left/c7n_left/providers/terraform/graph.py
+++ b/tools/c7n_left/c7n_left/providers/terraform/graph.py
@@ -16,14 +16,8 @@ def get_resources_by_type(self, types=()):
         if isinstance(types, str):
             types = (types,)
         for type_name, type_items in self.resource_data.items():
-            if types and type_name not in types:
+            if types and (type_name not in types and f"data.{type_name}" not in types):
                 continue
-            if type_name == "data":
-                for data_type, data_items in type_items.items():
-                    resources = []
-                    for name, data in data_items.items():
-                        resources.append(self.as_resource(name, data))
-                    yield "%s.%s" % (type_name, data_type), resources
             elif type_name == "moved":
                 yield type_name, [self.as_resource(type_name, d) for d in type_items]
             elif type_name == "locals":
@@ -31,11 +25,26 @@ def get_resources_by_type(self, types=()):
             elif type_name == "terraform":
                 yield type_name, [self.as_resource(type_name, d) for d in type_items]
             else:
+                data_resources = []
                 resources = []
-                for data in type_items:
-                    name = data["__tfmeta"]["path"]
-                    resources.append(self.as_resource(name, data))
-                yield type_name, resources
+                for item in type_items:
+                    name = item["__tfmeta"]["path"]
+                    resource = self.as_resource(name, item)
+                    if item['__tfmeta'].get('type', 'resource') == 'data':
+                        data_resources.append(resource)
+                    else:
+                        resources.append(resource)
+
+                if resources:
+                    if types and type_name in types:
+                        yield type_name, resources
+                    elif not types:
+                        yield type_name, resources
+                if data_resources:
+                    if types and f"data.{type_name}" in types:
+                        yield f"data.{type_name}", data_resources
+                    elif not types:
+                        yield f"data.{type_name}", data_resources
 
     def as_resource(self, name, data):
         data["__tfmeta"]["src_dir"] = self.src_dir
@@ -68,6 +77,8 @@ def resolve_refs(self, block, types=None):
         for rid in refs:
             r = self._id_map[rid]
             rtype = r["__tfmeta"]["label"]
+            if r["__tfmeta"].get("type") == "data":
+                rtype = f"data.{rtype}"
             if types and rtype not in types:
                 continue
             yield r


Test patch:
diff --git a/tools/c7n_left/tests/test_left.py b/tools/c7n_left/tests/test_left.py
--- a/tools/c7n_left/tests/test_left.py
+++ b/tools/c7n_left/tests/test_left.py
@@ -207,6 +207,26 @@ def test_graph_resolver_id():
     assert resolver.is_id_ref("a" * 36) is False
 
 
+def test_data_policy(policy_env):
+    policy_env.write_tf(
+        """
+data "aws_ami" "ubuntu" {
+  most_recent = true
+
+  filter {
+    name   = "name"
+    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
+  }
+
+  owners = ["099720109477"] # Canonical
+}
+        """
+    )
+    policy_env.write_policy({"name": "check-data", "resource": "terraform.data.aws_ami"})
+    results = policy_env.run()
+    assert len(results) == 1
+
+
 def test_moved_and_local(policy_env):
     # mostly for coverage
     policy_env.write_tf(
@@ -279,6 +299,43 @@ def test_taggable(policy_env):
     assert results[0].resource['name'] == 'Yada'
 
 
+def test_traverse_to_data(policy_env):
+    policy_env.write_tf(
+        """
+data "aws_ami" "ubuntu" {
+  most_recent = true
+
+  filter {
+    name   = "name"
+    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
+  }
+
+  owners = ["099720109477"] # Canonical
+}
+
+resource "aws_instance" "app" {
+  ami           = data.aws_ami.ubuntu.id
+  instance_type = "t3.micro"
+}
+        """
+    )
+    policy_env.write_policy(
+        {
+            "name": "check-image",
+            "resource": "terraform.aws_instance",
+            "filters": [
+                {"type": "traverse", "resources": "data.aws_ami", "attrs": [{"owners": "present"}]}
+            ],
+        }
+    )
+    graph = TerraformProvider().parse(policy_env.policy_dir)
+    assert not list(graph.get_resources_by_type('aws_ami'))
+    assert list(graph.get_resources_by_type('data.aws_ami'))
+
+    results = policy_env.run()
+    assert len(results) == 1
+
+
 def test_traverse_multi_resource_multi_set(tmp_path):
     resources = run_policy(
         {