Repository: aws-cloudformation/cfn-lint

Issue:
False Positive for Primary Identifiers E3019 When using Fn::ForEach and value from a Mapping
### CloudFormation Lint Version

cfn-lint 1.3.3

### What operating system are you using?

Arch

### Describe the bug

Using Fn::ForEach to generate Resources cfn-lint reports that the PrimaryIdentifier is not unique across resources. I've only tried this with `AWS::IAM::Role` so not sure how many Resource Types this affects.

```shell
E3019 Primary identifiers {'RoleName': 'Bravo'} should have unique values across the resources {'A', 'B'}
```

### Expected behavior

When using Fn::ForEach cfn-lint should evaluate that PrimaryIdentifiers are unique.

### Reproduction template

```yaml
Mappings:
  Roles:
    A:
      RoleName: Alpha
    B:
      RoleName: Bravo


Transform: AWS::LanguageExtensions

Resources:
  Fn::ForEach::TestRole:
    - Role
    - - A
      - B
    - ${Role}:
        Type: AWS::IAM::Role
        Properties:
          RoleName: !FindInMap
            - Roles
            - !Ref Role
            - RoleName
          AssumeRolePolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Principal:
                  AWS: '*'
                Effect: Allow
                Action: sts:AssumeRole
```

This only seems to occur when using a Mapping - the following does **not** produce an E3019

```yaml
Transform: AWS::LanguageExtensions

Resources:
  Fn::ForEach::TestRole:
    - Role
    - - A
      - B
    - ${Role}:
        Type: AWS::IAM::Role
        Properties:
          RoleName: !Ref Role
          AssumeRolePolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Principal:
                  AWS: '*'
                Effect: Allow
                Action: sts:AssumeRole
``` 




Discussion:

Patch:
diff --git a/src/cfnlint/template/transforms/_language_extensions.py b/src/cfnlint/template/transforms/_language_extensions.py
index 045a5714df..3733542626 100644
--- a/src/cfnlint/template/transforms/_language_extensions.py
+++ b/src/cfnlint/template/transforms/_language_extensions.py
@@ -364,7 +364,7 @@ def value(
 
         if mapping:
             try:
-                t_map[1].value(cfn)
+                t_map[1].value(cfn, params, only_params)
             except _ResolveError:
                 try:
                     t_map[2].value(cfn)


Test patch:
diff --git a/test/unit/module/template/transforms/test_language_extensions.py b/test/unit/module/template/transforms/test_language_extensions.py
index a5bb10bfc0..7c9e31d26e 100644
--- a/test/unit/module/template/transforms/test_language_extensions.py
+++ b/test/unit/module/template/transforms/test_language_extensions.py
@@ -188,7 +188,12 @@ def setUp(self) -> None:
                             "Development": "1",
                             "Stage": ["1", "2"],
                             "Production": ["1", "2", "3"],
-                        }
+                        },
+                        "Instances": {
+                            "Development": "A",
+                            "Stage": ["A", "B"],
+                            "Production": ["A", "B", "C"],
+                        },
                     },
                 },
             }
@@ -289,6 +294,17 @@ def test_find_in_map_values_without_default(self):
         with self.assertRaises(_ResolveError):
             map.value(self.cfn, None, False, False)
 
+    def test_second_key_resolution(self):
+        map = _ForEachValueFnFindInMap("a", ["Config", {"Ref": "Value"}, "Production"])
+
+        self.assertEqual(
+            map.value(self.cfn, {"Value": "DBInstances"}, False, True), ["1", "2", "3"]
+        )
+
+        self.assertEqual(
+            map.value(self.cfn, {"Value": "Instances"}, False, True), ["A", "B", "C"]
+        )
+
     def test_find_in_map_values_without_default_resolve_error(self):
         map = _ForEachValueFnFindInMap(
             "a", ["Bucket", "Production", {"Ref": "SSMParameter"}]