From 1c30b5b17d6b45f2bb6b65021093e9cab7c69c9e Mon Sep 17 00:00:00 2001 From: Zachary Karpinski Date: Sat, 21 Sep 2024 11:41:27 -0400 Subject: [PATCH] [BUG FIX] route53 list_tags_for_resources no results & hostedzone ids not decoding properly (#8131) --- moto/route53/models.py | 8 ++-- moto/route53/responses.py | 22 +++++---- tests/test_route53/test_route53.py | 72 +++++++++++++++++++++++++++++- 3 files changed, 90 insertions(+), 12 deletions(-) diff --git a/moto/route53/models.py b/moto/route53/models.py index 4fe146320658..dc8e4e21eb4e 100644 --- a/moto/route53/models.py +++ b/moto/route53/models.py @@ -647,10 +647,12 @@ def list_tags_for_resource(self, resource_id: str) -> Dict[str, str]: return self.resource_tags[resource_id] return {} - def list_tags_for_resources(self, resource_ids: List[str]) -> Dict[str, str]: - resources = {} + def list_tags_for_resources(self, resource_ids: List[str]) -> List[Dict[str, Any]]: + resources = [] for id in resource_ids: - resources.update(self.list_tags_for_resource(id)) + resource_set = {"ResourceId": id, "Tags": {}} + resource_set["Tags"] = self.list_tags_for_resource(id) + resources.append(resource_set) return resources def list_resource_record_sets( diff --git a/moto/route53/responses.py b/moto/route53/responses.py index 9dbc164231e5..6150ae02d796 100644 --- a/moto/route53/responses.py +++ b/moto/route53/responses.py @@ -2,7 +2,7 @@ import re from typing import Any -from urllib.parse import parse_qs +from urllib.parse import parse_qs, unquote import xmltodict from jinja2 import Template @@ -346,8 +346,10 @@ def list_or_change_tags_for_resource_request( # type: ignore[return] ) -> TYPE_RESPONSE: self.setup_class(request, full_url, headers) - id_ = self.parsed_url.path.split("/")[-1] - type_ = self.parsed_url.path.split("/")[-2] + id_matcher = re.search(r"/tags/([-a-z]+)/(.+)", self.parsed_url.path) + assert id_matcher + type_ = id_matcher.group(1) + id_ = unquote(id_matcher.group(2)) if request.method == "GET": tags = self.backend.list_tags_for_resource(id_) @@ -371,12 +373,16 @@ def list_or_change_tags_for_resource_request( # type: ignore[return] return 200, headers, template.render() def list_tags_for_resources(self) -> str: + if id_matcher := re.search(r"/tags/[-a-z]+/(.+)", self.parsed_url.path): + resource_type = unquote(id_matcher.group(1)) + else: + resource_type = "" resource_ids = xmltodict.parse(self.body)["ListTagsForResourcesRequest"][ "ResourceIds" - ] + ]["ResourceId"] tag_sets = self.backend.list_tags_for_resources(resource_ids=resource_ids) template = Template(LIST_TAGS_FOR_RESOURCES_RESPONSE) - return template.render(tag_sets) + return template.render(tag_sets=tag_sets, resource_type=resource_type) def get_change(self) -> str: change_id = self.parsed_url.path.rstrip("/").rsplit("/", 1)[1] @@ -494,10 +500,10 @@ def delete_reusable_delegation_set(self) -> str: {% for set in tag_sets %} - {{set.resource_type}} - {{set.resource_id}} + {{resource_type}} + {{set["ResourceId"]}} - {% for key, value in set.tags.items() %} + {% for key, value in set["Tags"].items() %} {{key}} {{value}} diff --git a/tests/test_route53/test_route53.py b/tests/test_route53/test_route53.py index cc34720a6cef..309333f3fc5e 100644 --- a/tests/test_route53/test_route53.py +++ b/tests/test_route53/test_route53.py @@ -553,6 +553,7 @@ def test_list_or_change_tags_for_resource_request(): @mock_aws def test_list_tags_for_resources(): conn = boto3.client("route53", region_name="us-east-1") + # Create two hosted zones zone1 = conn.create_hosted_zone( Name="testdns1.aws.com", CallerReference=str(hash("foo")) ) @@ -562,11 +563,48 @@ def test_list_tags_for_resources(): ) zone2_id = zone2["HostedZone"]["Id"] - # confirm this works for resources with zero tags + # Create two healthchecks + health_check1 = conn.create_health_check( + CallerReference="foo", + HealthCheckConfig={ + "IPAddress": "192.0.2.44", + "Port": 123, + "Type": "HTTP", + "ResourcePath": "/", + "RequestInterval": 30, + "FailureThreshold": 123, + "HealthThreshold": 123, + }, + ) + healthcheck1_id = health_check1["HealthCheck"]["Id"] + + health_check2 = conn.create_health_check( + CallerReference="bar", + HealthCheckConfig={ + "IPAddress": "192.0.2.44", + "Port": 123, + "Type": "HTTP", + "ResourcePath": "/", + "RequestInterval": 30, + "FailureThreshold": 123, + "HealthThreshold": 123, + }, + ) + healthcheck2_id = health_check2["HealthCheck"]["Id"] + + # confirm this works for resources with zero tags for hostedzone response = conn.list_tags_for_resources( ResourceIds=[zone1_id, zone2_id], ResourceType="hostedzone" ) + for set in response["ResourceTagSets"]: + assert set["Tags"] == [] + + # confirm this works for resources with zero tags for healthchecks + response = conn.list_tags_for_resources( + ResourceIds=[healthcheck1_id, healthcheck2_id], ResourceType="healthcheck" + ) + for set in response["ResourceTagSets"]: assert set["Tags"] == [] @@ -582,14 +620,46 @@ def test_list_tags_for_resources(): ResourceType="hostedzone", ResourceId=zone2_id, AddTags=[tag3, tag4] ) + conn.change_tags_for_resource( + ResourceType="healthcheck", ResourceId=healthcheck1_id, AddTags=[tag1, tag2] + ) + conn.change_tags_for_resource( + ResourceType="healthcheck", ResourceId=healthcheck2_id, AddTags=[tag3, tag4] + ) + + # Test hostedzone response = conn.list_tags_for_resources( ResourceIds=[zone1_id, zone2_id], ResourceType="hostedzone" ) + assert len(response["ResourceTagSets"]) == 2 for set in response["ResourceTagSets"]: + assert set["ResourceId"] in (zone1_id, zone2_id) if set["ResourceId"] == zone1_id: assert tag1 in set["Tags"] assert tag2 in set["Tags"] + assert tag3 not in set["Tags"] + assert tag4 not in set["Tags"] elif set["ResourceId"] == zone2_id: + assert tag1 not in set["Tags"] + assert tag2 not in set["Tags"] + assert tag3 in set["Tags"] + assert tag4 in set["Tags"] + + # Test healthcheck + response = conn.list_tags_for_resources( + ResourceIds=[healthcheck1_id, healthcheck2_id], ResourceType="healthcheck" + ) + assert len(response["ResourceTagSets"]) == 2 + for set in response["ResourceTagSets"]: + assert set["ResourceId"] in (healthcheck1_id, healthcheck2_id) + if set["ResourceId"] == healthcheck1_id: + assert tag1 in set["Tags"] + assert tag2 in set["Tags"] + assert tag3 not in set["Tags"] + assert tag4 not in set["Tags"] + elif set["ResourceId"] == healthcheck2_id: + assert tag1 not in set["Tags"] + assert tag2 not in set["Tags"] assert tag3 in set["Tags"] assert tag4 in set["Tags"]