aws-cloudformation / cfn-lint

CloudFormation Linter
MIT No Attribution
2.44k stars 592 forks source link

Validation of Properties on EC2 code modules #3472

Closed randybasrs closed 3 months ago

randybasrs commented 3 months ago

cfn-lint: 1.5.0 operation system: AWS serverless & Windows 11

Resources:
  instance:
    Type: ASRS::EC2::EC2::MODULE
    Properties:
      name: DEVSERVER
      instanceType: r5.2xlarge
      imageID: "{resolveEC2Im3ageID:windowsServer}"
      subnetID: "{{resolve:ssm:devServerSubnetID}}"
      securityGroupIDs:
        - asdfasdfasdfasdf
        - !Ref devTeamSecurityGroup
        - !Ref baselineSecurityGroupID
        - "{{resolve:ssm:baselineSecurityGroupID}}"
      numberOfAdditionalVolumes: 1
      volume2Size: 80
  devTeamSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security Group used for accessing devTeam instance
      GroupName: devTeam
      VpcId: "{{resolve:ssm:devVPCID}}"
      SecurityGroupIngress:
        - Description: RDP
          CidrIp: 10.0.0.0/24
          FromPort: 3389
          ToPort: 3389
          IpProtocol: tcp

This EC2 module seems to lint successfully no matter how I alter the schema for the code module or add patches to the override_spec. The one thing I was able to get to lint properly was the primaryIdentifier as was discussed in #3446 , thank you again!

The 2 basic changes I'm looking to apply are a regex validation of {resolveEC2ImageID:.+} to the imageID similar to what I was doing in #3460 for the regular AWS::EC2::Instance resource type and to validate the securityGroupIDs as valid IDs or dynamic references like a regular EC2 instance.

This is the whole test template; the baselineSecurityGroupID !Ref is not valid in the above template and should fail validation due to the references being invalid. asdfasdfasdfasdf should also fail validation according to the schemas/override_specs I've tried to supply. the SSM resolution and the devTeamSecurityGroup !Ref should both pass validation, again like a normal EC2 instance.

I am trying to use the Amazon provided schema as a reference for my changes, but in #3446 it was mentioned that the module validation is minimal by design. Here's the part of that schema I've been trying to insert into the code module schema or use in the override spec for patching (adding/removing the property/parameter, etc). I did update the casing from below to match the casing on the code module in both the schema and the template as shown above.

    "SecurityGroupIds": {
      "format": "AWS::EC2::SecurityGroup.Ids",
      "insertionOrder": false,
      "items": {
        "format": "AWS::EC2::SecurityGroup.GroupId",
        "type": "string"
      },
      "type": "array",
      "uniqueItems": false
    }

So I suppose the question is: can I achieve the type of validation I'm looking for with code modules to ensure our developers are providing valid inputs? I do plan to add other validation like the length of the name etc, but I figure if I can get these 2 properties to lint the rest should be easy to add.

kddejong commented 3 months ago

If I create a registry document like

{
    "typeName": "ASRS::EC2::EC2::MODULE",
    "description": "A fun little resource",
    "additionalProperties": false,
    "properties": {
      "name": {
        "type" : "string"
      },
      "instanceType": {
        "type" : "string"
      },
      "imageID": {
        "type" : "string"
      },
      "subnetID": {
        "type" : "string"
      },
      "securityGroupIDs": {
        "format": "AWS::EC2::SecurityGroup.Ids",
        "insertionOrder": false,
        "items": {
          "format": "AWS::EC2::SecurityGroup.GroupId",
          "type": "string"
        },
        "type": "array",
        "uniqueItems": false
      },
      "numberOfAdditionalVolumes": {
        "type" : "integer"
      },
      "volume2Size": {
        "type" : "integer"
      }
    },
    "primaryIdentifier": []
  }

I should be able to do cfn-lint -s path/to/folder/with/schema/files -- template.yaml

This will output:

1020 'baselineSecurityGroupID' is not one of ['instance', 'devTeamSecurityGroup', 'AWS::AccountId', 'AWS::NoValue', 'AWS::NotificationARNs', 'AWS::Partition', 'AWS::Region', 'AWS::StackId', 'AWS::StackName', 'AWS::URLSuffix']
local/issue/3472/template.yaml:9:7

