Route53: list_hosted_zones() now supports pagination (#7328)

This commit is contained in:
Bert Blommers 2024-02-09 22:17:37 +00:00 committed by GitHub
parent 2c3f735e85
commit ff9dda224f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 75 additions and 16 deletions

View File

@ -723,7 +723,11 @@ class Route53Backend(BaseBackend):
the_zone.delete_rrset(record_set) the_zone.delete_rrset(record_set)
the_zone.rr_changes.append(original_change) the_zone.rr_changes.append(original_change)
@paginate(pagination_model=PAGINATION_MODEL) # type: ignore[misc]
def list_hosted_zones(self) -> List[FakeZone]: def list_hosted_zones(self) -> List[FakeZone]:
"""
The parameters DelegationSetId and HostedZoneType are not yet implemented
"""
return list(self.zones.values()) return list(self.zones.values())
def list_hosted_zones_by_name( def list_hosted_zones_by_name(
@ -733,7 +737,7 @@ class Route53Backend(BaseBackend):
dnsname = dnsnames[0] dnsname = dnsnames[0]
if dnsname[-1] != ".": if dnsname[-1] != ".":
dnsname += "." dnsname += "."
zones = [zone for zone in self.list_hosted_zones() if zone.name == dnsname] zones = [zone for zone in self.zones.values() if zone.name == dnsname]
else: else:
dnsname = None dnsname = None
# sort by names, but with domain components reversed # sort by names, but with domain components reversed
@ -745,8 +749,7 @@ class Route53Backend(BaseBackend):
domains = domains[-1:] + domains[:-1] domains = domains[-1:] + domains[:-1]
return ".".join(reversed(domains)) return ".".join(reversed(domains))
zones = self.list_hosted_zones() zones = sorted(self.zones.values(), key=sort_key)
zones = sorted(zones, key=sort_key)
return dnsname, zones return dnsname, zones
def list_hosted_zones_by_vpc(self, vpc_id: str) -> List[Dict[str, Any]]: def list_hosted_zones_by_vpc(self, vpc_id: str) -> List[Dict[str, Any]]:
@ -754,7 +757,7 @@ class Route53Backend(BaseBackend):
Pagination is not yet implemented Pagination is not yet implemented
""" """
zone_list = [] zone_list = []
for zone in self.list_hosted_zones(): for zone in self.zones.values():
if zone.private_zone is True: if zone.private_zone is True:
this_zone = self.get_hosted_zone(zone.id) this_zone = self.get_hosted_zone(zone.id)
for vpc in this_zone.vpcs: for vpc in this_zone.vpcs:
@ -776,10 +779,10 @@ class Route53Backend(BaseBackend):
return the_zone return the_zone
def get_hosted_zone_count(self) -> int: def get_hosted_zone_count(self) -> int:
return len(self.list_hosted_zones()) return len(self.zones.values())
def get_hosted_zone_by_name(self, name: str) -> Optional[FakeZone]: def get_hosted_zone_by_name(self, name: str) -> Optional[FakeZone]:
for zone in self.list_hosted_zones(): for zone in self.zones.values():
if zone.name == name: if zone.name == name:
return zone return zone
return None return None
@ -875,8 +878,7 @@ class Route53Backend(BaseBackend):
) -> QueryLoggingConfig: ) -> QueryLoggingConfig:
"""Process the create_query_logging_config request.""" """Process the create_query_logging_config request."""
# Does the hosted_zone_id exist? # Does the hosted_zone_id exist?
response = self.list_hosted_zones() zones = list(self.zones.values())
zones = list(response) if response else []
for zone in zones: for zone in zones:
if zone.id == hosted_zone_id: if zone.id == hosted_zone_id:
break break
@ -940,8 +942,7 @@ class Route53Backend(BaseBackend):
"""Return a list of query logging configs.""" """Return a list of query logging configs."""
if hosted_zone_id: if hosted_zone_id:
# Does the hosted_zone_id exist? # Does the hosted_zone_id exist?
response = self.list_hosted_zones() zones = list(self.zones.values())
zones = list(response) if response else []
for zone in zones: for zone in zones:
if zone.id == hosted_zone_id: if zone.id == hosted_zone_id:
break break

View File

@ -81,16 +81,27 @@ class Route53(BaseResponse):
vpcregion=vpcregion, vpcregion=vpcregion,
delegation_set_id=delegation_set_id, delegation_set_id=delegation_set_id,
) )
template = Template(CREATE_HOSTED_ZONE_RESPONSE) template = Template(CREATE_HOSTED_ZONE_RESPONSE).render(zone=new_zone)
headers = { headers = {
"Location": f"https://route53.amazonaws.com/2013-04-01/hostedzone/{new_zone.id}" "Location": f"https://route53.amazonaws.com/2013-04-01/hostedzone/{new_zone.id}"
} }
return 201, headers, template.render(zone=new_zone) return 201, headers, template
elif request.method == "GET": elif request.method == "GET":
all_zones = self.backend.list_hosted_zones() max_size = self.querystring.get("maxitems", [None])[0]
template = Template(LIST_HOSTED_ZONES_RESPONSE) if max_size:
return 200, headers, template.render(zones=all_zones) max_size = int(max_size)
marker = self.querystring.get("marker", [None])[0]
zone_page, next_marker = self.backend.list_hosted_zones(
marker=marker, max_size=max_size
)
template = Template(LIST_HOSTED_ZONES_RESPONSE).render(
zones=zone_page,
marker=marker,
next_marker=next_marker,
max_items=max_size,
)
return 200, headers, template
def list_hosted_zones_by_name_response( def list_hosted_zones_by_name_response(
self, request: Any, full_url: str, headers: Any self, request: Any, full_url: str, headers: Any
@ -704,7 +715,10 @@ LIST_HOSTED_ZONES_RESPONSE = """<ListHostedZonesResponse xmlns="https://route53.
</HostedZone> </HostedZone>
{% endfor %} {% endfor %}
</HostedZones> </HostedZones>
<IsTruncated>false</IsTruncated> {% if marker %}<Marker>{{ marker }}</Marker>{% endif %}
{%if next_marker %}<NextMarker>{{ next_marker }}</NextMarker>{% endif %}
{%if max_items %}<MaxItems>{{ max_items }}</MaxItems>{% endif %}
<IsTruncated>{{ 'true' if next_marker else 'false'}}</IsTruncated>
</ListHostedZonesResponse>""" </ListHostedZonesResponse>"""
LIST_HOSTED_ZONES_BY_NAME_RESPONSE = """<ListHostedZonesByNameResponse xmlns="{{ xmlns }}"> LIST_HOSTED_ZONES_BY_NAME_RESPONSE = """<ListHostedZonesByNameResponse xmlns="{{ xmlns }}">

View File

@ -2,6 +2,12 @@
from .exceptions import InvalidPaginationToken from .exceptions import InvalidPaginationToken
PAGINATION_MODEL = { PAGINATION_MODEL = {
"list_hosted_zones": {
"input_token": "marker",
"limit_key": "max_size",
"limit_default": 100,
"unique_attribute": "id",
},
"list_query_logging_configs": { "list_query_logging_configs": {
"input_token": "next_token", "input_token": "next_token",
"limit_key": "max_results", "limit_key": "max_results",

View File

@ -602,6 +602,44 @@ def test_list_hosted_zones_by_dns_name():
assert zones["HostedZones"][3]["CallerReference"] == str(hash("bar")) assert zones["HostedZones"][3]["CallerReference"] == str(hash("bar"))
@mock_aws
def test_list_hosted_zones_pagination():
conn = boto3.client("route53", region_name="us-east-1")
for idx in range(150):
conn.create_hosted_zone(
Name=f"test{idx}.com.", CallerReference=str(hash(f"h{idx}"))
)
page1 = conn.list_hosted_zones()
assert "Marker" not in page1
assert page1["IsTruncated"] is True
assert "NextMarker" in page1
assert "MaxItems" not in page1
assert len(page1["HostedZones"]) == 100
page2 = conn.list_hosted_zones(Marker=page1["NextMarker"])
assert page2["Marker"] == page1["NextMarker"]
assert page2["IsTruncated"] is False
assert "NextMarker" not in page2
assert "MaxItems" not in page2
assert len(page2["HostedZones"]) == 50
small_page = conn.list_hosted_zones(MaxItems="75")
assert "Marker" not in small_page
assert small_page["IsTruncated"] is True
assert "NextMarker" in small_page
assert small_page["MaxItems"] == "75"
assert len(small_page["HostedZones"]) == 75
remainer = conn.list_hosted_zones(Marker=small_page["NextMarker"])
assert remainer["Marker"] == small_page["NextMarker"]
assert remainer["IsTruncated"] is False
assert "NextMarker" not in remainer
assert "MaxItems" not in remainer
assert len(remainer["HostedZones"]) == 75
@mock_aws @mock_aws
def test_change_resource_record_sets_crud_valid(): def test_change_resource_record_sets_crud_valid():
conn = boto3.client("route53", region_name="us-east-1") conn = boto3.client("route53", region_name="us-east-1")