summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xsimpleddns.py87
1 files changed, 79 insertions, 8 deletions
diff --git a/simpleddns.py b/simpleddns.py
index cf2862a..5f9dfe4 100755
--- a/simpleddns.py
+++ b/simpleddns.py
@@ -415,7 +415,7 @@ class Route53Domain(Domain):
return self._client
def _get_hosted_zones(self) -> Optional[list[dict[str, Any]]]:
- '''Get list of hosted zones from AWS'''
+ '''Get list of hosted zones from AWS. Returns None on failure.'''
import botocore
try:
route53 = self._get_client()
@@ -432,8 +432,9 @@ class Route53Domain(Domain):
warn(f'Error listing AWS hosted zones: {e}')
return None
- def _list_record_sets(self) -> list[dict[str, Any]]:
- '''Get A and AAAA record sets for this domain.'''
+ def _list_record_sets(self) -> Optional[list[dict[str, Any]]]:
+ '''Get A and AAAA record sets for this domain. Returns None on failure.'''
+ import botocore
route53 = self._get_client()
record_sets: list[dict[str, Any]] = []
start_record_name = self.full_domain + '.'
@@ -448,7 +449,11 @@ class Route53Domain(Domain):
}
if start_record_identifier is not None:
options['StartRecordIdentifier'] = start_record_identifier
- results = route53.list_resource_record_sets(**options)
+ try:
+ results = route53.list_resource_record_sets(**options)
+ except botocore.exceptions.BotoCoreError as e:
+ self._error(f'Error listing record sets: {e}')
+ return None
record_sets.extend(filter(
lambda record: record['Name'] == self.full_domain + '.' and \
record['Type'] in ['A', 'AAAA'],
@@ -462,8 +467,16 @@ class Route53Domain(Domain):
start_record_name = ''
sleep(self._request_delay())
return record_sets
-
+ def _print_record_set_changes(self, changes: list[dict[str, Any]]) -> None:
+ for change in changes:
+ action = change['Action']
+ record_set = change['ResourceRecordSet']
+ domain = record_set['Name'].rstrip('.')
+ kind = record_set['Type']
+ values = [record['Value'] for record in record_set['ResourceRecords']]
+ self._info(f'{action} {kind} record for {domain}: {values}')
def update(self, ips: list[str]) -> bool:
+ import botocore
self._had_error = False
if not self._id:
zones = self._get_hosted_zones()
@@ -476,9 +489,67 @@ class Route53Domain(Domain):
if not self._id:
self._error(f'Domain {self.root_domain} not found in Route 53. Are you sure it is set up there?')
return False
- print('ID:',self._id)
- print(self._list_record_sets())
- return True
+ record_sets = self._list_record_sets()
+ if record_sets is None:
+ return False
+ changes = []
+ ips_remaining = set(ips)
+ for record_set in record_sets:
+ values = {record['Value'] for record in record_set['ResourceRecords']}
+ if record_set['TTL'] == self._ttl() and values.issubset(ips_remaining):
+ ips_remaining -= values
+ else:
+ # remove this record set
+ changes.append({'Action': 'DELETE', 'ResourceRecordSet': record_set})
+ ipv4s = [ip for ip in ips_remaining if ':' not in ip]
+ ipv6s = [ip for ip in ips_remaining if ':' in ip]
+ # See https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html
+ if len(ipv4s) > 100 or len(ipv6s) > 100:
+ fatal_error(f'Too many IP addresses for domain {self.full_domain} ({len(ipv4s) + len(ipv6s)})')
+ if ipv4s:
+ # Create A record set
+ changes.append({
+ 'Action': 'CREATE',
+ 'ResourceRecordSet': {
+ 'Name': self.full_domain + '.',
+ 'Type': 'A',
+ 'TTL': self._ttl(),
+ 'ResourceRecords': [{'Value': ip} for ip in ipv4s],
+ }
+ })
+ if ipv6s:
+ # Create AAAA record set
+ changes.append({
+ 'Action': 'CREATE',
+ 'ResourceRecordSet': {
+ 'Name': self.full_domain + '.',
+ 'Type': 'AAAA',
+ 'TTL': self._ttl(),
+ 'ResourceRecords': [{'Value': ip} for ip in ipv6s],
+ }
+ })
+ if self.settings.dry_run:
+ self._info('Would make the following changes to DNS record sets:')
+ self._print_record_set_changes(changes)
+ else:
+ # Actually perform the changes
+ route53 = self._get_client()
+ try:
+ response = route53.change_resource_record_sets(
+ HostedZoneId=self._id,
+ ChangeBatch={
+ 'Comment': 'simpleddns update',
+ 'Changes': changes
+ }
+ )
+ except botocore.exceptions.BotoCoreError as e:
+ self._error(f'Error making changes to DNS record sets: {e}')
+ response = None
+ if isinstance(response, dict):
+ self._info('Made the following changes to DNS record sets:')
+ self._print_record_set_changes(changes)
+ return not self._had_error
+
def validate_specifics(self) -> str:
# no provided-specific options for Route 53
return ''