From 0201ffa60fb8a325ac10636ca6a3a75f3f58b92c Mon Sep 17 00:00:00 2001 From: Ramon Date: Thu, 19 May 2022 15:06:52 +0200 Subject: [PATCH] Make pycfmodel available for Python3.10 (#100) * make pycfmodel available for Python3.10 * update version and changelog * update changelog * update changelog * update version * update cloudformation_actions.py Co-authored-by: Ramon Co-authored-by: Jordi Soucheiron --- .github/workflows/lint-and-test.yml | 2 +- CHANGELOG.md | 3 +- .../properties/test_policy_document.py | 23 +-- .../properties/test_statement_condition.py | 10 +- .../resources/test_ec2_vpc_endpoint_policy.py | 9 +- tests/resources/test_es_domain.py | 16 +- tests/resources/test_generic_resoure.py | 6 +- tests/resources/test_iam_role.py | 10 +- tests/resources/test_opensearch_domain.py | 22 +-- tests/resources/test_s3_bucket.py | 5 +- tests/resources/test_sns_topic_policy.py | 8 +- tests/test_resolver.py | 52 +---- tests/test_resource.py | 178 +++--------------- tests/test_types.py | 11 +- 14 files changed, 68 insertions(+), 287 deletions(-) diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index 829a1939..e5eae075 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.7', '3.8', '3.9'] + python-version: ['3.7', '3.8', '3.9', '3.10'] name: Python ${{ matrix.python-version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ff46136..f7e963af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,11 @@ All notable changes to this project will be documented in this file. ## 0.20.0 ### Additions -- New `RDSDBSecurityGroup` and `RDSDBSecurityGroupIngress` resources +- New `RDSDBSecurityGroup` and `RDSDBSecurityGroupIngress` resources [#103] ### Improvements - IAM Role is able to return its `AssumeRolePolicyDocument` as a list of `OptionallyNamedPolicyDocument`. [#102](https://github.com/Skyscanner/pycfmodel/pull/102) ### Updates +- Compatible with Python3.10 [#100](https://github.com/Skyscanner/pycfmodel/pull/100) - Update `CLOUDFORMATION_ACTIONS`. ## 0.19.1 diff --git a/tests/resources/properties/test_policy_document.py b/tests/resources/properties/test_policy_document.py index e644c891..f3d95c7e 100644 --- a/tests/resources/properties/test_policy_document.py +++ b/tests/resources/properties/test_policy_document.py @@ -73,18 +73,13 @@ def policy_document_not_principal(): **{ "Statement": [ { - "Action": [ - "iam:Delete*", - "s3:GetObject*", - ], + "Action": ["iam:Delete*", "s3:GetObject*"], "Effect": "Allow", "Resource": "arn:aws:s3:::fakebucketfakebucket/*", "NotPrincipal": {"AWS": ["156460612806"]}, }, { - "Action": [ - "s3:List*", - ], + "Action": ["s3:List*"], "Effect": "Deny", "Resource": "arn:aws:s3:::fakebucketfakebucket/*", "NotPrincipal": {"AWS": ["156460612806"]}, @@ -287,13 +282,11 @@ def policy_document_not_action(): **{ "Statement": [ { - "NotAction": [ - "rds:*", - ], + "NotAction": ["rds:*"], "Effect": "Allow", "Resource": "arn:aws:s3:::fakebucketfakebucket/*", "NotPrincipal": {"AWS": ["156460612806"]}, - }, + } ] } ) @@ -315,8 +308,8 @@ def policy_document_condition_with_source_ip(): "Effect": "Allow", "Principal": {"AWS": "*"}, "Resource": "arn:aws:s3:::fakebucketfakebucket/*", - }, - ], + } + ] } ) @@ -332,8 +325,8 @@ def policy_document_condition_with_source_vpce(): "Effect": "Allow", "Principal": {"AWS": "*"}, "Resource": "arn:aws:s3:::fakebucketfakebucket/*", - }, - ], + } + ] } ) diff --git a/tests/resources/properties/test_statement_condition.py b/tests/resources/properties/test_statement_condition.py index 723c40a5..4bba5918 100644 --- a/tests/resources/properties/test_statement_condition.py +++ b/tests/resources/properties/test_statement_condition.py @@ -155,10 +155,7 @@ def test_build_evaluator_null(function: str, arg_a: Any, arg_b: Any, params: Dic @pytest.mark.parametrize( "function, arg_a, arg_b, params, expected_output", - [ - ("NumericEquals", "patata", 1, {"patata": 1}, True), - ("NumericEquals", "patata", 1, {"patata": 2}, False), - ], + [("NumericEquals", "patata", 1, {"patata": 1}, True), ("NumericEquals", "patata", 1, {"patata": 2}, False)], ) def test_build_evaluator_numeric_equals(function: str, arg_a: Any, arg_b: Any, params: Dict, expected_output: bool): node = build_evaluator(function, arg_a, arg_b) @@ -167,10 +164,7 @@ def test_build_evaluator_numeric_equals(function: str, arg_a: Any, arg_b: Any, p @pytest.mark.parametrize( "function, arg_a, arg_b, params, expected_output", - [ - ("NumericNotEquals", "patata", 1, {"patata": 1}, False), - ("NumericNotEquals", "patata", 1, {"patata": 2}, True), - ], + [("NumericNotEquals", "patata", 1, {"patata": 1}, False), ("NumericNotEquals", "patata", 1, {"patata": 2}, True)], ) def test_build_evaluator_numeric_not_equals(function: str, arg_a: Any, arg_b: Any, params: Dict, expected_output: bool): node = build_evaluator(function, arg_a, arg_b) diff --git a/tests/resources/test_ec2_vpc_endpoint_policy.py b/tests/resources/test_ec2_vpc_endpoint_policy.py index 7426f07f..eabb994c 100644 --- a/tests/resources/test_ec2_vpc_endpoint_policy.py +++ b/tests/resources/test_ec2_vpc_endpoint_policy.py @@ -12,14 +12,7 @@ def ec2_vpc_endpoint_policy(): "Properties": { "PolicyDocument": { "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": ["s3:GetObject"], - "Resource": "*", - } - ], + "Statement": [{"Effect": "Allow", "Principal": "*", "Action": ["s3:GetObject"], "Resource": "*"}], }, "RouteTableIds": [{"Ref": "routetableA"}, {"Ref": "routetableB"}], "ServiceName": "com.amazonaws.eu-west-1.s3", diff --git a/tests/resources/test_es_domain.py b/tests/resources/test_es_domain.py index a2c42c2b..fca444df 100644 --- a/tests/resources/test_es_domain.py +++ b/tests/resources/test_es_domain.py @@ -6,12 +6,7 @@ @pytest.fixture() def valid_empty_es_domain(): - return ESDomain( - **{ - "Type": "AWS::Elasticsearch::Domain", - "Properties": {}, - } - ) + return ESDomain(**{"Type": "AWS::Elasticsearch::Domain", "Properties": {}}) @pytest.fixture() @@ -168,14 +163,7 @@ def test_valid_es_domain_from_aws_documentation_examples_resource_can_be_built( def test_raise_error_if_invalid_fields_in_resource(): with pytest.raises(ValidationError) as exc_info: - ESDomain( - **{ - "Type": "AWS::Elasticsearch::Domain", - "Properties": { - "DomainName": [], - }, - } - ) + ESDomain(**{"Type": "AWS::Elasticsearch::Domain", "Properties": {"DomainName": []}}) assert exc_info.value.errors() == [ {"loc": ("Properties", "DomainName"), "msg": "str type expected", "type": "type_error.str"}, diff --git a/tests/resources/test_generic_resoure.py b/tests/resources/test_generic_resoure.py index 32c67572..b8a3bd58 100644 --- a/tests/resources/test_generic_resoure.py +++ b/tests/resources/test_generic_resoure.py @@ -82,11 +82,7 @@ def test_generic_resource_with_bad_json_as_string_is_converted_to_a_string_prope def test_parse_generic_resource_without_properties(): - resource = GenericResource.parse_obj( - { - "Type": "AWS::SNS::Topic", - } - ) + resource = GenericResource.parse_obj({"Type": "AWS::SNS::Topic"}) assert isinstance(resource, GenericResource) assert resource.Properties is None assert resource.Type == "AWS::SNS::Topic" diff --git a/tests/resources/test_iam_role.py b/tests/resources/test_iam_role.py index b000bf99..5b536723 100644 --- a/tests/resources/test_iam_role.py +++ b/tests/resources/test_iam_role.py @@ -18,10 +18,8 @@ def iam_role(): "Action": ["sts:AssumeRole"], "Condition": { "StringLike": { - "iam:AssociatedResourceARN": [ - "arn:aws:ec2:us-east-1:999999999999:instance/*", - ] - }, + "iam:AssociatedResourceARN": ["arn:aws:ec2:us-east-1:999999999999:instance/*"] + } }, }, }, @@ -69,9 +67,7 @@ def test_all_conditions(iam_role): } assert iam_role.assume_role_statement_conditions[0].StringLike == { - "iam:AssociatedResourceARN": [ - "arn:aws:ec2:us-east-1:999999999999:instance/*", - ] + "iam:AssociatedResourceARN": ["arn:aws:ec2:us-east-1:999999999999:instance/*"] } diff --git a/tests/resources/test_opensearch_domain.py b/tests/resources/test_opensearch_domain.py index 68afc627..6ec61d5e 100644 --- a/tests/resources/test_opensearch_domain.py +++ b/tests/resources/test_opensearch_domain.py @@ -9,12 +9,7 @@ @pytest.fixture() def valid_empty_opensearch_domain(): - return OpenSearchDomain( - **{ - "Type": "AWS::OpenSearchService::Domain", - "Properties": {}, - } - ) + return OpenSearchDomain(**{"Type": "AWS::OpenSearchService::Domain", "Properties": {}}) @pytest.fixture() @@ -82,8 +77,8 @@ def valid_opensearch_domain_with_access_policies(): "Action": "es:*", "Resource": "arn:aws:es:us-east-1:123456789012:domain/test/*", } - ], - }, + ] + } }, } ) @@ -201,14 +196,7 @@ def test_valid_opensearch_domain_from_aws_documentation_examples_resource_can_be def test_raise_error_if_invalid_fields_in_resource(): with pytest.raises(ValidationError) as exc_info: - OpenSearchDomain( - **{ - "Type": "AWS::OpenSearchService::Domain", - "Properties": { - "DomainName": [], - }, - } - ) + OpenSearchDomain(**{"Type": "AWS::OpenSearchService::Domain", "Properties": {"DomainName": []}}) assert exc_info.value.errors() == [ {"loc": ("Properties", "DomainName"), "msg": "str type expected", "type": "type_error.str"}, @@ -240,5 +228,5 @@ def test_can_obtain_policy_documents_from_inherited_method(valid_opensearch_doma ] ), name=None, - ), + ) ] diff --git a/tests/resources/test_s3_bucket.py b/tests/resources/test_s3_bucket.py index 7ae3295d..38ae5637 100644 --- a/tests/resources/test_s3_bucket.py +++ b/tests/resources/test_s3_bucket.py @@ -20,10 +20,7 @@ def valid_s3_bucket(): "Id": "MyRule1", "Status": "Enabled", "Prefix": "MyPrefix", - "Destination": { - "Bucket": "arn:aws:s3:::my-replication-bucket", - "StorageClass": "STANDARD", - }, + "Destination": {"Bucket": "arn:aws:s3:::my-replication-bucket", "StorageClass": "STANDARD"}, }, { "Status": "Enabled", diff --git a/tests/resources/test_sns_topic_policy.py b/tests/resources/test_sns_topic_policy.py index 4fbf1464..a3372d51 100644 --- a/tests/resources/test_sns_topic_policy.py +++ b/tests/resources/test_sns_topic_policy.py @@ -15,13 +15,7 @@ def sns_topic_policy(): "Id": "MyTopicPolicy", "Version": "2012-10-17", "Statement": [ - { - "Sid": "Stmt1", - "Effect": "Allow", - "Principal": "*", - "Action": "sns:Publish", - "Resource": "*", - }, + {"Sid": "Stmt1", "Effect": "Allow", "Principal": "*", "Action": "sns:Publish", "Resource": "*"}, { "Sid": "Stmt2", "Effect": "Allow", diff --git a/tests/test_resolver.py b/tests/test_resolver.py index a81e46cd..e0bc27b8 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -317,30 +317,10 @@ def test_template_conditions(): @pytest.mark.parametrize( "conditions, expected", [ - ( - { - "testCondition": {"Fn::Equals": [True, False]}, - }, - [], - ), - ( - { - "testCondition": {"Fn::Equals": [True, True]}, - }, - ["test_resource_id"], - ), - ( - { - "whatever": {"Fn::Equals": [True, False]}, - }, - ["test_resource_id"], - ), - ( - { - "whatever": {"Fn::Equals": [True, True]}, - }, - ["test_resource_id"], - ), + ({"testCondition": {"Fn::Equals": [True, False]}}, []), + ({"testCondition": {"Fn::Equals": [True, True]}}, ["test_resource_id"]), + ({"whatever": {"Fn::Equals": [True, False]}}, ["test_resource_id"]), + ({"whatever": {"Fn::Equals": [True, True]}}, ["test_resource_id"]), ], ) def test_resolve_include_resource_when_condition_is_true_or_doesnt_exist(conditions: Dict, expected: List): @@ -352,10 +332,7 @@ def test_resolve_include_resource_when_condition_is_true_or_doesnt_exist(conditi "Type": "AWS::IAM::Role", "Condition": "testCondition", "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [], - }, + "AssumeRolePolicyDocument": {"Version": "2012-10-17", "Statement": []}, "Path": "/", "Policies": [], }, @@ -382,10 +359,7 @@ def test_resolve_include_resource_when_condition_is_not_present(): "test_resource_id": { "Type": "AWS::IAM::Role", "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [], - }, + "AssumeRolePolicyDocument": {"Version": "2012-10-17", "Statement": []}, "Path": "/", "Policies": [], }, @@ -591,10 +565,7 @@ def test_resolve_booleans(): "Properties": { "Enabled": True, "EnableKeyRotation": True, - "KeyPolicy": { - "Version": "2012-10-17", - "Statement": [], - }, + "KeyPolicy": {"Version": "2012-10-17", "Statement": []}, }, } }, @@ -679,14 +650,7 @@ def test_resolve_booleans_different_properties_for_generic_resource(): def test_resolve_template_with_a_valid_resource_without_properties(): - template = { - "AWSTemplateFormatVersion": "2010-09-09", - "Resources": { - "MySNSTopic": { - "Type": "AWS::SNS::Topic", - } - }, - } + template = {"AWSTemplateFormatVersion": "2010-09-09", "Resources": {"MySNSTopic": {"Type": "AWS::SNS::Topic"}}} model = parse(template).resolve() resource = model.Resources["MySNSTopic"] diff --git a/tests/test_resource.py b/tests/test_resource.py index 236a8755..b51c099f 100644 --- a/tests/test_resource.py +++ b/tests/test_resource.py @@ -42,13 +42,7 @@ "Properties": { "PropertyRandom": "One", "PolicyDocument": { - "Statement": [ - { - "Effect": "Allow", - "Action": ["service:GetService"], - "Resource": "*", - } - ], + "Statement": [{"Effect": "Allow", "Action": ["service:GetService"], "Resource": "*"}] }, }, } @@ -57,13 +51,7 @@ [ OptionallyNamedPolicyDocument( policy_document=PolicyDocument( - Statement=[ - Statement( - Effect="Allow", - Action=["service:GetService"], - Resource="*", - ) - ] + Statement=[Statement(Effect="Allow", Action=["service:GetService"], Resource="*")] ), name=None, ) @@ -80,22 +68,10 @@ "Properties": { "PropertyRandom": "One", "PolicyDocumentOne": { - "Statement": [ - { - "Effect": "Allow", - "Action": ["service:GetService"], - "Resource": "*", - } - ], + "Statement": [{"Effect": "Allow", "Action": ["service:GetService"], "Resource": "*"}] }, "PolicyDocumentTwo": { - "Statement": [ - { - "Effect": "Allow", - "Action": ["service:GetService"], - "Resource": "*", - } - ], + "Statement": [{"Effect": "Allow", "Action": ["service:GetService"], "Resource": "*"}] }, }, } @@ -104,25 +80,13 @@ [ OptionallyNamedPolicyDocument( policy_document=PolicyDocument( - Statement=[ - Statement( - Effect="Allow", - Action=["service:GetService"], - Resource="*", - ) - ] + Statement=[Statement(Effect="Allow", Action=["service:GetService"], Resource="*")] ), name=None, ), OptionallyNamedPolicyDocument( policy_document=PolicyDocument( - Statement=[ - Statement( - Effect="Allow", - Action=["service:GetService"], - Resource="*", - ) - ] + Statement=[Statement(Effect="Allow", Action=["service:GetService"], Resource="*")] ), name=None, ), @@ -139,24 +103,14 @@ "Properties": { "PropertyRandom": "One", "PolicyDocumentOne": { - "Statement": [ - { - "Effect": "Allow", - "Action": ["service:GetService"], - "Resource": "*", - } - ], + "Statement": [{"Effect": "Allow", "Action": ["service:GetService"], "Resource": "*"}] }, "PropertyTwo": { "PolicyDocumentTwo": { "Statement": [ - { - "Effect": "Allow", - "Action": ["service:GetService"], - "Resource": "*", - } - ], - }, + {"Effect": "Allow", "Action": ["service:GetService"], "Resource": "*"} + ] + } }, }, } @@ -165,25 +119,13 @@ [ OptionallyNamedPolicyDocument( policy_document=PolicyDocument( - Statement=[ - Statement( - Effect="Allow", - Action=["service:GetService"], - Resource="*", - ) - ] + Statement=[Statement(Effect="Allow", Action=["service:GetService"], Resource="*")] ), name=None, ), OptionallyNamedPolicyDocument( policy_document=PolicyDocument( - Statement=[ - Statement( - Effect="Allow", - Action=["service:GetService"], - Resource="*", - ) - ] + Statement=[Statement(Effect="Allow", Action=["service:GetService"], Resource="*")] ), name=None, ), @@ -202,24 +144,16 @@ "PropertyOne": { "PolicyDocumentOne": { "Statement": [ - { - "Effect": "Allow", - "Action": ["service:GetService"], - "Resource": "*", - } - ], - }, + {"Effect": "Allow", "Action": ["service:GetService"], "Resource": "*"} + ] + } }, "PropertyTwo": { "PolicyDocumentTwo": { "Statement": [ - { - "Effect": "Allow", - "Action": ["service:GetService"], - "Resource": "*", - } - ], - }, + {"Effect": "Allow", "Action": ["service:GetService"], "Resource": "*"} + ] + } }, }, } @@ -228,25 +162,13 @@ [ OptionallyNamedPolicyDocument( policy_document=PolicyDocument( - Statement=[ - Statement( - Effect="Allow", - Action=["service:GetService"], - Resource="*", - ) - ] + Statement=[Statement(Effect="Allow", Action=["service:GetService"], Resource="*")] ), name=None, ), OptionallyNamedPolicyDocument( policy_document=PolicyDocument( - Statement=[ - Statement( - Effect="Allow", - Action=["service:GetService"], - Resource="*", - ) - ] + Statement=[Statement(Effect="Allow", Action=["service:GetService"], Resource="*")] ), name=None, ), @@ -262,23 +184,11 @@ "Type": "AWS::Non::Existent", "Properties": { "PolicyDocumentTwo": [ + {"Statement": [{"Effect": "Allow", "Action": ["service:GetService"], "Resource": "*"}]}, { "Statement": [ - { - "Effect": "Allow", - "Action": ["service:GetService"], - "Resource": "*", - } - ], - }, - { - "Statement": [ - { - "Effect": "Allow", - "Action": ["service:GetServiceAnother"], - "Resource": "*", - } - ], + {"Effect": "Allow", "Action": ["service:GetServiceAnother"], "Resource": "*"} + ] }, ] }, @@ -288,25 +198,13 @@ [ OptionallyNamedPolicyDocument( policy_document=PolicyDocument( - Statement=[ - Statement( - Effect="Allow", - Action=["service:GetService"], - Resource="*", - ) - ] + Statement=[Statement(Effect="Allow", Action=["service:GetService"], Resource="*")] ), name=None, ), OptionallyNamedPolicyDocument( policy_document=PolicyDocument( - Statement=[ - Statement( - Effect="Allow", - Action=["service:GetServiceAnother"], - Resource="*", - ) - ] + Statement=[Statement(Effect="Allow", Action=["service:GetServiceAnother"], Resource="*")] ), name=None, ), @@ -326,15 +224,11 @@ "PolicyName": "APolicyName", "PolicyDocument": { "Statement": [ - { - "Effect": "Allow", - "Action": ["service:GetService"], - "Resource": "*", - } - ], + {"Effect": "Allow", "Action": ["service:GetService"], "Resource": "*"} + ] }, } - ], + ] }, } }, @@ -342,16 +236,10 @@ [ OptionallyNamedPolicyDocument( policy_document=PolicyDocument( - Statement=[ - Statement( - Effect="Allow", - Action=["service:GetService"], - Resource="*", - ) - ] + Statement=[Statement(Effect="Allow", Action=["service:GetService"], Resource="*")] ), name="APolicyName", - ), + ) ], 1, ), @@ -359,11 +247,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", "Description": "Test resolving a nonexistent resource without properties", - "Resources": { - "NonexistentResource": { - "Type": "AWS::Non::Existent", - } - }, + "Resources": {"NonexistentResource": {"Type": "AWS::Non::Existent"}}, }, [], 0, diff --git a/tests/test_types.py b/tests/test_types.py index d7217d85..a99c6d01 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -67,10 +67,7 @@ class Model(BaseModel): assert Model(ip=value).ip == IPv6Network(value) -@pytest.mark.parametrize( - "value", - [("213.174.214.100/27"), ("192.168.56.101/16"), ("192.0.2.1/24")], -) +@pytest.mark.parametrize("value", [("213.174.214.100/27"), ("192.168.56.101/16"), ("192.0.2.1/24")]) def test_loose_ip_v4_is_not_strict(value): class Model(BaseModel): ip: LooseIPv4Network = None @@ -82,11 +79,7 @@ class Model(BaseModel): @pytest.mark.parametrize( "value", - [ - ("2012::1234:abcd:ffff:c0a8:101/64"), - ("2022::1234:abcd:ffff:c0a8:101/64"), - ("2032::1234:abcd:ffff:c0a8:101/64"), - ], + [("2012::1234:abcd:ffff:c0a8:101/64"), ("2022::1234:abcd:ffff:c0a8:101/64"), ("2032::1234:abcd:ffff:c0a8:101/64")], ) def test_loose_ip_v6_is_not_strict(value): class Model(BaseModel):