Route53: Validate deletion of RRsets (#5743)
This commit is contained in:
parent
0588db704a
commit
b2300f1eae
@ -20,6 +20,7 @@ from moto.route53.exceptions import (
|
|||||||
QueryLoggingConfigAlreadyExists,
|
QueryLoggingConfigAlreadyExists,
|
||||||
DnsNameInvalidForZone,
|
DnsNameInvalidForZone,
|
||||||
ChangeSetAlreadyExists,
|
ChangeSetAlreadyExists,
|
||||||
|
InvalidInput,
|
||||||
)
|
)
|
||||||
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
|
||||||
@ -262,6 +263,20 @@ def reverse_domain_name(domain_name):
|
|||||||
return ".".join(reversed(domain_name.split(".")))
|
return ".".join(reversed(domain_name.split(".")))
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeList(list):
|
||||||
|
"""
|
||||||
|
Contains a 'clean' list of ResourceRecordChangeSets
|
||||||
|
"""
|
||||||
|
|
||||||
|
def append(self, item) -> None:
|
||||||
|
item["ResourceRecordSet"]["Name"] = item["ResourceRecordSet"]["Name"].strip(".")
|
||||||
|
super().append(item)
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
item["ResourceRecordSet"]["Name"] = item["ResourceRecordSet"]["Name"].strip(".")
|
||||||
|
return super().__contains__(item)
|
||||||
|
|
||||||
|
|
||||||
class FakeZone(CloudFormationModel):
|
class FakeZone(CloudFormationModel):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -279,7 +294,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 = []
|
self.rr_changes = ChangeList()
|
||||||
|
|
||||||
def add_rrset(self, record_set):
|
def add_rrset(self, record_set):
|
||||||
record_set = RecordSet(record_set)
|
record_set = RecordSet(record_set)
|
||||||
@ -540,6 +555,20 @@ class Route53Backend(BaseBackend):
|
|||||||
action=rr["Action"], name=name, _type=_type
|
action=rr["Action"], name=name, _type=_type
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for value in change_list:
|
||||||
|
if value["Action"] == "DELETE":
|
||||||
|
# To delete a resource record set, you must specify all the same values that you specified when you created it.
|
||||||
|
corresponding_create = copy.deepcopy(value)
|
||||||
|
corresponding_create["Action"] = "CREATE"
|
||||||
|
corresponding_upsert = copy.deepcopy(value)
|
||||||
|
corresponding_upsert["Action"] = "UPSERT"
|
||||||
|
if (
|
||||||
|
corresponding_create not in the_zone.rr_changes
|
||||||
|
and corresponding_upsert not in the_zone.rr_changes
|
||||||
|
):
|
||||||
|
msg = f"Invalid request: Expected exactly one of [AliasTarget, all of [TTL, and ResourceRecords], or TrafficPolicyInstanceId], but found none in Change with [Action=DELETE, Name={value['ResourceRecordSet']['Name']}, Type={value['ResourceRecordSet']['Type']}, SetIdentifier={value['ResourceRecordSet'].get('SetIdentifier', 'null')}]"
|
||||||
|
raise InvalidInput(msg)
|
||||||
|
|
||||||
for value in change_list:
|
for value in change_list:
|
||||||
original_change = copy.deepcopy(value)
|
original_change = copy.deepcopy(value)
|
||||||
action = value["Action"]
|
action = value["Action"]
|
||||||
|
58
tests/test_route53/test_change_set_model.py
Normal file
58
tests/test_route53/test_change_set_model.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import copy
|
||||||
|
import sure # noqa # pylint: disable=unused-import
|
||||||
|
from moto.route53.models import ChangeList
|
||||||
|
|
||||||
|
|
||||||
|
def test_last_dot_in_name_is_ignored():
|
||||||
|
change1 = {
|
||||||
|
"Action": "CREATE",
|
||||||
|
"ResourceRecordSet": {
|
||||||
|
"Name": "test.without.dot",
|
||||||
|
"Type": "CNAME",
|
||||||
|
"TTL": 600,
|
||||||
|
"ResourceRecords": [{"Value": "0.0.0.0"}],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
change1_alt = copy.deepcopy(change1)
|
||||||
|
change1_alt["ResourceRecordSet"]["Name"] = "test.without.dot."
|
||||||
|
|
||||||
|
change2 = copy.deepcopy(change1)
|
||||||
|
change2["ResourceRecordSet"]["Name"] = "test.with.dot."
|
||||||
|
change2_alt = copy.deepcopy(change1)
|
||||||
|
change2_alt["ResourceRecordSet"]["Name"] = "test.with.dot"
|
||||||
|
|
||||||
|
change_list = ChangeList()
|
||||||
|
change_list.append(change1)
|
||||||
|
change_list.append(change2)
|
||||||
|
|
||||||
|
change_list.should.contain(change1)
|
||||||
|
change_list.should.contain(change1_alt)
|
||||||
|
change_list.should.contain(change2)
|
||||||
|
change_list.should.contain(change2_alt)
|
||||||
|
|
||||||
|
|
||||||
|
def test_last_dot_is_not_stored():
|
||||||
|
change1 = {
|
||||||
|
"Action": "CREATE",
|
||||||
|
"ResourceRecordSet": {
|
||||||
|
"Name": "test.google.com.",
|
||||||
|
"Type": "CNAME",
|
||||||
|
"TTL": 600,
|
||||||
|
"ResourceRecords": [{"Value": "0.0.0.0"}],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
change_list = ChangeList()
|
||||||
|
change_list.append(change1)
|
||||||
|
|
||||||
|
change_list[0]["ResourceRecordSet"]["Name"].should.equal("test.google.com")
|
||||||
|
|
||||||
|
|
||||||
|
def test_optional_fields():
|
||||||
|
change = {
|
||||||
|
"Action": "UPSERT",
|
||||||
|
"ResourceRecordSet": {"Name": "test.google.com.", "Type": "CNAME"},
|
||||||
|
}
|
||||||
|
change_list = ChangeList()
|
||||||
|
change_list.append(change)
|
||||||
|
|
||||||
|
change_list.should.equal([change])
|
@ -308,6 +308,7 @@ def test_deleting_weighted_route():
|
|||||||
"Name": "cname.testdns.aws.com",
|
"Name": "cname.testdns.aws.com",
|
||||||
"Type": "CNAME",
|
"Type": "CNAME",
|
||||||
"SetIdentifier": "success-test-foo",
|
"SetIdentifier": "success-test-foo",
|
||||||
|
"Weight": 50,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -374,6 +375,8 @@ def test_deleting_latency_route():
|
|||||||
"Name": "cname.testdns.aws.com",
|
"Name": "cname.testdns.aws.com",
|
||||||
"Type": "CNAME",
|
"Type": "CNAME",
|
||||||
"SetIdentifier": "success-test-foo",
|
"SetIdentifier": "success-test-foo",
|
||||||
|
"Region": "us-west-2",
|
||||||
|
"ResourceRecords": [{"Value": "example.com"}],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -708,29 +711,18 @@ def test_change_resource_record_sets_crud_valid():
|
|||||||
)
|
)
|
||||||
cname_alias_record_detail.should_not.contain("ResourceRecords")
|
cname_alias_record_detail.should_not.contain("ResourceRecords")
|
||||||
|
|
||||||
# Delete record with wrong type.
|
|
||||||
delete_payload = {
|
|
||||||
"Comment": "delete prod.redis.db",
|
|
||||||
"Changes": [
|
|
||||||
{
|
|
||||||
"Action": "DELETE",
|
|
||||||
"ResourceRecordSet": {"Name": "prod.redis.db", "Type": "CNAME"},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
conn.change_resource_record_sets(
|
|
||||||
HostedZoneId=hosted_zone_id, ChangeBatch=delete_payload
|
|
||||||
)
|
|
||||||
response = conn.list_resource_record_sets(HostedZoneId=hosted_zone_id)
|
|
||||||
len(response["ResourceRecordSets"]).should.equal(2)
|
|
||||||
|
|
||||||
# Delete record.
|
# Delete record.
|
||||||
delete_payload = {
|
delete_payload = {
|
||||||
"Comment": "delete prod.redis.db",
|
"Comment": "delete prod.redis.db",
|
||||||
"Changes": [
|
"Changes": [
|
||||||
{
|
{
|
||||||
"Action": "DELETE",
|
"Action": "DELETE",
|
||||||
"ResourceRecordSet": {"Name": "prod.redis.db", "Type": "A"},
|
"ResourceRecordSet": {
|
||||||
|
"Name": "prod.redis.db.",
|
||||||
|
"Type": "A",
|
||||||
|
"TTL": 60,
|
||||||
|
"ResourceRecords": [{"Value": "192.168.1.1"}],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@ -818,7 +810,12 @@ def test_change_resource_record_sets_crud_valid_with_special_xml_chars():
|
|||||||
"Changes": [
|
"Changes": [
|
||||||
{
|
{
|
||||||
"Action": "DELETE",
|
"Action": "DELETE",
|
||||||
"ResourceRecordSet": {"Name": "prod.redis.db", "Type": "TXT"},
|
"ResourceRecordSet": {
|
||||||
|
"Name": "prod.redis.db.",
|
||||||
|
"Type": "TXT",
|
||||||
|
"TTL": 10,
|
||||||
|
"ResourceRecords": [{"Value": "SomeInitialValue"}],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@ -829,6 +826,57 @@ def test_change_resource_record_sets_crud_valid_with_special_xml_chars():
|
|||||||
len(response["ResourceRecordSets"]).should.equal(1)
|
len(response["ResourceRecordSets"]).should.equal(1)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_route53
|
||||||
|
def test_change_resource_record_set__delete_should_match_create():
|
||||||
|
# To delete a resource record set, you must specify all the same values that you specified when you created it.
|
||||||
|
client = boto3.client("route53", region_name="us-east-1")
|
||||||
|
name = "example.com"
|
||||||
|
hosted_zone_id = client.create_hosted_zone(Name=name, CallerReference=name)[
|
||||||
|
"HostedZone"
|
||||||
|
]["Id"]
|
||||||
|
|
||||||
|
create_call = client.change_resource_record_sets(
|
||||||
|
HostedZoneId=hosted_zone_id,
|
||||||
|
ChangeBatch={
|
||||||
|
"Changes": [
|
||||||
|
{
|
||||||
|
"Action": "CREATE",
|
||||||
|
"ResourceRecordSet": {
|
||||||
|
"Name": name,
|
||||||
|
"Type": "A",
|
||||||
|
"TTL": 300,
|
||||||
|
"ResourceRecords": [{"Value": "192.168.0.1"}],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
waiter = client.get_waiter("resource_record_sets_changed")
|
||||||
|
waiter.wait(Id=create_call["ChangeInfo"]["Id"])
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.change_resource_record_sets(
|
||||||
|
HostedZoneId=hosted_zone_id,
|
||||||
|
ChangeBatch={
|
||||||
|
"Changes": [
|
||||||
|
{
|
||||||
|
"Action": "DELETE",
|
||||||
|
"ResourceRecordSet": {
|
||||||
|
"Name": name,
|
||||||
|
"Type": "A"
|
||||||
|
# Missing TTL and ResourceRecords
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidInput")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
"Invalid request: Expected exactly one of [AliasTarget, all of [TTL, and ResourceRecords], or TrafficPolicyInstanceId], but found none in Change with [Action=DELETE, Name=example.com, Type=A, SetIdentifier=null]"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock_route53
|
@mock_route53
|
||||||
def test_change_weighted_resource_record_sets():
|
def test_change_weighted_resource_record_sets():
|
||||||
conn = boto3.client("route53", region_name="us-east-2")
|
conn = boto3.client("route53", region_name="us-east-2")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user