From fa6d871bdae6f3052b75beb173276d4aa857755f Mon Sep 17 00:00:00 2001 From: Kevin DeJong Date: Wed, 22 Jan 2025 07:38:11 -0800 Subject: [PATCH] Add new capabilities for schema comparison --- scripts/update_schemas_format.py | 7 +- .../all/aws_ec2_securitygroup/format.json | 11 +- .../af_south_1/aws-ec2-clientvpnendpoint.json | 2 +- .../ap_east_1/aws-ec2-clientvpnendpoint.json | 2 +- .../ap_northeast_1/aws-pcs-cluster.json | 2 + .../ap_northeast_1/aws-sagemaker-domain.json | 6 +- .../aws-sagemaker-userprofile.json | 2 +- .../ap_northeast_2/aws-m2-environment.json | 2 +- .../aws-ec2-clientvpnendpoint.json | 2 +- .../ap_south_2/aws-ec2-clientvpnendpoint.json | 2 +- .../ap_southeast_1/aws-pcs-cluster.json | 2 + .../aws-pcs-computenodegroup.json | 1 + .../aws-ec2-clientvpnendpoint.json | 2 +- .../aws-ec2-clientvpnendpoint.json | 2 +- .../eu_central_1/aws-pcs-cluster.json | 2 + .../aws-ec2-clientvpnendpoint.json | 2 +- .../eu_north_1/aws-m2-environment.json | 2 +- .../eu_south_1/aws-ec2-clientvpnendpoint.json | 2 +- .../eu_south_1/aws-m2-environment.json | 2 +- .../eu_south_2/aws-ec2-clientvpnendpoint.json | 2 +- .../eu_south_2/aws-m2-environment.json | 2 +- .../eu_west_1/aws-mediaconnect-flow.json | 2 +- .../providers/eu_west_1/aws-pcs-cluster.json | 2 + .../eu_west_3/aws-ec2-clientvpnendpoint.json | 2 +- .../eu_west_3/aws-m2-environment.json | 2 +- .../aws-ec2-clientvpnendpoint.json | 2 +- .../aws-ec2-clientvpnendpoint.json | 2 +- .../me_central_1/aws-sagemaker-domain.json | 6 +- .../aws-sagemaker-userprofile.json | 2 +- .../us_east_1/aws-ec2-securitygroup.json | 9 +- .../us_east_2/aws-m2-environment.json | 2 +- .../us_gov_east_1/aws-m2-environment.json | 2 +- .../us_gov_west_1/aws-m2-environment.json | 2 +- .../us_west_1/aws-mediaconnect-flow.json | 2 +- .../providers/us_west_2/aws-pcs-cluster.json | 2 + src/cfnlint/rules/formats/_schema_comparer.py | 112 ++++++++++++++++++ src/cfnlint/rules/functions/RefFormat.py | 38 +++--- src/cfnlint/schema/_ref.py | 5 +- .../templates/integration/ref-types.yaml | 15 +++ test/unit/module/schema/test_ref.py | 51 ++++++++ .../rules/formats/test_schema_comparer.py | 68 +++++++++++ test/unit/rules/functions/test_ref_format.py | 4 +- 42 files changed, 333 insertions(+), 58 deletions(-) create mode 100644 src/cfnlint/rules/formats/_schema_comparer.py create mode 100644 test/unit/module/schema/test_ref.py create mode 100644 test/unit/rules/formats/test_schema_comparer.py diff --git a/scripts/update_schemas_format.py b/scripts/update_schemas_format.py index c0ea2a108f..32d54fdbcd 100755 --- a/scripts/update_schemas_format.py +++ b/scripts/update_schemas_format.py @@ -158,7 +158,12 @@ def _create_patch(value: dict[str, str], ref: Sequence[str], resolver: RefResolv path="/properties/GroupName", ), Patch( - values={"format": "AWS::EC2::SecurityGroup.Id"}, + values={ + "anyOf": [ + {"format": "AWS::EC2::SecurityGroup.Id"}, + {"format": "AWS::EC2::SecurityGroup.Name"}, + ] + }, path="/properties/Id", ), ], diff --git a/src/cfnlint/data/schemas/patches/extensions/all/aws_ec2_securitygroup/format.json b/src/cfnlint/data/schemas/patches/extensions/all/aws_ec2_securitygroup/format.json index ef03a8d188..c1a74cc5e8 100644 --- a/src/cfnlint/data/schemas/patches/extensions/all/aws_ec2_securitygroup/format.json +++ b/src/cfnlint/data/schemas/patches/extensions/all/aws_ec2_securitygroup/format.json @@ -11,8 +11,15 @@ }, { "op": "add", - "path": "/properties/Id/format", - "value": "AWS::EC2::SecurityGroup.Id" + "path": "/properties/Id/anyOf", + "value": [ + { + "format": "AWS::EC2::SecurityGroup.Id" + }, + { + "format": "AWS::EC2::SecurityGroup.Name" + } + ] }, { "op": "add", diff --git a/src/cfnlint/data/schemas/providers/af_south_1/aws-ec2-clientvpnendpoint.json b/src/cfnlint/data/schemas/providers/af_south_1/aws-ec2-clientvpnendpoint.json index 456d6c66ca..f6410e35e5 100644 --- a/src/cfnlint/data/schemas/providers/af_south_1/aws-ec2-clientvpnendpoint.json +++ b/src/cfnlint/data/schemas/providers/af_south_1/aws-ec2-clientvpnendpoint.json @@ -191,7 +191,7 @@ "SecurityGroupIds": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "type": "string" }, "type": "array", diff --git a/src/cfnlint/data/schemas/providers/ap_east_1/aws-ec2-clientvpnendpoint.json b/src/cfnlint/data/schemas/providers/ap_east_1/aws-ec2-clientvpnendpoint.json index 456d6c66ca..f6410e35e5 100644 --- a/src/cfnlint/data/schemas/providers/ap_east_1/aws-ec2-clientvpnendpoint.json +++ b/src/cfnlint/data/schemas/providers/ap_east_1/aws-ec2-clientvpnendpoint.json @@ -191,7 +191,7 @@ "SecurityGroupIds": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "type": "string" }, "type": "array", diff --git a/src/cfnlint/data/schemas/providers/ap_northeast_1/aws-pcs-cluster.json b/src/cfnlint/data/schemas/providers/ap_northeast_1/aws-pcs-cluster.json index bd9b718b7d..0eb3ff4219 100644 --- a/src/cfnlint/data/schemas/providers/ap_northeast_1/aws-pcs-cluster.json +++ b/src/cfnlint/data/schemas/providers/ap_northeast_1/aws-pcs-cluster.json @@ -64,6 +64,7 @@ "type": "object" }, "SecurityGroupId": { + "format": "AWS::EC2::SecurityGroup.Id", "pattern": "sg-\\w{8,17}", "type": "string" }, @@ -139,6 +140,7 @@ "additionalProperties": false, "properties": { "SecurityGroupIds": { + "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { "$ref": "#/definitions/SecurityGroupId" diff --git a/src/cfnlint/data/schemas/providers/ap_northeast_1/aws-sagemaker-domain.json b/src/cfnlint/data/schemas/providers/ap_northeast_1/aws-sagemaker-domain.json index 94720f00bb..69e42e460e 100644 --- a/src/cfnlint/data/schemas/providers/ap_northeast_1/aws-sagemaker-domain.json +++ b/src/cfnlint/data/schemas/providers/ap_northeast_1/aws-sagemaker-domain.json @@ -180,7 +180,7 @@ "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "maxLength": 32, "pattern": "[-0-9a-zA-Z]+", "type": "string" @@ -254,7 +254,7 @@ "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "maxLength": 32, "pattern": "[-0-9a-zA-Z]+", "type": "string" @@ -738,7 +738,7 @@ "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "maxLength": 32, "pattern": "[-0-9a-zA-Z]+", "type": "string" diff --git a/src/cfnlint/data/schemas/providers/ap_northeast_1/aws-sagemaker-userprofile.json b/src/cfnlint/data/schemas/providers/ap_northeast_1/aws-sagemaker-userprofile.json index 2d4d1a2f88..a21ea9e798 100644 --- a/src/cfnlint/data/schemas/providers/ap_northeast_1/aws-sagemaker-userprofile.json +++ b/src/cfnlint/data/schemas/providers/ap_northeast_1/aws-sagemaker-userprofile.json @@ -571,7 +571,7 @@ "SecurityGroups": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "maxLength": 32, "pattern": "[-0-9a-zA-Z]+", "type": "string" diff --git a/src/cfnlint/data/schemas/providers/ap_northeast_2/aws-m2-environment.json b/src/cfnlint/data/schemas/providers/ap_northeast_2/aws-m2-environment.json index 65f6cde82f..eefe1e876a 100644 --- a/src/cfnlint/data/schemas/providers/ap_northeast_2/aws-m2-environment.json +++ b/src/cfnlint/data/schemas/providers/ap_northeast_2/aws-m2-environment.json @@ -170,7 +170,7 @@ "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "pattern": "^\\S{1,50}$", "type": "string" }, diff --git a/src/cfnlint/data/schemas/providers/ap_northeast_3/aws-ec2-clientvpnendpoint.json b/src/cfnlint/data/schemas/providers/ap_northeast_3/aws-ec2-clientvpnendpoint.json index 456d6c66ca..f6410e35e5 100644 --- a/src/cfnlint/data/schemas/providers/ap_northeast_3/aws-ec2-clientvpnendpoint.json +++ b/src/cfnlint/data/schemas/providers/ap_northeast_3/aws-ec2-clientvpnendpoint.json @@ -191,7 +191,7 @@ "SecurityGroupIds": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "type": "string" }, "type": "array", diff --git a/src/cfnlint/data/schemas/providers/ap_south_2/aws-ec2-clientvpnendpoint.json b/src/cfnlint/data/schemas/providers/ap_south_2/aws-ec2-clientvpnendpoint.json index 456d6c66ca..f6410e35e5 100644 --- a/src/cfnlint/data/schemas/providers/ap_south_2/aws-ec2-clientvpnendpoint.json +++ b/src/cfnlint/data/schemas/providers/ap_south_2/aws-ec2-clientvpnendpoint.json @@ -191,7 +191,7 @@ "SecurityGroupIds": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "type": "string" }, "type": "array", diff --git a/src/cfnlint/data/schemas/providers/ap_southeast_1/aws-pcs-cluster.json b/src/cfnlint/data/schemas/providers/ap_southeast_1/aws-pcs-cluster.json index bd9b718b7d..0eb3ff4219 100644 --- a/src/cfnlint/data/schemas/providers/ap_southeast_1/aws-pcs-cluster.json +++ b/src/cfnlint/data/schemas/providers/ap_southeast_1/aws-pcs-cluster.json @@ -64,6 +64,7 @@ "type": "object" }, "SecurityGroupId": { + "format": "AWS::EC2::SecurityGroup.Id", "pattern": "sg-\\w{8,17}", "type": "string" }, @@ -139,6 +140,7 @@ "additionalProperties": false, "properties": { "SecurityGroupIds": { + "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { "$ref": "#/definitions/SecurityGroupId" diff --git a/src/cfnlint/data/schemas/providers/ap_southeast_2/aws-pcs-computenodegroup.json b/src/cfnlint/data/schemas/providers/ap_southeast_2/aws-pcs-computenodegroup.json index c87f471efd..18a817ec0f 100644 --- a/src/cfnlint/data/schemas/providers/ap_southeast_2/aws-pcs-computenodegroup.json +++ b/src/cfnlint/data/schemas/providers/ap_southeast_2/aws-pcs-computenodegroup.json @@ -52,6 +52,7 @@ ], "properties": { "AmiId": { + "format": "AWS::EC2::Image.Id", "pattern": "^ami-[a-z0-9]+$", "type": "string" }, diff --git a/src/cfnlint/data/schemas/providers/ap_southeast_3/aws-ec2-clientvpnendpoint.json b/src/cfnlint/data/schemas/providers/ap_southeast_3/aws-ec2-clientvpnendpoint.json index 456d6c66ca..f6410e35e5 100644 --- a/src/cfnlint/data/schemas/providers/ap_southeast_3/aws-ec2-clientvpnendpoint.json +++ b/src/cfnlint/data/schemas/providers/ap_southeast_3/aws-ec2-clientvpnendpoint.json @@ -191,7 +191,7 @@ "SecurityGroupIds": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "type": "string" }, "type": "array", diff --git a/src/cfnlint/data/schemas/providers/ap_southeast_4/aws-ec2-clientvpnendpoint.json b/src/cfnlint/data/schemas/providers/ap_southeast_4/aws-ec2-clientvpnendpoint.json index 456d6c66ca..f6410e35e5 100644 --- a/src/cfnlint/data/schemas/providers/ap_southeast_4/aws-ec2-clientvpnendpoint.json +++ b/src/cfnlint/data/schemas/providers/ap_southeast_4/aws-ec2-clientvpnendpoint.json @@ -191,7 +191,7 @@ "SecurityGroupIds": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "type": "string" }, "type": "array", diff --git a/src/cfnlint/data/schemas/providers/eu_central_1/aws-pcs-cluster.json b/src/cfnlint/data/schemas/providers/eu_central_1/aws-pcs-cluster.json index bd9b718b7d..0eb3ff4219 100644 --- a/src/cfnlint/data/schemas/providers/eu_central_1/aws-pcs-cluster.json +++ b/src/cfnlint/data/schemas/providers/eu_central_1/aws-pcs-cluster.json @@ -64,6 +64,7 @@ "type": "object" }, "SecurityGroupId": { + "format": "AWS::EC2::SecurityGroup.Id", "pattern": "sg-\\w{8,17}", "type": "string" }, @@ -139,6 +140,7 @@ "additionalProperties": false, "properties": { "SecurityGroupIds": { + "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { "$ref": "#/definitions/SecurityGroupId" diff --git a/src/cfnlint/data/schemas/providers/eu_central_2/aws-ec2-clientvpnendpoint.json b/src/cfnlint/data/schemas/providers/eu_central_2/aws-ec2-clientvpnendpoint.json index 456d6c66ca..f6410e35e5 100644 --- a/src/cfnlint/data/schemas/providers/eu_central_2/aws-ec2-clientvpnendpoint.json +++ b/src/cfnlint/data/schemas/providers/eu_central_2/aws-ec2-clientvpnendpoint.json @@ -191,7 +191,7 @@ "SecurityGroupIds": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "type": "string" }, "type": "array", diff --git a/src/cfnlint/data/schemas/providers/eu_north_1/aws-m2-environment.json b/src/cfnlint/data/schemas/providers/eu_north_1/aws-m2-environment.json index 65f6cde82f..eefe1e876a 100644 --- a/src/cfnlint/data/schemas/providers/eu_north_1/aws-m2-environment.json +++ b/src/cfnlint/data/schemas/providers/eu_north_1/aws-m2-environment.json @@ -170,7 +170,7 @@ "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "pattern": "^\\S{1,50}$", "type": "string" }, diff --git a/src/cfnlint/data/schemas/providers/eu_south_1/aws-ec2-clientvpnendpoint.json b/src/cfnlint/data/schemas/providers/eu_south_1/aws-ec2-clientvpnendpoint.json index 456d6c66ca..f6410e35e5 100644 --- a/src/cfnlint/data/schemas/providers/eu_south_1/aws-ec2-clientvpnendpoint.json +++ b/src/cfnlint/data/schemas/providers/eu_south_1/aws-ec2-clientvpnendpoint.json @@ -191,7 +191,7 @@ "SecurityGroupIds": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "type": "string" }, "type": "array", diff --git a/src/cfnlint/data/schemas/providers/eu_south_1/aws-m2-environment.json b/src/cfnlint/data/schemas/providers/eu_south_1/aws-m2-environment.json index 65f6cde82f..eefe1e876a 100644 --- a/src/cfnlint/data/schemas/providers/eu_south_1/aws-m2-environment.json +++ b/src/cfnlint/data/schemas/providers/eu_south_1/aws-m2-environment.json @@ -170,7 +170,7 @@ "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "pattern": "^\\S{1,50}$", "type": "string" }, diff --git a/src/cfnlint/data/schemas/providers/eu_south_2/aws-ec2-clientvpnendpoint.json b/src/cfnlint/data/schemas/providers/eu_south_2/aws-ec2-clientvpnendpoint.json index 456d6c66ca..f6410e35e5 100644 --- a/src/cfnlint/data/schemas/providers/eu_south_2/aws-ec2-clientvpnendpoint.json +++ b/src/cfnlint/data/schemas/providers/eu_south_2/aws-ec2-clientvpnendpoint.json @@ -191,7 +191,7 @@ "SecurityGroupIds": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "type": "string" }, "type": "array", diff --git a/src/cfnlint/data/schemas/providers/eu_south_2/aws-m2-environment.json b/src/cfnlint/data/schemas/providers/eu_south_2/aws-m2-environment.json index 65f6cde82f..eefe1e876a 100644 --- a/src/cfnlint/data/schemas/providers/eu_south_2/aws-m2-environment.json +++ b/src/cfnlint/data/schemas/providers/eu_south_2/aws-m2-environment.json @@ -170,7 +170,7 @@ "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "pattern": "^\\S{1,50}$", "type": "string" }, diff --git a/src/cfnlint/data/schemas/providers/eu_west_1/aws-mediaconnect-flow.json b/src/cfnlint/data/schemas/providers/eu_west_1/aws-mediaconnect-flow.json index ddfebe3e19..113156efe8 100644 --- a/src/cfnlint/data/schemas/providers/eu_west_1/aws-mediaconnect-flow.json +++ b/src/cfnlint/data/schemas/providers/eu_west_1/aws-mediaconnect-flow.json @@ -512,7 +512,7 @@ "SecurityGroupIds": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "type": "string" }, "type": "array" diff --git a/src/cfnlint/data/schemas/providers/eu_west_1/aws-pcs-cluster.json b/src/cfnlint/data/schemas/providers/eu_west_1/aws-pcs-cluster.json index bd9b718b7d..0eb3ff4219 100644 --- a/src/cfnlint/data/schemas/providers/eu_west_1/aws-pcs-cluster.json +++ b/src/cfnlint/data/schemas/providers/eu_west_1/aws-pcs-cluster.json @@ -64,6 +64,7 @@ "type": "object" }, "SecurityGroupId": { + "format": "AWS::EC2::SecurityGroup.Id", "pattern": "sg-\\w{8,17}", "type": "string" }, @@ -139,6 +140,7 @@ "additionalProperties": false, "properties": { "SecurityGroupIds": { + "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { "$ref": "#/definitions/SecurityGroupId" diff --git a/src/cfnlint/data/schemas/providers/eu_west_3/aws-ec2-clientvpnendpoint.json b/src/cfnlint/data/schemas/providers/eu_west_3/aws-ec2-clientvpnendpoint.json index 456d6c66ca..f6410e35e5 100644 --- a/src/cfnlint/data/schemas/providers/eu_west_3/aws-ec2-clientvpnendpoint.json +++ b/src/cfnlint/data/schemas/providers/eu_west_3/aws-ec2-clientvpnendpoint.json @@ -191,7 +191,7 @@ "SecurityGroupIds": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "type": "string" }, "type": "array", diff --git a/src/cfnlint/data/schemas/providers/eu_west_3/aws-m2-environment.json b/src/cfnlint/data/schemas/providers/eu_west_3/aws-m2-environment.json index 65f6cde82f..eefe1e876a 100644 --- a/src/cfnlint/data/schemas/providers/eu_west_3/aws-m2-environment.json +++ b/src/cfnlint/data/schemas/providers/eu_west_3/aws-m2-environment.json @@ -170,7 +170,7 @@ "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "pattern": "^\\S{1,50}$", "type": "string" }, diff --git a/src/cfnlint/data/schemas/providers/il_central_1/aws-ec2-clientvpnendpoint.json b/src/cfnlint/data/schemas/providers/il_central_1/aws-ec2-clientvpnendpoint.json index 456d6c66ca..f6410e35e5 100644 --- a/src/cfnlint/data/schemas/providers/il_central_1/aws-ec2-clientvpnendpoint.json +++ b/src/cfnlint/data/schemas/providers/il_central_1/aws-ec2-clientvpnendpoint.json @@ -191,7 +191,7 @@ "SecurityGroupIds": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "type": "string" }, "type": "array", diff --git a/src/cfnlint/data/schemas/providers/me_central_1/aws-ec2-clientvpnendpoint.json b/src/cfnlint/data/schemas/providers/me_central_1/aws-ec2-clientvpnendpoint.json index 456d6c66ca..f6410e35e5 100644 --- a/src/cfnlint/data/schemas/providers/me_central_1/aws-ec2-clientvpnendpoint.json +++ b/src/cfnlint/data/schemas/providers/me_central_1/aws-ec2-clientvpnendpoint.json @@ -191,7 +191,7 @@ "SecurityGroupIds": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "type": "string" }, "type": "array", diff --git a/src/cfnlint/data/schemas/providers/me_central_1/aws-sagemaker-domain.json b/src/cfnlint/data/schemas/providers/me_central_1/aws-sagemaker-domain.json index 94720f00bb..69e42e460e 100644 --- a/src/cfnlint/data/schemas/providers/me_central_1/aws-sagemaker-domain.json +++ b/src/cfnlint/data/schemas/providers/me_central_1/aws-sagemaker-domain.json @@ -180,7 +180,7 @@ "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "maxLength": 32, "pattern": "[-0-9a-zA-Z]+", "type": "string" @@ -254,7 +254,7 @@ "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "maxLength": 32, "pattern": "[-0-9a-zA-Z]+", "type": "string" @@ -738,7 +738,7 @@ "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "maxLength": 32, "pattern": "[-0-9a-zA-Z]+", "type": "string" diff --git a/src/cfnlint/data/schemas/providers/me_central_1/aws-sagemaker-userprofile.json b/src/cfnlint/data/schemas/providers/me_central_1/aws-sagemaker-userprofile.json index 2d4d1a2f88..a21ea9e798 100644 --- a/src/cfnlint/data/schemas/providers/me_central_1/aws-sagemaker-userprofile.json +++ b/src/cfnlint/data/schemas/providers/me_central_1/aws-sagemaker-userprofile.json @@ -571,7 +571,7 @@ "SecurityGroups": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "maxLength": 32, "pattern": "[-0-9a-zA-Z]+", "type": "string" diff --git a/src/cfnlint/data/schemas/providers/us_east_1/aws-ec2-securitygroup.json b/src/cfnlint/data/schemas/providers/us_east_1/aws-ec2-securitygroup.json index 67733e7fb2..f5f5f4cfff 100644 --- a/src/cfnlint/data/schemas/providers/us_east_1/aws-ec2-securitygroup.json +++ b/src/cfnlint/data/schemas/providers/us_east_1/aws-ec2-securitygroup.json @@ -140,7 +140,14 @@ "type": "string" }, "Id": { - "format": "AWS::EC2::SecurityGroup.Id", + "anyOf": [ + { + "format": "AWS::EC2::SecurityGroup.Id" + }, + { + "format": "AWS::EC2::SecurityGroup.Name" + } + ], "type": "string" }, "SecurityGroupEgress": { diff --git a/src/cfnlint/data/schemas/providers/us_east_2/aws-m2-environment.json b/src/cfnlint/data/schemas/providers/us_east_2/aws-m2-environment.json index 65f6cde82f..eefe1e876a 100644 --- a/src/cfnlint/data/schemas/providers/us_east_2/aws-m2-environment.json +++ b/src/cfnlint/data/schemas/providers/us_east_2/aws-m2-environment.json @@ -170,7 +170,7 @@ "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "pattern": "^\\S{1,50}$", "type": "string" }, diff --git a/src/cfnlint/data/schemas/providers/us_gov_east_1/aws-m2-environment.json b/src/cfnlint/data/schemas/providers/us_gov_east_1/aws-m2-environment.json index 65f6cde82f..eefe1e876a 100644 --- a/src/cfnlint/data/schemas/providers/us_gov_east_1/aws-m2-environment.json +++ b/src/cfnlint/data/schemas/providers/us_gov_east_1/aws-m2-environment.json @@ -170,7 +170,7 @@ "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "pattern": "^\\S{1,50}$", "type": "string" }, diff --git a/src/cfnlint/data/schemas/providers/us_gov_west_1/aws-m2-environment.json b/src/cfnlint/data/schemas/providers/us_gov_west_1/aws-m2-environment.json index 65f6cde82f..eefe1e876a 100644 --- a/src/cfnlint/data/schemas/providers/us_gov_west_1/aws-m2-environment.json +++ b/src/cfnlint/data/schemas/providers/us_gov_west_1/aws-m2-environment.json @@ -170,7 +170,7 @@ "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "pattern": "^\\S{1,50}$", "type": "string" }, diff --git a/src/cfnlint/data/schemas/providers/us_west_1/aws-mediaconnect-flow.json b/src/cfnlint/data/schemas/providers/us_west_1/aws-mediaconnect-flow.json index ddfebe3e19..113156efe8 100644 --- a/src/cfnlint/data/schemas/providers/us_west_1/aws-mediaconnect-flow.json +++ b/src/cfnlint/data/schemas/providers/us_west_1/aws-mediaconnect-flow.json @@ -512,7 +512,7 @@ "SecurityGroupIds": { "format": "AWS::EC2::SecurityGroup.Ids", "items": { - "format": "AWS::EC2::SecurityGroup.GroupId", + "format": "AWS::EC2::SecurityGroup.Id", "type": "string" }, "type": "array" diff --git a/src/cfnlint/data/schemas/providers/us_west_2/aws-pcs-cluster.json b/src/cfnlint/data/schemas/providers/us_west_2/aws-pcs-cluster.json index bd9b718b7d..0eb3ff4219 100644 --- a/src/cfnlint/data/schemas/providers/us_west_2/aws-pcs-cluster.json +++ b/src/cfnlint/data/schemas/providers/us_west_2/aws-pcs-cluster.json @@ -64,6 +64,7 @@ "type": "object" }, "SecurityGroupId": { + "format": "AWS::EC2::SecurityGroup.Id", "pattern": "sg-\\w{8,17}", "type": "string" }, @@ -139,6 +140,7 @@ "additionalProperties": false, "properties": { "SecurityGroupIds": { + "format": "AWS::EC2::SecurityGroup.Ids", "insertionOrder": false, "items": { "$ref": "#/definitions/SecurityGroupId" diff --git a/src/cfnlint/rules/formats/_schema_comparer.py b/src/cfnlint/rules/formats/_schema_comparer.py new file mode 100644 index 0000000000..bfec40cc06 --- /dev/null +++ b/src/cfnlint/rules/formats/_schema_comparer.py @@ -0,0 +1,112 @@ +""" +Helpers for loading resources, managing specs, constants, etc. + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: MIT-0 +""" + +from __future__ import annotations + +from typing import Any, Callable + +from cfnlint.jsonschema.exceptions import ValidationError + +_keyword = "format" + + +def _any_of( + source: str, + any_ofs: list[dict[str, Any]], +) -> tuple[bool, list[str]]: + """Compare the source schema with the destination schema and + return a ValidationError if they don't match. + + Args: + source (str): The source schema to compare. + destination (list[dict[str, Any]]): The anyOf schemas + + Returns: + tuple[bool, list[str]]: A bool value if format matches and a + list of format values if they don't match. + """ + formats = [] + for subschema in any_ofs: + if _keyword in subschema: + if source == subschema[_keyword]: + return True, [] + + formats.append(subschema[_keyword]) + + return False, formats + + +_schema_composition: dict[str, Callable] = { + "anyOf": _any_of, +} + + +def _compare_schemas( + source: str, + destination: dict[str, Any], +) -> tuple[bool, list[str]]: + """Compare two schemas and return a ValidationError if they don't match. + + Args: + source (str): The source schema to compare. + destination (dict[str, Any]): The destination schema to compare against. + + Returns: + tuple[bool, list[str]]: True/False if the format keyword matches + """ + + dest_f = destination.get(_keyword) + if dest_f == source: + return True, [dest_f] # Nothing else matters to be on the safe side + + all_formats = [dest_f] if dest_f else [] + for composition_key in _schema_composition: + if composition_key in destination: + match, formats = _schema_composition[composition_key]( + source, destination[composition_key] + ) + if match: + return True, [] + all_formats.extend(formats) + + return False, all_formats + + +def compare_schemas( + source: dict[str, Any], + destination: dict[str, Any], +) -> ValidationError | None: + """Compare two schemas and return a ValidationError if they don't match. + + Args: + source (dict[str, Any]): The source schema to compare. + destination (dict[str, Any]): The destination schema to compare against. + + Returns: + ValidationError: A ValidationError if the schemas don't match, otherwise None. + """ + + f = source.get(_keyword) + if f is None: + return None + + match, formats = _compare_schemas(f, destination) + if match: + return None + + if formats: + return ValidationError( + f"{f!r} format is incompatible with formats {formats!r}", + schema={"format": f}, + instance=formats, + ) + + return ValidationError( + f"{f!r} format is incompatible", + schema={"format": f}, + instance=None, + ) diff --git a/src/cfnlint/rules/functions/RefFormat.py b/src/cfnlint/rules/functions/RefFormat.py index 682a7f5b2f..66f80b8b3c 100644 --- a/src/cfnlint/rules/functions/RefFormat.py +++ b/src/cfnlint/rules/functions/RefFormat.py @@ -5,7 +5,9 @@ from typing import Any -from cfnlint.jsonschema import ValidationError, ValidationResult, Validator +from cfnlint.jsonschema import ValidationResult, Validator +from cfnlint.jsonschema._utils import Unset +from cfnlint.rules.formats._schema_comparer import compare_schemas from cfnlint.rules.jsonschema import CfnLintKeyword from cfnlint.schema import PROVIDER_SCHEMA_MANAGER @@ -29,10 +31,6 @@ def validate( if instance in validator.context.parameters: return - fmt = schema.get("format") - if not fmt: - return - if instance not in validator.context.resources: return t = validator.context.resources[instance].type @@ -46,21 +44,21 @@ def validate( ref_schema = validator.context.resources[instance].ref(region) - ref_fmt = ref_schema.get("format") - if ref_fmt != fmt: - if ref_fmt is None: - yield ValidationError( - ( - f"{{'Ref': {instance!r}}} does not match " - f"destination format of {fmt!r}" - ), - rule=self, + err = compare_schemas(schema, ref_schema) + if err: + if err.instance: + err.message = ( + f"{{'Ref': {instance!r}}} with formats {err.instance!r} " + "does not match destination format of " + f"{err.schema.get('format')!r}" ) else: - yield ValidationError( - ( - f"{{'Ref': {instance!r}}} with format {ref_fmt!r} does not " - f"match destination format of {fmt!r}" - ), - rule=self, + err.message = ( + f"{{'Ref': {instance!r}}} does not match " + f"destination format of {err.schema.get('format')!r}" ) + + err.instance = Unset() + err.schema = Unset() + err.rule = self + yield err diff --git a/src/cfnlint/schema/_ref.py b/src/cfnlint/schema/_ref.py index 782c553054..7861c27a1f 100644 --- a/src/cfnlint/schema/_ref.py +++ b/src/cfnlint/schema/_ref.py @@ -17,8 +17,9 @@ def __init__(self, schema: "Schema") -> None: primary_ids = schema.schema.get("primaryIdentifier", []) if len(primary_ids) > 1: self._ref = {"type": "string"} - for primary_id in primary_ids: - self._ref = schema.resolver.resolve_cfn_pointer(primary_id) + else: + for primary_id in primary_ids: + self._ref = schema.resolver.resolve_cfn_pointer(primary_id) @property def ref(self) -> dict[str, Any]: diff --git a/test/fixtures/templates/integration/ref-types.yaml b/test/fixtures/templates/integration/ref-types.yaml index d9a5fd4cf3..a02527c846 100644 --- a/test/fixtures/templates/integration/ref-types.yaml +++ b/test/fixtures/templates/integration/ref-types.yaml @@ -43,6 +43,21 @@ Resources: Properties: CidrBlock: 10.0.2.0/24 VpcId: !Ref FargateTaskRole # Wrong type + SecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: SG + VpcId: !Ref Vpc + LoadBalancer: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Type: application + Scheme: internal + SecurityGroups: + - !Ref SecurityGroup + Subnets: + - !Ref Subnet1 + - !Ref Subnet2 LogGroup: Type: AWS::Logs::LogGroup DeletionPolicy: Delete diff --git a/test/unit/module/schema/test_ref.py b/test/unit/module/schema/test_ref.py new file mode 100644 index 0000000000..3c47a164e3 --- /dev/null +++ b/test/unit/module/schema/test_ref.py @@ -0,0 +1,51 @@ +""" +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: MIT-0 +""" + +import logging + +import pytest + +from cfnlint.schema._schema import Schema + +LOGGER = logging.getLogger("cfnlint.schema.manager") +LOGGER.disabled = True + + +@pytest.mark.parametrize( + "name,resource_schema,expected", + [ + ( + "Standard ref", + { + "additionalProperties": False, + "createOnlyProperties": [], + "primaryIdentifier": ["/properties/One"], + "properties": {"One": {"type": "string"}, "Two": {"type": "boolean"}}, + "readOnlyProperties": [], + "typeName": "Foo::Foo::Foo", + "writeOnlyProperties": [], + }, + {"type": "string"}, + ), + ( + "Standard with multiple identifiers", + { + "additionalProperties": False, + "createOnlyProperties": [], + "primaryIdentifier": ["/properties/One", "/properties/Two"], + "properties": {"One": {"type": "integer"}, "Two": {"type": "boolean"}}, + "readOnlyProperties": [], + "typeName": "Foo::Foo::Foo", + "writeOnlyProperties": [], + }, + {"type": "string"}, + ), + ], +) +def test_schema(name, resource_schema, expected): + + schema = Schema(resource_schema) + + assert schema.ref == expected, f"{name!r} test got {schema.ref!r}" diff --git a/test/unit/rules/formats/test_schema_comparer.py b/test/unit/rules/formats/test_schema_comparer.py new file mode 100644 index 0000000000..c41b1447f6 --- /dev/null +++ b/test/unit/rules/formats/test_schema_comparer.py @@ -0,0 +1,68 @@ +""" +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: MIT-0 +""" + +import pytest + +from cfnlint.jsonschema import ValidationError +from cfnlint.rules.formats._schema_comparer import compare_schemas + + +@pytest.mark.parametrize( + "name,source,destination,expected", + [ + ( + "basic valid", + {"format": "foo"}, + {"format": "foo"}, + None, + ), + ( + "basic invalid", + {"format": "foo"}, + {"format": "bar"}, + ValidationError( + "'foo' format is incompatible with formats ['bar']", + ), + ), + ( + "basic invalid with no destination", + {"format": "foo"}, + {}, + ValidationError( + "'foo' format is incompatible", + ), + ), + ( + "basic valid with anyOf source", + {"anyOf": [{"format": "foo"}, {"format": "bar"}]}, + {"format": "foo"}, + None, + ), + ( + "basic valid with anyOf source with no valid formats", + {"anyOf": [{"format": "bar"}, {"format": "foobar"}]}, + {"format": "foo"}, + None, + ), + ( + "basic valid with anyOf destination", + {"format": "foo"}, + {"anyOf": [{"format": "foo"}, {"format": "bar"}]}, + None, + ), + ( + "basic invalid with anyOf destination with many invalid", + {"format": "foo"}, + {"anyOf": [{"format": "bar"}, {"format": "foobar"}]}, + ValidationError( + "'foo' format is incompatible with formats ['bar', 'foobar']", + ), + ), + ], +) +def test_schema_comparer(name, source, destination, expected): + result = compare_schemas(source, destination) + + assert result == expected, f"{name!r} got errors {result!r}" diff --git a/test/unit/rules/functions/test_ref_format.py b/test/unit/rules/functions/test_ref_format.py index d0601a2b03..9e13087c03 100644 --- a/test/unit/rules/functions/test_ref_format.py +++ b/test/unit/rules/functions/test_ref_format.py @@ -41,8 +41,8 @@ def template(): [ ValidationError( ( - "{'Ref': 'MyVpc'} with format " - "'AWS::EC2::VPC.Id' does not match " + "{'Ref': 'MyVpc'} with formats " + "['AWS::EC2::VPC.Id'] does not match " "destination format of 'AWS::EC2::Image.Id'" ), rule=RefFormat(),