Skip to content

Instantly share code, notes, and snippets.

@JohnLBevan
Created February 7, 2024 12:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JohnLBevan/19c1fee89fc13660452381a615a9b17f to your computer and use it in GitHub Desktop.
Save JohnLBevan/19c1fee89fc13660452381a615a9b17f to your computer and use it in GitHub Desktop.
AWS Route53 Zone Migration
<#
.SYNOPSIS
Used to help migrate R53 zones by converting the JSON obtained by
extracting all record sets from a zone to the JSON required to upload
these recordsets to another zone.
.DESCRIPTION
Covers those tasks described in step 4 of [Migrating an AWS Zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-migrating.html#hosted-zones-migrating-create-file)
i.e. to convert the output of `aws route53 list-resource-record-sets --hosted-zone-id <hosted-zone-id>`
... to the input of `aws route53 change-resource-record-sets --hosted-zone-id id-of-new-hosted-zone --change-batch file://path-to-file-that-contains-records`
.PARAMETER Path
Path to the JSON file containing the ResourceRecordSets output by
`aws route53 list-resource-record-sets --hosted-zone-id <hosted-zone-id>`
.PARAMETER InputObject
The JSON ResourceRecordSets data, converted to a PSCustomObject.
.EXAMPLE
```
$sourceHostedZoneId = 'XXXXXX'
$destinationHostedZoneId = 'YYYYYY'
$sourceJsonFile = 'c:\temp\sourceZone.json'
$destinationJsonFile = 'c:\temp\destinationZone.json'
aws route53 list-resource-record-sets --hosted-zone-id $sourceHostedZoneId > $sourceJsonFile
ConvertTo-AwsR53RecordSetChange -Path $sourceJsonFile -Destination $destinationJsonFile -SourceZone $sourceHostedZoneId -DestinationZone $destinationHostedZoneId
aws route53 change-resource-record-sets --hosted-zone-id $destinationHostedZoneId --change-batch $destinationJsonFile
```
.NOTES
This doesn't chunk the file into sets of 1000 records / 32Kb (step 5
of [Migrating an AWS Zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-migrating.html#hosted-zones-migrating-create-file)) as this was YAGNI for my requirements.
#>
function ConvertTo-AwsR53RecordSetChange {
[CmdletBinding(DefaultParameterSetName = 'ByPath')]
Param (
[Parameter(ParameterSetName='ByPath', Mandatory)]
[Parameter(ParameterSetName='ByPathPassthru', Mandatory)]
[string]$Path
,
[Parameter(ParameterSetName='ByJsonObject', Mandatory)]
[PSCustomObject]$InputObject
,
[Parameter(ParameterSetName='ByPath', Mandatory)]
[Parameter(ParameterSetName='ByJsonObject', Mandatory)]
[string]$Destination
,
[Parameter(Mandatory)]
[string]$SourceZone
,
[Parameter(Mandatory)]
[string]$DestinationZone
,
[Parameter()]
[string]$Comment = ''
,
[Parameter(ParameterSetName='ByPathPassthru', Mandatory)]
[Parameter(ParameterSetName='ByJsonObjectPassthru', Mandatory)]
[Switch]$Passthru
)
if ($PSCmdlet.ParameterSetName -eq 'ByPath') {
$InputObject = Get-Content -Path $Path -Raw | ConvertFrom-Json
}
$DomainRoot = $InputObject.ResourceRecordSets.Name | Sort-Object -Property @{E={$_.Length}} | Select-Object -First 1
[PSCustomObject[]]$records = $InputObject.ResourceRecordSets |
Skip-AwsR53RootSoaAndNsRecords -DomainRoot $DomainRoot |
Convert-AwsR53AliasZoneId -OldZoneId $SourceZone -NewZoneId $DestinationZone |
New-AwsR53ChangeResourceRecord
$result = [PSCustomObject]@{
Comment = $Comment
Changes = $records
}
if ($Passthru.IsPresent) {
$result
} else {
[System.IO.File]::WriteAllLines($Destination,(
$result | ConvertTo-Json -Depth 10
), [System.Text.Encoding]::UTF8)
}
}
<#
.SYNOPSIS
Ensures we don't copy the SOA and NS records of the zone being copied; as these will be created by AWS when the target zone is created.
However, we need to ensure that if we've got child NS records (e.g. to point to a different nameserver for managing a subdomain) those
records are included.
.PARAMETER DomainRoot
The domain associated with our zone. Note: this entry should be in the same format as the `Name` value in the AWS recordsets; i.e. should
include a terminating `.`; e.g. `example.com.`.
.PARAMETER InputObject
This should accept individual ResourceRecordSet elements.
#>
Function Skip-AwsR53RootSoaAndNsRecords {
[CmdletBinding()]
Param (
[Parameter(Mandatory)]
[string]$DomainRoot
,
[Parameter(ValueFromPipeline, Mandatory)]
[PSCustomObject]$InputObject
)
Process {
if (!(
($InputObject.Type -in ('SOA', 'NS')) -and
($InputObject.Name -eq $DomainRoot)
)) {
$InputObject
}
}
}
<#
.SYNOPSIS
For any alias records, checks if the alias had been referring to a record in the old hosted zone, and where that's the case, update it
to target our new zone's ID. All other zone references should not be touched.
.PARAMETER OldZoneId
The AWS ID of the old hosted zone (i.e. the value provided to parameter `hosted-zone-id` in `aws route53 list-resource-record-sets --hosted-zone-id ...`)
.PARAMETER NewZoneId
The AWS ID of the new hosted zone (i.e. the value provided to parameter `hosted-zone-id` in `aws route53 change-resource-record-sets --hosted-zone-id ... --change-batch ...`)
.PARAMETER InputObject
This should accept individual ResourceRecordSet elements.
#>
Function Convert-AwsR53AliasZoneId {
Param (
[Parameter(Mandatory)]
[string]$OldZoneId
,
[Parameter(Mandatory)]
[string]$NewZoneId
,
[Parameter(ValueFromPipeline, Mandatory)]
[PSCustomObject]$InputObject
)
Process {
if ("$($InputObject.AliasTarget.HostedZoneId)" -eq $OldZoneId) {
$InputObject.AliasTarget.HostedZoneId = $NewZoneId
}
$InputObject
}
}
<#
.SYNOPSIS
Converts a ResourceRecordSet element as output by `list-resource-record-sets` to the format required
to be used in `change-resource-record-sets`.
.PARAMETER Action
What action should be given for this record; e.g. `CREATE`, `DELETE, or `UPSERT`
See https://docs.aws.amazon.com/cli/latest/reference/route53/change-resource-record-sets.html for more
.PARAMETER InputObject
This should accept individual ResourceRecordSet elements.
#>
Function New-AwsR53ChangeResourceRecord {
[CmdletBinding()]
Param (
[Parameter()]
[string]$Action = 'UPSERT'
,
[Parameter(ValueFromPipeline, Mandatory)]
[PSCustomObject]$InputObject
)
Process {
([PSCustomObject]@{
Action = $Action
ResourceRecordSet = $InputObject
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment