mirror of
https://github.com/ansible/awx.git
synced 2026-01-22 06:58:06 -03:30
This separates file parsing logic that was mixed in with other important code inside of the inventory import command. The logic around MemObject data structures was moved to utils, and the file parsing was moved to a legacy module. As of this commit, that module can operate within the Tower environment but it will be removed. Also refactor the loggers to fix old bug and work inside of the different contexts - the Loader classes, mem objects, and hopefully the inventory modules eventually.
316 lines
11 KiB
Python
316 lines
11 KiB
Python
# Copyright (c) 2017 Ansible by Red Hat
|
|
# All Rights Reserved.
|
|
|
|
# Python
|
|
import re
|
|
import logging
|
|
from collections import OrderedDict
|
|
|
|
|
|
# Logger is used for any data-related messages so that the log level
|
|
# can be adjusted on command invocation
|
|
logger = logging.getLogger('awx.main.commands.inventory_import')
|
|
|
|
|
|
__all__ = ['MemHost', 'MemGroup', 'MemInventory',
|
|
'mem_data_to_dict', 'dict_to_mem_data']
|
|
|
|
|
|
ipv6_port_re = re.compile(r'^\[([A-Fa-f0-9:]{3,})\]:(\d+?)$')
|
|
|
|
|
|
# Models for in-memory objects that represent an inventory
|
|
|
|
|
|
class MemObject(object):
|
|
'''
|
|
Common code shared between in-memory groups and hosts.
|
|
'''
|
|
|
|
def __init__(self, name):
|
|
assert name, 'no name'
|
|
self.name = name
|
|
|
|
|
|
class MemGroup(MemObject):
|
|
'''
|
|
In-memory representation of an inventory group.
|
|
'''
|
|
|
|
def __init__(self, name):
|
|
super(MemGroup, self).__init__(name)
|
|
self.children = []
|
|
self.hosts = []
|
|
self.variables = {}
|
|
self.parents = []
|
|
# Used on the "all" group in place of previous global variables.
|
|
# maps host and group names to hosts to prevent redudant additions
|
|
self.all_hosts = {}
|
|
self.all_groups = {}
|
|
self.variables = {}
|
|
logger.debug('Loaded group: %s', self.name)
|
|
|
|
def __repr__(self):
|
|
return '<_in-memory-group_ `{}`>'.format(self.name)
|
|
|
|
def add_child_group(self, group):
|
|
assert group.name is not 'all', 'group name is all'
|
|
assert isinstance(group, MemGroup), 'not MemGroup instance'
|
|
logger.debug('Adding child group %s to parent %s', group.name, self.name)
|
|
if group not in self.children:
|
|
self.children.append(group)
|
|
if self not in group.parents:
|
|
group.parents.append(self)
|
|
|
|
def add_host(self, host):
|
|
assert isinstance(host, MemHost), 'not MemHost instance'
|
|
logger.debug('Adding host %s to group %s', host.name, self.name)
|
|
if host not in self.hosts:
|
|
self.hosts.append(host)
|
|
|
|
def debug_tree(self, group_names=None):
|
|
group_names = group_names or set()
|
|
if self.name in group_names:
|
|
return
|
|
logger.debug('Dumping tree for group "%s":', self.name)
|
|
logger.debug('- Vars: %r', self.variables)
|
|
for h in self.hosts:
|
|
logger.debug('- Host: %s, %r', h.name, h.variables)
|
|
for g in self.children:
|
|
logger.debug('- Child: %s', g.name)
|
|
logger.debug('----')
|
|
group_names.add(self.name)
|
|
for g in self.children:
|
|
g.debug_tree(group_names)
|
|
|
|
|
|
class MemHost(MemObject):
|
|
'''
|
|
In-memory representation of an inventory host.
|
|
'''
|
|
|
|
def __init__(self, name, port=None):
|
|
super(MemHost, self).__init__(name)
|
|
self.variables = {}
|
|
self.instance_id = None
|
|
self.name = name
|
|
if port:
|
|
# was `ansible_ssh_port` in older Ansible/Tower versions
|
|
self.variables['ansible_port'] = port
|
|
logger.debug('Loaded host: %s', self.name)
|
|
|
|
def __repr__(self):
|
|
return '<_in-memory-host_ `{}`>'.format(self.name)
|
|
|
|
|
|
class MemInventory(object):
|
|
'''
|
|
Common functions for an inventory loader from a given source.
|
|
'''
|
|
def __init__(self, all_group=None, group_filter_re=None, host_filter_re=None):
|
|
if all_group:
|
|
assert isinstance(all_group, MemGroup), '{} is not MemGroup instance'.format(all_group)
|
|
self.all_group = all_group
|
|
else:
|
|
self.all_group = self.create_group('all')
|
|
self.group_filter_re = group_filter_re
|
|
self.host_filter_re = host_filter_re
|
|
|
|
def create_host(self, host_name, port):
|
|
host = MemHost(host_name, port)
|
|
self.all_group.all_hosts[host_name] = host
|
|
return host
|
|
|
|
def get_host(self, name):
|
|
'''
|
|
Return a MemHost instance from host name, creating if needed. If name
|
|
contains brackets, they will NOT be interpreted as a host pattern.
|
|
'''
|
|
m = ipv6_port_re.match(name)
|
|
if m:
|
|
host_name = m.groups()[0]
|
|
port = int(m.groups()[1])
|
|
elif name.count(':') == 1:
|
|
host_name = name.split(':')[0]
|
|
try:
|
|
port = int(name.split(':')[1])
|
|
except (ValueError, UnicodeDecodeError):
|
|
logger.warning(u'Invalid port "%s" for host "%s"',
|
|
name.split(':')[1], host_name)
|
|
port = None
|
|
else:
|
|
host_name = name
|
|
port = None
|
|
if self.host_filter_re and not self.host_filter_re.match(host_name):
|
|
logger.debug('Filtering host %s', host_name)
|
|
return None
|
|
if host_name not in self.all_group.all_hosts:
|
|
self.create_host(host_name, port)
|
|
return self.all_group.all_hosts[host_name]
|
|
|
|
def create_group(self, group_name):
|
|
group = MemGroup(group_name)
|
|
if group_name not in ['all', 'ungrouped']:
|
|
self.all_group.all_groups[group_name] = group
|
|
return group
|
|
|
|
def get_group(self, name, all_group=None, child=False):
|
|
'''
|
|
Return a MemGroup instance from group name, creating if needed.
|
|
'''
|
|
all_group = all_group or self.all_group
|
|
if name in ['all', 'ungrouped']:
|
|
return all_group
|
|
if self.group_filter_re and not self.group_filter_re.match(name):
|
|
logger.debug('Filtering group %s', name)
|
|
return None
|
|
if name not in self.all_group.all_groups:
|
|
group = self.create_group(name)
|
|
if not child:
|
|
all_group.add_child_group(group)
|
|
return self.all_group.all_groups[name]
|
|
|
|
def delete_empty_groups(self):
|
|
for name, group in self.all_group.all_groups.items():
|
|
if not group.children and not group.hosts and not group.variables:
|
|
logger.debug('Removing empty group %s', name)
|
|
for parent in group.parents:
|
|
if group in parent.children:
|
|
parent.children.remove(group)
|
|
del self.all_group.all_groups[name]
|
|
|
|
|
|
# Conversion utilities
|
|
|
|
def mem_data_to_dict(inventory):
|
|
'''
|
|
Given an in-memory construct of an inventory, returns a dictionary that
|
|
follows Ansible guidelines on the structure of dynamic inventory sources
|
|
|
|
May be replaced by removing in-memory constructs within this file later
|
|
'''
|
|
all_group = inventory.all_group
|
|
inventory_data = OrderedDict([])
|
|
# Save hostvars to _meta
|
|
inventory_data['_meta'] = OrderedDict([])
|
|
hostvars = OrderedDict([])
|
|
for name, host_obj in all_group.all_hosts.items():
|
|
hostvars[name] = host_obj.variables
|
|
inventory_data['_meta']['hostvars'] = hostvars
|
|
# Save children of `all` group
|
|
inventory_data['all'] = OrderedDict([])
|
|
if all_group.variables:
|
|
inventory_data['all']['vars'] = all_group.variables
|
|
inventory_data['all']['children'] = [c.name for c in all_group.children]
|
|
inventory_data['all']['children'].append('ungrouped')
|
|
# Save details of declared groups individually
|
|
ungrouped_hosts = set(all_group.all_hosts.keys())
|
|
for name, group_obj in all_group.all_groups.items():
|
|
group_host_names = [h.name for h in group_obj.hosts]
|
|
group_children_names = [c.name for c in group_obj.children]
|
|
group_data = OrderedDict([])
|
|
if group_host_names:
|
|
group_data['hosts'] = group_host_names
|
|
ungrouped_hosts.difference_update(group_host_names)
|
|
if group_children_names:
|
|
group_data['children'] = group_children_names
|
|
if group_obj.variables:
|
|
group_data['vars'] = group_obj.variables
|
|
inventory_data[name] = group_data
|
|
# Save ungrouped hosts
|
|
inventory_data['ungrouped'] = OrderedDict([])
|
|
if ungrouped_hosts:
|
|
inventory_data['ungrouped']['hosts'] = list(ungrouped_hosts)
|
|
return inventory_data
|
|
|
|
|
|
def dict_to_mem_data(data, inventory=None):
|
|
'''
|
|
In-place operation on `inventory`, adds contents from `data` to the
|
|
in-memory representation of memory.
|
|
May be destructive on `data`
|
|
'''
|
|
assert isinstance(data, dict), 'Expected dict, received {}'.format(type(data))
|
|
if inventory is None:
|
|
inventory = MemInventory()
|
|
|
|
_meta = data.pop('_meta', {})
|
|
|
|
for k,v in data.iteritems():
|
|
group = inventory.get_group(k)
|
|
if not group:
|
|
continue
|
|
|
|
# Load group hosts/vars/children from a dictionary.
|
|
if isinstance(v, dict):
|
|
# Process hosts within a group.
|
|
hosts = v.get('hosts', {})
|
|
if isinstance(hosts, dict):
|
|
for hk, hv in hosts.iteritems():
|
|
host = inventory.get_host(hk)
|
|
if not host:
|
|
continue
|
|
if isinstance(hv, dict):
|
|
host.variables.update(hv)
|
|
else:
|
|
logger.warning('Expected dict of vars for '
|
|
'host "%s", got %s instead',
|
|
hk, str(type(hv)))
|
|
group.add_host(host)
|
|
elif isinstance(hosts, (list, tuple)):
|
|
for hk in hosts:
|
|
host = inventory.get_host(hk)
|
|
if not host:
|
|
continue
|
|
group.add_host(host)
|
|
else:
|
|
logger.warning('Expected dict or list of "hosts" for '
|
|
'group "%s", got %s instead', k,
|
|
str(type(hosts)))
|
|
# Process group variables.
|
|
vars = v.get('vars', {})
|
|
if isinstance(vars, dict):
|
|
group.variables.update(vars)
|
|
else:
|
|
logger.warning('Expected dict of vars for '
|
|
'group "%s", got %s instead',
|
|
k, str(type(vars)))
|
|
# Process child groups.
|
|
children = v.get('children', [])
|
|
if isinstance(children, (list, tuple)):
|
|
for c in children:
|
|
child = inventory.get_group(c, inventory.all_group, child=True)
|
|
if child and c != 'ungrouped':
|
|
group.add_child_group(child)
|
|
else:
|
|
logger.warning('Expected list of children for '
|
|
'group "%s", got %s instead',
|
|
k, str(type(children)))
|
|
|
|
# Load host names from a list.
|
|
elif isinstance(v, (list, tuple)):
|
|
for h in v:
|
|
host = inventory.get_host(h)
|
|
if not host:
|
|
continue
|
|
group.add_host(host)
|
|
else:
|
|
logger.warning('')
|
|
logger.warning('Expected dict or list for group "%s", '
|
|
'got %s instead', k, str(type(v)))
|
|
|
|
if k not in ['all', 'ungrouped']:
|
|
inventory.all_group.add_child_group(group)
|
|
|
|
if _meta:
|
|
for k,v in inventory.all_group.all_hosts.iteritems():
|
|
meta_hostvars = _meta['hostvars'].get(k, {})
|
|
if isinstance(meta_hostvars, dict):
|
|
v.variables.update(meta_hostvars)
|
|
else:
|
|
logger.warning('Expected dict of vars for '
|
|
'host "%s", got %s instead',
|
|
k, str(type(meta_hostvars)))
|
|
|
|
return inventory
|