"""Handles Route53 API requests, invokes method and returns response."""
import re
from typing import Any
from urllib.parse import parse_qs, unquote
import xmltodict
from jinja2 import Template
from moto.core.common_types import TYPE_RESPONSE
from moto.core.responses import BaseResponse
from moto.core.utils import iso_8601_datetime_with_milliseconds
from moto.route53.exceptions import InvalidChangeBatch
from moto.route53.models import Route53Backend, route53_backends
XMLNS = "https://route53.amazonaws.com/doc/2013-04-01/"
class Route53(BaseResponse):
"""Handler for Route53 requests and responses."""
def __init__(self) -> None:
super().__init__(service_name="route53")
@staticmethod
def _convert_to_bool(bool_str: Any) -> bool: # type: ignore[misc]
if isinstance(bool_str, bool):
return bool_str
if isinstance(bool_str, str):
return str(bool_str).lower() == "true"
return False
@property
def backend(self) -> Route53Backend:
return route53_backends[self.current_account][self.partition]
def create_hosted_zone(self) -> TYPE_RESPONSE:
vpcid = None
vpcregion = None
elements = xmltodict.parse(self.body)
zone_request = elements["CreateHostedZoneRequest"]
if "HostedZoneConfig" in zone_request:
zone_config = zone_request["HostedZoneConfig"]
comment = zone_config["Comment"]
if zone_request.get("VPC", {}).get("VPCId", None):
private_zone = True
else:
private_zone = self._convert_to_bool(
zone_config.get("PrivateZone", False)
)
else:
comment = None
private_zone = False
# It is possible to create a Private Hosted Zone without
# associating VPC at the time of creation.
if self._convert_to_bool(private_zone):
if zone_request.get("VPC", None) is not None:
vpcid = zone_request["VPC"].get("VPCId", None)
vpcregion = zone_request["VPC"].get("VPCRegion", None)
name = zone_request["Name"]
caller_reference = zone_request["CallerReference"]
if name[-1] != ".":
name += "."
delegation_set_id = zone_request.get("DelegationSetId")
new_zone = self.backend.create_hosted_zone(
name,
comment=comment,
private_zone=private_zone,
caller_reference=caller_reference,
vpcid=vpcid,
vpcregion=vpcregion,
delegation_set_id=delegation_set_id,
)
template = Template(CREATE_HOSTED_ZONE_RESPONSE).render(zone=new_zone)
headers = {
"Location": f"https://route53.amazonaws.com/2013-04-01/hostedzone/{new_zone.id}",
"status": 201,
}
return 201, headers, template
def list_hosted_zones(self) -> str:
max_size = self.querystring.get("maxitems", [None])[0]
if max_size:
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 template
def list_hosted_zones_by_name(self) -> str:
query_params = parse_qs(self.parsed_url.query)
dnsnames = query_params.get("dnsname")
dnsname, zones = self.backend.list_hosted_zones_by_name(dnsnames)
template = Template(LIST_HOSTED_ZONES_BY_NAME_RESPONSE)
return template.render(zones=zones, dnsname=dnsname, xmlns=XMLNS)
def list_hosted_zones_by_vpc(self) -> str:
query_params = parse_qs(self.parsed_url.query)
vpc_id = query_params.get("vpcid")[0] # type: ignore
zones = self.backend.list_hosted_zones_by_vpc(vpc_id)
template = Template(LIST_HOSTED_ZONES_BY_VPC_RESPONSE)
return template.render(zones=zones, xmlns=XMLNS)
def get_hosted_zone_count(self) -> str:
num_zones = self.backend.get_hosted_zone_count()
template = Template(GET_HOSTED_ZONE_COUNT_RESPONSE)
return template.render(zone_count=num_zones, xmlns=XMLNS)
def get_hosted_zone(self) -> str:
zoneid = self.parsed_url.path.rstrip("/").rsplit("/", 1)[1]
the_zone = self.backend.get_hosted_zone(zoneid)
template = Template(GET_HOSTED_ZONE_RESPONSE)
return template.render(zone=the_zone)
def delete_hosted_zone(self) -> str:
zoneid = self.parsed_url.path.rstrip("/").rsplit("/", 1)[1]
self.backend.delete_hosted_zone(zoneid)
return DELETE_HOSTED_ZONE_RESPONSE
def update_hosted_zone_comment(self) -> str:
zoneid = self.parsed_url.path.rstrip("/").rsplit("/", 1)[1]
elements = xmltodict.parse(self.body)
comment = elements.get("UpdateHostedZoneCommentRequest", {}).get(
"Comment", None
)
zone = self.backend.update_hosted_zone_comment(zoneid, comment)
template = Template(UPDATE_HOSTED_ZONE_COMMENT_RESPONSE)
return template.render(zone=zone)
def get_dnssec(self) -> str:
# TODO: implement enable/disable dnssec apis
zoneid = self.parsed_url.path.rstrip("/").rsplit("/", 2)[1]
self.backend.get_dnssec(zoneid)
return GET_DNSSEC
def associate_vpc_with_hosted_zone(self) -> str:
zoneid = self.parsed_url.path.rstrip("/").rsplit("/", 2)[1]
elements = xmltodict.parse(self.body)
comment = elements.get("AssociateVPCWithHostedZoneRequest", {}).get(
"Comment", {}
)
vpc = elements.get("AssociateVPCWithHostedZoneRequest", {}).get("VPC", {})
vpcid = vpc.get("VPCId", None)
vpcregion = vpc.get("VPCRegion", None)
self.backend.associate_vpc_with_hosted_zone(zoneid, vpcid, vpcregion)
template = Template(ASSOCIATE_VPC_RESPONSE)
return template.render(comment=comment)
def disassociate_vpc_from_hosted_zone(self) -> str:
zoneid = self.parsed_url.path.rstrip("/").rsplit("/", 2)[1]
elements = xmltodict.parse(self.body)
comment = elements.get("DisassociateVPCFromHostedZoneRequest", {}).get(
"Comment", {}
)
vpc = elements.get("DisassociateVPCFromHostedZoneRequest", {}).get("VPC", {})
vpcid = vpc.get("VPCId", None)
self.backend.disassociate_vpc_from_hosted_zone(zoneid, vpcid)
template = Template(DISASSOCIATE_VPC_RESPONSE)
return template.render(comment=comment)
def change_resource_record_sets(self) -> str:
zoneid = self.parsed_url.path.rstrip("/").rsplit("/", 2)[1]
elements = xmltodict.parse(self.body)
change_list = elements["ChangeResourceRecordSetsRequest"]["ChangeBatch"][
"Changes"
]["Change"]
if not isinstance(change_list, list):
change_list = [
elements["ChangeResourceRecordSetsRequest"]["ChangeBatch"]["Changes"][
"Change"
]
]
# Enforce quotas https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests-changeresourcerecordsets
# - A request cannot contain more than 1,000 ResourceRecord elements. When the value of the Action element is UPSERT, each ResourceRecord element is counted twice.
effective_rr_count = 0
for value in change_list:
record_set = value["ResourceRecordSet"]
if "ResourceRecords" not in record_set or not record_set["ResourceRecords"]:
continue
resource_records = list(record_set["ResourceRecords"].values())[0]
effective_rr_count += len(resource_records)
if value["Action"] == "UPSERT":
effective_rr_count += len(resource_records)
if effective_rr_count > 1000:
raise InvalidChangeBatch
self.backend.change_resource_record_sets(zoneid, change_list)
return CHANGE_RRSET_RESPONSE
def list_resource_record_sets(self) -> TYPE_RESPONSE:
zoneid = self.parsed_url.path.rstrip("/").rsplit("/", 2)[1]
querystring = parse_qs(self.parsed_url.query)
template = Template(LIST_RRSET_RESPONSE)
start_type = querystring.get("type", [None])[0]
start_name = querystring.get("name", [None])[0]
max_items = int(querystring.get("maxitems", ["300"])[0])
if start_type and not start_name:
return 400, {"status": 400}, "The input is not valid"
(
record_sets,
next_name,
next_type,
is_truncated,
) = self.backend.list_resource_record_sets(
zoneid,
start_type=start_type, # type: ignore
start_name=start_name, # type: ignore
max_items=max_items,
)
r_template = template.render(
record_sets=record_sets,
next_name=next_name,
next_type=next_type,
max_items=max_items,
is_truncated=is_truncated,
)
return 200, {}, r_template
def create_health_check(self) -> TYPE_RESPONSE:
json_body = xmltodict.parse(self.body)["CreateHealthCheckRequest"]
caller_reference = json_body["CallerReference"]
config = json_body["HealthCheckConfig"]
health_check_args = {
"ip_address": config.get("IPAddress"),
"port": config.get("Port"),
"type": config["Type"],
"resource_path": config.get("ResourcePath"),
"fqdn": config.get("FullyQualifiedDomainName"),
"search_string": config.get("SearchString"),
"request_interval": config.get("RequestInterval"),
"failure_threshold": config.get("FailureThreshold"),
"health_threshold": config.get("HealthThreshold"),
"measure_latency": config.get("MeasureLatency"),
"inverted": config.get("Inverted"),
"disabled": config.get("Disabled"),
"enable_sni": config.get("EnableSNI"),
"children": config.get("ChildHealthChecks", {}).get("ChildHealthCheck"),
"regions": config.get("Regions", {}).get("Region"),
}
health_check = self.backend.create_health_check(
caller_reference, health_check_args
)
template = Template(CREATE_HEALTH_CHECK_RESPONSE)
return (
201,
{"status": 201},
template.render(health_check=health_check, xmlns=XMLNS),
)
def list_health_checks(self) -> str:
template = Template(LIST_HEALTH_CHECKS_RESPONSE)
health_checks = self.backend.list_health_checks()
return template.render(health_checks=health_checks, xmlns=XMLNS)
def get_health_check(self) -> str:
health_check_id = self.parsed_url.path.split("/")[-1]
health_check = self.backend.get_health_check(health_check_id)
template = Template(GET_HEALTH_CHECK_RESPONSE)
return template.render(health_check=health_check)
def delete_health_check(self) -> str:
health_check_id = self.parsed_url.path.split("/")[-1]
self.backend.delete_health_check(health_check_id)
template = Template(DELETE_HEALTH_CHECK_RESPONSE)
return template.render(xmlns=XMLNS)
def update_health_check(self) -> str:
health_check_id = self.parsed_url.path.split("/")[-1]
config = xmltodict.parse(self.body)["UpdateHealthCheckRequest"]
health_check_args = {
"ip_address": config.get("IPAddress"),
"port": config.get("Port"),
"resource_path": config.get("ResourcePath"),
"fqdn": config.get("FullyQualifiedDomainName"),
"search_string": config.get("SearchString"),
"failure_threshold": config.get("FailureThreshold"),
"health_threshold": config.get("HealthThreshold"),
"inverted": config.get("Inverted"),
"disabled": config.get("Disabled"),
"enable_sni": config.get("EnableSNI"),
"children": config.get("ChildHealthChecks", {}).get("ChildHealthCheck"),
"regions": config.get("Regions", {}).get("Region"),
}
health_check = self.backend.update_health_check(
health_check_id, health_check_args
)
template = Template(UPDATE_HEALTH_CHECK_RESPONSE)
return template.render(health_check=health_check)
def get_health_check_status(self) -> str:
health_check_match = re.search(
r"healthcheck/(?P<health_check_id>[^/]+)/status$", self.parsed_url.path
)
health_check_id = health_check_match.group("health_check_id") # type: ignore[union-attr]
self.backend.get_health_check(health_check_id)
template = Template(GET_HEALTH_CHECK_STATUS_RESPONSE)
return template.render(timestamp=iso_8601_datetime_with_milliseconds())
def not_implemented_response(
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
action = ""
if "tags" in full_url:
action = "tags"
elif "trafficpolicyinstances" in full_url:
action = "policies"
raise NotImplementedError(
f"The action for {action} has not been implemented for route 53"
)
def list_or_change_tags_for_resource_request( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
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_)
template = Template(LIST_TAGS_FOR_RESOURCE_RESPONSE)
return (
200,
headers,
template.render(resource_type=type_, resource_id=id_, tags=tags),
)
if request.method == "POST":
tags = xmltodict.parse(self.body)["ChangeTagsForResourceRequest"]
if "AddTags" in tags:
tags = tags["AddTags"] # type: ignore
elif "RemoveTagKeys" in tags:
tags = tags["RemoveTagKeys"] # type: ignore
self.backend.change_tags_for_resource(id_, tags)
template = Template(CHANGE_TAGS_FOR_RESOURCE_RESPONSE)
return 200, headers, template.render()
def list_tags_for_resources(self) -> str:
if id_matcher := re.search(r"/tags/(.+)", self.parsed_url.path):
resource_type = unquote(id_matcher.group(1))
else:
resource_type = ""
resource_ids = xmltodict.parse(self.body)["ListTagsForResourcesRequest"][
"ResourceIds"
]["ResourceId"]
# If only one resource id is passed, it is a string, not a list
if not isinstance(resource_ids, list):
resource_ids = [resource_ids]
tag_sets = self.backend.list_tags_for_resources(resource_ids=resource_ids)
template = Template(LIST_TAGS_FOR_RESOURCES_RESPONSE)
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]
template = Template(GET_CHANGE_RESPONSE)
return template.render(change_id=change_id, xmlns=XMLNS)
def create_query_logging_config(self) -> TYPE_RESPONSE:
json_body = xmltodict.parse(self.body)["CreateQueryLoggingConfigRequest"]
hosted_zone_id = json_body["HostedZoneId"]
log_group_arn = json_body["CloudWatchLogsLogGroupArn"]
query_logging_config = self.backend.create_query_logging_config(
self.region, hosted_zone_id, log_group_arn
)
template = Template(CREATE_QUERY_LOGGING_CONFIG_RESPONSE)
headers = {"Location": query_logging_config.location, "status": 201}
return (
201,
headers,
template.render(query_logging_config=query_logging_config, xmlns=XMLNS),
)
def list_query_logging_configs(self) -> str:
hosted_zone_id = self._get_param("hostedzoneid")
next_token = self._get_param("nexttoken")
max_results = self._get_int_param("maxresults")
# The paginator picks up named arguments, returns tuple.
# pylint: disable=unbalanced-tuple-unpacking
all_configs, next_token = self.backend.list_query_logging_configs(
hosted_zone_id=hosted_zone_id,
next_token=next_token,
max_results=max_results,
)
template = Template(LIST_QUERY_LOGGING_CONFIGS_RESPONSE)
return template.render(
query_logging_configs=all_configs, next_token=next_token, xmlns=XMLNS
)
def get_query_logging_config(self) -> str:
query_logging_config_id = self.parsed_url.path.rstrip("/").rsplit("/", 1)[1]
query_logging_config = self.backend.get_query_logging_config(
query_logging_config_id
)
template = Template(GET_QUERY_LOGGING_CONFIG_RESPONSE)
return template.render(query_logging_config=query_logging_config, xmlns=XMLNS)
def delete_query_logging_config(self) -> str:
query_logging_config_id = self.parsed_url.path.rstrip("/").rsplit("/", 1)[1]
self.backend.delete_query_logging_config(query_logging_config_id)
return ""
def list_reusable_delegation_sets(self) -> str:
delegation_sets = self.backend.list_reusable_delegation_sets()
template = self.response_template(LIST_REUSABLE_DELEGATION_SETS_TEMPLATE)
return template.render(
delegation_sets=delegation_sets,
marker=None,
is_truncated=False,
max_items=100,
)
def create_reusable_delegation_set(self) -> TYPE_RESPONSE:
elements = xmltodict.parse(self.body)
root_elem = elements["CreateReusableDelegationSetRequest"]
caller_reference = root_elem.get("CallerReference")
hosted_zone_id = root_elem.get("HostedZoneId")
delegation_set = self.backend.create_reusable_delegation_set(
caller_reference=caller_reference, hosted_zone_id=hosted_zone_id
)
template = self.response_template(CREATE_REUSABLE_DELEGATION_SET_TEMPLATE)
return (
201,
{"Location": delegation_set.location, "status": 201},
template.render(delegation_set=delegation_set),
)
def get_reusable_delegation_set(self) -> str:
ds_id = self.parsed_url.path.rstrip("/").rsplit("/")[-1]
delegation_set = self.backend.get_reusable_delegation_set(
delegation_set_id=ds_id
)
template = self.response_template(GET_REUSABLE_DELEGATION_SET_TEMPLATE)
return template.render(delegation_set=delegation_set)
def delete_reusable_delegation_set(self) -> str:
ds_id = self.parsed_url.path.rstrip("/").rsplit("/")[-1]
self.backend.delete_reusable_delegation_set(delegation_set_id=ds_id)
template = self.response_template(DELETE_REUSABLE_DELEGATION_SET_TEMPLATE)
return template.render()
LIST_TAGS_FOR_RESOURCE_RESPONSE = """
<ListTagsForResourceResponse xmlns="https://route53.amazonaws.com/doc/2015-01-01/">
<ResourceTagSet>
<ResourceType>{{resource_type}}</ResourceType>
<ResourceId>{{resource_id}}</ResourceId>
<Tags>
{% for key, value in tags.items() %}
<Tag>
<Key>{{key}}</Key>
<Value>{{value}}</Value>
</Tag>
{% endfor %}
</Tags>
</ResourceTagSet>
</ListTagsForResourceResponse>
"""
LIST_TAGS_FOR_RESOURCES_RESPONSE = """
<ListTagsForResourcesResponse xmlns="https://route53.amazonaws.com/doc/2015-01-01/">
<ResourceTagSets>
{% for set in tag_sets %}
<ResourceTagSet>
<ResourceType>{{resource_type}}</ResourceType>
<ResourceId>{{set["ResourceId"]}}</ResourceId>
<Tags>
{% for key, value in set["Tags"].items() %}
<Tag>
<Key>{{key}}</Key>
<Value>{{value}}</Value>
</Tag>
{% endfor %}
</Tags>
</ResourceTagSet>
{% endfor %}
</ResourceTagSets>
</ListTagsForResourcesResponse>
"""
CHANGE_TAGS_FOR_RESOURCE_RESPONSE = """<ChangeTagsForResourceResponse xmlns="https://route53.amazonaws.com/doc/2015-01-01/">
</ChangeTagsForResourceResponse>
"""
LIST_RRSET_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<ListResourceRecordSetsResponse xmlns="https://route53.amazonaws.com/doc/2012-12-12/">
<ResourceRecordSets>
{% for record in record_sets %}
<ResourceRecordSet>
<Name>{{ record.name }}</Name>
<Type>{{ record.type_ }}</Type>
{% if record.set_identifier %}
<SetIdentifier>{{ record.set_identifier }}</SetIdentifier>
{% endif %}
{% if record.weight %}
<Weight>{{ record.weight }}</Weight>
{% endif %}
{% if record.region %}
<Region>{{ record.region }}</Region>
{% endif %}
{% if record.ttl %}
<TTL>{{ record.ttl }}</TTL>
{% endif %}
{% if record.failover %}
<Failover>{{ record.failover }}</Failover>
{% endif %}
{% if record.multi_value %}
<MultiValueAnswer>{{ record.multi_value }}</MultiValueAnswer>
{% endif %}
{% if record.geo_location %}
<GeoLocation>
{% for geo_key in ['ContinentCode','CountryCode','SubdivisionCode'] %}
{% if record.geo_location[geo_key] %}<{{ geo_key }}>{{ record.geo_location[geo_key] }}</{{ geo_key }}>{% endif %}
{% endfor %}
</GeoLocation>
{% endif %}
{% if record.alias_target %}
<AliasTarget>
<HostedZoneId>{{ record.alias_target['HostedZoneId'] }}</HostedZoneId>
<DNSName>{{ record.alias_target['DNSName'] }}</DNSName>
<EvaluateTargetHealth>{{ record.alias_target['EvaluateTargetHealth'] }}</EvaluateTargetHealth>
</AliasTarget>
{% else %}
<ResourceRecords>
{% for resource in record.records %}
<ResourceRecord>
<Value><![CDATA[{{ resource }}]]></Value>
</ResourceRecord>
{% endfor %}
</ResourceRecords>
{% endif %}
{% if record.health_check %}
<HealthCheckId>{{ record.health_check }}</HealthCheckId>
{% endif %}
</ResourceRecordSet>
{% endfor %}
</ResourceRecordSets>
{% if is_truncated %}<NextRecordName>{{ next_name }}</NextRecordName>{% endif %}
{% if is_truncated %}<NextRecordType>{{ next_type }}</NextRecordType>{% endif %}
<MaxItems>{{ max_items }}</MaxItems>
<IsTruncated>{{ 'true' if is_truncated else 'false' }}</IsTruncated>
</ListResourceRecordSetsResponse>"""
CHANGE_RRSET_RESPONSE = """<ChangeResourceRecordSetsResponse xmlns="https://route53.amazonaws.com/doc/2012-12-12/">
<ChangeInfo>
<Status>INSYNC</Status>
<SubmittedAt>2010-09-10T01:36:41.958Z</SubmittedAt>
<Id>/change/C2682N5HXP0BZ4</Id>
</ChangeInfo>
</ChangeResourceRecordSetsResponse>"""
DELETE_HOSTED_ZONE_RESPONSE = """<DeleteHostedZoneResponse xmlns="https://route53.amazonaws.com/doc/2012-12-12/">
<ChangeInfo>
<Status>INSYNC</Status>
<SubmittedAt>2010-09-10T01:36:41.958Z</SubmittedAt>
<Id>/change/C2682N5HXP0BZ4</Id>
</ChangeInfo>
</DeleteHostedZoneResponse>"""
GET_HOSTED_ZONE_COUNT_RESPONSE = """<GetHostedZoneCountResponse> xmlns="https://route53.amazonaws.com/doc/2012-12-12/">
<HostedZoneCount>{{ zone_count }}</HostedZoneCount>
</GetHostedZoneCountResponse>"""
GET_HOSTED_ZONE_RESPONSE = """<GetHostedZoneResponse xmlns="https://route53.amazonaws.com/doc/2012-12-12/">
<HostedZone>
<Id>/hostedzone/{{ zone.id }}</Id>
<Name>{{ zone.name }}</Name>
<CallerReference>{{ zone.caller_reference }}</CallerReference>
<ResourceRecordSetCount>{{ zone.rrsets|count }}</ResourceRecordSetCount>
<Config>
{% if zone.comment %}
<Comment>{{ zone.comment }}</Comment>
{% endif %}
<PrivateZone>{{ 'true' if zone.private_zone else 'false' }}</PrivateZone>
</Config>
</HostedZone>
{% if not zone.private_zone %}
<DelegationSet>
<Id>{{ zone.delegation_set.id }}</Id>
<NameServers>
{% for name in zone.delegation_set.name_servers %}<NameServer>{{ name }}</NameServer>{% endfor %}
</NameServers>
</DelegationSet>
{% endif %}
{% if zone.private_zone %}
<VPCs>
{% for vpc in zone.vpcs %}
<VPC>
<VPCId>{{vpc.vpc_id}}</VPCId>
<VPCRegion>{{vpc.vpc_region}}</VPCRegion>
</VPC>
{% endfor %}
</VPCs>
{% endif %}
</GetHostedZoneResponse>"""
CREATE_HOSTED_ZONE_RESPONSE = """<CreateHostedZoneResponse xmlns="https://route53.amazonaws.com/doc/2012-12-12/">
{% if zone.private_zone %}
<VPC>
<VPCId>{{zone.vpcid}}</VPCId>
<VPCRegion>{{zone.vpcregion}}</VPCRegion>
</VPC>
{% endif %}
<HostedZone>
<Id>/hostedzone/{{ zone.id }}</Id>
<Name>{{ zone.name }}</Name>
<CallerReference>{{ zone.caller_reference }}</CallerReference>
<ResourceRecordSetCount>{{ zone.rrsets|count }}</ResourceRecordSetCount>
<Config>
{% if zone.comment %}
<Comment>{{ zone.comment }}</Comment>
{% endif %}
<PrivateZone>{{ 'true' if zone.private_zone else 'false' }}</PrivateZone>
</Config>
</HostedZone>
{% if not zone.private_zone %}
<DelegationSet>
<Id>{{ zone.delegation_set.id }}</Id>
<NameServers>
{% for name in zone.delegation_set.name_servers %}<NameServer>{{ name }}</NameServer>{% endfor %}
</NameServers>
</DelegationSet>
{% endif %}
<ChangeInfo>
<Id>/change/C1PA6795UKMFR9</Id>
<Status>INSYNC</Status>
<SubmittedAt>2017-03-15T01:36:41.958Z</SubmittedAt>
</ChangeInfo>
</CreateHostedZoneResponse>"""
LIST_HOSTED_ZONES_RESPONSE = """<ListHostedZonesResponse xmlns="https://route53.amazonaws.com/doc/2012-12-12/">
<HostedZones>
{% for zone in zones %}
<HostedZone>
<Id>/hostedzone/{{ zone.id }}</Id>
<Name>{{ zone.name }}</Name>
<CallerReference>{{ zone.caller_reference }}</CallerReference>
<Config>
{% if zone.comment %}
<Comment>{{ zone.comment }}</Comment>
{% endif %}
<PrivateZone>{{ 'true' if zone.private_zone else 'false' }}</PrivateZone>
</Config>
<ResourceRecordSetCount>{{ zone.rrsets|count }}</ResourceRecordSetCount>
</HostedZone>
{% endfor %}
</HostedZones>
{% 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>"""
LIST_HOSTED_ZONES_BY_NAME_RESPONSE = """<ListHostedZonesByNameResponse xmlns="{{ xmlns }}">
{% if dnsname %}
<DNSName>{{ dnsname }}</DNSName>
{% endif %}
<HostedZones>
{% for zone in zones %}
<HostedZone>
<Id>/hostedzone/{{ zone.id }}</Id>
<Name>{{ zone.name }}</Name>
<CallerReference>{{ zone.caller_reference }}</CallerReference>
<Config>
{% if zone.comment %}
<Comment>{{ zone.comment }}</Comment>
{% endif %}
<PrivateZone>{{ 'true' if zone.private_zone else 'false' }}</PrivateZone>
</Config>
<ResourceRecordSetCount>{{ zone.rrsets|count }}</ResourceRecordSetCount>
</HostedZone>
{% endfor %}
</HostedZones>
<IsTruncated>false</IsTruncated>
</ListHostedZonesByNameResponse>"""
LIST_HOSTED_ZONES_BY_VPC_RESPONSE = """<ListHostedZonesByVpcResponse xmlns="{{xmlns}}">
<HostedZoneSummaries>
{% for zone in zones -%}
<HostedZoneSummary>
<HostedZoneId>{{zone["HostedZoneId"]}}</HostedZoneId>
<Name>{{zone["Name"]}}</Name>
<Owner>
{% if zone["Owner"]["OwningAccount"] -%}
<OwningAccount>{{zone["Owner"]["OwningAccount"]}}</OwningAccount>
{% endif -%}
{% if zone["Owner"]["OwningService"] -%}
<OwningService>zone["Owner"]["OwningService"]</OwningService>
{% endif -%}
</Owner>
</HostedZoneSummary>
{% endfor -%}
</HostedZoneSummaries>
</ListHostedZonesByVpcResponse>"""
CREATE_HEALTH_CHECK_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<CreateHealthCheckResponse xmlns="{{ xmlns }}">
{{ health_check.to_xml() }}
</CreateHealthCheckResponse>"""
UPDATE_HEALTH_CHECK_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<UpdateHealthCheckResponse>
{{ health_check.to_xml() }}
</UpdateHealthCheckResponse>
"""
LIST_HEALTH_CHECKS_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<ListHealthChecksResponse xmlns="{{ xmlns }}">
<HealthChecks>
{% for health_check in health_checks %}
{{ health_check.to_xml() }}
{% endfor %}
</HealthChecks>
<IsTruncated>false</IsTruncated>
<MaxItems>{{ health_checks|length }}</MaxItems>
</ListHealthChecksResponse>"""
DELETE_HEALTH_CHECK_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<DeleteHealthCheckResponse xmlns="{{ xmlns }}">
</DeleteHealthCheckResponse>"""
GET_CHANGE_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<GetChangeResponse xmlns="{{ xmlns }}">
<ChangeInfo>
<Status>INSYNC</Status>
<SubmittedAt>2010-09-10T01:36:41.958Z</SubmittedAt>
<Id>{{ change_id }}</Id>
</ChangeInfo>
</GetChangeResponse>"""
CREATE_QUERY_LOGGING_CONFIG_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<CreateQueryLoggingConfigResponse xmlns="{{ xmlns }}">
{{ query_logging_config.to_xml() }}
</CreateQueryLoggingConfigResponse>"""
GET_QUERY_LOGGING_CONFIG_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<CreateQueryLoggingConfigResponse xmlns="{{ xmlns }}">
{{ query_logging_config.to_xml() }}
</CreateQueryLoggingConfigResponse>"""
LIST_QUERY_LOGGING_CONFIGS_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<ListQueryLoggingConfigsResponse xmlns="{{ xmlns }}">
<QueryLoggingConfigs>
{% for query_logging_config in query_logging_configs %}
{{ query_logging_config.to_xml() }}
{% endfor %}
</QueryLoggingConfigs>
{% if next_token %}
<NextToken>{{ next_token }}</NextToken>
{% endif %}
</ListQueryLoggingConfigsResponse>"""
CREATE_REUSABLE_DELEGATION_SET_TEMPLATE = """<CreateReusableDelegationSetResponse>
<DelegationSet>
<Id>{{ delegation_set.id }}</Id>
<CallerReference>{{ delegation_set.caller_reference }}</CallerReference>
<NameServers>
{% for name in delegation_set.name_servers %}<NameServer>{{ name }}</NameServer>{% endfor %}
</NameServers>
</DelegationSet>
</CreateReusableDelegationSetResponse>
"""
LIST_REUSABLE_DELEGATION_SETS_TEMPLATE = """<ListReusableDelegationSetsResponse>
<DelegationSets>
{% for delegation in delegation_sets %}
<DelegationSet>
<Id>{{ delegation.id }}</Id>
<CallerReference>{{ delegation.caller_reference }}</CallerReference>
<NameServers>
{% for name in delegation.name_servers %}<NameServer>{{ name }}</NameServer>{% endfor %}
</NameServers>
</DelegationSet>
{% endfor %}
</DelegationSets>
<Marker>{{ marker }}</Marker>
<IsTruncated>{{ is_truncated }}</IsTruncated>
<MaxItems>{{ max_items }}</MaxItems>
</ListReusableDelegationSetsResponse>
"""
DELETE_REUSABLE_DELEGATION_SET_TEMPLATE = """<DeleteReusableDelegationSetResponse>
<DeleteReusableDelegationSetResponse/>
</DeleteReusableDelegationSetResponse>
"""
GET_REUSABLE_DELEGATION_SET_TEMPLATE = """<GetReusableDelegationSetResponse>
<DelegationSet>
<Id>{{ delegation_set.id }}</Id>
<CallerReference>{{ delegation_set.caller_reference }}</CallerReference>
<NameServers>
{% for name in delegation_set.name_servers %}<NameServer>{{ name }}</NameServer>{% endfor %}
</NameServers>
</DelegationSet>
</GetReusableDelegationSetResponse>
"""
GET_DNSSEC = """<?xml version="1.0"?>
<GetDNSSECResponse>
<Status>
<ServeSignature>NOT_SIGNING</ServeSignature>
</Status>
<KeySigningKeys/>
</GetDNSSECResponse>
"""
GET_HEALTH_CHECK_RESPONSE = """<?xml version="1.0"?>
<GetHealthCheckResponse>
{{ health_check.to_xml() }}
</GetHealthCheckResponse>
"""
GET_HEALTH_CHECK_STATUS_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<GetHealthCheckStatusResponse>
<HealthCheckObservations>
<HealthCheckObservation>
<IPAddress>127.0.13.37</IPAddress>
<Region>us-east-1</Region>
<StatusReport>
<CheckedTime>{{ timestamp }}</CheckedTime>
<Status>Success: HTTP Status Code: 200. Resolved IP: 127.0.13.37. OK</Status>
</StatusReport>
</HealthCheckObservation>
</HealthCheckObservations>
</GetHealthCheckStatusResponse>
"""
UPDATE_HOSTED_ZONE_COMMENT_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<UpdateHostedZoneCommentResponse>
<HostedZone>
<Config>
{% if zone.comment %}
<Comment>{{ zone.comment }}</Comment>
{% endif %}
<PrivateZone>{{ 'true' if zone.private_zone else 'false' }}</PrivateZone>
</Config>
<Id>/hostedzone/{{ zone.id }}</Id>
<Name>{{ zone.name }}</Name>
<ResourceRecordSetCount>{{ zone.rrsets|count }}</ResourceRecordSetCount>
</HostedZone>
</UpdateHostedZoneCommentResponse>
"""
ASSOCIATE_VPC_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<AssociateVPCWithHostedZoneResponse>
<ChangeInfo>
<Comment>{{ comment or "" }}</Comment>
<Id>/change/a1b2c3d4</Id>
<Status>INSYNC</Status>
<SubmittedAt>2017-03-31T01:36:41.958Z</SubmittedAt>
</ChangeInfo>
</AssociateVPCWithHostedZoneResponse>
"""
DISASSOCIATE_VPC_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<DisassociateVPCFromHostedZoneResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
<ChangeInfo>
<Comment>{{ comment or "" }}</Comment>
<Id>/change/a1b2c3d4</Id>
<Status>INSYNC</Status>
<SubmittedAt>2017-03-31T01:36:41.958Z</SubmittedAt>
</ChangeInfo>
</DisassociateVPCFromHostedZoneResponse>
"""