Repository: aws-cloudformation/cfn-lint

Issue:
E1011 error on Fn::FindInMap
### CloudFormation Lint Version

1.3.6

### What operating system are you using?

MacOS, Alpine Linux

### Describe the bug

cfn-lint on FindInMap returns `E1011 'EnvironmentMapping' is not one of []`

### Expected behavior

No error

### Reproduction template

```json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "EBS Volume",
  "Resources": {
    "EBS": {
      "Type": "AWS::EC2::Volume",
      "Properties": {
        "AutoEnableIO": false,
        "AvailabilityZone": { "Ref": "AvailabilityZone" },
        "Encrypted": true,
        "KmsKeyId": { "Fn::FindInMap": ["EnvironmentMapping", "kms", "key"] },
        "VolumeType": "st1",
        "Size": 512
      },
      "DeletionPolicy": "Snapshot",
      "UpdateReplacePolicy" : "Retain"
    }
  },
  "Mappings": {
    "EnvironmentMapping": {
      "Fn::Transform": {
        "Name": "AWS::Include",
        "Parameters": {
          "Location": {
            "Fn::Sub": "s3://my-bucket/snippets/${Environment}.json"
          }
        }
      }
    }
  },
  "Outputs": {
    "EBSID": {
      "Description": "Physical ID of EBS Volume",
      "Value": { "Ref": "EBS" }
    }
  },
  "Parameters": {
    "AvailabilityZone": {
      "Description": "Availability Zone for EBS volume",
      "Type": "String",
      "AllowedValues": ["us-west-2a", "us-west-2b", "us-west-2c"],
      "Default": "us-west-2a"
    },
    "Environment": {
      "Description": "Defines the environment that this stack lives in",
      "Type": "String",
      "AllowedValues": [
        "development",
        "test",
        "qa",
        "prod-internal",
        "management"
      ],
      "Default": "test"
    }
  }
}

```


Discussion:
I'll take a look into it. By quick a look the Fn::Transform is messing with the logic.  We don't fully support Fn::Transform.  Clearing up this issue may result in another error so need to think if we can handle a transform here.
@kddejong  - Note that this was working in 1.3.4 so it looks like a regression happened >=1.3.5
I'm going to add some logic to handle Fn::Transform inside mappings.  This will prevent the resolvers from failing and throwing the error you are seeing.
v0 version of these errors for reference.  This should allow us to do a lot better than we were doing in v0.  We can now reasonably handle Fn::Transforms inside of Mappings.

```
W1011 FindInMap first key "kms" doesn't exist in map "EnvironmentMapping" at Resources/EBS/Properties/KmsKeyId/Fn::FindInMap
local/issue/3416.json:11:65

E7001 Mapping EnvironmentMapping has invalid property at Parameters
local/issue/3416.json:23:11
```



Patch:
diff --git a/src/cfnlint/context/context.py b/src/cfnlint/context/context.py
index 7a095d8eea..99f818c435 100644
--- a/src/cfnlint/context/context.py
+++ b/src/cfnlint/context/context.py
@@ -384,11 +384,15 @@ class _MappingSecondaryKey:
         init=False, default_factory=dict
     )
     instance: InitVar[Any]
+    is_transform: bool = field(init=False, default=False)
 
     def __post_init__(self, instance) -> None:
         if not isinstance(instance, dict):
             raise ValueError("Secondary keys must be a object")
         for k, v in instance.items():
+            if k == "Fn::Transform":
+                self.is_transform = True
+                continue
             if isinstance(v, (str, list, int, float)):
                 self.keys[k] = v
             else:
@@ -408,12 +412,16 @@ class Map:
 
     keys: dict[str, _MappingSecondaryKey] = field(init=False, default_factory=dict)
     resource: InitVar[Any]
+    is_transform: bool = field(init=False, default=False)
 
     def __post_init__(self, mapping) -> None:
         if not isinstance(mapping, dict):
             raise ValueError("Mapping must be a object")
         for k, v in mapping.items():
