neurons firing from a keyboard

thoughts about devops, technology, and faster business from a random guy from dallas.

How to empty an AWS Routed53 Hosted Zone with AWS CLI

Reading Time: Approximately 3 minutes.
View this post on GitHub.

So you’re managing an AWS VPC with Terraform or something like that, but some other pesky thing decided to add Route53 records that Terraform doesn’t know about.

As a result, when you try to delete the zone with Terraform, you’re no longer able to because of this:

│ Error: deleting Route53 Hosted Zone (REDACTED): HostedZoneNotEmpty: The specified hosted zone contains non-required resource record sets and so cannot be deleted.
│       status code: 400, request id: REDACTED

Ughh. Now you’ve gotta log into the AWS Console, go to Route53, drill into the Hosted Zone and delete all of those pesky records.

Or do you?

You’re reading my blog. Of course you don’t.

Here’s a one-liner for the AWS CLI that easily takes care of that for you.

Tools Needed

  • The AWS CLI, obviously
  • jq, the Swiss Army Knife of shell-based JSON manipulation. Check out the [docs](https://jqlang.github.io] to learn how to install it.

The One-Liner

id=$(aws route53 list-hosted-zones --query 'HostedZones[?Name==`$YOUR_HOSTED_ZONE_HERE.`].Id' --output text); \
aws route53 change-resource-record-sets --hosted-zone-id "$id" --change-batch "$(aws route53 list-resource-record-sets --hosted-zone-id "$id" | jq -c '{Changes: ([.ResourceRecordSets[]|select(.Type != "SOA" and .Type != "NS")|{Action: "DELETE", ResourceRecordSet: .}])}')"

This will delete ALL non-essential resource records in a zone. Replace $YOUR_HOSTED_ZONE_HERE with the name of the zone to modify. Leave the period.

Not tested with Private Hosted Zones.

Breaking it down

id=$(aws route53 list-hosted-zones --query 'HostedZones[?Name==`$YOUR_HOSTED_ZONE_HERE.`].Id' --output text);

This uses aws route53 list-hosted-zones and a JMESPath query to find your hosted zone in Route53 and sets it to $id so that we can use it throughout our operation.

jq -c '{Changes: ([.ResourceRecordSets[]|select(.Type != "SOA" and .Type != "NS")|{Action: "DELETE", ResourceRecordSet: .}])}

searches for all resource record sets returned by:

aws route53 list-resource-record-sets --hosted-zone-id "$id"

whose types are neither NS or SOA records (the resource record sets required within a Route53 zone), creates a new JSON object for each result that looks like this:

{
    "Action": "DELETE",
    "ResourceRecordSet": {
        "Name": "rrname.$YOUR_HOSTED_ZONE_HERE.",
        "Type": "A",
        "TTL": 60,
        "ResourceRecords": [{
            "Value": "1.2.3.4"
        }]
    }
}

and adds these results to a top-level array object called “Changes”.

The final result of that looks like this:

{
    "Changes": [{
        "Action": "DELETE",
        "ResourceRecordSet": {
            "Name": "rrname.$YOUR_HOSTED_ZONE_HERE.",
            "Type": "A",
            "TTL": 60,
            "ResourceRecords": [{
                "Value": "1.2.3.4"
            }]
        },
        "Changes": ...
    ] 
}

Which jq -c compacts into a single line that’s easier for the shell to work with.

All of that is fed into the --change-batch flag of the command

aws route53 change-resource-record-sets

to indicate that we want to change all of these RRSets in bulk. (AWS Route53 does things this way because there are multiple things you can do to RRsets other than deleting them, and because the CLI tends to be a one-to-one mapping to API calls within AWS, including a delete-resource-record-sets subcommand would break convention.

I hope this helps someone!