Route53: Intercept duplicate calls to change_rr_sets (#5725)
This commit is contained in:
parent
a270e92dc4
commit
42597decb5
@ -162,3 +162,20 @@ class NoSuchDelegationSet(Route53ClientError):
|
|||||||
def __init__(self, delegation_set_id):
|
def __init__(self, delegation_set_id):
|
||||||
super().__init__("NoSuchDelegationSet", delegation_set_id)
|
super().__init__("NoSuchDelegationSet", delegation_set_id)
|
||||||
self.content_type = "text/xml"
|
self.content_type = "text/xml"
|
||||||
|
|
||||||
|
|
||||||
|
class DnsNameInvalidForZone(Route53ClientError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, name, zone_name):
|
||||||
|
error_msg = (
|
||||||
|
f"""RRSet with DNS name {name} is not permitted in zone {zone_name}"""
|
||||||
|
)
|
||||||
|
super().__init__("InvalidChangeBatch", error_msg)
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeSetAlreadyExists(Route53ClientError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("InvalidChangeBatch", "Provided Change is a duplicate")
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Route53Backend class with methods for supported APIs."""
|
"""Route53Backend class with methods for supported APIs."""
|
||||||
|
import copy
|
||||||
import itertools
|
import itertools
|
||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
@ -17,6 +18,8 @@ from moto.route53.exceptions import (
|
|||||||
NoSuchQueryLoggingConfig,
|
NoSuchQueryLoggingConfig,
|
||||||
PublicZoneVPCAssociation,
|
PublicZoneVPCAssociation,
|
||||||
QueryLoggingConfigAlreadyExists,
|
QueryLoggingConfigAlreadyExists,
|
||||||
|
DnsNameInvalidForZone,
|
||||||
|
ChangeSetAlreadyExists,
|
||||||
)
|
)
|
||||||
from moto.core import BaseBackend, BackendDict, BaseModel, CloudFormationModel
|
from moto.core import BaseBackend, BackendDict, BaseModel, CloudFormationModel
|
||||||
from moto.moto_api._internal import mock_random as random
|
from moto.moto_api._internal import mock_random as random
|
||||||
@ -276,6 +279,7 @@ class FakeZone(CloudFormationModel):
|
|||||||
self.private_zone = private_zone
|
self.private_zone = private_zone
|
||||||
self.rrsets = []
|
self.rrsets = []
|
||||||
self.delegation_set = delegation_set
|
self.delegation_set = delegation_set
|
||||||
|
self.rr_changes = []
|
||||||
|
|
||||||
def add_rrset(self, record_set):
|
def add_rrset(self, record_set):
|
||||||
record_set = RecordSet(record_set)
|
record_set = RecordSet(record_set)
|
||||||
@ -525,9 +529,14 @@ class Route53Backend(BaseBackend):
|
|||||||
is_truncated = next_record is not None
|
is_truncated = next_record is not None
|
||||||
return records, next_start_name, next_start_type, is_truncated
|
return records, next_start_name, next_start_type, is_truncated
|
||||||
|
|
||||||
def change_resource_record_sets(self, zoneid, change_list):
|
def change_resource_record_sets(self, zoneid, change_list) -> None:
|
||||||
the_zone = self.get_hosted_zone(zoneid)
|
the_zone = self.get_hosted_zone(zoneid)
|
||||||
|
|
||||||
|
if any([rr for rr in change_list if rr in the_zone.rr_changes]):
|
||||||
|
raise ChangeSetAlreadyExists
|
||||||
|
|
||||||
for value in change_list:
|
for value in change_list:
|
||||||
|
original_change = copy.deepcopy(value)
|
||||||
action = value["Action"]
|
action = value["Action"]
|
||||||
|
|
||||||
if action not in ("CREATE", "UPSERT", "DELETE"):
|
if action not in ("CREATE", "UPSERT", "DELETE"):
|
||||||
@ -539,11 +548,9 @@ class Route53Backend(BaseBackend):
|
|||||||
cleaned_hosted_zone_name = the_zone.name.strip(".")
|
cleaned_hosted_zone_name = the_zone.name.strip(".")
|
||||||
|
|
||||||
if not cleaned_record_name.endswith(cleaned_hosted_zone_name):
|
if not cleaned_record_name.endswith(cleaned_hosted_zone_name):
|
||||||
error_msg = f"""
|
raise DnsNameInvalidForZone(
|
||||||
An error occurred (InvalidChangeBatch) when calling the ChangeResourceRecordSets operation:
|
name=record_set["Name"], zone_name=the_zone.name
|
||||||
RRSet with DNS name {record_set["Name"]} is not permitted in zone {the_zone.name}
|
)
|
||||||
"""
|
|
||||||
return error_msg
|
|
||||||
|
|
||||||
if not record_set["Name"].endswith("."):
|
if not record_set["Name"].endswith("."):
|
||||||
record_set["Name"] += "."
|
record_set["Name"] += "."
|
||||||
@ -567,7 +574,7 @@ class Route53Backend(BaseBackend):
|
|||||||
the_zone.delete_rrset_by_id(record_set["SetIdentifier"])
|
the_zone.delete_rrset_by_id(record_set["SetIdentifier"])
|
||||||
else:
|
else:
|
||||||
the_zone.delete_rrset(record_set)
|
the_zone.delete_rrset(record_set)
|
||||||
return None
|
the_zone.rr_changes.append(original_change)
|
||||||
|
|
||||||
def list_hosted_zones(self):
|
def list_hosted_zones(self):
|
||||||
return self.zones.values()
|
return self.zones.values()
|
||||||
@ -612,7 +619,7 @@ class Route53Backend(BaseBackend):
|
|||||||
|
|
||||||
return zone_list
|
return zone_list
|
||||||
|
|
||||||
def get_hosted_zone(self, id_):
|
def get_hosted_zone(self, id_) -> FakeZone:
|
||||||
the_zone = self.zones.get(id_.replace("/hostedzone/", ""))
|
the_zone = self.zones.get(id_.replace("/hostedzone/", ""))
|
||||||
if not the_zone:
|
if not the_zone:
|
||||||
raise NoSuchHostedZone(id_)
|
raise NoSuchHostedZone(id_)
|
||||||
|
@ -219,9 +219,7 @@ class Route53(BaseResponse):
|
|||||||
if effective_rr_count > 1000:
|
if effective_rr_count > 1000:
|
||||||
raise InvalidChangeBatch
|
raise InvalidChangeBatch
|
||||||
|
|
||||||
error_msg = self.backend.change_resource_record_sets(zoneid, change_list)
|
self.backend.change_resource_record_sets(zoneid, change_list)
|
||||||
if error_msg:
|
|
||||||
return 400, headers, error_msg
|
|
||||||
|
|
||||||
return 200, headers, CHANGE_RRSET_RESPONSE
|
return 200, headers, CHANGE_RRSET_RESPONSE
|
||||||
|
|
||||||
|
@ -1118,6 +1118,37 @@ def test_change_resource_record_invalid_action_value():
|
|||||||
len(response["ResourceRecordSets"]).should.equal(1)
|
len(response["ResourceRecordSets"]).should.equal(1)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_route53
|
||||||
|
def test_change_resource_record_set_twice():
|
||||||
|
ZONE = "cname.local"
|
||||||
|
FQDN = f"test.{ZONE}"
|
||||||
|
FQDN_TARGET = "develop.domain.com"
|
||||||
|
|
||||||
|
client = boto3.client("route53", region_name="us-east-1")
|
||||||
|
zone_id = client.create_hosted_zone(
|
||||||
|
Name=ZONE, CallerReference="ref", DelegationSetId="string"
|
||||||
|
)["HostedZone"]["Id"]
|
||||||
|
changes = {
|
||||||
|
"Changes": [
|
||||||
|
{
|
||||||
|
"Action": "CREATE",
|
||||||
|
"ResourceRecordSet": {
|
||||||
|
"Name": FQDN,
|
||||||
|
"Type": "CNAME",
|
||||||
|
"TTL": 600,
|
||||||
|
"ResourceRecords": [{"Value": FQDN_TARGET}],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
client.change_resource_record_sets(HostedZoneId=zone_id, ChangeBatch=changes)
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.change_resource_record_sets(HostedZoneId=zone_id, ChangeBatch=changes)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidChangeBatch")
|
||||||
|
|
||||||
|
|
||||||
@mock_route53
|
@mock_route53
|
||||||
def test_list_resource_record_sets_name_type_filters():
|
def test_list_resource_record_sets_name_type_filters():
|
||||||
conn = boto3.client("route53", region_name="us-east-1")
|
conn = boto3.client("route53", region_name="us-east-1")
|
||||||
|
Loading…
Reference in New Issue
Block a user