mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
nmap: fix cache support (#2282)
* add cache support * pep8 e501 fix * revert verify_file function * revert description update * add changelog fragment Co-authored-by: Dennis Israelsson <github@mdh.nu>
This commit is contained in:
parent
6f6b80fd89
commit
31c9ed0fe6
2 changed files with 108 additions and 61 deletions
2
changelogs/fragments/2282-nmap-fix-cache-support.yml
Normal file
2
changelogs/fragments/2282-nmap-fix-cache-support.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
bugfixes:
|
||||
- nmap inventory plugin - fix cache and constructed group support (https://github.com/ansible-collections/community.general/issues/2242).
|
|
@ -71,6 +71,25 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
|||
self._nmap = None
|
||||
super(InventoryModule, self).__init__()
|
||||
|
||||
def _populate(self, hosts):
|
||||
# Use constructed if applicable
|
||||
strict = self.get_option('strict')
|
||||
|
||||
for host in hosts:
|
||||
hostname = host['name']
|
||||
self.inventory.add_host(hostname)
|
||||
for var, value in host.items():
|
||||
self.inventory.set_variable(hostname, var, value)
|
||||
|
||||
# Composed variables
|
||||
self._set_composite_vars(self.get_option('compose'), host, hostname, strict=strict)
|
||||
|
||||
# Complex groups based on jinja2 conditionals, hosts that meet the conditional are added to group
|
||||
self._add_host_to_composed_groups(self.get_option('groups'), host, hostname, strict=strict)
|
||||
|
||||
# Create groups based on variable values and add the corresponding hosts to it
|
||||
self._add_host_to_keyed_groups(self.get_option('keyed_groups'), host, hostname, strict=strict)
|
||||
|
||||
def verify_file(self, path):
|
||||
|
||||
valid = False
|
||||
|
@ -82,7 +101,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
|||
|
||||
return valid
|
||||
|
||||
def parse(self, inventory, loader, path, cache=False):
|
||||
def parse(self, inventory, loader, path, cache=True):
|
||||
|
||||
try:
|
||||
self._nmap = get_bin_path('nmap')
|
||||
|
@ -93,75 +112,101 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
|||
|
||||
self._read_config_data(path)
|
||||
|
||||
# setup command
|
||||
cmd = [self._nmap]
|
||||
if not self._options['ports']:
|
||||
cmd.append('-sP')
|
||||
cache_key = self.get_cache_key(path)
|
||||
|
||||
if self._options['ipv4'] and not self._options['ipv6']:
|
||||
cmd.append('-4')
|
||||
elif self._options['ipv6'] and not self._options['ipv4']:
|
||||
cmd.append('-6')
|
||||
elif not self._options['ipv6'] and not self._options['ipv4']:
|
||||
raise AnsibleParserError('One of ipv4 or ipv6 must be enabled for this plugin')
|
||||
# cache may be True or False at this point to indicate if the inventory is being refreshed
|
||||
# get the user's cache option too to see if we should save the cache if it is changing
|
||||
user_cache_setting = self.get_option('cache')
|
||||
|
||||
if self._options['exclude']:
|
||||
cmd.append('--exclude')
|
||||
cmd.append(','.join(self._options['exclude']))
|
||||
|
||||
cmd.append(self._options['address'])
|
||||
try:
|
||||
# execute
|
||||
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise AnsibleParserError('Failed to run nmap, rc=%s: %s' % (p.returncode, to_native(stderr)))
|
||||
|
||||
# parse results
|
||||
host = None
|
||||
ip = None
|
||||
ports = []
|
||||
# read if the user has caching enabled and the cache isn't being refreshed
|
||||
attempt_to_read_cache = user_cache_setting and cache
|
||||
# update if the user has caching enabled and the cache is being refreshed; update this value to True if the cache has expired below
|
||||
cache_needs_update = user_cache_setting and not cache
|
||||
|
||||
if attempt_to_read_cache:
|
||||
try:
|
||||
t_stdout = to_text(stdout, errors='surrogate_or_strict')
|
||||
except UnicodeError as e:
|
||||
raise AnsibleParserError('Invalid (non unicode) input returned: %s' % to_native(e))
|
||||
results = self._cache[cache_key]
|
||||
except KeyError:
|
||||
# This occurs if the cache_key is not in the cache or if the cache_key expired, so the cache needs to be updated
|
||||
cache_needs_update = True
|
||||
|
||||
for line in t_stdout.splitlines():
|
||||
hits = self.find_host.match(line)
|
||||
if hits:
|
||||
if host is not None:
|
||||
self.inventory.set_variable(host, 'ports', ports)
|
||||
if cache_needs_update:
|
||||
# setup command
|
||||
cmd = [self._nmap]
|
||||
if not self._options['ports']:
|
||||
cmd.append('-sP')
|
||||
|
||||
# if dns only shows arpa, just use ip instead as hostname
|
||||
if hits.group(1).endswith('.in-addr.arpa'):
|
||||
host = hits.group(2)
|
||||
else:
|
||||
host = hits.group(1)
|
||||
if self._options['ipv4'] and not self._options['ipv6']:
|
||||
cmd.append('-4')
|
||||
elif self._options['ipv6'] and not self._options['ipv4']:
|
||||
cmd.append('-6')
|
||||
elif not self._options['ipv6'] and not self._options['ipv4']:
|
||||
raise AnsibleParserError('One of ipv4 or ipv6 must be enabled for this plugin')
|
||||
|
||||
# if no reverse dns exists, just use ip instead as hostname
|
||||
if hits.group(2) is not None:
|
||||
ip = hits.group(2)
|
||||
else:
|
||||
ip = hits.group(1)
|
||||
if self._options['exclude']:
|
||||
cmd.append('--exclude')
|
||||
cmd.append(','.join(self._options['exclude']))
|
||||
|
||||
if host is not None:
|
||||
# update inventory
|
||||
self.inventory.add_host(host)
|
||||
self.inventory.set_variable(host, 'ip', ip)
|
||||
ports = []
|
||||
continue
|
||||
cmd.append(self._options['address'])
|
||||
try:
|
||||
# execute
|
||||
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise AnsibleParserError('Failed to run nmap, rc=%s: %s' % (p.returncode, to_native(stderr)))
|
||||
|
||||
host_ports = self.find_port.match(line)
|
||||
if host is not None and host_ports:
|
||||
ports.append({'port': host_ports.group(1), 'protocol': host_ports.group(2), 'state': host_ports.group(3), 'service': host_ports.group(4)})
|
||||
continue
|
||||
# parse results
|
||||
host = None
|
||||
ip = None
|
||||
ports = []
|
||||
results = []
|
||||
|
||||
# TODO: parse more data, OS?
|
||||
try:
|
||||
t_stdout = to_text(stdout, errors='surrogate_or_strict')
|
||||
except UnicodeError as e:
|
||||
raise AnsibleParserError('Invalid (non unicode) input returned: %s' % to_native(e))
|
||||
|
||||
# if any leftovers
|
||||
if host and ports:
|
||||
self.inventory.set_variable(host, 'ports', ports)
|
||||
for line in t_stdout.splitlines():
|
||||
hits = self.find_host.match(line)
|
||||
if hits:
|
||||
if host is not None and ports:
|
||||
results[-1]['ports'] = ports
|
||||
|
||||
except Exception as e:
|
||||
raise AnsibleParserError("failed to parse %s: %s " % (to_native(path), to_native(e)))
|
||||
# if dns only shows arpa, just use ip instead as hostname
|
||||
if hits.group(1).endswith('.in-addr.arpa'):
|
||||
host = hits.group(2)
|
||||
else:
|
||||
host = hits.group(1)
|
||||
|
||||
# if no reverse dns exists, just use ip instead as hostname
|
||||
if hits.group(2) is not None:
|
||||
ip = hits.group(2)
|
||||
else:
|
||||
ip = hits.group(1)
|
||||
|
||||
if host is not None:
|
||||
# update inventory
|
||||
results.append(dict())
|
||||
results[-1]['name'] = host
|
||||
results[-1]['ip'] = ip
|
||||
ports = []
|
||||
continue
|
||||
|
||||
host_ports = self.find_port.match(line)
|
||||
if host is not None and host_ports:
|
||||
ports.append({'port': host_ports.group(1),
|
||||
'protocol': host_ports.group(2),
|
||||
'state': host_ports.group(3),
|
||||
'service': host_ports.group(4)})
|
||||
continue
|
||||
|
||||
# if any leftovers
|
||||
if host and ports:
|
||||
results[-1]['ports'] = ports
|
||||
|
||||
except Exception as e:
|
||||
raise AnsibleParserError("failed to parse %s: %s " % (to_native(path), to_native(e)))
|
||||
|
||||
self._cache[cache_key] = results
|
||||
|
||||
self._populate(results)
|
||||
|
|
Loading…
Reference in a new issue