From aed0dac2e62a65bf246e3577a694ca81a0433083 Mon Sep 17 00:00:00 2001 From: Kevin DeJong Date: Mon, 21 Nov 2022 16:04:50 -0800 Subject: [PATCH] More testing and code cleanup --- src/cfnlint/data/CloudSpecs/us-east-1.json | 8 +- ...5a0d14c62111ff864923fc7b7960dda6.meta.json | 2 +- ...63a1bf4413531ad420ff60a5a0d7965d.meta.json | 2 +- ...3fbf0a0fb76625ba46dbe42abd34333c.meta.json | 2 +- ...f60a7b5acfc406ebb10d5748cbb8ed41.meta.json | 2 +- ...f35136af536e92a84ccbaf062c315066.meta.json | 2 +- ...548d44cc32e246ec9d7742088a2c17f8.meta.json | 2 +- ...98ac34d4109512e0e0947ef752dcb9c9.meta.json | 2 +- ...69b7533eabab32ecfc0a00cb19e55a5f.meta.json | 2 +- ...05f2567698dfdfa979bf0ccdb68cb856.meta.json | 2 +- ...25c8d66a1f84939600616bab42579541.meta.json | 2 +- ...cf908a34e6b4c3fb3e97e2b584f651ca.meta.json | 2 +- ...6cd7e4ced378cacdb93f76ed227b5c5d.meta.json | 2 +- ...41c64a9c91b2fa5b4928c0d9b2f780b0.meta.json | 2 +- ...3b69878d351cffd417dc9a457df808af.meta.json | 2 +- .../aws-ivs-recordingconfiguration.json | 128 +----- .../ap-southeast-3/aws-elasticache-user.json | 1 + .../aws-elasticache-usergroup.json | 1 + ...lasticbeanstalk-configurationtemplate.json | 121 ++--- .../ProviderSchemas/me-central-1/__init__.py | 0 src/cfnlint/helpers.py | 42 +- .../rules/resources/properties/JsonSchema.py | 32 +- src/cfnlint/schema_manager.py | 37 ++ .../resources/properties/test_json_schema.py | 421 +++++++++++++++--- .../properties/test_value_primitive_type.py | 41 +- 25 files changed, 515 insertions(+), 345 deletions(-) mode change 100644 => 120000 src/cfnlint/data/ProviderSchemas/ap-northeast-1/aws-ivs-recordingconfiguration.json create mode 120000 src/cfnlint/data/ProviderSchemas/ap-southeast-3/aws-elasticache-user.json create mode 120000 src/cfnlint/data/ProviderSchemas/ap-southeast-3/aws-elasticache-usergroup.json create mode 100644 src/cfnlint/data/ProviderSchemas/me-central-1/__init__.py create mode 100644 src/cfnlint/schema_manager.py diff --git a/src/cfnlint/data/CloudSpecs/us-east-1.json b/src/cfnlint/data/CloudSpecs/us-east-1.json index b73628fbe7..ae07b4a0a4 100644 --- a/src/cfnlint/data/CloudSpecs/us-east-1.json +++ b/src/cfnlint/data/CloudSpecs/us-east-1.json @@ -151665,7 +151665,7 @@ ] }, "AWS::GameLift::Fleet.BuildId": { - "AllowedPatternRegex": "^build-\\S+|^arn:.*:build\\/build-\\S+" + "AllowedPatternRegex": "^build-\\S+|^arn:.*:build/build-\\S+" }, "AWS::GameLift::Fleet.CertificateConfiguration.CertificateType": { "AllowedValues": [ @@ -151830,7 +151830,7 @@ "NumberMin": 1 }, "AWS::GameLift::Fleet.IpPermission.IpRange": { - "AllowedPatternRegex": "(^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/([0-9]|[1-2][0-9]|3[0-2]))$)" + "AllowedPatternRegex": "(^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(/([0-9]|[1-2][0-9]|3[0-2]))$)" }, "AWS::GameLift::Fleet.IpPermission.Protocol": { "AllowedValues": [ @@ -151843,7 +151843,7 @@ "NumberMin": 1 }, "AWS::GameLift::Fleet.LocationConfiguration.Location": { - "AllowedPatternRegex": "^[a-z]+(-([a-z]+|\\d))*", + "AllowedPatternRegex": "^[A-Za-z0-9\\-]+", "StringMax": 64, "StringMin": 1 }, @@ -151876,7 +151876,7 @@ "NumberMin": 1 }, "AWS::GameLift::Fleet.ScriptId": { - "AllowedPatternRegex": "^script-\\S+|^arn:.*:script\\/script-\\S+" + "AllowedPatternRegex": "^script-\\S+|^arn:.*:script/script-\\S+" }, "AWS::GameLift::Fleet.ServerProcess.LaunchPath": { "AllowedPatternRegex": "^([Cc]:\\\\game\\S+|/local/game/\\S+)", diff --git a/src/cfnlint/data/DownloadsMetadata/123ba181485ae293d5bd09722af0c19d5a0d14c62111ff864923fc7b7960dda6.meta.json b/src/cfnlint/data/DownloadsMetadata/123ba181485ae293d5bd09722af0c19d5a0d14c62111ff864923fc7b7960dda6.meta.json index b89d915e5f..12657c8958 100644 --- a/src/cfnlint/data/DownloadsMetadata/123ba181485ae293d5bd09722af0c19d5a0d14c62111ff864923fc7b7960dda6.meta.json +++ b/src/cfnlint/data/DownloadsMetadata/123ba181485ae293d5bd09722af0c19d5a0d14c62111ff864923fc7b7960dda6.meta.json @@ -1 +1 @@ -{"etag": "\"2481f3782711bb14d6845d7e8283aaed\"", "url": "https://schema.cloudformation.eu-south-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file +{"etag": "\"854ad8e919512bf5dae6d73f1704f6b0\"", "url": "https://schema.cloudformation.eu-south-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file diff --git a/src/cfnlint/data/DownloadsMetadata/227d6e59c86482f7153466759080e65963a1bf4413531ad420ff60a5a0d7965d.meta.json b/src/cfnlint/data/DownloadsMetadata/227d6e59c86482f7153466759080e65963a1bf4413531ad420ff60a5a0d7965d.meta.json index 5ab4fa9217..308abf523e 100644 --- a/src/cfnlint/data/DownloadsMetadata/227d6e59c86482f7153466759080e65963a1bf4413531ad420ff60a5a0d7965d.meta.json +++ b/src/cfnlint/data/DownloadsMetadata/227d6e59c86482f7153466759080e65963a1bf4413531ad420ff60a5a0d7965d.meta.json @@ -1 +1 @@ -{"etag": "\"ad84c734b0b05ab328ba22c04ded4823\"", "url": "https://schema.cloudformation.me-south-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file +{"etag": "\"d97c4455f0fc833fc262942c45a105dd\"", "url": "https://schema.cloudformation.me-south-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file diff --git a/src/cfnlint/data/DownloadsMetadata/371e40c90b2e47c99f6e275e060ee83a3fbf0a0fb76625ba46dbe42abd34333c.meta.json b/src/cfnlint/data/DownloadsMetadata/371e40c90b2e47c99f6e275e060ee83a3fbf0a0fb76625ba46dbe42abd34333c.meta.json index 978a46397c..52cea7d0c0 100644 --- a/src/cfnlint/data/DownloadsMetadata/371e40c90b2e47c99f6e275e060ee83a3fbf0a0fb76625ba46dbe42abd34333c.meta.json +++ b/src/cfnlint/data/DownloadsMetadata/371e40c90b2e47c99f6e275e060ee83a3fbf0a0fb76625ba46dbe42abd34333c.meta.json @@ -1 +1 @@ -{"etag": "\"06f88cce0a130a2e041a7d8b0773a1ee\"", "url": "https://schema.cloudformation.cn-northwest-1.amazonaws.com.cn/CloudformationSchema.zip"} \ No newline at end of file +{"etag": "\"3905f23c2563b53184bddb98cc9d9667\"", "url": "https://schema.cloudformation.cn-northwest-1.amazonaws.com.cn/CloudformationSchema.zip"} \ No newline at end of file diff --git a/src/cfnlint/data/DownloadsMetadata/42e9df95722b6524cd001503b6750b86f60a7b5acfc406ebb10d5748cbb8ed41.meta.json b/src/cfnlint/data/DownloadsMetadata/42e9df95722b6524cd001503b6750b86f60a7b5acfc406ebb10d5748cbb8ed41.meta.json index 42238f4937..585c7fad22 100644 --- a/src/cfnlint/data/DownloadsMetadata/42e9df95722b6524cd001503b6750b86f60a7b5acfc406ebb10d5748cbb8ed41.meta.json +++ b/src/cfnlint/data/DownloadsMetadata/42e9df95722b6524cd001503b6750b86f60a7b5acfc406ebb10d5748cbb8ed41.meta.json @@ -1 +1 @@ -{"etag": "\"76e705400b23d6fc5a04e48b3a05660e\"", "url": "https://schema.cloudformation.us-west-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file +{"etag": "\"312b95b1bd82cd1f06078294e741174b\"", "url": "https://schema.cloudformation.us-west-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file diff --git a/src/cfnlint/data/DownloadsMetadata/4fbb29b69678acdd32c5758ed43ead9bf35136af536e92a84ccbaf062c315066.meta.json b/src/cfnlint/data/DownloadsMetadata/4fbb29b69678acdd32c5758ed43ead9bf35136af536e92a84ccbaf062c315066.meta.json index abf84aace8..f54157f24b 100644 --- a/src/cfnlint/data/DownloadsMetadata/4fbb29b69678acdd32c5758ed43ead9bf35136af536e92a84ccbaf062c315066.meta.json +++ b/src/cfnlint/data/DownloadsMetadata/4fbb29b69678acdd32c5758ed43ead9bf35136af536e92a84ccbaf062c315066.meta.json @@ -1 +1 @@ -{"etag": "\"d8df69b734f281994b70fb6a8f0d0c06\"", "url": "https://schema.cloudformation.eu-central-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file +{"etag": "\"b3102d7b242f02bfc2d270834d02d06e\"", "url": "https://schema.cloudformation.eu-central-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file diff --git a/src/cfnlint/data/DownloadsMetadata/6316ae24f21cb620947aa250bebbee69548d44cc32e246ec9d7742088a2c17f8.meta.json b/src/cfnlint/data/DownloadsMetadata/6316ae24f21cb620947aa250bebbee69548d44cc32e246ec9d7742088a2c17f8.meta.json index 8753c147f7..057044cb80 100644 --- a/src/cfnlint/data/DownloadsMetadata/6316ae24f21cb620947aa250bebbee69548d44cc32e246ec9d7742088a2c17f8.meta.json +++ b/src/cfnlint/data/DownloadsMetadata/6316ae24f21cb620947aa250bebbee69548d44cc32e246ec9d7742088a2c17f8.meta.json @@ -1 +1 @@ -{"etag": "\"81f62f7551ecdc8e314f33d85739e2aa\"", "url": "https://schema.cloudformation.us-east-2.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file +{"etag": "\"b586cfde0ea1ddb42edc6e0f6e968a1a\"", "url": "https://schema.cloudformation.us-east-2.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file diff --git a/src/cfnlint/data/DownloadsMetadata/81e1cc73ff2daf7d1e1eca393c2d1fdd98ac34d4109512e0e0947ef752dcb9c9.meta.json b/src/cfnlint/data/DownloadsMetadata/81e1cc73ff2daf7d1e1eca393c2d1fdd98ac34d4109512e0e0947ef752dcb9c9.meta.json index 3427e22a18..ce82aa1523 100644 --- a/src/cfnlint/data/DownloadsMetadata/81e1cc73ff2daf7d1e1eca393c2d1fdd98ac34d4109512e0e0947ef752dcb9c9.meta.json +++ b/src/cfnlint/data/DownloadsMetadata/81e1cc73ff2daf7d1e1eca393c2d1fdd98ac34d4109512e0e0947ef752dcb9c9.meta.json @@ -1 +1 @@ -{"etag": "\"821eb3cfd08ce02cc015f4a09ba67d7a\"", "url": "https://schema.cloudformation.ap-southeast-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file +{"etag": "\"198bfd8016be558198e9cc2a83f0e819\"", "url": "https://schema.cloudformation.ap-southeast-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file diff --git a/src/cfnlint/data/DownloadsMetadata/8b8b0cee4df1ef0947a8289e8ec0c67869b7533eabab32ecfc0a00cb19e55a5f.meta.json b/src/cfnlint/data/DownloadsMetadata/8b8b0cee4df1ef0947a8289e8ec0c67869b7533eabab32ecfc0a00cb19e55a5f.meta.json index c912a32bfc..3672a10cc8 100644 --- a/src/cfnlint/data/DownloadsMetadata/8b8b0cee4df1ef0947a8289e8ec0c67869b7533eabab32ecfc0a00cb19e55a5f.meta.json +++ b/src/cfnlint/data/DownloadsMetadata/8b8b0cee4df1ef0947a8289e8ec0c67869b7533eabab32ecfc0a00cb19e55a5f.meta.json @@ -1 +1 @@ -{"etag": "\"49a2d78bc5ae667211055ca4fcd45009\"", "url": "https://schema.cloudformation.ap-southeast-3.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file +{"etag": "\"1551fe8a4eefcd87edf4ec3f3ba58805\"", "url": "https://schema.cloudformation.ap-southeast-3.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file diff --git a/src/cfnlint/data/DownloadsMetadata/c7ada205073390b33b7593ef8f304b9705f2567698dfdfa979bf0ccdb68cb856.meta.json b/src/cfnlint/data/DownloadsMetadata/c7ada205073390b33b7593ef8f304b9705f2567698dfdfa979bf0ccdb68cb856.meta.json index 711b609d67..cca70d65ff 100644 --- a/src/cfnlint/data/DownloadsMetadata/c7ada205073390b33b7593ef8f304b9705f2567698dfdfa979bf0ccdb68cb856.meta.json +++ b/src/cfnlint/data/DownloadsMetadata/c7ada205073390b33b7593ef8f304b9705f2567698dfdfa979bf0ccdb68cb856.meta.json @@ -1 +1 @@ -{"etag": "\"4f6fd78d2343e113950b544b382a35f8\"", "url": "https://schema.cloudformation.sa-east-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file +{"etag": "\"893f1af0ed3a725eaad150645b1edb60\"", "url": "https://schema.cloudformation.sa-east-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file diff --git a/src/cfnlint/data/DownloadsMetadata/dd98171253ebc36f5b78e247f3132b5f25c8d66a1f84939600616bab42579541.meta.json b/src/cfnlint/data/DownloadsMetadata/dd98171253ebc36f5b78e247f3132b5f25c8d66a1f84939600616bab42579541.meta.json index 8b902645b5..af9cf41baa 100644 --- a/src/cfnlint/data/DownloadsMetadata/dd98171253ebc36f5b78e247f3132b5f25c8d66a1f84939600616bab42579541.meta.json +++ b/src/cfnlint/data/DownloadsMetadata/dd98171253ebc36f5b78e247f3132b5f25c8d66a1f84939600616bab42579541.meta.json @@ -1 +1 @@ -{"etag": "\"f6a04ec1c41ac73dbd99ab1491aa739b\"", "url": "https://schema.cloudformation.eu-north-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file +{"etag": "\"0064feaf1c05b8ad7db2e08b5dddd08a\"", "url": "https://schema.cloudformation.eu-north-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file diff --git a/src/cfnlint/data/DownloadsMetadata/e8b3dacc1675b478e8c7392b51f41467cf908a34e6b4c3fb3e97e2b584f651ca.meta.json b/src/cfnlint/data/DownloadsMetadata/e8b3dacc1675b478e8c7392b51f41467cf908a34e6b4c3fb3e97e2b584f651ca.meta.json index 4615a1ba4b..fe758d2405 100644 --- a/src/cfnlint/data/DownloadsMetadata/e8b3dacc1675b478e8c7392b51f41467cf908a34e6b4c3fb3e97e2b584f651ca.meta.json +++ b/src/cfnlint/data/DownloadsMetadata/e8b3dacc1675b478e8c7392b51f41467cf908a34e6b4c3fb3e97e2b584f651ca.meta.json @@ -1 +1 @@ -{"etag": "\"b6d099e4ee90e3a6cd30772150a1e45a\"", "url": "https://schema.cloudformation.eu-west-3.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file +{"etag": "\"3209385473a21967d806d84381e46a9b\"", "url": "https://schema.cloudformation.eu-west-3.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file diff --git a/src/cfnlint/data/DownloadsMetadata/f1896c9151984eec294af1eddf64260f6cd7e4ced378cacdb93f76ed227b5c5d.meta.json b/src/cfnlint/data/DownloadsMetadata/f1896c9151984eec294af1eddf64260f6cd7e4ced378cacdb93f76ed227b5c5d.meta.json index 346c0e8f5c..aa44eeb667 100644 --- a/src/cfnlint/data/DownloadsMetadata/f1896c9151984eec294af1eddf64260f6cd7e4ced378cacdb93f76ed227b5c5d.meta.json +++ b/src/cfnlint/data/DownloadsMetadata/f1896c9151984eec294af1eddf64260f6cd7e4ced378cacdb93f76ed227b5c5d.meta.json @@ -1 +1 @@ -{"etag": "\"00ce71359e953de9dd5049672926e225\"", "url": "https://schema.cloudformation.us-west-2.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file +{"etag": "\"4ed7ed63d02f581f988a5549fb31f38a\"", "url": "https://schema.cloudformation.us-west-2.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file diff --git a/src/cfnlint/data/DownloadsMetadata/f54eee6f8ad9619f41835b700369cdbb41c64a9c91b2fa5b4928c0d9b2f780b0.meta.json b/src/cfnlint/data/DownloadsMetadata/f54eee6f8ad9619f41835b700369cdbb41c64a9c91b2fa5b4928c0d9b2f780b0.meta.json index 735634f05c..0ce257ea3d 100644 --- a/src/cfnlint/data/DownloadsMetadata/f54eee6f8ad9619f41835b700369cdbb41c64a9c91b2fa5b4928c0d9b2f780b0.meta.json +++ b/src/cfnlint/data/DownloadsMetadata/f54eee6f8ad9619f41835b700369cdbb41c64a9c91b2fa5b4928c0d9b2f780b0.meta.json @@ -1 +1 @@ -{"etag": "\"9a2623046bc548aa82cb5f13d1304c2b\"", "url": "https://schema.cloudformation.us-east-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file +{"etag": "\"d9c831c53fef5ade162a6397dc82dfd2\"", "url": "https://schema.cloudformation.us-east-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file diff --git a/src/cfnlint/data/DownloadsMetadata/ff02b7d808c1c00053f09aa43a50addf3b69878d351cffd417dc9a457df808af.meta.json b/src/cfnlint/data/DownloadsMetadata/ff02b7d808c1c00053f09aa43a50addf3b69878d351cffd417dc9a457df808af.meta.json index bd576258a8..4541f41b07 100644 --- a/src/cfnlint/data/DownloadsMetadata/ff02b7d808c1c00053f09aa43a50addf3b69878d351cffd417dc9a457df808af.meta.json +++ b/src/cfnlint/data/DownloadsMetadata/ff02b7d808c1c00053f09aa43a50addf3b69878d351cffd417dc9a457df808af.meta.json @@ -1 +1 @@ -{"etag": "\"59fe345daacecd3eec4df0bafbf39e31\"", "url": "https://schema.cloudformation.ap-northeast-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file +{"etag": "\"fca4818590f1cba0e1c9c4b6ad2dd8fb\"", "url": "https://schema.cloudformation.ap-northeast-1.amazonaws.com/CloudformationSchema.zip"} \ No newline at end of file diff --git a/src/cfnlint/data/ProviderSchemas/ap-northeast-1/aws-ivs-recordingconfiguration.json b/src/cfnlint/data/ProviderSchemas/ap-northeast-1/aws-ivs-recordingconfiguration.json deleted file mode 100644 index dc3caff09b..0000000000 --- a/src/cfnlint/data/ProviderSchemas/ap-northeast-1/aws-ivs-recordingconfiguration.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "typeName" : "AWS::IVS::RecordingConfiguration", - "description" : "Resource Type definition for AWS::IVS::RecordingConfiguration", - "sourceUrl" : "https://github.com/aws-cloudformation/aws-cloudformation-rpdk.git", - "definitions" : { - "Tag" : { - "type" : "object", - "additionalProperties" : false, - "properties" : { - "Key" : { - "type" : "string", - "minLength" : 1, - "maxLength" : 128 - }, - "Value" : { - "type" : "string", - "minLength" : 1, - "maxLength" : 256 - } - }, - "required" : [ "Value", "Key" ] - }, - "DestinationConfiguration" : { - "description" : "Recording Destination Configuration.", - "type" : "object", - "additionalProperties" : false, - "properties" : { - "S3" : { - "$ref" : "#/definitions/S3DestinationConfiguration" - } - }, - "required" : [ "S3" ] - }, - "S3DestinationConfiguration" : { - "description" : "Recording S3 Destination Configuration.", - "type" : "object", - "additionalProperties" : false, - "properties" : { - "BucketName" : { - "type" : "string", - "minLength" : 3, - "maxLength" : 63, - "pattern" : "^[a-z0-9-.]+$" - } - }, - "required" : [ "BucketName" ] - }, - "ThumbnailConfiguration" : { - "description" : "Recording Thumbnail Configuration.", - "type" : "object", - "additionalProperties" : false, - "properties" : { - "RecordingMode" : { - "description" : "Thumbnail Recording Mode, which determines whether thumbnails are recorded at an interval or are disabled.", - "type" : "string", - "enum" : [ "INTERVAL", "DISABLED" ] - }, - "TargetIntervalSeconds" : { - "description" : "Thumbnail recording Target Interval Seconds defines the interval at which thumbnails are recorded. This field is required if RecordingMode is INTERVAL.", - "type" : "integer", - "minimum" : 5, - "maximum" : 60 - } - }, - "required" : [ "RecordingMode" ] - } - }, - "properties" : { - "Arn" : { - "description" : "Recording Configuration ARN is automatically generated on creation and assigned as the unique identifier.", - "type" : "string", - "pattern" : "^arn:aws[-a-z]*:ivs:[a-z0-9-]+:[0-9]+:recording-configuration/[a-zA-Z0-9-]+$", - "minLength" : 1, - "maxLength" : 128 - }, - "Name" : { - "description" : "Recording Configuration Name.", - "type" : "string", - "minLength" : 0, - "maxLength" : 128, - "pattern" : "^[a-zA-Z0-9-_]*$" - }, - "State" : { - "description" : "Recording Configuration State.", - "type" : "string", - "enum" : [ "CREATING", "CREATE_FAILED", "ACTIVE" ] - }, - "DestinationConfiguration" : { - "$ref" : "#/definitions/DestinationConfiguration" - }, - "Tags" : { - "description" : "A list of key-value pairs that contain metadata for the asset model.", - "type" : "array", - "uniqueItems" : true, - "insertionOrder" : false, - "maxItems" : 50, - "items" : { - "$ref" : "#/definitions/Tag" - } - }, - "ThumbnailConfiguration" : { - "$ref" : "#/definitions/ThumbnailConfiguration" - } - }, - "additionalProperties" : false, - "required" : [ "DestinationConfiguration" ], - "primaryIdentifier" : [ "/properties/Arn" ], - "readOnlyProperties" : [ "/properties/Arn", "/properties/State" ], - "createOnlyProperties" : [ "/properties/Name", "/properties/DestinationConfiguration", "/properties/DestinationConfiguration/S3", "/properties/DestinationConfiguration/S3/BucketName", "/properties/ThumbnailConfiguration", "/properties/ThumbnailConfiguration/RecordingMode", "/properties/ThumbnailConfiguration/TargetIntervalSeconds" ], - "handlers" : { - "create" : { - "permissions" : [ "ivs:CreateRecordingConfiguration", "iam:CreateServiceLinkedRole", "iam:PutRolePolicy", "iam:AttachRolePolicy", "s3:ListBucket", "s3:GetBucketLocation", "cloudformation:ListExports" ] - }, - "read" : { - "permissions" : [ "ivs:GetRecordingConfiguration" ] - }, - "update" : { - "permissions" : [ "sts:AssumeRole", "iam:CreateServiceLinkedRole", "ivs:TagResource", "ivs:UntagResource", "ivs:ListTagsForResource" ] - }, - "delete" : { - "permissions" : [ "ivs:DeleteRecordingConfiguration", "iam:CreateServiceLinkedRole" ] - }, - "list" : { - "permissions" : [ "ivs:ListRecordingConfigurations" ] - } - } -} \ No newline at end of file diff --git a/src/cfnlint/data/ProviderSchemas/ap-northeast-1/aws-ivs-recordingconfiguration.json b/src/cfnlint/data/ProviderSchemas/ap-northeast-1/aws-ivs-recordingconfiguration.json new file mode 120000 index 0000000000..512b108944 --- /dev/null +++ b/src/cfnlint/data/ProviderSchemas/ap-northeast-1/aws-ivs-recordingconfiguration.json @@ -0,0 +1 @@ +/Users/kddejong/code/github.com/aws-cloudformation/cfn-python-lint/src/cfnlint/data/ProviderSchemas/us-east-1/aws-ivs-recordingconfiguration.json \ No newline at end of file diff --git a/src/cfnlint/data/ProviderSchemas/ap-southeast-3/aws-elasticache-user.json b/src/cfnlint/data/ProviderSchemas/ap-southeast-3/aws-elasticache-user.json new file mode 120000 index 0000000000..25f1318b1e --- /dev/null +++ b/src/cfnlint/data/ProviderSchemas/ap-southeast-3/aws-elasticache-user.json @@ -0,0 +1 @@ +/Users/kddejong/code/github.com/aws-cloudformation/cfn-python-lint/src/cfnlint/data/ProviderSchemas/us-east-1/aws-elasticache-user.json \ No newline at end of file diff --git a/src/cfnlint/data/ProviderSchemas/ap-southeast-3/aws-elasticache-usergroup.json b/src/cfnlint/data/ProviderSchemas/ap-southeast-3/aws-elasticache-usergroup.json new file mode 120000 index 0000000000..f1b515b030 --- /dev/null +++ b/src/cfnlint/data/ProviderSchemas/ap-southeast-3/aws-elasticache-usergroup.json @@ -0,0 +1 @@ +/Users/kddejong/code/github.com/aws-cloudformation/cfn-python-lint/src/cfnlint/data/ProviderSchemas/us-east-1/aws-elasticache-usergroup.json \ No newline at end of file diff --git a/src/cfnlint/data/ProviderSchemas/eu-central-1/aws-elasticbeanstalk-configurationtemplate.json b/src/cfnlint/data/ProviderSchemas/eu-central-1/aws-elasticbeanstalk-configurationtemplate.json index 9d89edad91..a6d3a3e149 100644 --- a/src/cfnlint/data/ProviderSchemas/eu-central-1/aws-elasticbeanstalk-configurationtemplate.json +++ b/src/cfnlint/data/ProviderSchemas/eu-central-1/aws-elasticbeanstalk-configurationtemplate.json @@ -1,111 +1,76 @@ { "typeName" : "AWS::ElasticBeanstalk::ConfigurationTemplate", "description" : "Resource Type definition for AWS::ElasticBeanstalk::ConfigurationTemplate", - "sourceUrl" : "https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-elasticbeanstalk.git", - "$schema" : "https://raw.githubusercontent.com/aws-cloudformation/cloudformation-resource-schema/master/src/main/resources/schema/provider.definition.schema.v1.json", "additionalProperties" : false, + "properties" : { + "EnvironmentId" : { + "type" : "string" + }, + "PlatformArn" : { + "type" : "string" + }, + "ApplicationName" : { + "type" : "string" + }, + "Description" : { + "type" : "string" + }, + "OptionSettings" : { + "type" : "array", + "uniqueItems" : false, + "items" : { + "$ref" : "#/definitions/ConfigurationOptionSetting" + } + }, + "SourceConfiguration" : { + "$ref" : "#/definitions/SourceConfiguration" + }, + "TemplateName" : { + "type" : "string" + }, + "SolutionStackName" : { + "type" : "string" + }, + "Id" : { + "type" : "string" + } + }, "definitions" : { "SourceConfiguration" : { "type" : "object", "additionalProperties" : false, "properties" : { - "ApplicationName" : { - "description" : "The name of the application associated with the configuration.", + "TemplateName" : { "type" : "string" }, - "TemplateName" : { - "description" : "The name of the configuration template.", + "ApplicationName" : { "type" : "string" } }, - "required" : [ "TemplateName", "ApplicationName" ] + "required" : [ "ApplicationName", "TemplateName" ] }, "ConfigurationOptionSetting" : { "type" : "object", "additionalProperties" : false, "properties" : { - "Namespace" : { - "description" : "A unique namespace that identifies the option's associated AWS resource.", + "Value" : { "type" : "string" }, - "OptionName" : { - "description" : "The name of the configuration option.", + "ResourceName" : { "type" : "string" }, - "ResourceName" : { - "description" : "A unique resource name for the option setting. Use it for a time–based scaling configuration option. ", + "Namespace" : { "type" : "string" }, - "Value" : { - "description" : "The current value for the configuration option.", + "OptionName" : { "type" : "string" } }, "required" : [ "Namespace", "OptionName" ] } }, - "properties" : { - "ApplicationName" : { - "description" : "The name of the Elastic Beanstalk application to associate with this configuration template. ", - "type" : "string" - }, - "Description" : { - "description" : "An optional description for this configuration.", - "type" : "string" - }, - "EnvironmentId" : { - "description" : "The ID of an environment whose settings you want to use to create the configuration template. You must specify EnvironmentId if you don't specify PlatformArn, SolutionStackName, or SourceConfiguration. ", - "type" : "string" - }, - "OptionSettings" : { - "description" : "Option values for the Elastic Beanstalk configuration, such as the instance type. If specified, these values override the values obtained from the solution stack or the source configuration template. For a complete list of Elastic Beanstalk configuration options, see [Option Values](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options.html) in the AWS Elastic Beanstalk Developer Guide. ", - "type" : "array", - "uniqueItems" : false, - "insertionOrder" : false, - "items" : { - "$ref" : "#/definitions/ConfigurationOptionSetting" - } - }, - "PlatformArn" : { - "description" : "The Amazon Resource Name (ARN) of the custom platform. For more information, see [Custom Platforms](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/custom-platforms.html) in the AWS Elastic Beanstalk Developer Guide. ", - "type" : "string" - }, - "SolutionStackName" : { - "description" : "The name of an Elastic Beanstalk solution stack (platform version) that this configuration uses. For example, 64bit Amazon Linux 2013.09 running Tomcat 7 Java 7. A solution stack specifies the operating system, runtime, and application server for a configuration template. It also determines the set of configuration options as well as the possible and default values. For more information, see [Supported Platforms](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/concepts.platforms.html) in the AWS Elastic Beanstalk Developer Guide.\n\n You must specify SolutionStackName if you don't specify PlatformArn, EnvironmentId, or SourceConfiguration.\n\n Use the ListAvailableSolutionStacks API to obtain a list of available solution stacks. ", - "type" : "string" - }, - "SourceConfiguration" : { - "description" : "An Elastic Beanstalk configuration template to base this one on. If specified, Elastic Beanstalk uses the configuration values from the specified configuration template to create a new configuration.\n\nValues specified in OptionSettings override any values obtained from the SourceConfiguration.\n\nYou must specify SourceConfiguration if you don't specify PlatformArn, EnvironmentId, or SolutionStackName.\n\nConstraint: If both solution stack name and source configuration are specified, the solution stack of the source configuration template must match the specified solution stack name. ", - "$ref" : "#/definitions/SourceConfiguration" - }, - "TemplateName" : { - "description" : "The name of the configuration template", - "type" : "string" - } - }, "required" : [ "ApplicationName" ], - "tagging" : { - "taggable" : false - }, - "createOnlyProperties" : [ "/properties/ApplicationName", "/properties/EnvironmentId", "/properties/PlatformArn", "/properties/SolutionStackName", "/properties/SourceConfiguration" ], - "primaryIdentifier" : [ "/properties/ApplicationName", "/properties/TemplateName" ], - "readOnlyProperties" : [ "/properties/TemplateName" ], - "writeOnlyProperties" : [ "/properties/EnvironmentId", "/properties/SourceConfiguration/ApplicationName", "/properties/SourceConfiguration/TemplateName" ], - "handlers" : { - "create" : { - "permissions" : [ "elasticbeanstalk:CreateConfigurationTemplate" ] - }, - "read" : { - "permissions" : [ "elasticbeanstalk:DescribeConfigurationSettings" ] - }, - "update" : { - "permissions" : [ "elasticbeanstalk:UpdateConfigurationTemplate" ] - }, - "delete" : { - "permissions" : [ "elasticbeanstalk:DeleteConfigurationTemplate", "elasticbeanstalk:DescribeConfigurationSettings" ] - }, - "list" : { - "permissions" : [ "elasticbeanstalk:DescribeApplications" ] - } - } + "createOnlyProperties" : [ "/properties/PlatformArn", "/properties/ApplicationName", "/properties/SolutionStackName", "/properties/EnvironmentId", "/properties/SourceConfiguration" ], + "primaryIdentifier" : [ "/properties/Id" ], + "readOnlyProperties" : [ "/properties/TemplateName", "/properties/Id" ] } \ No newline at end of file diff --git a/src/cfnlint/data/ProviderSchemas/me-central-1/__init__.py b/src/cfnlint/data/ProviderSchemas/me-central-1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/cfnlint/helpers.py b/src/cfnlint/helpers.py index b024f99ad1..30538237af 100644 --- a/src/cfnlint/helpers.py +++ b/src/cfnlint/helpers.py @@ -15,7 +15,7 @@ import inspect import gzip from io import BytesIO -from typing import Any, Dict, List +from typing import Dict, List import importlib.resources as pkg_resources import importlib from urllib.request import urlopen, Request, urlretrieve @@ -384,8 +384,6 @@ def load_resource(package, filename='us-east-1.json'): RESOURCE_SPECS: Dict[str, dict] = {} REGISTRY_SCHEMAS: List[dict] = [] -PROVIDER_SCHEMAS_AWS: Dict[str, Dict[str, dict]] = {} -__provider_schema_modules: Dict[str, Any] = {} def merge_spec(source, destination): @@ -490,45 +488,7 @@ def load_region(region): RESOURCE_SPECS[region] = load_region(region) -def initialize_provider_spec_modules() -> None: - """Initialize the provider spec variables and modules. - To be dynamic we import the modules from the regions variable - - Args: - - Returns: - None: returns nothing when complete - """ - for reg in REGIONS: - PROVIDER_SCHEMAS_AWS[reg] = {} - # provider specs don't exist for me-central-1 yet - if reg != 'me-central-1': - __provider_schema_modules[reg] = __import__( - f'cfnlint.data.ProviderSchemas.{reg}', fromlist=[''] - ) - - -def get_provider_schema(region: str, resource_type: str) -> dict: - """Get the provider resource shcema and cache it to speed up future lookups - - Args: - region (str): the region in which do ge the provider schema for - resource_type (str): the :: version of the resource type - Returns: - dict: returns the schema - """ - rt = resource_type.replace('::', '-').lower() - schema = PROVIDER_SCHEMAS_AWS[region].get(resource_type) - if schema is None: - PROVIDER_SCHEMAS_AWS[region][resource_type] = load_resource( - __provider_schema_modules[region], filename=(f'{rt}.json') - ) - return PROVIDER_SCHEMAS_AWS[region][resource_type] - return schema - - initialize_specs() -initialize_provider_spec_modules() def format_json_string(json_string): diff --git a/src/cfnlint/rules/resources/properties/JsonSchema.py b/src/cfnlint/rules/resources/properties/JsonSchema.py index ecec8dc1f3..9992c00631 100644 --- a/src/cfnlint/rules/resources/properties/JsonSchema.py +++ b/src/cfnlint/rules/resources/properties/JsonSchema.py @@ -9,7 +9,6 @@ import jsonschema from cfnlint.rules import CloudFormationLintRule from cfnlint.rules import RuleMatch -import cfnlint.helpers from cfnlint.helpers import ( REGEX_DYN_REF, PSEUDOPARAMS, @@ -17,6 +16,7 @@ UNCONVERTED_SUFFIXES, REGISTRY_SCHEMAS, ) +from cfnlint.schema_manager import JSON_SCHEMA_MANAGER LOGGER = logging.getLogger('cfnlint.rules.resources.properties.JsonSchema') @@ -67,16 +67,11 @@ def __init__(self): super().__init__() self.cfn = {} self.rules = RuleSet() - self.config_definition = {'enabled': {'default': False, 'type': 'boolean'}} + self.config_definition = { + 'enable-aws-schemas': {'default': False, 'type': 'boolean'} + } # pylint: disable=unused-argument - def validate_required(self, validator, required, instance, schema): - if not validator.is_type(instance, 'object'): - return - for p in required: - if p not in instance: - yield jsonschema.ValidationError(f'{p!r} is a required property') - def properties(self, validator, properties, instance, schema): if not validator.is_type(instance, 'object'): return @@ -125,6 +120,15 @@ def json_schema_validate(self, validator, properties, path): kwargs = {} if hasattr(e, 'extra_args'): kwargs = getattr(e, 'extra_args') + if len(e.path) > 0: + key = e.path[-1] + if hasattr(key, 'start_mark'): + kwargs['location'] = ( + key.start_mark.line, + key.start_mark.column, + key.end_mark.line, + key.end_mark.column, + ) matches.append( RuleMatch( path + list(e.path), @@ -156,9 +160,9 @@ def _setup_validator(self): 'properties': self.properties, } for js, rule_id in self.rules.__dict__.items(): - if rule_id == 'E3012': - rule = self.child_rules.get(rule_id) - if rule is not None: + rule = self.child_rules.get(rule_id) + if rule is not None: + if hasattr(rule, 'validate') and callable(getattr(rule, 'validate')): validators[js] = rule.validate self.validator = jsonschema.validators.extend( @@ -195,7 +199,7 @@ def match(self, cfn): ) ) - if not self.config.get('enabled'): + if not self.config.get('enable-aws-schemas'): return matches self._setup_validator() @@ -208,7 +212,7 @@ def match(self, cfn): if p and t: for region in cfn.regions: try: - schema = cfnlint.helpers.get_provider_schema(region, t) + schema = JSON_SCHEMA_MANAGER.get_resource_schema(region, t) except KeyError as e: if e.args[0] == region: LOGGER.info('No specs for region %s', region) diff --git a/src/cfnlint/schema_manager.py b/src/cfnlint/schema_manager.py new file mode 100644 index 0000000000..2769680cbb --- /dev/null +++ b/src/cfnlint/schema_manager.py @@ -0,0 +1,37 @@ +from typing import Dict, Any +from cfnlint.helpers import REGIONS, load_resource + + +class JsonSchemaManager: + _schemas: Dict[str, Dict[str, dict]] = {} + _provider_schema_modules: Dict[str, Any] = {} + + def __init__(self) -> None: + self._schemas: Dict[str, Dict[str, dict]] = {} + for reg in REGIONS: + self._schemas[reg] = {} + + self._provider_schema_modules[reg] = __import__( + f'cfnlint.data.ProviderSchemas.{reg}', fromlist=[''] + ) + + def get_resource_schema(self, region: str, resource_type: str) -> Dict: + """Get the provider resource shcema and cache it to speed up future lookups + + Args: + region (str): the region in which do ge the provider schema for + resource_type (str): the :: version of the resource type + Returns: + dict: returns the schema + """ + rt = resource_type.replace('::', '-').lower() + schema = self._schemas[region].get(resource_type) + if schema is None: + self._schemas[region][resource_type] = load_resource( + self._provider_schema_modules[region], filename=(f'{rt}.json') + ) + return self._schemas[region][resource_type] + return schema + + +JSON_SCHEMA_MANAGER: JsonSchemaManager = JsonSchemaManager() diff --git a/test/unit/rules/resources/properties/test_json_schema.py b/test/unit/rules/resources/properties/test_json_schema.py index 8f6a049429..f4c22af953 100644 --- a/test/unit/rules/resources/properties/test_json_schema.py +++ b/test/unit/rules/resources/properties/test_json_schema.py @@ -2,13 +2,14 @@ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: MIT-0 """ +from typing import List from copy import deepcopy from test.unit.rules import BaseRuleTestCase from cfnlint.rules.resources.properties.JsonSchema import ( JsonSchema, RuleSet, ) # pylint: disable=E0401 -import cfnlint.helpers +from cfnlint.schema_manager import JSON_SCHEMA_MANAGER from cfnlint.rules import RuleMatch from cfnlint.rules.resources.properties import ( Properties as RuleProperties, @@ -20,7 +21,11 @@ NumberSize as RuleNumberSize, ListSize as RuleListSize, AllowedPattern as RuleStringPattern, + OnlyOne as RuleOneOf, ) +from cfnlint.decode.node import str_node +from cfnlint.decode.cfn_json import Mark + class TestJsonSchema(BaseRuleTestCase): """Test Json Size""" @@ -29,7 +34,16 @@ def setUp(self): """Setup""" super(TestJsonSchema, self).setUp() self.rule = JsonSchema() - self.table = DynamoDBTable() + self.table = { + build_key("A"): [{"AttributeName": "pk", "KeyType": "HASH"}], + build_key("B"): { + build_key("C"): "TTL", + build_key("D"): True, + }, + build_key("E"): "string", + build_key("F"): 5, + build_key("G"): ["A", "A"], + } self.ruleset = RuleSet() self.rule.child_rules = { @@ -42,94 +56,371 @@ def setUp(self): "E3033": RuleStringSize.StringSize(), "E3034": RuleNumberSize.NumberSize(), "E3037": RuleListDuplicates.ListDuplicates(), + "E2523": RuleOneOf.OnlyOne(), } - - schema = deepcopy(cfnlint.helpers.get_provider_schema(region="us-east-1", resource_type="AWS::DynamoDB::Table")) - self.schema = self.rule.clense_schema(schema) self.rule._setup_validator() + self.path = ["Resources", "Table", "Properties"] - def test_required_empty(self): - """Remove conditions and still validate""" + def build_result( + self, rule_id: str, message: str, path: List[str], **kwargs + ) -> RuleMatch: + return RuleMatch( + self.path + path[:], + message, + rule=self.rule.child_rules.get(rule_id), + location=( + f'{path[-1]}-sm-line', + f'{path[-1]}-sm-column', + f'{path[-1]}-em-line', + f'{path[-1]}-em-column', + ), + **kwargs, + ) + + def validate(self, schema, expected, object=None): + if object is None: + object = self.table + cleansed_schema = self.rule.clense_schema(schema) - path = ["Resources", "Table", "Properties"] matches = self.rule.json_schema_validate( - self.rule.validator(self.schema), {}, path + self.rule.validator(cleansed_schema), object, self.path ) + + self.assertListEqual(list(map(vars, expected)), list(map(vars, matches))) + + def test_required_empty(self): + schema = { + "properties": { + "A": { + "type": "string", + }, + }, + "required": ["A"], + } + expected = [ RuleMatch( - path, - "'KeySchema' is a required property", + self.path, + "'A' is a required property", rule=self.rule.child_rules.get('E3003'), ) ] - self.assertListEqual(list(map(vars, expected)), list(map(vars, matches))) + self.validate(schema, expected, {}) def test_required_nested(self): - """Remove conditions and still validate""" + schema = { + "definitions": { + "Z": { + "type": "object", + "properties": { + "X": { + "type": "string", + }, + }, + "required": ["X"], + }, + }, + "properties": { + "B": {"$ref": "#/definitions/Z"}, + }, + "required": ["B"], + } - path = ["Resources", "Table", "Properties"] - matches = self.rule.json_schema_validate( - self.rule.validator(self.schema), self.table.get_object_required(), path - ) expected = [ - RuleMatch( - path + ["TimeToLiveSpecification"], - "'Enabled' is a required property", - rule=self.rule.child_rules.get('E3003'), - ) + self.build_result('E3003', "'X' is a required property", ["B"]), ] - self.assertListEqual(list(map(vars, expected)), list(map(vars, matches))) + self.validate(schema, expected) def test_additional_properties(self): - """Test additional properties""" + schema = { + "definitions": { + "Z": { + "type": "object", + "properties": { + "C": { + "type": "string", + } + }, + "additionalProperties": False, + } + }, + "properties": { + "A": { + "type": "array", + }, + "B": {"$ref": "#/definitions/Z"}, + "F": {"type": "number"}, + "G": {"type": "array"}, + }, + "additionalProperties": False, + } - path = ["Resources", "Table", "Properties"] - matches = self.rule.json_schema_validate( - self.rule.validator(self.schema), - self.table.get_additional_properties(), - path, - ) expected = [ - RuleMatch( - path + ["Arn"], - "Additional properties are not allowed (Arn unexpected)", - rule=self.rule.child_rules.get('E3002'), + self.build_result( + 'E3002', + "Additional properties are not allowed (D unexpected)", + ["B", "D"], ), - RuleMatch( - path + ["StreamSpecification", "Extra"], - "Additional properties are not allowed (Extra unexpected)", - rule=self.rule.child_rules.get('E3002'), + self.build_result( + 'E3002', "Additional properties are not allowed (E unexpected)", ["E"] ), ] - self.assertListEqual(list(map(vars, expected)), list(map(vars, matches))) + self.validate(schema, expected) + + def test_one_of(self): + schema = { + "definitions": { + "Z": { + "oneOf": [ + { + "type": "object", + "properties": { + "C": { + "type": "string", + } + }, + # there are additional properties + "additionalProperties": False, + }, + { + "type": "object", + "properties": { + # Z doesn't exist + "Z": { + "type": "string", + } + }, + "required": ["Z"], + }, + ] + } + }, + "properties": { + "B": {"$ref": "#/definitions/Z"}, + }, + } + + expected = [ + self.build_result( + 'E2523', + "{'C': 'TTL', 'D': True} is not valid under any of the given schemas", + ["B"], + ), + ] + + self.validate(schema, expected) + + def test_pattern(self): + schema = { + "properties": { + "E": {"type": "string", "pattern": "^number$"}, + }, + } + + expected = [ + self.build_result('E3031', "'string' does not match '^number$'", ["E"]), + ] + + self.validate(schema, expected) + + def test_min_items(self): + schema = { + "properties": { + "A": { + "type": "array", + "minItems": 5, + }, + }, + } + + expected = [ + self.build_result( + 'E3032', + "[{'AttributeName': 'pk', 'KeyType': 'HASH'}] is too short", + ["A"], + ), + ] + + self.validate(schema, expected) + + def test_max_items(self): + schema = { + "properties": { + "A": { + "type": "array", + "maxItems": 0, + }, + }, + } + + expected = [ + self.build_result( + 'E3032', + "[{'AttributeName': 'pk', 'KeyType': 'HASH'}] is too long", + ["A"], + ), + ] + + self.validate(schema, expected) + + def test_min_number(self): + schema = { + "properties": { + "F": { + "type": "number", + "minimum": 6, + }, + }, + } + + expected = [ + self.build_result('E3034', "5 is less than the minimum of 6", ["F"]), + ] + + self.validate(schema, expected) + + def test_max_number(self): + schema = { + "properties": { + "F": { + "type": "number", + "maximum": 4, + }, + }, + } + + expected = [ + self.build_result('E3034', "5 is greater than the maximum of 4", ["F"]), + ] + + self.validate(schema, expected) + + def test_exclusive_min_number(self): + schema = { + "properties": { + "F": { + "type": "number", + "minimum": 6, + }, + }, + } + + expected = [ + self.build_result('E3034', "5 is less than the minimum of 6", ["F"]), + ] + + self.validate(schema, expected) + + def test_exclusive_max_number(self): + schema = { + "properties": { + "F": { + "type": "number", + "maximum": 4, + }, + }, + } + + expected = [ + self.build_result('E3034', "5 is greater than the maximum of 4", ["F"]), + ] + + self.validate(schema, expected) + + def test_unique_array(self): + schema = { + "properties": { + "G": { + "type": "array", + "uniqueItems": True, + }, + }, + } + + expected = [ + self.build_result('E3037', "['A', 'A'] has non-unique elements", ["G"]), + ] + + self.validate(schema, expected) + + def test_min_length(self): + schema = { + "properties": { + "E": { + "type": "string", + "minLength": 10, + }, + }, + } + + expected = [ + self.build_result('E3033', "'string' is too short", ["E"]), + ] + + self.validate(schema, expected) + + def test_max_length(self): + schema = { + "properties": { + "E": { + "type": "string", + "maxLength": 3, + }, + }, + } + + expected = [ + self.build_result('E3033', "'string' is too long", ["E"]), + ] + + self.validate(schema, expected) + + def test_enum(self): + schema = { + "properties": { + "E": {"type": "string", "enum": ["number"]}, + }, + } + + expected = [ + self.build_result('E3030', "'string' is not one of ['number']", ["E"]), + ] + + self.validate(schema, expected) + + def test_type(self): + schema = { + "properties": { + "E": { + "type": "array", + }, + }, + } + + expected = [ + self.build_result( + 'E3012', + "'string' is not of type 'array'", + ["E"], + actual_type='str', + expected_type="'array'", + ), + ] + + self.validate(schema, expected) -class DynamoDBTable(object): - _table = { - "KeySchema": [{"AttributeName": "pk", "KeyType": "HASH"}], - "TimeToLiveSpecification": { - "AttributeName": "TTL", - "Enabled": True, - }, - "AttributeDefinitions": [{"AttributeName": "pk", "AttributeType": "S"}], - } - - def get_table(self): - return self._table - - def get_object_required(self): - table = deepcopy(self._table) - del table["TimeToLiveSpecification"]["Enabled"] - return table - - def get_additional_properties(self): - table = deepcopy(self._table) - table["Arn"] = "arn" # Read only - table["StreamSpecification"] = { - "StreamViewType": "KEYS_ONLY", - "Extra": False, - } - return table +def build_key(key: str): + caps = [] + for s in key: + if s.isupper(): + caps.append(s) + abbr = ''.join(caps) + return str_node( + key, + Mark(f'{abbr}-sm-line', f'{abbr}-sm-column'), + Mark(f'{abbr}-em-line', f'{abbr}-em-column'), + ) diff --git a/test/unit/rules/resources/properties/test_value_primitive_type.py b/test/unit/rules/resources/properties/test_value_primitive_type.py index 3560e7cedc..b30573ff43 100644 --- a/test/unit/rules/resources/properties/test_value_primitive_type.py +++ b/test/unit/rules/resources/properties/test_value_primitive_type.py @@ -202,8 +202,6 @@ def test_file_check_value_bad_object(self): 'Fn::Select, Fn::Sub, Fn::ToJsonString, Ref] when ' 'providing a value of type [String]') - - self.rule.config["strict"] = False def test_file_positive(self): @@ -243,6 +241,45 @@ def test_file_positive(self): self.assertEqual(self.rule._schema_check_primitive_type(1.34, ["boolean", "string"]), True) self.assertEqual(self.rule._schema_check_primitive_type("1.34", ["boolean", "number"]), True) + def test_positive_strict(self): + self.rule.config['strict'] = True + # Test Booleans + self.assertEqual( + self.rule._schema_check_primitive_type("True", ["boolean"]), False + ) + self.assertEqual( + self.rule._schema_check_primitive_type("False", ["boolean"]), False + ) + self.assertEqual(self.rule._schema_check_primitive_type(1, ["boolean"]), False) + # Test Strings + self.assertEqual(self.rule._schema_check_primitive_type(1, ["string"]), False) + self.assertEqual(self.rule._schema_check_primitive_type(2, ["string"]), False) + self.assertEqual(self.rule._schema_check_primitive_type(True, ["string"]), False) + # Test Integer + self.assertEqual(self.rule._schema_check_primitive_type("1", ["integer"]), False) + self.assertEqual( + self.rule._schema_check_primitive_type("1.2", ["integer"]), False + ) + self.assertEqual(self.rule._schema_check_primitive_type(True, ["integer"]), False) + self.assertEqual( + self.rule._schema_check_primitive_type("test", ["integer"]), False + ) + # Test Double + self.assertEqual(self.rule._schema_check_primitive_type("1", ["number"]), False) + self.assertEqual(self.rule._schema_check_primitive_type("1.2", ["number"]), False) + self.assertEqual(self.rule._schema_check_primitive_type(1, ["number"]), True) + self.assertEqual(self.rule._schema_check_primitive_type(True, ["number"]), False) + self.assertEqual( + self.rule._schema_check_primitive_type("test", ["number"]), False + ) + # Test multiple types + self.assertEqual(self.rule._schema_check_primitive_type("1", ["string", "integer"]), True) + self.assertEqual(self.rule._schema_check_primitive_type("test", ["boolean", "integer"]), False) + self.assertEqual(self.rule._schema_check_primitive_type(1.34, ["boolean", "string"]), False) + self.assertEqual(self.rule._schema_check_primitive_type("1.34", ["boolean", "number"]), False) + + self.rule.config['strict'] = False + class TestResourceValuePrimitiveTypeJsonSchemaValidate(BaseRuleTestCase): """Test Primitive Value Types for Json Schema non strict"""