Skip to content

Commit f8061f0

Browse files
Merge branch 'release-1.32.2'
* release-1.32.2: Bumping version to 1.32.2 Update changelog based on model updates Support Fn::ForEach intrinsic function (#8096)
2 parents 896dd9c + d77247e commit f8061f0

File tree

9 files changed

+224
-7
lines changed

9 files changed

+224
-7
lines changed

.changes/1.32.2.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[
2+
{
3+
"category": "``cloudformation package``",
4+
"description": "Add support for intrinsic Fn:ForEach (fixes `#8075 <https://github.com/aws/aws-cli/issues/8075>`__)",
5+
"type": "enhancement"
6+
},
7+
{
8+
"category": "``cloud9``",
9+
"description": "Updated Cloud9 API documentation for AL2023 release",
10+
"type": "api-change"
11+
},
12+
{
13+
"category": "``connect``",
14+
"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.",
15+
"type": "api-change"
16+
},
17+
{
18+
"category": "``connectcases``",
19+
"description": "Increase number of fields that can be included in CaseEventIncludedData from 50 to 200",
20+
"type": "api-change"
21+
},
22+
{
23+
"category": "``kms``",
24+
"description": "Documentation updates for AWS Key Management Service",
25+
"type": "api-change"
26+
},
27+
{
28+
"category": "``rds``",
29+
"description": "Updates Amazon RDS documentation by adding code examples",
30+
"type": "api-change"
31+
},
32+
{
33+
"category": "``sagemaker``",
34+
"description": "This release 1) introduces a new API: DeleteCompilationJob , and 2) adds InfraCheckConfig for Create/Describe training job API",
35+
"type": "api-change"
36+
}
37+
]

CHANGELOG.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22
CHANGELOG
33
=========
44

5+
1.32.2
6+
======
7+
8+
* enhancement:``cloudformation package``: Add support for intrinsic Fn:ForEach (fixes `#8075 <https://github.com/aws/aws-cli/issues/8075>`__)
9+
* api-change:``cloud9``: Updated Cloud9 API documentation for AL2023 release
10+
* 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.
11+
* api-change:``connectcases``: Increase number of fields that can be included in CaseEventIncludedData from 50 to 200
12+
* api-change:``kms``: Documentation updates for AWS Key Management Service
13+
* api-change:``rds``: Updates Amazon RDS documentation by adding code examples
14+
* api-change:``sagemaker``: This release 1) introduces a new API: DeleteCompilationJob , and 2) adds InfraCheckConfig for Create/Describe training job API
15+
16+
517
1.32.1
618
======
719

