From 7c83ca157f80b946d3922beaf3d88b50ede45dc6 Mon Sep 17 00:00:00 2001 From: steffyP Date: Wed, 22 Nov 2023 20:44:22 +0100 Subject: [PATCH] Core: Parameter handling: fix parsing of >= 10 members (#7056) --- moto/core/utils.py | 17 ++++---- .../test_autoscaling/test_autoscaling_tags.py | 35 ++++++++++++++++ .../test_cloudwatch/test_cloudwatch_boto3.py | 40 +++++++++++++++++++ 3 files changed, 85 insertions(+), 7 deletions(-) diff --git a/moto/core/utils.py b/moto/core/utils.py index e40c98fb4..6cbc7cd3f 100644 --- a/moto/core/utils.py +++ b/moto/core/utils.py @@ -326,16 +326,19 @@ def extract_region_from_aws_authorization(string: str) -> Optional[str]: return region -def params_sort_function(item: Tuple[str, Any]) -> Tuple[str, Any]: +def params_sort_function(item: Tuple[str, Any]) -> Tuple[str, int, str]: """ - Comparison function used to sort params appropriately taking tags non - alphabetical order into consideration + sort by .member..: + in case there are more than 10 members, the default-string sort would lead to IndexError when parsing the content. + + Note: currently considers only the first occurence of `member`, but there may be cases with nested members """ key, _ = item - if key.startswith("Tags.member"): - member_num = int(key.split(".")[2]) - return ("Tags.member", member_num) - return item + + match = re.search(r"(.*?member)\.(\d+)(.*)", key) + if match: + return (match.group(1), int(match.group(2)), match.group(3)) + return (key, 0, "") def gzip_decompress(body: bytes) -> bytes: diff --git a/tests/test_autoscaling/test_autoscaling_tags.py b/tests/test_autoscaling/test_autoscaling_tags.py index 69cb07cb0..13e82dc89 100644 --- a/tests/test_autoscaling/test_autoscaling_tags.py +++ b/tests/test_autoscaling/test_autoscaling_tags.py @@ -1,3 +1,5 @@ +import copy + import boto3 from moto import mock_autoscaling, mock_ec2 @@ -231,6 +233,39 @@ def test_describe_tags_filter_by_propgateatlaunch(): ] +@mock_autoscaling +def test_create_20_tags_auto_scaling_group(): + """test to verify that the tag-members are sorted correctly, and there is no regression for + https://github.com/getmoto/moto/issues/6033 + """ + subnet = setup_networking()["subnet1"] + client = boto3.client("autoscaling", region_name="us-east-1") + original = { + "ResourceId": "test_asg", + "PropagateAtLaunch": True, + } + tags = [] + for i in range(0, 20): + cp = copy.deepcopy(original) + cp["Key"] = f"test_key{i}" + cp["Value"] = f"random-value-{i}" + tags.append(cp) + client.create_launch_configuration( + LaunchConfigurationName="test_launch_configuration", + ImageId=EXAMPLE_AMI_ID, + InstanceType="t2.medium", + ) + client.create_auto_scaling_group( + AutoScalingGroupName="test_asg", + LaunchConfigurationName="test_launch_configuration", + MinSize=0, + MaxSize=20, + DesiredCapacity=5, + Tags=tags, + VPCZoneIdentifier=subnet, + ) + + def create_asgs(client, subnet): _ = client.create_launch_configuration( LaunchConfigurationName="test_launch_configuration", diff --git a/tests/test_cloudwatch/test_cloudwatch_boto3.py b/tests/test_cloudwatch/test_cloudwatch_boto3.py index 5a34e9d99..2857b1110 100644 --- a/tests/test_cloudwatch/test_cloudwatch_boto3.py +++ b/tests/test_cloudwatch/test_cloudwatch_boto3.py @@ -1,3 +1,5 @@ +import copy + import boto3 import pytest @@ -1903,3 +1905,41 @@ def test_get_metric_data_with_custom_label(): ) assert len(metric_result_data) == 1 assert metric_result_data[0]["Label"] == expected_value + + +@mock_cloudwatch +def test_get_metric_data_queries(): + """ + verify that >= 10 queries can still be parsed + there was an error with the order of parsing items, leading to IndexError + """ + now = datetime.utcnow().replace(microsecond=0) + start_time = now - timedelta(minutes=10) + end_time = now + timedelta(minutes=5) + original_query = { + "MetricStat": { + "Metric": { + "Namespace": "AWS/RDS", + "MetricName": "CPUUtilization", + "Dimensions": [{"Name": "DBInstanceIdentifier", "Value": ""}], + }, + "Period": 1, + "Stat": "Average", + "Unit": "Percent", + }, + } + + queries = [] + for i in range(0, 20): + query = copy.deepcopy(original_query) + query["Id"] = f"q{i}" + query["MetricStat"]["Metric"]["Dimensions"][0]["Value"] = f"id-{i}" + queries.append(query) + + assert len(queries) == 20 + response = boto3.client("cloudwatch", "eu-west-1").get_metric_data( + StartTime=start_time, EndTime=end_time, MetricDataQueries=queries + ) + + # we expect twenty results + assert len(response["MetricDataResults"]) == 20