Skip to content

Commit

Permalink
Add reverse_lookup lookup plugin.
Browse files Browse the repository at this point in the history
  • Loading branch information
felixfontein committed Nov 18, 2024
1 parent 4770efe commit f1907de
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 0 deletions.
175 changes: 175 additions & 0 deletions plugins/lookup/reverse_lookup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# -*- coding: utf-8 -*-

# Copyright (c) 2023, Felix Fontein <felix@fontein.de>
# 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 absolute_import, division, print_function
__metaclass__ = type

DOCUMENTATION = r'''
name: reverse_lookup
author: Felix Fontein (@felixfontein)
short_description: Reverse-look up IP addresses
version_added: 3.1.0
requirements:
- dnspython >= 1.15.0 (maybe older versions also work)
description:
- Look up hostnames for IP addresses using DNS reverse lookup.
options:
_terms:
description:
- IP address(es) to look up.
type: list
elements: str
required: true
query_retry:
description:
- Number of retries for DNS query timeouts.
type: int
default: 3
query_timeout:
description:
- Timeout per DNS query in seconds.
type: float
default: 10
server:
description:
- The DNS server(s) to use to look up the result. Must be a list of one or more IP addresses.
- By default, the system's standard resolver is used.
type: list
elements: str
servfail_retries:
description:
- How often to retry on SERVFAIL errors.
type: int
default: 0
notes:
- Note that when using this lookup plugin with V(lookup(\)), and the result is a one-element list,
Ansible simply returns the one element not as a list. Since this behavior is surprising and
can cause problems, it is better to use V(query(\)) instead of V(lookup(\)). See the examples
and also R(Forcing lookups to return lists, query) in the Ansible documentation.
'''

EXAMPLES = """
- name: Look up hostname of IPv4 address
ansible.builtin.debug:
msg: "{{ query('community.dns.reverse_lookup', '192.168.1.1') }}"
- name: Look up hostname of IPv6 address
ansible.builtin.debug:
msg: "{{ query('community.dns.reverse_lookup', '1:2:3::4') }}"
"""

RETURN = """
_result:
description:
- The hostname(s) returned for the queried IP addresses.
- If multiple IP addresses are queried in O(_terms), the resulting lists have been concatenated.
type: list
elements: str
sample:
- example.com
- example.org
"""

from ansible.errors import AnsibleLookupError
from ansible.plugins.lookup import LookupBase
from ansible.module_utils.common.text.converters import to_text

from ansible_collections.community.dns.plugins.module_utils.ips import (
is_ip_address,
)

from ansible_collections.community.dns.plugins.module_utils.resolver import (
SimpleResolver,
)

from ansible_collections.community.dns.plugins.plugin_utils.ips import (
assert_requirements_present as assert_requirements_present_ipaddress,
)

from ansible_collections.community.dns.plugins.plugin_utils.resolver import (
assert_requirements_present as assert_requirements_present_dnspython,
guarded_run,
)

try:
import dns.resolver
except ImportError:
# handled by assert_requirements_present_dnspython
pass

try:
import ipaddress
except ImportError:
# handled by assert_requirements_present
pass


class LookupModule(LookupBase):
@staticmethod
def _resolve(resolver, name, rdtype, server_addresses):
def callback():
try:
rrset = resolver.resolve(
name,
rdtype=rdtype,
server_addresses=server_addresses,
nxdomain_is_empty=True,
target_can_be_relative=False,
search=False,
)
if not rrset:
return []
return [to_text(data) for data in rrset]
except dns.resolver.NXDOMAIN:
raise AnsibleLookupError('Got NXDOMAIN when querying {name}'.format(name=name))

return guarded_run(
callback,
error_class=AnsibleLookupError,
server=name,
)

def run(self, terms, variables=None, **kwargs):
assert_requirements_present_dnspython('community.dns.reverse_lookup', 'lookup')
assert_requirements_present_ipaddress('community.dns.reverse_lookup', 'lookup')

self.set_options(var_options=variables, direct=kwargs)

resolver = SimpleResolver(
timeout=self.get_option('query_timeout'),
timeout_retries=self.get_option('query_retry'),
servfail_retries=self.get_option('servfail_retries'),
)

server_addresses = None
if self.get_option('server'):
server_addresses = []
for server in self.get_option('server'):
if is_ip_address(server):
server_addresses.append(server)
continue
else:
server_addresses.extend(guarded_run(
lambda: resolver.resolve_addresses(server),
error_class=AnsibleLookupError,
server=server,
))

ip_adresses = []
for ip_address in terms:
try:
ipaddr = ipaddress.ip_address(to_text(ip_address))
name = ipaddr.reverse_pointer
if not name.endswith(u'.'):
name += u'.'
ip_adresses.append(name)
except Exception as e:
raise AnsibleLookupError('Cannot parse IP address {ip!r}: {error}'.format(ip=ip_address, error=e))

result = []
for name in ip_adresses:
result.extend(self._resolve(resolver, name, dns.rdatatype.PTR, server_addresses))
return result
5 changes: 5 additions & 0 deletions tests/integration/targets/lookup_reverse_lookup/aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright (c) Ansible Project
# 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

shippable/posix/group1
20 changes: 20 additions & 0 deletions tests/integration/targets/lookup_reverse_lookup/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
# Copyright (c) Ansible Project
# 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

- name: Look up some records
ansible.builtin.set_fact:
ansible_a: >-
{{ query('community.dns.reverse_lookup', '9.9.9.9') }}
ansible_aaaa: >-
{{ query('community.dns.reverse_lookup', '2620:fe::fe') }}
ansible_both: >-
{{ query('community.dns.reverse_lookup', '9.9.9.9', '2620:fe::fe') }}
- name: Check results
assert:
that:
- ansible_a == ['dns9.quad9.net.']
- ansible_aaaa == ['dns.quad9.net.']
- ansible_both == ['dns9.quad9.net.', 'dns.quad9.net.']

0 comments on commit f1907de

Please sign in to comment.