awscli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"""
1818
import os
1919

20-
__version__ = '1.32.1'
20+
__version__ = '1.32.2'
2121

2222
#
2323
# Get our data path to be added to botocore's search path

awscli/customizations/cloudformation/artifact_exporter.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,18 @@ def export(self):
659659

660660
self.template_dict = self.export_global_artifacts(self.template_dict)
661661

662-
for resource_id, resource in self.template_dict["Resources"].items():
662+
self.export_resources(self.template_dict["Resources"])
663+
664+
return self.template_dict
665+
666+
def export_resources(self, resource_dict):
667+
for resource_id, resource in resource_dict.items():
668+
669+
if resource_id.startswith("Fn::ForEach::"):
670+
if not isinstance(resource, list) or len(resource) != 3:
671+
raise exceptions.InvalidForEachIntrinsicFunctionError(resource_id=resource_id)
672+
self.export_resources(resource[2])
673+
continue
663674

664675
resource_type = resource.get("Type", None)
665676
resource_dict = resource.get("Properties", None)
@@ -671,5 +682,3 @@ def export(self):
671682
# Export code resources
672683
exporter = exporter_class(self.uploader)
673684
exporter.export(resource_id, resource_dict, self.template_dir)
674-
675-
return self.template_dict

awscli/customizations/cloudformation/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,7 @@ class DeployBucketRequiredError(CloudFormationCommandError):
5353
"via an S3 Bucket. Please add the --s3-bucket parameter to your "
5454
"command. The local template will be copied to that S3 bucket and "
5555
"then deployed.")
56+
57+
58+
class InvalidForEachIntrinsicFunctionError(CloudFormationCommandError):
59+
fmt = 'The value of {resource_id} has an invalid "Fn::ForEach::" format: Must be a list of three entries'

doc/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
# The short X.Y version.
5353
version = '1.32'
5454
# The full version, including alpha/beta/rc tags.
55-
release = '1.32.1'
55+
release = '1.32.2'
5656

5757
# The language for content autogenerated by Sphinx. Refer to documentation
5858
# for a list of supported languages.

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ universal = 0
33

44
[metadata]
55
requires_dist =
6-
botocore==1.34.1
6+
botocore==1.34.2
77
docutils>=0.10,<0.17
88
s3transfer>=0.9.0,<0.10.0
99
PyYAML>=3.10,<6.1

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def find_version(*file_paths):
2424

2525

2626
install_requires = [
27-
'botocore==1.34.1',
27+
'botocore==1.34.2',
2828
'docutils>=0.10,<0.17',
2929
's3transfer>=0.9.0,<0.10.0',
3030
'PyYAML>=3.10,<6.1',

tests/unit/customizations/cloudformation/test_artifact_exporter.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,161 @@ def test_template_export(self, yaml_parse_mock):
10161016
resource_type2_instance.export.assert_called_once_with(
10171017
"Resource2", mock.ANY, template_dir)
10181018

1019+
@mock.patch("awscli.customizations.cloudformation.artifact_exporter.yaml_parse")
1020+
def test_template_export_foreach_valid(self, yaml_parse_mock):
1021+
parent_dir = os.path.sep
1022+
template_dir = os.path.join(parent_dir, 'foo', 'bar')
1023+
template_path = os.path.join(template_dir, 'path')
1024+
template_str = self.example_yaml_template()
1025+
1026+
resource_type1_class = mock.Mock()
1027+
resource_type1_class.RESOURCE_TYPE = "resource_type1"
1028+
resource_type1_instance = mock.Mock()
1029+
resource_type1_class.return_value = resource_type1_instance
1030+
resource_type2_class = mock.Mock()
1031+
resource_type2_class.RESOURCE_TYPE = "resource_type2"
1032+
resource_type2_instance = mock.Mock()
1033+
resource_type2_class.return_value = resource_type2_instance
1034+
1035+
resources_to_export = [
1036+
resource_type1_class,
1037+
resource_type2_class
1038+
]
1039+
1040+
properties = {"foo": "bar"}
1041+
template_dict = {
1042+
"Resources": {
1043+
"Resource1": {
1044+
"Type": "resource_type1",
1045+
"Properties": properties
1046+
},
1047+
"Resource2": {
1048+
"Type": "resource_type2",
1049+
"Properties": properties
1050+
},
1051+
"Resource3": {
1052+
"Type": "some-other-type",
1053+
"Properties": properties
1054+
},
1055+
"Fn::ForEach::OuterLoopName": [
1056+
"Identifier1",
1057+
["4", "5"],
1058+
{
1059+
"Fn::ForEach::InnerLoopName": [
1060+
"Identifier2",
1061+
["6", "7"],
1062+
{
1063+
"Resource${Identifier1}${Identifier2}": {
1064+
"Type": "resource_type2",
1065+
"Properties": properties
1066+
}
1067+
}
1068+
],
1069+
"Resource${Identifier1}": {
1070+
"Type": "resource_type1",
1071+
"Properties": properties
1072+
}
1073+
}
1074+
]
1075+
}
1076+
}
1077+
1078+
open_mock = mock.mock_open()
1079+
yaml_parse_mock.return_value = template_dict
1080+
1081+
# Patch the file open method to return template string
1082+
with mock.patch(
1083+
"awscli.customizations.cloudformation.artifact_exporter.open",
1084+
open_mock(read_data=template_str)) as open_mock:
1085+
1086+
template_exporter = Template(
1087+
template_path, parent_dir, self.s3_uploader_mock,
1088+
resources_to_export)
1089+
exported_template = template_exporter.export()
1090+
self.assertEqual(exported_template, template_dict)
1091+
1092+
open_mock.assert_called_once_with(
1093+
make_abs_path(parent_dir, template_path), "r")
1094+
1095+
self.assertEqual(1, yaml_parse_mock.call_count)
1096+
1097+
resource_type1_class.assert_called_with(self.s3_uploader_mock)
1098+
self.assertEqual(
1099+
resource_type1_instance.export.call_args_list,
1100+
[
1101+
mock.call("Resource1", properties, template_dir),
1102+
mock.call("Resource${Identifier1}", properties, template_dir)
1103+
]
1104+
)
1105+
resource_type2_class.assert_called_with(self.s3_uploader_mock)
1106+
self.assertEqual(
1107+
resource_type2_instance.export.call_args_list,
1108+
[
1109+
mock.call("Resource2", properties, template_dir),
1110+
mock.call("Resource${Identifier1}${Identifier2}", properties, template_dir)
1111+
]
1112+
)
1113+
1114+
@mock.patch("awscli.customizations.cloudformation.artifact_exporter.yaml_parse")
1115+
def test_template_export_foreach_invalid(self, yaml_parse_mock):
1116+
parent_dir = os.path.sep
1117+
template_dir = os.path.join(parent_dir, 'foo', 'bar')
1118+
template_path = os.path.join(template_dir, 'path')
1119+
template_str = self.example_yaml_template()
1120+
1121+
resource_type1_class = mock.Mock()
1122+
resource_type1_class.RESOURCE_TYPE = "resource_type1"
1123+
resource_type1_instance = mock.Mock()
1124+
resource_type1_class.return_value = resource_type1_instance
1125+
resource_type2_class = mock.Mock()
1126+
resource_type2_class.RESOURCE_TYPE = "resource_type2"
1127+
resource_type2_instance = mock.Mock()
1128+
resource_type2_class.return_value = resource_type2_instance
1129+
1130+
resources_to_export = [
1131+
resource_type1_class,
1132+
resource_type2_class
1133+
]
1134+
1135+
properties = {"foo": "bar"}
1136+
template_dict = {
1137+
"Resources": {
1138+
"Resource1": {
1139+
"Type": "resource_type1",
1140+
"Properties": properties
1141+
},
1142+
"Resource2": {
1143+
"Type": "resource_type2",
1144+
"Properties": properties
1145+
},
1146+
"Resource3": {
1147+
"Type": "some-other-type",
1148+
"Properties": properties
1149+
},
1150+
"Fn::ForEach::OuterLoopName": [
1151+
"Identifier1",
1152+
{
1153+
"Resource${Identifier1}": {
1154+
}
1155+
}
1156+
]
1157+
}
1158+
}
1159+
1160+
open_mock = mock.mock_open()
1161+
yaml_parse_mock.return_value = template_dict
1162+
1163+
# Patch the file open method to return template string
1164+
with mock.patch(
1165+
"awscli.customizations.cloudformation.artifact_exporter.open",
1166+
open_mock(read_data=template_str)) as open_mock:
1167+
template_exporter = Template(
1168+
template_path, parent_dir, self.s3_uploader_mock,
1169+
resources_to_export)
1170+
with self.assertRaises(exceptions.InvalidForEachIntrinsicFunctionError):
1171+
template_exporter.export()
1172+
1173+
10191174
@mock.patch("awscli.customizations.cloudformation.artifact_exporter.yaml_parse")
10201175
def test_template_global_export(self, yaml_parse_mock):
10211176
parent_dir = os.path.sep

0 commit comments

Comments
 (0)