E1150 'asdfasdfasdfasdf' is not a 'AWS::EC2::SecurityGroup.GroupId'
local/issue/3472/template.yaml:10:11
randybasrs commented 3 months ago

I must have been very tired trying variations of this exact thing, because this worked perfectly. I was also able to update my module schema.json (though i have yet to test the module registration process/usage):

{
  "typeName": "ASRS::EC2::EC2::MODULE",
  "description": "Schema for Module Fragment of type ASRS::EC2::EC2::MODULE",
  "properties": {
    "securityGroupIDs": {
      "format": "AWS::EC2::SecurityGroup.Ids",
      "insertionOrder": false,
      "items": {
        "format": "AWS::EC2::SecurityGroup.GroupId",
        "type": "string"
      }
    },
    "Parameters": {
      "type": "object",
      "properties": {
        "name": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "The name tag for the EC2 instance."
        },
        "imageID": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Base AMI for the EC2 instance."
        },
        "instanceType": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "EC2 instance type."
        },
        "subnetID": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "The ID of the subnet to launch the instance into."
        },
        "osVolumeSize": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of the volume that has the OS."
        },
        "securityGroupIDs": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "The IDs of the security groups."
        },
        "instanceKeyName": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Key pair name"
        },
        "ebsOptimized": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Indicates whether the instance is optimized for Amazon EBS I/O."
        }
      }
    },
    "Resources": {
      "properties": {
        "Instance": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string",
              "const": "AWS::EC2::Instance"
            },
            "Properties": {
              "type": "object"
            }
          }
        }
      },
      "type": "object",
      "additionalProperties": false
    }
  },
  "primaryIdentifier": [
    "/properties/name"
  ],
  "additionalProperties": true
}

Thanks again.

randybasrs commented 3 months ago

After testing, the code module fails to deploy with the primary identifier specified, however the module still deploys with the subnetIDs property. I suppose, as you said, if I define a schema specifically for lint validation that will definitely solve the problem. I tried the following override spec to try and add the validation on top of the schema, but it doesn't seem to apply:

This may be blurring the lines at this point, but I did notice your name on the AWS article talking about publishing CodeModules as well. Is there a way to either make the CodeModule happy with this primaryIdentifier in the schema or a way to get the override to work? I can't quite seem to close this last issue out.

Here be Lotsa Code This is the module schema without primaryIdentifier including all the extra junk i cut out for sanity before but for completeness now. We ... do have some instances with almost 26 volumes. ```json { "typeName": "ASRS::EC2::EC2::MODULE", "description": "Schema for Module Fragment of type ASRS::EC2::EC2::MODULE", "properties": { "securityGroupIDs": { "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { "format": "AWS::EC2::SecurityGroup.GroupId", "type": "string" } }, "Parameters": { "type": "object", "properties": { "name": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "The name tag for the EC2 instance." }, "imageID": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Base AMI for the EC2 instance." }, "instanceType": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "EC2 instance type." }, "subnetID": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "The ID of the subnet to launch the instance into." }, "osVolumeSize": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of the volume that has the OS." }, "securityGroupIDs": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "The IDs of the security groups." }, "instanceKeyName": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Key pair name" }, "ebsOptimized": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Indicates whether the instance is optimized for Amazon EBS I/O." }, "numberOfAdditionalVolumes": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Additional volumes in addition to OS volume. Max is 25." }, "volume2Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 2" }, "volume3Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 3" }, "volume4Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 4" }, "volume5Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 5" }, "volume6Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 6" }, "volume7Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 7" }, "volume8Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 8" }, "volume9Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 9" }, "volume10Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 10" }, "volume11Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 11" }, "volume12Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 12" }, "volume13Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 13" }, "volume14Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 14" }, "volume15Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 15" }, "volume16Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 16" }, "volume17Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 17" }, "volume18Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 18" }, "volume19Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 19" }, "volume20Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 20" }, "volume21Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 21" }, "volume22Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 22" }, "volume23Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 23" }, "volume24Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 24" }, "volume25Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 25" }, "volume26Size": { "type": "object", "properties": { "Type": { "type": "string" }, "Description": { "type": "string" } }, "required": [ "Type", "Description" ], "description": "Size of volume 26" } } }, "Resources": { "properties": { "Instance": { "type": "object", "properties": { "Type": { "type": "string", "const": "AWS::EC2::Instance" }, "Properties": { "type": "object" } } } }, "type": "object", "additionalProperties": false } }, "additionalProperties": true } ``` Here's the override spec that does work as the ImageId for normal EC2s validates properly but the EC2 module doesn't: ```json { "Patches": { "AWS::EC2::Instance": [ { "op": "remove", "path": "/properties/ImageId/format", "value": "true" }, { "op": "add", "path": "/properties/ImageId/pattern", "value": "{resolveEC2ImageID:.+}" } ] }, "ASRS::EC2::EC2::Module": [ { "op": "remove", "path": "/primaryIdentifier", "value": "true" }, { "op": "add", "path": "/primaryIdentifier", "value": "/properties/Name" } ] } ``` I tried it without the remove section as there should be no primaryIdentifier but also with it just in case. I also tried a few variations of parameters/properties, but maybe I'm missing something simple again.
kddejong commented 3 months ago

