Repository: aws-cloudformation/cfn-lint

Issue:
Feature Request: `AWS::CodeBuild::Project` - recognize if 2 resources have the same `Name`
### Is this feature request related to a new rule or cfn-lint capabilities?

_No response_

### Describe the feature you'd like to request

It would be great if cfn-lint would recognize if a `AWS::CodeBuild::Project` has the same name as another `AWS::CodeBuild::Project` - which will cause a deployment failure:

```yaml
Project1:
  Type: AWS::CodeBuild::Project
  Properties:
    Name: myProjectName
    ServiceRole: !GetAtt ServiceRole.Arn
    Artifacts:
      Type: no_artifacts
    Environment:
      Type: LINUX_CONTAINER
      ComputeType: BUILD_GENERAL1_SMALL
      Image: aws/codebuild/java:openjdk-8
      EnvironmentVariables:
      - Name: varName
        Type: varType
        Value: varValue
    Source:
      Location: codebuild-demo-test/0123ab9a371ebf0187b0fe5614fbb72c
      Type: S3
    TimeoutInMinutes: 10

Project2:
  Type: AWS::CodeBuild::Project
  Properties:
    Name: myProjectName
    ServiceRole: !GetAtt ServiceRole.Arn
    Artifacts:
      Type: no_artifacts
    Environment:
      Type: LINUX_CONTAINER
      ComputeType: BUILD_GENERAL1_SMALL
      Image: aws/codebuild/java:openjdk-8
      EnvironmentVariables:
      - Name: varName
        Type: varType
        Value: varValue
    Source:
      Location: codebuild-demo-test/0123ab9a371ebf0187b0fe5614fbb72c
      Type: S3
    TimeoutInMinutes: 10
```

### Describe the solution you'd like

cfn-lint throws an error that both resources have the same name.

### Additional context

_No response_

### Is this something that you'd be interested in working on?

- [ ] 👋 I may be able to implement this feature request

### Would this feature include a breaking change?

- [ ] ⚠️ This feature might incur a breaking change

Discussion:
@kddejong: Going to need a way to add this validation logically. The unfortunate part is we do this validation for [primaryIdentifier](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html#schema-properties-primaryidentifier) but for this resource this field is a read only property which means its not the name.  

Solution patch:
diff --git a/src/cfnlint/rules/resources/PrimaryIdentifiers.py b/src/cfnlint/rules/resources/PrimaryIdentifiers.py
index bf3305c44..3c9cc7a31 100644
--- a/src/cfnlint/rules/resources/PrimaryIdentifiers.py
+++ b/src/cfnlint/rules/resources/PrimaryIdentifiers.py
@@ -31,6 +31,22 @@ class PrimaryIdentifiers(CloudFormationLintRule):
     source_url = "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html"
     tags = ["parameters", "resources"]
 
+    def __init__(self):
+        super().__init__()
+
+        self.config_definition = {
+            "exceptions": {
+                "default": {
+                    "AWS::CodeBuild::Project": {
+                        "primaryIdentifier": ["/properties/Name"]
+                    }
+                },
+                "type": "object",
+                "itemtype": "object",
+            }
+        }
+        self.configure()
+
     def _merge_conditions(
         self, conditions1: Dict[str, Set[bool]], conditions2: Dict[str, Set[bool]]
     ):
@@ -139,16 +155,21 @@ class PrimaryIdentifiers(CloudFormationLintRule):
         matches = []
         for t in tS:
             try:
-                schema = PROVIDER_SCHEMA_MANAGER.get_resource_schema(cfn.regions[0], t)
+                if t in self.config.get("exceptions", {}):
+                    schema = self.config.get("exceptions", {}).get(t)
+                else:
+                    schema = PROVIDER_SCHEMA_MANAGER.get_resource_schema(
+                        cfn.regions[0], t
+                    ).schema
 
                 # we are worried about primary identifiers that can be set
                 # by the customer so if any primary identifiers are read
                 # only we have to skip evaluation
-                primary_ids = schema.schema.get("primaryIdentifier", [])
+                primary_ids = schema.get("primaryIdentifier", [])
                 if not primary_ids:
                     continue
 
-                read_only_ids = schema.schema.get("readOnlyProperties", [])
+                read_only_ids = schema.get("readOnlyProperties", [])
 
                 if any(id in read_only_ids for id in primary_ids):
                     continue


Test patch:
diff --git a/test/fixtures/templates/bad/resources/primary_identifiers.yaml b/test/fixtures/templates/bad/resources/primary_identifiers.yaml
index 3aea9828c..632c424ad 100644
--- a/test/fixtures/templates/bad/resources/primary_identifiers.yaml
+++ b/test/fixtures/templates/bad/resources/primary_identifiers.yaml
@@ -162,3 +162,42 @@ Resources:
     Type: MyCompany::MODULE
     Properties:
       Attribute2: test
+  Project1:
+    Type: AWS::CodeBuild::Project
+    Properties:
+      Name: myProjectName
+      ServiceRole: arn
+      Artifacts:
+        Type: no_artifacts
+      Environment:
+        Type: LINUX_CONTAINER
+        ComputeType: BUILD_GENERAL1_SMALL
+        Image: aws/codebuild/java:openjdk-8
+        EnvironmentVariables:
+        - Name: varName
+          Type: varType
+          Value: varValue
+      Source:
+        Location: codebuild-demo-test/0123ab9a371ebf0187b0fe5614fbb72c
+        Type: S3
+      TimeoutInMinutes: 10
+
+  Project2:
+    Type: AWS::CodeBuild::Project
+    Properties:
+      Name: myProjectName
+      ServiceRole: arn
+      Artifacts:
+        Type: no_artifacts
+      Environment:
+        Type: LINUX_CONTAINER
+        ComputeType: BUILD_GENERAL1_SMALL
+        Image: aws/codebuild/java:openjdk-8
+        EnvironmentVariables:
+        - Name: varName
+          Type: varType
+          Value: varValue
+      Source:
+        Location: codebuild-demo-test/0123ab9a371ebf0187b0fe5614fbb72c
+        Type: S3
+      TimeoutInMinutes: 10
diff --git a/test/unit/rules/resources/test_primary_identifiers.py b/test/unit/rules/resources/test_primary_identifiers.py
index c49795f20..54d4d76e9 100644
--- a/test/unit/rules/resources/test_primary_identifiers.py
+++ b/test/unit/rules/resources/test_primary_identifiers.py
@@ -27,5 +27,5 @@ class TestPrimaryIdentifiers(BaseRuleTestCase):
     def test_file_negative_alias(self):
         """Test failure"""
         self.helper_file_negative(
-            "test/fixtures/templates/bad/resources/primary_identifiers.yaml", 8
+            "test/fixtures/templates/bad/resources/primary_identifiers.yaml", 10
         )