From 2676a4699e03eac953e3189ff756de2044ffd7f2 Mon Sep 17 00:00:00 2001 From: Arthur Boghossian Date: Thu, 14 Dec 2023 14:52:00 -0800 Subject: [PATCH 1/3] Support Fn::ForEach intrinsic function (#8096) Allow "aws cloudformation package" to succeed when a Resource is not a key-value pair. --- ...hancement-cloudformationpackage-39279.json | 5 + .../cloudformation/artifact_exporter.py | 15 +- .../cloudformation/exceptions.py | 4 + .../cloudformation/test_artifact_exporter.py | 155 ++++++++++++++++++ 4 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 .changes/next-release/enhancement-cloudformationpackage-39279.json diff --git a/.changes/next-release/enhancement-cloudformationpackage-39279.json b/.changes/next-release/enhancement-cloudformationpackage-39279.json new file mode 100644 index 000000000000..329081a2ec00 --- /dev/null +++ b/.changes/next-release/enhancement-cloudformationpackage-39279.json @@ -0,0 +1,5 @@ +{ + "type": "enhancement", + "category": "``cloudformation package``", + "description": "Add support for intrinsic Fn:ForEach (fixes `#8075 `__)" +} diff --git a/awscli/customizations/cloudformation/artifact_exporter.py b/awscli/customizations/cloudformation/artifact_exporter.py index 9bb150660c02..64eb5a06e1a4 100644 --- a/awscli/customizations/cloudformation/artifact_exporter.py +++ b/awscli/customizations/cloudformation/artifact_exporter.py @@ -659,7 +659,18 @@ def export(self): self.template_dict = self.export_global_artifacts(self.template_dict) - for resource_id, resource in self.template_dict["Resources"].items(): + self.export_resources(self.template_dict["Resources"]) + + return self.template_dict + + def export_resources(self, resource_dict): + for resource_id, resource in resource_dict.items(): + + if resource_id.startswith("Fn::ForEach::"): + if not isinstance(resource, list) or len(resource) != 3: + raise exceptions.InvalidForEachIntrinsicFunctionError(resource_id=resource_id) + self.export_resources(resource[2]) + continue resource_type = resource.get("Type", None) resource_dict = resource.get("Properties", None) @@ -671,5 +682,3 @@ def export(self): # Export code resources exporter = exporter_class(self.uploader) exporter.export(resource_id, resource_dict, self.template_dir) - - return self.template_dict diff --git a/awscli/customizations/cloudformation/exceptions.py b/awscli/customizations/cloudformation/exceptions.py index a31cf25ea492..b2625cdd27f9 100644 --- a/awscli/customizations/cloudformation/exceptions.py +++ b/awscli/customizations/cloudformation/exceptions.py @@ -53,3 +53,7 @@ class DeployBucketRequiredError(CloudFormationCommandError): "via an S3 Bucket. Please add the --s3-bucket parameter to your " "command. The local template will be copied to that S3 bucket and " "then deployed.") + + +class InvalidForEachIntrinsicFunctionError(CloudFormationCommandError): + fmt = 'The value of {resource_id} has an invalid "Fn::ForEach::" format: Must be a list of three entries' diff --git a/tests/unit/customizations/cloudformation/test_artifact_exporter.py b/tests/unit/customizations/cloudformation/test_artifact_exporter.py index 93df4297d660..1b071101cc7e 100644 --- a/tests/unit/customizations/cloudformation/test_artifact_exporter.py +++ b/tests/unit/customizations/cloudformation/test_artifact_exporter.py @@ -1016,6 +1016,161 @@ def test_template_export(self, yaml_parse_mock): resource_type2_instance.export.assert_called_once_with( "Resource2", mock.ANY, template_dir) + @mock.patch("awscli.customizations.cloudformation.artifact_exporter.yaml_parse") + def test_template_export_foreach_valid(self, yaml_parse_mock): + parent_dir = os.path.sep + template_dir = os.path.join(parent_dir, 'foo', 'bar') + template_path = os.path.join(template_dir, 'path') + template_str = self.example_yaml_template() + + resource_type1_class = mock.Mock() + resource_type1_class.RESOURCE_TYPE = "resource_type1" + resource_type1_instance = mock.Mock() + resource_type1_class.return_value = resource_type1_instance + resource_type2_class = mock.Mock() + resource_type2_class.RESOURCE_TYPE = "resource_type2" + resource_type2_instance = mock.Mock() + resource_type2_class.return_value = resource_type2_instance + + resources_to_export = [ + resource_type1_class, + resource_type2_class + ] + + properties = {"foo": "bar"} + template_dict = { + "Resources": { + "Resource1": { + "Type": "resource_type1", + "Properties": properties + }, + "Resource2": { + "Type": "resource_type2", + "Properties": properties + }, + "Resource3": { + "Type": "some-other-type", + "Properties": properties + }, + "Fn::ForEach::OuterLoopName": [ + "Identifier1", + ["4", "5"], + { + "Fn::ForEach::InnerLoopName": [ + "Identifier2", + ["6", "7"], + { + "Resource${Identifier1}${Identifier2}": { + "Type": "resource_type2", + "Properties": properties + } + } + ], + "Resource${Identifier1}": { + "Type": "resource_type1", + "Properties": properties + } + } + ] + } + } + + open_mock = mock.mock_open() + yaml_parse_mock.return_value = template_dict + + # Patch the file open method to return template string + with mock.patch( + "awscli.customizations.cloudformation.artifact_exporter.open", + open_mock(read_data=template_str)) as open_mock: + + template_exporter = Template( + template_path, parent_dir, self.s3_uploader_mock, + resources_to_export) + exported_template = template_exporter.export() + self.assertEqual(exported_template, template_dict) + + open_mock.assert_called_once_with( + make_abs_path(parent_dir, template_path), "r") + + self.assertEqual(1, yaml_parse_mock.call_count) + + resource_type1_class.assert_called_with(self.s3_uploader_mock) + self.assertEqual( + resource_type1_instance.export.call_args_list, + [ + mock.call("Resource1", properties, template_dir), + mock.call("Resource${Identifier1}", properties, template_dir) + ] + ) + resource_type2_class.assert_called_with(self.s3_uploader_mock) + self.assertEqual( + resource_type2_instance.export.call_args_list, + [ + mock.call("Resource2", properties, template_dir), + mock.call("Resource${Identifier1}${Identifier2}", properties, template_dir) + ] + ) + + @mock.patch("awscli.customizations.cloudformation.artifact_exporter.yaml_parse") + def test_template_export_foreach_invalid(self, yaml_parse_mock): + parent_dir = os.path.sep + template_dir = os.path.join(parent_dir, 'foo', 'bar') + template_path = os.path.join(template_dir, 'path') + template_str = self.example_yaml_template() + + resource_type1_class = mock.Mock() + resource_type1_class.RESOURCE_TYPE = "resource_type1" + resource_type1_instance = mock.Mock() + resource_type1_class.return_value = resource_type1_instance + resource_type2_class = mock.Mock() + resource_type2_class.RESOURCE_TYPE = "resource_type2" + resource_type2_instance = mock.Mock() + resource_type2_class.return_value = resource_type2_instance + + resources_to_export = [ + resource_type1_class, + resource_type2_class + ] + + properties = {"foo": "bar"} + template_dict = { + "Resources": { + "Resource1": { + "Type": "resource_type1", + "Properties": properties + }, + "Resource2": { + "Type": "resource_type2", + "Properties": properties + }, + "Resource3": { + "Type": "some-other-type", + "Properties": properties + }, + "Fn::ForEach::OuterLoopName": [ + "Identifier1", + { + "Resource${Identifier1}": { + } + } + ] + } + } + + open_mock = mock.mock_open() + yaml_parse_mock.return_value = template_dict + + # Patch the file open method to return template string + with mock.patch( + "awscli.customizations.cloudformation.artifact_exporter.open", + open_mock(read_data=template_str)) as open_mock: + template_exporter = Template( + template_path, parent_dir, self.s3_uploader_mock, + resources_to_export) + with self.assertRaises(exceptions.InvalidForEachIntrinsicFunctionError): + template_exporter.export() + + @mock.patch("awscli.customizations.cloudformation.artifact_exporter.yaml_parse") def test_template_global_export(self, yaml_parse_mock): parent_dir = os.path.sep From db635c31ce8e36041a05425ba40f58ba34b18db5 Mon Sep 17 00:00:00 2001 From: aws-sdk-python-automation Date: Fri, 15 Dec 2023 19:29:05 +0000 Subject: [PATCH 2/3] Update changelog based on model updates --- .changes/next-release/api-change-cloud9-69638.json | 5 +++++ .changes/next-release/api-change-connect-79729.json | 5 +++++ .changes/next-release/api-change-connectcases-67861.json | 5 +++++ .changes/next-release/api-change-kms-3778.json | 5 +++++ .changes/next-release/api-change-rds-94571.json | 5 +++++ .changes/next-release/api-change-sagemaker-75500.json | 5 +++++ 6 files changed, 30 insertions(+) create mode 100644 .changes/next-release/api-change-cloud9-69638.json create mode 100644 .changes/next-release/api-change-connect-79729.json create mode 100644 .changes/next-release/api-change-connectcases-67861.json create mode 100644 .changes/next-release/api-change-kms-3778.json create mode 100644 .changes/next-release/api-change-rds-94571.json create mode 100644 .changes/next-release/api-change-sagemaker-75500.json diff --git a/.changes/next-release/api-change-cloud9-69638.json b/.changes/next-release/api-change-cloud9-69638.json new file mode 100644 index 000000000000..f8a861cabf73 --- /dev/null +++ b/.changes/next-release/api-change-cloud9-69638.json @@ -0,0 +1,5 @@ +{ + "type": "api-change", + "category": "``cloud9``", + "description": "Updated Cloud9 API documentation for AL2023 release" +} diff --git a/.changes/next-release/api-change-connect-79729.json b/.changes/next-release/api-change-connect-79729.json new file mode 100644 index 000000000000..26704b217604 --- /dev/null +++ b/.changes/next-release/api-change-connect-79729.json @@ -0,0 +1,5 @@ +{ + "type": "api-change", + "category": "``connect``", + "description": "Adds relatedContactId field to StartOutboundVoiceContact API input. Introduces PauseContact API and ResumeContact API for Task contacts. Adds pause duration, number of pauses, timestamps for last paused and resumed events to DescribeContact API response. Adds new Rule type and new Rule action." +} diff --git a/.changes/next-release/api-change-connectcases-67861.json b/.changes/next-release/api-change-connectcases-67861.json new file mode 100644 index 000000000000..73685f13e2ce --- /dev/null +++ b/.changes/next-release/api-change-connectcases-67861.json @@ -0,0 +1,5 @@ +{ + "type": "api-change", + "category": "``connectcases``", + "description": "Increase number of fields that can be included in CaseEventIncludedData from 50 to 200" +} diff --git a/.changes/next-release/api-change-kms-3778.json b/.changes/next-release/api-change-kms-3778.json new file mode 100644 index 000000000000..d6d1e602a1d8 --- /dev/null +++ b/.changes/next-release/api-change-kms-3778.json @@ -0,0 +1,5 @@ +{ + "type": "api-change", + "category": "``kms``", + "description": "Documentation updates for AWS Key Management Service" +} diff --git a/.changes/next-release/api-change-rds-94571.json b/.changes/next-release/api-change-rds-94571.json new file mode 100644 index 000000000000..c77840fde84c --- /dev/null +++ b/.changes/next-release/api-change-rds-94571.json @@ -0,0 +1,5 @@ +{ + "type": "api-change", + "category": "``rds``", + "description": "Updates Amazon RDS documentation by adding code examples" +} diff --git a/.changes/next-release/api-change-sagemaker-75500.json b/.changes/next-release/api-change-sagemaker-75500.json new file mode 100644 index 000000000000..0a7cc98b48e0 --- /dev/null +++ b/.changes/next-release/api-change-sagemaker-75500.json @@ -0,0 +1,5 @@ +{ + "type": "api-change", + "category": "``sagemaker``", + "description": "This release 1) introduces a new API: DeleteCompilationJob , and 2) adds InfraCheckConfig for Create/Describe training job API" +} From d77247e0a2849374f889b635deedd11aa475422d Mon Sep 17 00:00:00 2001 From: aws-sdk-python-automation Date: Fri, 15 Dec 2023 19:29:05 +0000 Subject: [PATCH 3/3] Bumping version to 1.32.2 --- .changes/1.32.2.json | 37 +++++++++++++++++++ .../next-release/api-change-cloud9-69638.json | 5 --- .../api-change-connect-79729.json | 5 --- .../api-change-connectcases-67861.json | 5 --- .../next-release/api-change-kms-3778.json | 5 --- .../next-release/api-change-rds-94571.json | 5 --- .../api-change-sagemaker-75500.json | 5 --- ...hancement-cloudformationpackage-39279.json | 5 --- CHANGELOG.rst | 12 ++++++ awscli/__init__.py | 2 +- doc/source/conf.py | 2 +- setup.cfg | 2 +- setup.py | 2 +- 13 files changed, 53 insertions(+), 39 deletions(-) create mode 100644 .changes/1.32.2.json delete mode 100644 .changes/next-release/api-change-cloud9-69638.json delete mode 100644 .changes/next-release/api-change-connect-79729.json delete mode 100644 .changes/next-release/api-change-connectcases-67861.json delete mode 100644 .changes/next-release/api-change-kms-3778.json delete mode 100644 .changes/next-release/api-change-rds-94571.json delete mode 100644 .changes/next-release/api-change-sagemaker-75500.json delete mode 100644 .changes/next-release/enhancement-cloudformationpackage-39279.json diff --git a/.changes/1.32.2.json b/.changes/1.32.2.json new file mode 100644 index 000000000000..58482956ed6b --- /dev/null +++ b/.changes/1.32.2.json @@ -0,0 +1,37 @@ +[ + { + "category": "``cloudformation package``", + "description": "Add support for intrinsic Fn:ForEach (fixes `#8075 `__)", + "type": "enhancement" + }, + { + "category": "``cloud9``", + "description": "Updated Cloud9 API documentation for AL2023 release", + "type": "api-change" + }, + { + "category": "``connect``", + "description": "Adds relatedContactId field to StartOutboundVoiceContact API input. Introduces PauseContact API and ResumeContact API for Task contacts. Adds pause duration, number of pauses, timestamps for last paused and resumed events to DescribeContact API response. Adds new Rule type and new Rule action.", + "type": "api-change" + }, + { + "category": "``connectcases``", + "description": "Increase number of fields that can be included in CaseEventIncludedData from 50 to 200", + "type": "api-change" + }, + { + "category": "``kms``", + "description": "Documentation updates for AWS Key Management Service", + "type": "api-change" + }, + { + "category": "``rds``", + "description": "Updates Amazon RDS documentation by adding code examples", + "type": "api-change" + }, + { + "category": "``sagemaker``", + "description": "This release 1) introduces a new API: DeleteCompilationJob , and 2) adds InfraCheckConfig for Create/Describe training job API", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/next-release/api-change-cloud9-69638.json b/.changes/next-release/api-change-cloud9-69638.json deleted file mode 100644 index f8a861cabf73..000000000000 --- a/.changes/next-release/api-change-cloud9-69638.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "api-change", - "category": "``cloud9``", - "description": "Updated Cloud9 API documentation for AL2023 release" -} diff --git a/.changes/next-release/api-change-connect-79729.json b/.changes/next-release/api-change-connect-79729.json deleted file mode 100644 index 26704b217604..000000000000 --- a/.changes/next-release/api-change-connect-79729.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "api-change", - "category": "``connect``", - "description": "Adds relatedContactId field to StartOutboundVoiceContact API input. Introduces PauseContact API and ResumeContact API for Task contacts. Adds pause duration, number of pauses, timestamps for last paused and resumed events to DescribeContact API response. Adds new Rule type and new Rule action." -} diff --git a/.changes/next-release/api-change-connectcases-67861.json b/.changes/next-release/api-change-connectcases-67861.json deleted file mode 100644 index 73685f13e2ce..000000000000 --- a/.changes/next-release/api-change-connectcases-67861.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "api-change", - "category": "``connectcases``", - "description": "Increase number of fields that can be included in CaseEventIncludedData from 50 to 200" -} diff --git a/.changes/next-release/api-change-kms-3778.json b/.changes/next-release/api-change-kms-3778.json deleted file mode 100644 index d6d1e602a1d8..000000000000 --- a/.changes/next-release/api-change-kms-3778.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "api-change", - "category": "``kms``", - "description": "Documentation updates for AWS Key Management Service" -} diff --git a/.changes/next-release/api-change-rds-94571.json b/.changes/next-release/api-change-rds-94571.json deleted file mode 100644 index c77840fde84c..000000000000 --- a/.changes/next-release/api-change-rds-94571.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "api-change", - "category": "``rds``", - "description": "Updates Amazon RDS documentation by adding code examples" -} diff --git a/.changes/next-release/api-change-sagemaker-75500.json b/.changes/next-release/api-change-sagemaker-75500.json deleted file mode 100644 index 0a7cc98b48e0..000000000000 --- a/.changes/next-release/api-change-sagemaker-75500.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "api-change", - "category": "``sagemaker``", - "description": "This release 1) introduces a new API: DeleteCompilationJob , and 2) adds InfraCheckConfig for Create/Describe training job API" -} diff --git a/.changes/next-release/enhancement-cloudformationpackage-39279.json b/.changes/next-release/enhancement-cloudformationpackage-39279.json deleted file mode 100644 index 329081a2ec00..000000000000 --- a/.changes/next-release/enhancement-cloudformationpackage-39279.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "enhancement", - "category": "``cloudformation package``", - "description": "Add support for intrinsic Fn:ForEach (fixes `#8075 `__)" -} diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 544ef770d536..cce93bcca5c3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,18 @@ CHANGELOG ========= +1.32.2 +====== + +* enhancement:``cloudformation package``: Add support for intrinsic Fn:ForEach (fixes `#8075 `__) +* api-change:``cloud9``: Updated Cloud9 API documentation for AL2023 release +* api-change:``connect``: Adds relatedContactId field to StartOutboundVoiceContact API input. Introduces PauseContact API and ResumeContact API for Task contacts. Adds pause duration, number of pauses, timestamps for last paused and resumed events to DescribeContact API response. Adds new Rule type and new Rule action. +* api-change:``connectcases``: Increase number of fields that can be included in CaseEventIncludedData from 50 to 200 +* api-change:``kms``: Documentation updates for AWS Key Management Service +* api-change:``rds``: Updates Amazon RDS documentation by adding code examples +* api-change:``sagemaker``: This release 1) introduces a new API: DeleteCompilationJob , and 2) adds InfraCheckConfig for Create/Describe training job API + + 1.32.1 ====== diff --git a/awscli/__init__.py b/awscli/__init__.py index 2fdbb4d35bec..cb0fec889763 100644 --- a/awscli/__init__.py +++ b/awscli/__init__.py @@ -17,7 +17,7 @@ """ import os -__version__ = '1.32.1' +__version__ = '1.32.2' # # Get our data path to be added to botocore's search path diff --git a/doc/source/conf.py b/doc/source/conf.py index efa4c85d9bb3..4699b250c017 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -52,7 +52,7 @@ # The short X.Y version. version = '1.32' # The full version, including alpha/beta/rc tags. -release = '1.32.1' +release = '1.32.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.cfg b/setup.cfg index 87427fab6a86..cad99ea0df1f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ universal = 0 [metadata] requires_dist = - botocore==1.34.1 + botocore==1.34.2 docutils>=0.10,<0.17 s3transfer>=0.9.0,<0.10.0 PyYAML>=3.10,<6.1 diff --git a/setup.py b/setup.py index 584e19786acc..b9158f354c60 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ def find_version(*file_paths): install_requires = [ - 'botocore==1.34.1', + 'botocore==1.34.2', 'docutils>=0.10,<0.17', 's3transfer>=0.9.0,<0.10.0', 'PyYAML>=3.10,<6.1',