Haven't had a chance to fully test this but...

/primaryIdentifier needs to be an array of pointers

{
      "op": "add",
      "path": "/primaryIdentifier",
      "value": "/properties/Name"
    }

should be

{
      "op": "add",
      "path": "/primaryIdentifier",
      "value": ["/properties/Name"]
    }

or

{
      "op": "replace",
      "path": "/primaryIdentifier",
      "value": ["/properties/Name"]
    }
randybasrs commented 3 months ago

The "replace" operation fails with the error that it can't replace a non-existent object which makes sense in this case.

The "add" operation results in a successful lint, though it should fail due to duplicate primaryIdentifier(s).

Here's my altered override to correctly identify the primary key as an array.

{
  "Patches": {
    "AWS::EC2::Instance": [
      {
        "op": "remove",
        "path": "/properties/ImageId/format",
        "value": "true"
      },
      {
        "op": "add",
        "path": "/properties/ImageId/pattern",
        "value": "{resolveEC2ImageID:.+}"
      }
    ],
    "ASRS::EC2::EC2::Module": [
      {
        "op": "add",
        "path": "/primaryIdentifier",
        "value": ["/properties/Name"]
      }
    ],
    "ASRS::CodePipeline::NormalStages::MODULE": [
      {
        "op": "add",
        "path": "/primaryIdentifier",
        "value": ["/properties/PipelineName"]
      }
    ]
  }
}

Test template:

Resources:
  PIPELINEA:
    Type: ASRS::CodePipeline::NormalStages::MODULE
    Properties:
      Description: Center target in the reticle, run the pipeline.
      PipelineName: PIPELINEA
      RepositoryName: Summer
      TargetTemplate: 2029.yaml
      TargetAccount: !Sub ${AWS::AccountId}
  PIPELINEB:
    Type: ASRS::CodePipeline::NormalStages::MODULE
    Properties:
      Description: Pipelines for fun and profit
      PipelineName: PIPELINEA
      RepositoryName: Fall
      TargetTemplate: 2024.yaml
      TargetAccount: !Sub ${AWS::AccountId}

I'm using the pipeline template to test though hypothetically the 2 modules should function the same.

kddejong commented 3 months ago

Looking into. Think I was able to replicate this.

kddejong commented 3 months ago

It should be "replace" but since we were trying to patch before the registry schemas are loaded you are trying to patch the default "module" schema. Flipping the order on operations will have us loading the registry schemas before doing the patching.

kddejong commented 3 months ago

Another option I forgot about

{
      "op": "add",
      "path": "/primaryIdentifier/-",
      "value": "/properties/Name"
    }
randybasrs commented 3 months ago
{
  "Patches": {
    "AWS::EC2::Instance": [
      {
        "op": "remove",
        "path": "/properties/ImageId/format",
        "value": "true"
      },
      {
        "op": "add",
        "path": "/properties/ImageId/pattern",
        "value": "{resolveEC2ImageID:.+}"
      }
    ],
    "ASRS::CodePipeline::NormalStages::MODULE": [
      {
        "op": "add",
        "path": "/primaryIdentifier/-",
        "value": "/properties/PipelineName"
      }
    ]
  }
}

Yields:

Traceback (most recent call last):
  File "C:\Program Files\Python310\lib\site-packages\jsonpointer.py", line 288, in walk
    return doc[part]
