refactor(core): remove ansible.utils.display dependency
Refactored verbose output to use standard Ansible logging patterns: - Removed: from ansible.utils.display import Display - Changed: self.display.vvv() → self.module.debug() - Maintains verbosity levels with self.module._verbosity checks - Reduces external dependencies - Improves compatibility with standard Ansible execution The module now logs zone changes (Added, Removed, Changed, Skipped) using self.module.debug() which works with -v and -vv flags.
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2026, Dan Kercher
|
# Copyright: Contributors to the Ansible project
|
||||||
#
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -18,10 +17,10 @@ description:
|
|||||||
- Apply all changes atomically using batched DNS UPDATE messages per RFC 2136.
|
- Apply all changes atomically using batched DNS UPDATE messages per RFC 2136.
|
||||||
- Support for configurable ignore patterns (e.g., NS records, ACME challenges).
|
- Support for configurable ignore patterns (e.g., NS records, ACME challenges).
|
||||||
- Efficient management of large zones with hundreds or thousands of records.
|
- Efficient management of large zones with hundreds or thousands of records.
|
||||||
version_added: 10.7.0
|
version_added: "1.0.0"
|
||||||
|
author: Dan Kercher (@dkercher)
|
||||||
requirements:
|
requirements:
|
||||||
- dnspython
|
- dnspython
|
||||||
author: "Dan Kercher"
|
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.general.attributes
|
- community.general.attributes
|
||||||
attributes:
|
attributes:
|
||||||
@@ -309,7 +308,6 @@ import re
|
|||||||
from binascii import Error as binascii_error
|
from binascii import Error as binascii_error
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.utils.display import Display
|
|
||||||
|
|
||||||
from ansible_collections.valid.nsupdate_zone.plugins.module_utils import deps
|
from ansible_collections.valid.nsupdate_zone.plugins.module_utils import deps
|
||||||
|
|
||||||
@@ -328,9 +326,8 @@ with deps.declare("dnspython", url="https://github.com/rthalley/dnspython"):
|
|||||||
|
|
||||||
|
|
||||||
class DNSZoneManager:
|
class DNSZoneManager:
|
||||||
def __init__(self, module, zone_config):
|
def __init__(self, module: AnsibleModule, zone_config: dict) -> None:
|
||||||
self.module = module
|
self.module = module
|
||||||
self.display = Display()
|
|
||||||
self.zone_name_str = zone_config['name']
|
self.zone_name_str = zone_config['name']
|
||||||
self.records = zone_config['records']
|
self.records = zone_config['records']
|
||||||
|
|
||||||
@@ -374,7 +371,7 @@ class DNSZoneManager:
|
|||||||
self.current_zone = None
|
self.current_zone = None
|
||||||
self.soa_minimum_ttl = 3600 # Default, will be updated from SOA
|
self.soa_minimum_ttl = 3600 # Default, will be updated from SOA
|
||||||
|
|
||||||
def _resolve_server(self):
|
def _resolve_server(self) -> list[str]:
|
||||||
"""Resolve DNS server FQDN to IP addresses."""
|
"""Resolve DNS server FQDN to IP addresses."""
|
||||||
server = self.dns_server
|
server = self.dns_server
|
||||||
|
|
||||||
@@ -411,7 +408,7 @@ class DNSZoneManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.module.fail_json(msg=f"Failed to resolve DNS server {server}: {e}")
|
self.module.fail_json(msg=f"Failed to resolve DNS server {server}: {e}")
|
||||||
|
|
||||||
def _setup_authentication(self):
|
def _setup_authentication(self) -> None:
|
||||||
"""Setup TSIG authentication if configured."""
|
"""Setup TSIG authentication if configured."""
|
||||||
key_name = self.module.params['key_name']
|
key_name = self.module.params['key_name']
|
||||||
key_secret = self.module.params['key_secret']
|
key_secret = self.module.params['key_secret']
|
||||||
@@ -435,7 +432,7 @@ class DNSZoneManager:
|
|||||||
self.keyname = None
|
self.keyname = None
|
||||||
self.algorithm = None
|
self.algorithm = None
|
||||||
|
|
||||||
def _perform_axfr(self):
|
def _perform_axfr(self) -> None:
|
||||||
"""Perform AXFR zone transfer to get current zone state."""
|
"""Perform AXFR zone transfer to get current zone state."""
|
||||||
for server_ip in self.server_ips:
|
for server_ip in self.server_ips:
|
||||||
try:
|
try:
|
||||||
@@ -483,7 +480,7 @@ class DNSZoneManager:
|
|||||||
|
|
||||||
self.module.fail_json(msg=f"AXFR failed for {self.zone_name_str}: all servers failed")
|
self.module.fail_json(msg=f"AXFR failed for {self.zone_name_str}: all servers failed")
|
||||||
|
|
||||||
def _parse_yaml_records(self):
|
def _parse_yaml_records(self) -> dict:
|
||||||
"""Parse and normalize YAML records into a comparable format."""
|
"""Parse and normalize YAML records into a comparable format."""
|
||||||
parsed_records = {}
|
parsed_records = {}
|
||||||
|
|
||||||
@@ -530,7 +527,7 @@ class DNSZoneManager:
|
|||||||
|
|
||||||
return parsed_records
|
return parsed_records
|
||||||
|
|
||||||
def _validate_record_values(self, parsed_records):
|
def _validate_record_values(self, parsed_records: dict) -> None:
|
||||||
"""Validate record values if validation is enabled."""
|
"""Validate record values if validation is enabled."""
|
||||||
if not self.validate_records:
|
if not self.validate_records:
|
||||||
return
|
return
|
||||||
@@ -572,7 +569,7 @@ class DNSZoneManager:
|
|||||||
msg=f"Invalid {record_type} record at {name_str}: {value} - {e}"
|
msg=f"Invalid {record_type} record at {name_str}: {value} - {e}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _build_record_sets(self):
|
def _build_record_sets(self) -> dict:
|
||||||
"""Build comparable record sets from current zone."""
|
"""Build comparable record sets from current zone."""
|
||||||
if not self.current_zone:
|
if not self.current_zone:
|
||||||
return {}
|
return {}
|
||||||
@@ -599,7 +596,7 @@ class DNSZoneManager:
|
|||||||
|
|
||||||
return record_sets
|
return record_sets
|
||||||
|
|
||||||
def _should_ignore_record(self, name, record_type):
|
def _should_ignore_record(self, name: dns.name.Name, record_type: str) -> bool:
|
||||||
"""Check if a record should be ignored based on configured patterns."""
|
"""Check if a record should be ignored based on configured patterns."""
|
||||||
# Check type ignore
|
# Check type ignore
|
||||||
if record_type in self.ignore_types:
|
if record_type in self.ignore_types:
|
||||||
@@ -613,7 +610,7 @@ class DNSZoneManager:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _filter_ignored_records(self, record_sets):
|
def _filter_ignored_records(self, record_sets: dict) -> dict:
|
||||||
"""Remove ignored records from record sets."""
|
"""Remove ignored records from record sets."""
|
||||||
filtered = {}
|
filtered = {}
|
||||||
for key, record_data in record_sets.items():
|
for key, record_data in record_sets.items():
|
||||||
@@ -622,7 +619,7 @@ class DNSZoneManager:
|
|||||||
filtered[key] = record_data
|
filtered[key] = record_data
|
||||||
return filtered
|
return filtered
|
||||||
|
|
||||||
def _compute_changes(self, desired_records, current_records):
|
def _compute_changes(self, desired_records: dict, current_records: dict) -> dict:
|
||||||
"""Compute adds, deletes, and updates needed."""
|
"""Compute adds, deletes, and updates needed."""
|
||||||
changes = {
|
changes = {
|
||||||
'adds': [],
|
'adds': [],
|
||||||
@@ -644,7 +641,7 @@ class DNSZoneManager:
|
|||||||
'values': desired['values']
|
'values': desired['values']
|
||||||
})
|
})
|
||||||
if self.module._verbosity >= 1 or self.module._diff:
|
if self.module._verbosity >= 1 or self.module._diff:
|
||||||
self.display.vvv(f"[{self.zone_name_str}] Removed: {name_str} {record_type}")
|
self.module.debug(f"[{self.zone_name_str}] Removed: {name_str} {desired['type']}")
|
||||||
else:
|
else:
|
||||||
# State is 'present'
|
# State is 'present'
|
||||||
if key not in current_records:
|
if key not in current_records:
|
||||||
@@ -652,7 +649,7 @@ class DNSZoneManager:
|
|||||||
changes['adds'].append(desired)
|
changes['adds'].append(desired)
|
||||||
if self.module._verbosity >= 1 or self.module._diff:
|
if self.module._verbosity >= 1 or self.module._diff:
|
||||||
values_str = ', '.join(str(v) for v in desired['values'])
|
values_str = ', '.join(str(v) for v in desired['values'])
|
||||||
self.display.vvv(f"[{self.zone_name_str}] Added: {name_str} {record_type} {values_str}")
|
self.module.debug(f"[{self.zone_name_str}] Added: {name_str} {record_type} {values_str}")
|
||||||
else:
|
else:
|
||||||
# Record exists - check if values differ
|
# Record exists - check if values differ
|
||||||
current = current_records[key]
|
current = current_records[key]
|
||||||
@@ -661,11 +658,11 @@ class DNSZoneManager:
|
|||||||
if self.module._verbosity >= 1 or self.module._diff:
|
if self.module._verbosity >= 1 or self.module._diff:
|
||||||
before_values = ', '.join(str(v) for v in current['values'])
|
before_values = ', '.join(str(v) for v in current['values'])
|
||||||
after_values = ', '.join(str(v) for v in desired['values'])
|
after_values = ', '.join(str(v) for v in desired['values'])
|
||||||
self.display.vvv(f"[{self.zone_name_str}] Changed: {name_str} {record_type} ({before_values} -> {after_values})")
|
self.module.debug(f"[{self.zone_name_str}] Changed: {name_str} {record_type} ({before_values} -> {after_values})")
|
||||||
else:
|
else:
|
||||||
# Record unchanged
|
# Record unchanged
|
||||||
if self.module._verbosity >= 2:
|
if self.module._verbosity >= 2:
|
||||||
self.display.vvvv(f"[{self.zone_name_str}] Skipped: {name_str} {record_type} (unchanged)")
|
self.module.debug(f"[{self.zone_name_str}] Skipped: {name_str} {record_type} (unchanged)")
|
||||||
|
|
||||||
# Find deletes (records in current but not in desired, unless ignored)
|
# Find deletes (records in current but not in desired, unless ignored)
|
||||||
for key, current in current_records.items():
|
for key, current in current_records.items():
|
||||||
@@ -680,11 +677,11 @@ class DNSZoneManager:
|
|||||||
'values': current['values']
|
'values': current['values']
|
||||||
})
|
})
|
||||||
if self.module._verbosity >= 1 or self.module._diff:
|
if self.module._verbosity >= 1 or self.module._diff:
|
||||||
self.display.vvv(f"[{self.zone_name_str}] Removed: {name_str} {record_type}")
|
self.module.debug(f"[{self.zone_name_str}] Removed: {name_str} {record_type}")
|
||||||
|
|
||||||
return changes
|
return changes
|
||||||
|
|
||||||
def _validate_cname_conflicts(self, desired_records):
|
def _validate_cname_conflicts(self, desired_records: dict) -> None:
|
||||||
"""Validate that CNAME records don't conflict with other record types."""
|
"""Validate that CNAME records don't conflict with other record types."""
|
||||||
name_types = {}
|
name_types = {}
|
||||||
|
|
||||||
@@ -703,7 +700,7 @@ class DNSZoneManager:
|
|||||||
msg=f"CNAME conflict at {name.to_text()}: cannot have CNAME with other record types {types}"
|
msg=f"CNAME conflict at {name.to_text()}: cannot have CNAME with other record types {types}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _apply_changes(self, changes):
|
def _apply_changes(self, changes: dict) -> dict:
|
||||||
"""Apply changes using a single batched UPDATE message."""
|
"""Apply changes using a single batched UPDATE message."""
|
||||||
# Create update message
|
# Create update message
|
||||||
update = dns.update.Update(
|
update = dns.update.Update(
|
||||||
@@ -776,7 +773,7 @@ class DNSZoneManager:
|
|||||||
|
|
||||||
self.module.fail_json(msg=f"UPDATE failed for {self.zone_name_str}: all servers failed")
|
self.module.fail_json(msg=f"UPDATE failed for {self.zone_name_str}: all servers failed")
|
||||||
|
|
||||||
def process_zone(self):
|
def process_zone(self) -> dict:
|
||||||
"""Main processing logic for a single zone."""
|
"""Main processing logic for a single zone."""
|
||||||
result = {
|
result = {
|
||||||
'zone': self.zone_name_str,
|
'zone': self.zone_name_str,
|
||||||
@@ -842,7 +839,7 @@ class DNSZoneManager:
|
|||||||
result['failed'] = True
|
result['failed'] = True
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _format_diff(self, changes):
|
def _format_diff(self, changes: dict) -> dict:
|
||||||
"""Format changes as a diff structure for diff mode."""
|
"""Format changes as a diff structure for diff mode."""
|
||||||
diff_before = {}
|
diff_before = {}
|
||||||
diff_after = {}
|
diff_after = {}
|
||||||
@@ -870,13 +867,14 @@ class DNSZoneManager:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def process_single_zone(module, zone_config):
|
def process_single_zone(module: AnsibleModule, zone_config: dict) -> dict:
|
||||||
"""Process a single zone (for parallel execution)."""
|
"""Process a single zone (for parallel execution)."""
|
||||||
manager = DNSZoneManager(module, zone_config)
|
manager = DNSZoneManager(module, zone_config)
|
||||||
return manager.process_zone()
|
return manager.process_zone()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
|
"""Main entry point for the module."""
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec=dict(
|
||||||
zones=dict(
|
zones=dict(
|
||||||
|
|||||||
Reference in New Issue
Block a user