Skip to content

Commit

Permalink
Merge branch 'release-1.32.2'
Browse files Browse the repository at this point in the history
* release-1.32.2:
  Bumping version to 1.32.2
  Update changelog based on model updates
  Support Fn::ForEach intrinsic function (#8096)
  • Loading branch information
aws-sdk-python-automation committed Dec 15, 2023
2 parents 896dd9c + d77247e commit f8061f0
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 7 deletions.
37 changes: 37 additions & 0 deletions .changes/1.32.2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[
{
"category": "``cloudformation package``",
"description": "Add support for intrinsic Fn:ForEach (fixes `#8075 <https://github.com/aws/aws-cli/issues/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"
}
]
12 changes: 12 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@
CHANGELOG
=========

1.32.2
======

* enhancement:``cloudformation package``: Add support for intrinsic Fn:ForEach (fixes `#8075 <https://github.com/aws/aws-cli/issues/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
======

Expand Down
2 changes: 1 addition & 1 deletion awscli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 12 additions & 3 deletions awscli/customizations/cloudformation/artifact_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
4 changes: 4 additions & 0 deletions awscli/customizations/cloudformation/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
2 changes: 1 addition & 1 deletion doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
155 changes: 155 additions & 0 deletions tests/unit/customizations/cloudformation/test_artifact_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit f8061f0

Please sign in to comment.