KeyError: 'primaryIdentifier'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Program Files\Python310\lib\runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Program Files\Python310\lib\runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "C:\Program Files\Python310\Scripts\cfn-lint.exe\__main__.py", line 7, in <module>
  File "C:\Program Files\Python310\lib\site-packages\cfnlint\runner.py", line 428, in main
    runner = Runner(config)
  File "C:\Program Files\Python310\lib\site-packages\cfnlint\runner.py", line 217, in __init__
    PROVIDER_SCHEMA_MANAGER.patch(
  File "C:\Program Files\Python310\lib\site-packages\cfnlint\schema\manager.py", line 452, in patch
    self._patch(schema_patch, region)
  File "C:\Program Files\Python310\lib\site-packages\cfnlint\schema\manager.py", line 518, in _patch
    schema.patch(patches=patches)
  File "C:\Program Files\Python310\lib\site-packages\cfnlint\schema\_schema.py", line 50, in patch
    jsonpatch.JsonPatch(patches).apply(self.schema, in_place=True)
  File "C:\Program Files\Python310\lib\site-packages\jsonpatch.py", line 669, in apply
    obj = operation.apply(obj)
  File "C:\Program Files\Python310\lib\site-packages\jsonpatch.py", line 272, in apply
    subobj, part = self.pointer.to_last(obj)
  File "C:\Program Files\Python310\lib\site-packages\jsonpointer.py", line 196, in to_last
    doc = self.walk(doc, part)
  File "C:\Program Files\Python310\lib\site-packages\jsonpointer.py", line 291, in walk
    raise JsonPointerException("member '%s' not found in %s" % (part, doc))
jsonpointer.JsonPointerException: member 'primaryIdentifier' not found in {'additionalProperties': True, 'type': 'object', 'typeName': 'Module'}

If I add something like:

{
  "Patches": {
    "AWS::EC2::Instance": [
      {
        "op": "remove",
        "path": "/properties/ImageId/format",
        "value": "true"
      },
      {
        "op": "add",
        "path": "/properties/ImageId/pattern",
        "value": "{resolveEC2ImageID:.+}"
      }
    ],
    "ASRS::CodePipeline::NormalStages::MODULE": [
      {
        "op": "add",
        "path": "/primaryIdentifier",
        "value": "true"
      },
      {
        "op": "add",
        "path": "/primaryIdentifier/-",
        "value": "/properties/PipelineName"
      }
    ]
  }
}

I get:

Traceback (most recent call last):
  File "C:\Program Files\Python310\lib\runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Program Files\Python310\lib\runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "C:\Program Files\Python310\Scripts\cfn-lint.exe\__main__.py", line 7, in <module>
  File "C:\Program Files\Python310\lib\site-packages\cfnlint\runner.py", line 428, in main
    runner = Runner(config)
  File "C:\Program Files\Python310\lib\site-packages\cfnlint\runner.py", line 217, in __init__
    PROVIDER_SCHEMA_MANAGER.patch(
  File "C:\Program Files\Python310\lib\site-packages\cfnlint\schema\manager.py", line 452, in patch
    self._patch(schema_patch, region)
  File "C:\Program Files\Python310\lib\site-packages\cfnlint\schema\manager.py", line 518, in _patch
    schema.patch(patches=patches)
  File "C:\Program Files\Python310\lib\site-packages\cfnlint\schema\_schema.py", line 50, in patch
    jsonpatch.JsonPatch(patches).apply(self.schema, in_place=True)
  File "C:\Program Files\Python310\lib\site-packages\jsonpatch.py", line 669, in apply
    obj = operation.apply(obj)
  File "C:\Program Files\Python310\lib\site-packages\jsonpatch.py", line 294, in apply
    raise JsonPatchConflict("unable to fully resolve json pointer {0}, part {1}".format(self.location, part))
jsonpatch.JsonPatchConflict: unable to fully resolve json pointer /primaryIdentifier/-, part -

I tried to use replace after add and also tried encapsulating the value in [ ] to make it an array.

kddejong commented 3 months ago

Can you check under the mainline branch? I meant use "Replace" or the "-" after my PR was released. Sorry for the confusion.

randybasrs commented 3 months ago

No worries! I get no errors now, but the pipeline file above continues to lint successfully. I tried replace/add with /- and a few permutations of with/without array brackets and mix/maxing replace/add and /- and those brackets.

kddejong commented 3 months ago

I'm getting errors on

{
    "Patches": {
        "ASRS::EC2::EC2::MODULE": [
            {
                "op": "add",
                "path": "/primaryIdentifier/-",
                "value": "/properties/name"
            }
        ]
    }
}

and

Resources:
  instanc1e:
    Type: ASRS::EC2::EC2::MODULE
    Properties:
      name: DEVSERVER
      instanceType: r5.2xlarge
      imageID: "{resolveEC2Im3ageID:windowsServer}"
      subnetID: "{{resolve:ssm:devServerSubnetID}}"
      securityGroupIDs:
        - "{{resolve:ssm:baselineSecurityGroupID}}"
      numberOfAdditionalVolumes: 1
      volume2Size: 80
  instance2:
    Type: ASRS::EC2::EC2::MODULE
    Properties:
      name: DEVSERVER
      instanceType: r5.2xlarge
      imageID: "{resolveEC2Im3ageID:windowsServer}"
      subnetID: "{{resolve:ssm:devServerSubnetID}}"
      securityGroupIDs:
        - "{{resolve:ssm:baselineSecurityGroupID}}"
      numberOfAdditionalVolumes: 1
      volume2Size: 80

when running cfn-lint -o patch.json -s registry -- template.yaml

will produce

E3019 Primary identifiers {'name': 'DEVSERVER'} should have unique values across the resources {'instance2', 'instanc1e'}
template.yaml:5:7

E3019 Primary identifiers {'name': 'DEVSERVER'} should have unique values across the resources {'instance2', 'instanc1e'}
template.yaml:16:7
kddejong commented 3 months ago

I was also able to replicate an error with the instance patch:

E3031 'test' does not match '{resolveEC2ImageID:.+}'
local/issue/3472/template.yaml:27:7
randybasrs commented 3 months ago

Well, as of cfn-lint 1.6.1 I'm getting the following error with this setup:

  File "C:\Program Files\Python310\lib\site-packages\jsonpointer.py", line 288, in walk
    return doc[part]
KeyError: 'primaryIdentifier'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Program Files\Python310\lib\runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Program Files\Python310\lib\runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "C:\Program Files\Python310\Scripts\cfn-lint.exe\__main__.py", line 7, in <module>
  File "C:\Program Files\Python310\lib\site-packages\cfnlint\runner.py", line 433, in main
    runner = Runner(config)
  File "C:\Program Files\Python310\lib\site-packages\cfnlint\runner.py", line 222, in __init__
    PROVIDER_SCHEMA_MANAGER.patch(
  File "C:\Program Files\Python310\lib\site-packages\cfnlint\schema\manager.py", line 452, in patch
    self._patch(schema_patch, region)
  File "C:\Program Files\Python310\lib\site-packages\cfnlint\schema\manager.py", line 518, in _patch
    schema.patch(patches=patches)
  File "C:\Program Files\Python310\lib\site-packages\cfnlint\schema\_schema.py", line 50, in patch
    jsonpatch.JsonPatch(patches).apply(self.schema, in_place=True)
  File "C:\Program Files\Python310\lib\site-packages\jsonpatch.py", line 669, in apply
    obj = operation.apply(obj)
  File "C:\Program Files\Python310\lib\site-packages\jsonpatch.py", line 272, in apply
    subobj, part = self.pointer.to_last(obj)
  File "C:\Program Files\Python310\lib\site-packages\jsonpointer.py", line 196, in to_last
    doc = self.walk(doc, part)
  File "C:\Program Files\Python310\lib\site-packages\jsonpointer.py", line 291, in walk
    raise JsonPointerException("member '%s' not found in %s" % (part, doc))
jsonpointer.JsonPointerException: member 'primaryIdentifier' not found in {'typeName': 'ASRS::EC2::EC2::MODULE', 'description': 'Schema for Module Fragment of type ASRS::EC2::EC2::MODULE', 'properties': {'securityGroupIDs': {'format': 'AWS::EC2::SecurityGroup.Ids', 'insertionOrder': False, 'items': {'format': 'AWS::EC2::SecurityGroup.GroupId', 'type': 'string'}}, 'Parameters': {'type': 'object', 'properties': {'name': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'The name tag for the EC2 instance.'}, 'imageID': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Base AMI for the EC2 instance.'}, 'instanceType': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'EC2 instance type.'}, 'subnetID': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'The ID of the subnet to launch the instance into.'}, 'osVolumeSize': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of the volume that has the OS.'}, 'securityGroupIDs': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'The IDs of the security groups.'}, 'instanceKeyName': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Key pair name'}, 'ebsOptimized': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Indicates whether the instance is optimized for Amazon EBS I/O.'}, 'numberOfAdditionalVolumes': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Additional volumes in addition to OS volume. Max is 25.'}, 'volume2Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 2'}, 'volume3Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 3'}, 'volume4Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 4'}, 'volume5Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 5'}, 'volume6Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 6'}, 'volume7Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 7'}, 'volume8Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 8'}, 'volume9Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 9'}, 'volume10Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 10'}, 'volume11Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 11'}, 'volume12Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 12'}, 'volume13Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 13'}, 'volume14Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 14'}, 'volume15Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 15'}, 'volume16Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 16'}, 'volume17Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 17'}, 'volume18Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 18'}, 'volume19Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 19'}, 'volume20Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 20'}, 'volume21Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 21'}, 'volume22Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 22'}, 'volume23Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 23'}, 'volume24Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 24'}, 'volume25Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 25'}, 'volume26Size': {'type': 'object', 'properties': {'Type': {'type': 'string'}, 'Description': {'type': 'string'}}, 'required': ['Type', 'Description'], 'description': 'Size of volume 26'}}}, 'Resources': {'properties': {'Instance': {'type': 'object', 'properties': {'Type': {'type': 'string', 'const': 'AWS::EC2::Instance'}, 'Properties': {'type': 'object'}}}}, 'type': 'object', 'additionalProperties': False}}, 'additionalProperties': True}

I'm relying on .cfnlintrc and here are the files I have at the moment: Overridespec:

{
  "Patches": {
    "AWS::EC2::Instance": [
      {
        "op": "remove",
        "path": "/properties/ImageId/format",
        "value": "true"
      },
      {
        "op": "add",
        "path": "/properties/ImageId/pattern",
        "value": "{resolveEC2ImageID:.+}"
      }
    ],
    "ASRS::EC2::EC2::MODULE": [
      {
        "op": "add",
        "path": "/primaryIdentifier/-",
        "value": "/properties/name"
      }
    ],
    "ASRS::CodePipeline::NormalStages::MODULE": [
      {
        "op": "add",
        "path": "/primaryIdentifier/-",
        "value": "/properties/PipelineName"
      }
    ]
  }
}

cfnlintrc

ignore_checks:
  - W3010 # Availability zones for subnets are tightly controlled
  - E1018 # Allows !Split to support dynamic references through macros
override_spec: Application/cfn-lint/lintOverrideSpecification.json
regions:
  - us-west-2
registry_schemas:
  - Application/asrs-aws-sr/modules

code module schema:

{
  "typeName": "ASRS::CodePipeline::NormalStages::MODULE",
  "description": "Schema for Module Fragment of type ASRS::CodePipeline::NormalStages::MODULE",
  "properties": {
    "Parameters": {
      "type": "object",
      "properties": {
        "PipelineName": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "The name of the stack to create/update."
        },
        "RepositoryName": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "The CodeCommit repository that contains the template."
        },
        "TargetTemplate": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "The filename of the template."
        },
        "TargetAccount": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "The account in which the template should run."
        }
      }
    },
    "Resources": {
      "properties": {
        "CodePipeline": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string",
              "const": "AWS::CodePipeline::Pipeline"
            },
            "Properties": {
              "type": "object"
            }
          }
        }
      },
      "type": "object",
      "additionalProperties": false
    }
  },
  "additionalProperties": true
}

