Skip to content

Instantly share code, notes, and snippets.

@dploeger
Last active September 29, 2021 10:14
Show Gist options
  • Save dploeger/a98e0d2489c9affdc3d5379a5ec5e08b to your computer and use it in GitHub Desktop.
Save dploeger/a98e0d2489c9affdc3d5379a5ec5e08b to your computer and use it in GitHub Desktop.
AWS to Azure Terraform script
resource "azurerm_dns_zone" "{{ resourceName zone }}" {
name = "{{ zone }}"
resource_group_name = {{ resourceGroupVar }}
}
{{#each records}}
{{#with this}}
{{#if (isRecordType "A" type)}}
resource "azurerm_dns_a_record" "{{ resourceName name }}_{{ resourceName ../zone }}" {
name = "{{ name }}"
zone_name = azurerm_dns_zone.{{ resourceName ../zone }}.name
resource_group_name = {{ ../resourceGroupVar }}
ttl = {{ ttl }}
records = {{{ literalArray records }}}
}
{{/if}}
{{#if (isRecordType "AAAA" type)}}
resource "azurerm_dns_aaaa_record" "{{ resourceName name }}_{{ resourceName ../zone }}" {
name = "{{ name }}"
zone_name = azurerm_dns_zone.{{ resourceName ../zone }}.name
resource_group_name = {{ ../resourceGroupVar }}
ttl = {{ ttl }}
records = {{{ literalArray records }}}
}
{{/if}}
{{#if (isRecordType "CAA" type)}}
resource "azurerm_dns_caa_record" "{{ resourceName name }}_{{ resourceName ../zone }}" {
name = "{{ name }}"
zone_name = azurerm_dns_zone.{{ resourceName ../zone }}.name
resource_group_name = {{ ../resourceGroupVar }}
ttl = {{ ttl }}
{{#each records}}
{{#with this}}
record {
flags = {{ flags }}
tag = {{ tag }}
value = {{ value }}
}
{{/with}}
{{/each}}
}
{{/if}}
{{#if (isRecordType "CNAME" type)}}
resource "azurerm_dns_cname_record" "{{ resourceName name }}_{{ resourceName ../zone }}" {
name = "{{ name }}"
zone_name = azurerm_dns_zone.{{ resourceName ../zone }}.name
resource_group_name = {{ ../resourceGroupVar }}
ttl = {{ ttl }}
record = "{{{ records.[0] }}}"
}
{{/if}}
{{#if (isRecordType "MX" type)}}
resource "azurerm_dns_mx_record" "{{ resourceName name }}_{{ resourceName ../zone }}" {
name = "{{ name }}"
zone_name = azurerm_dns_zone.{{ resourceName ../zone }}.name
resource_group_name = {{ ../resourceGroupVar }}
ttl = {{ ttl }}
{{#each records}}
{{#with this}}
record {
preference = {{ preference }}
exchange = "{{ exchange }}"
}
{{/with}}
{{/each}}
}
{{/if}}
{{#if (isRecordType "SRV" type)}}
resource "azurerm_dns_srv_record" "{{ resourceName name }}_{{ resourceName ../zone }}" {
name = "{{ name }}"
zone_name = azurerm_dns_zone.{{ resourceName ../zone }}.name
resource_group_name = {{ ../resourceGroupVar }}
ttl = {{ ttl }}
{{#each records}}
{{#with this}}
record {
priority = {{ priority }}
weight = {{ weight }}
port = {{ port }}
target = {{ target }}
}
{{/with}}
{{/each}}
}
{{/if}}
{{#if (isRecordType "TXT" type)}}
resource "azurerm_dns_txt_record" "{{ resourceName name }}_{{ resourceName ../zone }}" {
name = "{{ name }}"
zone_name = azurerm_dns_zone.{{ resourceName ../zone }}.name
resource_group_name = {{ ../resourceGroupVar }}
ttl = {{ ttl }}
{{#each records}}
record {
value = {{{ this }}}
}
{{/each}}
}
{{/if}}
{{#if (isRecordType "NS" type)}}
resource "azurerm_dns_ns_record" "{{ resourceName name }}_{{ resourceName ../zone }}" {
name = "{{ name }}"
zone_name = azurerm_dns_zone.{{ resourceName ../zone }}.name
resource_group_name = {{ ../resourceGroupVar }}
ttl = {{ ttl }}
records = {{{ literalArray records }}}
}
{{/if}}
{{/with}}
{{/each}}
const Handlebars = require("handlebars");
const process = require('process')
const fs = require('fs')
const dns = require('dns').promises
const DEFAULT_TTL = 300
const IGNORED_ZONE_TYPES = ['SOA', 'NS']
const args = process.argv.slice(2)
if (args.length !== 3) {
console.log(`
Usage:
route53JsonToTerraform.js <zone name> <JSON zone file> <resourcegroup var name>
`)
process.exit(1)
}
let context = {
zone: args[0],
resourceGroupVar: args[2],
records: []
}
const zoneFile = args[1]
const zones = JSON.parse(fs.readFileSync(zoneFile))
Handlebars.registerHelper('literalArray', (value) => {
return JSON.stringify(value)
})
Handlebars.registerHelper('isRecordType', (type, value) => {
return value === type
})
Handlebars.registerHelper('resourceName', (value) => {
let rewrittenValue = ''
if (value) {
if (value === '@') {
value = context.zone
}
rewrittenValue = value.replace(new RegExp('\\.', 'g'), '_')
}
if (!rewrittenValue.match(/^[a-z_]/)) {
rewrittenValue=`_${rewrittenValue}`
}
return rewrittenValue
})
async function generateRecords(context, zones) {
const records = []
for (const zone of zones.ResourceRecordSets) {
const record = {
name: zone.Name.replace(`.${context.zone}`, '').replace(/\.$/, ''),
ttl: 'TTL' in zone ? zone.TTL : DEFAULT_TTL,
records: []
}
if (record.name === context.zone) {
record.name = '@'
}
if (zone.Type === 'A') {
record.type = 'A'
if (record.name === context.zone) {
record.name = '@'
}
if ('AliasTarget' in zone) {
try {
const resolved = await dns.lookup(zone.AliasTarget.DNSName)
record.records = [resolved.address]
} catch (e) {
console.error(`Error resolving ${zone.AliasTarget.DNSName} for ${zone.Name}. Continuing: ${e}`)
continue
}
} else if ('ResourceRecords' in zone) {
record.records = zone.ResourceRecords.map(resourceRecord => resourceRecord.Value)
}
} else if (zone.Type === 'CNAME') {
record.type = 'CNAME'
record.records = zone.ResourceRecords.map(resourceRecord => resourceRecord.Value)
} else if (zone.Type === 'MX') {
record.type = 'MX'
record.records = zone.ResourceRecords.map(resourceRecord => {
let preference, exchange
[preference, exchange] = resourceRecord.Value.split(/ /)
return {
preference: preference,
exchange: exchange
}
})
} else if (zone.Type === 'NS' && zone.Name !== `${context.zone}.` ) {
record.type = 'NS'
record.records = record.records = zone.ResourceRecords.map(resourceRecord => resourceRecord.Value)
} else if (zone.Type === 'TXT' || zone.Type === 'SPF') {
record.type = 'TXT'
record.records = zone.ResourceRecords.map(resourceRecord => resourceRecord.Value)
} else if (!zone.Type in IGNORED_ZONE_TYPES) {
console.error(`Zone type ${zone.Type} not implemented yet: ${JSON.stringify(zone)}`)
continue
} else {
console.error(`Ignoring zone record ${JSON.stringify(zone)}`)
continue
}
records.push(record)
}
return records
}
async function main(context, zones) {
context.records = await generateRecords(context, zones)
const template = Handlebars.compile(fs.readFileSync("azure.tf.handlebars", {encoding: 'UTF-8'}))
console.log(template(context))
}
main(context, zones)
@dploeger
Copy link
Author

dploeger commented Aug 9, 2021

This converts JSON output from aws route53 list-record-sets to Terraform resources managing an Azure domain.

Currently supported record types: A, CNAME, MX and TXT (or SPF, which basically is the same)

Fetch all hosted zones using awscli:

for HOSTEDZONE in $(aws route53 list-hosted-zones | jq .HostedZones[].Id -r)
do 
  aws route53 list-resource-record-sets --hosted-zone-id $HOSTEDZONE > $(basename $HOSTEDZONE).json
done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment