diff options
author | pommicket <pommicket@gmail.com> | 2025-08-31 16:04:54 -0400 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2025-08-31 16:04:54 -0400 |
commit | 50bea1aa2065b8d26347045338c74e424c0f5099 (patch) | |
tree | 6c0a099c666ea4e46542aaaeb3240c85972a05d7 | |
parent | 29bf041f65bc89f8edba1bc7567a9f95e4f941bb (diff) |
Finish up AWS support
-rwxr-xr-x | simpleddns.py | 87 |
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 '' |