ec2 schema

{
  "typeName": "ASRS::EC2::EC2::MODULE",
  "description": "Schema for Module Fragment of type ASRS::EC2::EC2::MODULE",
  "properties": {
    "securityGroupIDs": {
      "format": "AWS::EC2::SecurityGroup.Ids",
      "insertionOrder": false,
      "items": {
        "format": "AWS::EC2::SecurityGroup.GroupId",
        "type": "string"
      }
    },
    "Parameters": {
      "type": "object",
      "properties": {
        "name": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "The name tag for the EC2 instance."
        },
        "imageID": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Base AMI for the EC2 instance."
        },
        "instanceType": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "EC2 instance type."
        },
        "subnetID": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "The ID of the subnet to launch the instance into."
        },
        "osVolumeSize": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of the volume that has the OS."
        },
        "securityGroupIDs": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "The IDs of the security groups."
        },
        "instanceKeyName": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Key pair name"
        },
        "ebsOptimized": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Indicates whether the instance is optimized for Amazon EBS I/O."
        },
        "numberOfAdditionalVolumes": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Additional volumes in addition to OS volume. Max is 25."
        },
        "volume2Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 2"
        },
        "volume3Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 3"
        },
        "volume4Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 4"
        },
        "volume5Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 5"
        },
        "volume6Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 6"
        },
        "volume7Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 7"
        },
        "volume8Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 8"
        },
        "volume9Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 9"
        },
        "volume10Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 10"
        },
        "volume11Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 11"
        },
        "volume12Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 12"
        },
        "volume13Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 13"
        },
        "volume14Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 14"
        },
        "volume15Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 15"
        },
        "volume16Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 16"
        },
        "volume17Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 17"
        },
        "volume18Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 18"
        },
        "volume19Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 19"
        },
        "volume20Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 20"
        },
        "volume21Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 21"
        },
        "volume22Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 22"
        },
        "volume23Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 23"
        },
        "volume24Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 24"
        },
        "volume25Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 25"
        },
        "volume26Size": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string"
            },
            "Description": {
              "type": "string"
            }
          },
          "required": [
            "Type",
            "Description"
          ],
          "description": "Size of volume 26"
        }
      }
    },
    "Resources": {
      "properties": {
        "Instance": {
          "type": "object",
          "properties": {
            "Type": {
              "type": "string",
              "const": "AWS::EC2::Instance"
            },
            "Properties": {
              "type": "object"
            }
          }
        }
      },
      "type": "object",
      "additionalProperties": false
    }
  },
  "additionalProperties": true
}

