From cd00703a1f0494d1c432b472b8bf9bce8803c96d Mon Sep 17 00:00:00 2001 From: Daniel Akulenok Date: Thu, 29 Jan 2026 22:16:38 +0100 Subject: [PATCH] Add default_ttl parameter and improve diff output to zone file format - Add default_ttl parameter for records without explicit TTL values - Change diff format to standard DNS zone file format for better readability - Remove section headers and use standard zone file notation: * - for deleted records * + for added records * Show both old and new for changed records - Fix newline handling in diff output (actual newlines instead of escaped strings) - Align columns for FQDN, TTL, record type, and value - Include TTL in diff output for all record operations --- plugins/modules/nsupdate_zone.py | 97 ++++++++++++++------------------ 1 file changed, 42 insertions(+), 55 deletions(-) diff --git a/plugins/modules/nsupdate_zone.py b/plugins/modules/nsupdate_zone.py index 17217b6..c80471e 100644 --- a/plugins/modules/nsupdate_zone.py +++ b/plugins/modules/nsupdate_zone.py @@ -157,6 +157,12 @@ options: - Prevents invalid records from being sent to the DNS server. type: bool default: true + default_ttl: + description: + - Default TTL (Time To Live) in seconds for records that don't specify a TTL. + - This value is used when a record does not have an explicit O(zones[].records[].ttl) value. + - If not specified, the zone SOA MINIMUM value will be used as the default. + type: int dns_server: description: - Global DNS server to use for all zones that do not specify their own. @@ -381,6 +387,7 @@ class DNSZoneManager: # State self.current_zone = None self.soa_minimum_ttl = 3600 # Default, will be updated from SOA + self.default_ttl = module.params.get('default_ttl') # User-specified default, or None to use SOA minimum def _resolve_server(self) -> list[str]: """Resolve DNS server FQDN to IP addresses.""" @@ -865,69 +872,48 @@ class DNSZoneManager: return result def _format_diff(self, changes: dict) -> dict: - """Format changes as a diff structure for diff mode with human-readable output.""" - diff_content = [] + """Format changes as a diff structure for diff mode using zone file format.""" + diff_lines = [] # Process deletes - records being removed - if changes['deletes']: - diff_content.append("DELETED RECORDS:") - for record in sorted(changes['deletes'], key=lambda r: (r['name'].to_text(), r['type'])): - record_name = record['name'].to_text() - record_type = record['type'] - ttl = record.get('ttl', 'default') - values = sorted(str(v) for v in record['values']) - diff_content.append(f" - {record_name:<50} {record_type:<10} TTL={ttl}") - for value in values: - diff_content.append(f" {value}") - diff_content.append("") + for record in sorted(changes['deletes'], key=lambda r: (r['name'].to_text(), r['type'])): + record_name = record['name'].to_text().rstrip('.') + record_type = record['type'] + ttl = record.get('ttl', self.soa_minimum_ttl) + values = sorted(str(v) for v in record['values']) + for value in values: + diff_lines.append(f"-{record_name:<35} {ttl:<10} {record_type:<10} {value}") # Process adds - records being added - if changes['adds']: - diff_content.append("ADDED RECORDS:") - for record in sorted(changes['adds'], key=lambda r: (r['name'].to_text(), r['type'])): - record_name = record['name'].to_text() - record_type = record['type'] - ttl = record.get('ttl', 'default') - values = sorted(str(v) for v in record['values']) - diff_content.append(f" + {record_name:<50} {record_type:<10} TTL={ttl}") - for value in values: - diff_content.append(f" {value}") - diff_content.append("") + for record in sorted(changes['adds'], key=lambda r: (r['name'].to_text(), r['type'])): + record_name = record['name'].to_text().rstrip('.') + record_type = record['type'] + ttl = record.get('ttl', self.soa_minimum_ttl) + values = sorted(str(v) for v in record['values']) + for value in values: + diff_lines.append(f"+{record_name:<35} {ttl:<10} {record_type:<10} {value}") # Process updates - records being changed - if changes['updates']: - diff_content.append("UPDATED RECORDS:") - for record in sorted(changes['updates'], key=lambda r: (r['name'].to_text(), r['type'])): - record_name = record['name'].to_text() - record_type = record['type'] - old_ttl = record.get('old_ttl', 'default') - new_ttl = record.get('new_ttl', 'default') - old_values = sorted(str(v) for v in record['old_values']) - new_values = sorted(str(v) for v in record['new_values']) - - diff_content.append(f" ~ {record_name:<50} {record_type:<10}") - - # Show TTL change if it changed - if old_ttl != new_ttl: - diff_content.append(f" TTL: {old_ttl} -> {new_ttl}") - else: - diff_content.append(f" TTL: {old_ttl}") - - # Show old values - diff_content.append(f" Before:") - for value in old_values: - diff_content.append(f" - {value}") - - # Show new values - diff_content.append(f" After:") - for value in new_values: - diff_content.append(f" + {value}") - diff_content.append("") + for record in sorted(changes['updates'], key=lambda r: (r['name'].to_text(), r['type'])): + record_name = record['name'].to_text().rstrip('.') + record_type = record['type'] + old_ttl = record.get('old_ttl', self.soa_minimum_ttl) + new_ttl = record.get('new_ttl', self.soa_minimum_ttl) + old_values = sorted(str(v) for v in record['old_values']) + new_values = sorted(str(v) for v in record['new_values']) + + # Show old values with - + for value in old_values: + diff_lines.append(f"-{record_name:<35} {old_ttl:<10} {record_type:<10} {value}") + # Show new values with + + for value in new_values: + diff_lines.append(f"+{record_name:<35} {new_ttl:<10} {record_type:<10} {value}") - # Format as before/after with the content as text + # Return diff with content - using actual line breaks, not escaped strings + diff_output = '\n'.join(diff_lines) if diff_lines else 'No changes' return { - 'before': 'DNS Zone Records (Before)', - 'after': '\n'.join(diff_content) if diff_content else 'No changes' + 'before': diff_output, + 'after': diff_output } @@ -1015,6 +1001,7 @@ def main() -> None: ignore_soa_records=dict(type='bool', default=True), ignore_ns_records=dict(type='bool', default=True), validate_records=dict(type='bool', default=True), + default_ttl=dict(type='int'), dns_server=dict(type='str') ), supports_check_mode=True,