-            self.keys[k] = _MappingSecondaryKey(v)
+            if k == "Fn::Transform":
+                self.is_transform = True
+            else:
+                self.keys[k] = _MappingSecondaryKey(v)
 
     def find_in_map(self, top_key: str, secondary_key: str) -> Iterator[Any]:
         if top_key not in self.keys:
diff --git a/src/cfnlint/jsonschema/_resolvers_cfn.py b/src/cfnlint/jsonschema/_resolvers_cfn.py
index 623cddca05..77edff1b6b 100644
--- a/src/cfnlint/jsonschema/_resolvers_cfn.py
+++ b/src/cfnlint/jsonschema/_resolvers_cfn.py
@@ -85,6 +85,8 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:
 
         top_key = map.keys.get(instance[1])
         if top_key is None:
+            if map.is_transform:
+                return
             if not default_value_found:
                 yield None, validator, ValidationError(
                     (
@@ -98,6 +100,8 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:
 
         value = top_key.keys.get(instance[2])
         if value is None:
+            if top_key.is_transform:
+                return
             if not default_value_found:
                 yield value, validator, ValidationError(
                     (


Test patch:
diff --git a/test/unit/module/context/test_mappings.py b/test/unit/module/context/test_mappings.py
index 14b36cff6c..edf67b30f3 100644
--- a/test/unit/module/context/test_mappings.py
+++ b/test/unit/module/context/test_mappings.py
@@ -24,3 +24,13 @@ def test_mapping_value(name, get_key_1, get_key_2, expected):
             list(mapping.find_in_map(get_key_1, get_key_2))
     else:
         assert list(mapping.find_in_map(get_key_1, get_key_2)) == expected
+
+
+def test_transforms():
+    mapping = Map({"A": {"Fn::Transform": "C"}})
+
+    assert mapping.keys.get("A").is_transform is True
+
+    mapping = Map({"Fn::Transform": {"B": "C"}})
+
+    assert mapping.is_transform is True
diff --git a/test/unit/module/jsonschema/test_resolvers_cfn.py b/test/unit/module/jsonschema/test_resolvers_cfn.py
index 93fa0409fb..4d08460123 100644
--- a/test/unit/module/jsonschema/test_resolvers_cfn.py
+++ b/test/unit/module/jsonschema/test_resolvers_cfn.py
@@ -240,7 +240,10 @@ def test_invalid_functions(name, instance, response):
                     None,
                     deque([]),
                     ValidationError(
-                        ("'bar' is not one of ['foo']"),
+                        (
+                            "'bar' is not one of ['foo', "
+                            "'transformFirstKey', 'transformSecondKey']"
+                        ),
                         path=deque(["Fn::FindInMap", 0]),
                     ),
                 )
@@ -304,6 +307,16 @@ def test_invalid_functions(name, instance, response):
             {"Fn::FindInMap": ["foo", "first", "third", {"DefaultValue": "default"}]},
             [("default", deque([4, "DefaultValue"]), None)],
         ),
+        (
+            "Valid FindInMap with a transform on first key",
+            {"Fn::FindInMap": ["transformFirstKey", "first", "third"]},
+            [],
+        ),
+        (
+            "Valid FindInMap with a transform on second key",
+            {"Fn::FindInMap": ["transformSecondKey", "first", "third"]},
+            [],
+        ),
         (
             "Valid Sub with a resolvable values",
             {"Fn::Sub": ["${a}-${b}", {"a": "foo", "b": "bar"}]},
@@ -319,6 +332,8 @@ def test_invalid_functions(name, instance, response):
 def test_valid_functions(name, instance, response):
     context = Context()
     context.mappings["foo"] = Map({"first": {"second": "bar"}})
+    context.mappings["transformFirstKey"] = Map({"Fn::Transform": {"second": "bar"}})
+    context.mappings["transformSecondKey"] = Map({"first": {"Fn::Transform": "bar"}})
 
     _resolve(name, instance, response, context=context)