It...kind of looks like cfn-lint is now checking to see if primaryIdentifier exists in the schema and failing if it doesn't? The code modules, as I mentioned before, can't have primaryIdentifier defined or the registration of the code module seems to fail.

It also seems like this is only happening on my Windows 11 desktop and not in my serverless lint process for pipelines. I'm trying to determine if something is wrong locally with lint for this error. I had updated to 1.6.1 before running any tests but it was working fine last night before the update. Well except being able to get the errors I wanted anyway.

kddejong commented 3 months ago

Because primaryIdentifier isn't in the schema files your patches should look like.

{
  "Patches": {
    "AWS::EC2::Instance": [
      {
        "op": "remove",
        "path": "/properties/ImageId/format",
        "value": "true"
      },
      {
        "op": "add",
        "path": "/properties/ImageId/pattern",
        "value": "{resolveEC2ImageID:.+}"
      }
    ],
    "ASRS::EC2::EC2::MODULE": [
      {
        "op": "add",
        "path": "/primaryIdentifier",
        "value": [
          "/properties/name"
        ]
      }
    ],
    "ASRS::CodePipeline::NormalStages::MODULE": [
      {
        "op": "add",
        "path": "/primaryIdentifier",
        "value": [
          "/properties/PipelineName"
        ]
      }
    ]
  }
}
E3019 Primary identifiers {'name': 'DEVSERVER'} should have unique values across the resources {'instanc1e', 'instance2'}
template.yaml:5:7

E3019 Primary identifiers {'name': 'DEVSERVER'} should have unique values across the resources {'instanc1e', 'instance2'}
template.yaml:16:7

E3031 'test' does not match '{resolveEC2ImageID:.+}'
template.yaml:27:7

If the schemas had "primaryIdentifier": [] your patches would work.

kddejong commented 3 months ago

I am concerned about the windows/pipeline differences. May have to add some integration tests for this.

randybasrs commented 3 months ago

In this case the variance makes sense -- the patch file is different between the two of them. Everything else is the same though, so I had wrongfully assumed it was just a schema problem and not a combination of the two of them. I think if the patch spec was the same I would get the same error. I can probably confirm that in a few minutes as I test the change there, thanks again for your patience.

kddejong commented 3 months ago

Thank you for yours and all the help in this discussion.

randybasrs commented 3 months ago

So after testing your changes worked great and it's properly linting now.

Additionally with that override spec the serverless run also threw the same errors regarding the missing primaryIdentifier, so no discrepancy after all.

Think we're all done here, finally! Have a great weekend.