Last active
December 13, 2022 23:12
-
-
Save thom-vend/231682561e4440b77ec4b2187a73b086 to your computer and use it in GitHub Desktop.
Script to import in terraform route53 dns zone and record when terraformer isn't working as expected
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# WARNING: You probably want to look at https://github.com/GoogleCloudPlatform/terraformer | |
# But was't working great for me so I end-up searching for a more custom solution | |
# Script isn't perfect but as it was a one shot operation.... | |
# Script work on my machine TM, using some gnu core utils like gsed, gwc etc | |
# refactor of https://www.daniloaz.com/en/how-to-quickly-import-all-records-from-a-route53-dns-zone-into-terraform/ | |
set -x | |
set -euo pipefail | |
# This script retrieves all DNS records from AWS Route53 DNS zone and imports all of them to Terraform | |
# required positional args | |
if [[ -z "${1:-}" ]];then | |
echo "usage: $0 zone-name" | |
exit 1 | |
fi | |
zone_name=$1 | |
shift | |
# Options | |
run_import=0 | |
clean_import=0 | |
much_match=0 | |
search_file="" | |
while [[ $# -gt 0 ]]; | |
do | |
case "$1" in | |
--import) | |
run_import=1 | |
shift | |
;; | |
--clean) | |
clean_import=1 | |
shift | |
;; | |
--grep-zone) | |
# Check if the record is present in this file | |
if ! [[ -f "$2" ]];then | |
echo "Wrong usage of --grep option ; usage --grep-zone /path/to/zone/file" | |
exit 1 | |
fi | |
search_file=$2 | |
much_match=1 | |
shift 2 | |
esac | |
done | |
# Get zone slug from zone name | |
zone_slug=$(echo "${zone_name}" | tr '.' '-') | |
zone_slug=${zone_slug%-} | |
########################################################### | |
# Helpers | |
########################################################### | |
function is_record_targeted { | |
# 0=yes ; 1=no | |
local name=${1%.} | |
local type=$2 | |
# route53 allowed type: | |
# A, AAAA, CAA, CNAME, DS, MX, NAPTR, NS, PTR, SOA, SPF, SRV and TXT. | |
if [[ $much_match -eq 0 ]] || [[ ! "$type" =~ ^A|AAAA|CAA|CNAME$ ]];then | |
return 0 # skip | |
fi | |
if [[ "$name" == "${zone_name%.}" ]];then | |
name="@" | |
fi | |
if grep "^${name}" "$search_file" || \ | |
grep -q "^${name/.${zone_name%.}/}" "$search_file" | |
then | |
return 0 | |
fi | |
return 1 | |
} | |
function run_terraform_import { | |
tf_name=$1 | |
aws_id=$2 | |
log_id="${tf_name}_${aws_id}" | |
if ! grep -qF "${log_id}" .imported.log && [[ $run_import -eq 1 ]];then | |
if [[ $clean_import -eq 1 ]];then | |
terraform state rm "$tf_name" ||true | |
fi | |
terraform import "$tf_name" "$aws_id" | |
echo "${log_id}" >> .imported.log | |
fi | |
} | |
function generate_code_record { | |
dns_record=$1 | |
is_alias=$2 | |
zone_slug=$3 | |
name="$(echo "${dns_record}" | jq -r '.Name'|gsed -r 's/\\052/\*/g')" | |
type="$(echo "${dns_record}" | jq -r '.Type')" | |
ttl="$(echo "${dns_record}" | jq -r '.TTL')" | |
setidentifier="$(echo "${dns_record}" | jq -r '.SetIdentifier')" | |
weight="$(echo "${dns_record}" | jq -r '.Weight')" | |
setidentifier_import="_$setidentifier" | |
if [[ "$setidentifier" == "null" ]];then | |
setidentifier="" | |
setidentifier_import="" | |
fi | |
name_slug="$(echo "${type}-${name}-${zone_slug}${setidentifier_import}" | gsed -E -e 's/[\._\ ]+/-/g' -e 's/(^-|-$)//g' -e 's/\*/wildcard/g')" | |
if [[ "$ttl" != "null" ]];then | |
ttl='"'"$ttl"'"' | |
fi | |
if ! is_record_targeted "$name" "$type"; then | |
echo "DEBUG: record skipped: $name $type" | |
return 0 # skip | |
fi | |
cat << EOF >> "dns-zone-${zone_slug}.tf" | |
resource "aws_route53_record" "${name_slug}" { | |
zone_id = aws_route53_zone.${zone_slug}.zone_id | |
name = "${name}" | |
type = "${type}" | |
ttl = ${ttl} | |
EOF | |
if [[ -n "$setidentifier" ]];then | |
echo " set_identifier = \"${setidentifier}\"" >> "dns-zone-${zone_slug}.tf" | |
fi | |
if [[ "$weight" != "null" ]];then | |
cat <<EOF >> "dns-zone-${zone_slug}.tf" | |
weighted_routing_policy { | |
weight = $weight | |
} | |
EOF | |
fi | |
if [[ "$is_alias" -eq 1 ]];then | |
alias_name="$(echo "${dns_record}" | jq -cr '.AliasTarget' | jq -r '.DNSName')" | |
alias_hosted_zone_id="$(echo "${dns_record}" | jq -cr '.AliasTarget' | jq -r '.HostedZoneId')" | |
evaluate_target_health="$(echo "${dns_record}" | jq -cr '.AliasTarget' | jq -r '.EvaluateTargetHealth')" | |
cat <<EOF >> "dns-zone-${zone_slug}.tf" | |
alias { | |
name = "${alias_name}" | |
zone_id = "${alias_hosted_zone_id}" | |
evaluate_target_health = ${evaluate_target_health} | |
} | |
EOF | |
else | |
# Handle TXT record magic quote from TF | |
echo ' records = [' >> "dns-zone-${zone_slug}.tf" | |
if [[ "$type" =~ ^TXT$ ]]; then | |
# handle quotes for terraform | |
# terraform magically quote TXT record so we don't have to | |
# But if we TXT container multiple section with double quote then we have to: | |
# add all the required quote and escape them for the TF code to be valid. | |
records="$(echo "${dns_record}" | jq -c -r '.ResourceRecords|.[].Value')" | |
while read -r rec | |
do | |
if [[ "$(tr -dc '"' <<<"$rec"|gwc -c)" -gt 2 ]];then | |
# sed: delete starting quote and end quote, escape all others | |
echo "\"$(gsed -r -e 's/^"//g' -e 's/"$//g' -e 's/"/\\"/g' <<<"${rec}")\"," >> "dns-zone-${zone_slug}.tf" | |
else | |
echo "${rec}," >> "dns-zone-${zone_slug}.tf" | |
fi | |
done <<<"${records}" | |
else | |
echo "${dns_record}" | jq -c '.ResourceRecords|.[].Value' |gsed -r 's/$/,/g' >> "dns-zone-${zone_slug}.tf" | |
fi | |
echo " ]" >> "dns-zone-${zone_slug}.tf" | |
fi | |
cat <<EOF >> "dns-zone-${zone_slug}.tf" | |
lifecycle { | |
ignore_changes = [weighted_routing_policy] | |
} | |
} | |
EOF | |
run_terraform_import "aws_route53_record.${name_slug}" "${zone_id}_${name}_${type}${setidentifier_import}" | |
} | |
###########################################################################3 | |
# Get DNS zone current data from AWS | |
zone="$(aws route53 list-hosted-zones | jq '.HostedZones[] | select (.Name=="'${zone_name}'")')" | |
if [[ -z "$zone" ]];then | |
echo "Error: Zone not found" | |
exit 1 | |
fi | |
zone_id="$(jq -r '.Id'<<<"$zone")" | |
if ! [[ "$zone_id" =~ ^/hostedzone/ ]];then | |
echo "Error: invalid zone id detected" | |
exit 1 | |
fi | |
zone_id=${zone_id//\/hostedzone\/} | |
# Another method to get DNS zone data searching by zone name instead of zone ID | |
zone_comment="$(jq -r '.Comment' <<<"$zone")" | |
if [[ "${zone_comment}" == 'null' ]];then | |
zone_comment="${zone_name} zone" | |
fi | |
# Write aws_route53_zone resource to terraform file | |
cat << EOF > "dns-zone-${zone_slug}.tf" | |
resource "aws_route53_zone" "${zone_slug}" { | |
name = "${zone_name}" | |
comment = "${zone_comment}" | |
tags = { | |
"Cost Center" = "Platform" | |
} | |
} | |
EOF | |
run_terraform_import "aws_route53_zone.${zone_slug}" "${zone_id}" | |
# Import DNS zone and records from file to terraform | |
aws_cli_output=$(mktemp) | |
aws route53 list-resource-record-sets --hosted-zone-id "${zone_id}" |tee "$aws_cli_output" | |
# Retrieve all regular records (not alias) from DNS zone and write them down to terraform file | |
regular_records=$(mktemp) | |
alias_records=$(mktemp) | |
jq -c '.ResourceRecordSets[] | select(has("AliasTarget") | not)' "$aws_cli_output" | tee "$regular_records" | |
jq -c '.ResourceRecordSets[] | select(has("AliasTarget"))' "$aws_cli_output" | tee "$alias_records" | |
while read -r dns_record | |
do | |
generate_code_record "$dns_record" 0 "$zone_slug" | |
done < "$regular_records" | |
while read -r dns_record | |
do | |
generate_code_record "$dns_record" 1 "$zone_slug" | |
done < "$alias_records" | |
terraform fmt -diff=true -recursive | |
echo "done" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment