Merge pull request #6120 from AlanCoding/ansible-inventory

Inventory updates through `ansible-inventory`
This commit is contained in:
Alan Rominger
2017-05-02 16:19:58 -04:00
committed by GitHub
20 changed files with 1232 additions and 2057 deletions

View File

@@ -1462,7 +1462,7 @@ class InventorySourceOptionsSerializer(BaseSerializer):
class Meta:
fields = ('*', 'source', 'source_path', 'source_script', 'source_vars', 'credential',
'source_regions', 'instance_filters', 'group_by', 'overwrite', 'overwrite_vars',
'timeout')
'timeout', 'verbosity')
def get_related(self, obj):
res = super(InventorySourceOptionsSerializer, self).get_related(obj)

View File

@@ -2363,7 +2363,7 @@ class InventorySourceUpdateView(RetrieveAPIView):
def post(self, request, *args, **kwargs):
obj = self.get_object()
if obj.source == 'file' and obj.scm_project_id is not None:
if obj.source == 'scm':
raise PermissionDenied(detail=_(
'Update the project `{}` in order to update this inventory source.'.format(
obj.scm_project.name)))

View File

@@ -2,32 +2,34 @@
# All Rights Reserved.
# Python
import glob
import json
import logging
from optparse import make_option
import os
import re
import shlex
import string
import subprocess
import sys
import time
import traceback
# PyYAML
import yaml
import shutil
# Django
from django.conf import settings
from django.core.management.base import NoArgsCommand, CommandError
from django.core.exceptions import ImproperlyConfigured
from django.db import connection, transaction
from django.utils.encoding import smart_text
# AWX
from awx.main.models import * # noqa
from awx.main.task_engine import TaskEnhancer
from awx.main.utils import ignore_inventory_computed_fields, check_proot_installed, wrap_args_with_proot
from awx.main.utils import (
ignore_inventory_computed_fields,
check_proot_installed,
wrap_args_with_proot,
build_proot_temp_dir
)
from awx.main.utils.mem_inventory import MemInventory, dict_to_mem_data
from awx.main.signals import disable_activity_stream
logger = logging.getLogger('awx.main.commands.inventory_import')
@@ -49,338 +51,111 @@ Demo mode free license count exceeded, would bring available instances to %(new_
See http://www.ansible.com/renew for licensing information.'''
class MemObject(object):
def functioning_dir(path):
if os.path.isdir(path):
return path
return os.path.dirname(path)
class AnsibleInventoryLoader(object):
'''
Common code shared between in-memory groups and hosts.
Given executable `source` (directory, executable, or file) this will
use the ansible-inventory CLI utility to convert it into in-memory
representational objects. Example:
/usr/bin/ansible/ansible-inventory -i hosts --list
If it fails to find this, it uses the backported script instead
'''
def __init__(self, name, source_dir):
assert name, 'no name'
assert source_dir, 'no source dir'
self.name = name
self.source_dir = source_dir
def load_vars(self, base_path):
all_vars = {}
files_found = 0
for suffix in ('', '.yml', '.yaml', '.json'):
path = ''.join([base_path, suffix]).encode("utf-8")
if not os.path.exists(path):
continue
if not os.path.isfile(path):
continue
files_found += 1
if files_found > 1:
raise RuntimeError('Multiple variable files found. There should only be one. %s ' % self.name)
vars_name = os.path.basename(os.path.dirname(path))
logger.debug('Loading %s from %s', vars_name, path)
try:
v = yaml.safe_load(file(path, 'r').read())
if hasattr(v, 'items'): # is a dict
all_vars.update(v)
except yaml.YAMLError as e:
if hasattr(e, 'problem_mark'):
logger.error('Invalid YAML in %s:%s col %s', path,
e.problem_mark.line + 1,
e.problem_mark.column + 1)
else:
logger.error('Error loading YAML from %s', path)
raise
return all_vars
class MemGroup(MemObject):
'''
In-memory representation of an inventory group.
'''
def __init__(self, name, source_dir):
super(MemGroup, self).__init__(name, source_dir)
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 = {}
group_vars = os.path.join(source_dir, 'group_vars', self.name)
self.variables = self.load_vars(group_vars)
logger.debug('Loaded group: %s', self.name)
def child_group_by_name(self, name, loader):
if name == 'all':
return
logger.debug('Looking for %s as child group of %s', name, self.name)
# slight hack here, passing in 'self' for all_group but child=True won't use it
group = loader.get_group(name, self, child=True)
if group:
# don't add to child groups if already there
for g in self.children:
if g.name == name:
return g
logger.debug('Adding child group %s to group %s', group.name, self.name)
self.children.append(group)
return group
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, source_dir, port=None):
super(MemHost, self).__init__(name, source_dir)
self.variables = {}
self.instance_id = None
self.name = name
if port:
self.variables['ansible_ssh_port'] = port
host_vars = os.path.join(source_dir, 'host_vars', name)
self.variables.update(self.load_vars(host_vars))
logger.debug('Loaded host: %s', self.name)
class BaseLoader(object):
'''
Common functions for an inventory loader from a given source.
'''
def __init__(self, source, all_group=None, group_filter_re=None, host_filter_re=None, is_custom=False):
def __init__(self, source, group_filter_re=None, host_filter_re=None, is_custom=False):
self.source = source
self.source_dir = os.path.dirname(self.source)
self.all_group = all_group or MemGroup('all', self.source_dir)
self.is_custom = is_custom
self.tmp_private_dir = None
self.method = 'ansible-inventory'
self.group_filter_re = group_filter_re
self.host_filter_re = host_filter_re
self.ipv6_port_re = re.compile(r'^\[([A-Fa-f0-9:]{3,})\]:(\d+?)$')
self.is_custom = is_custom
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 = self.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
def build_env(self):
# Use ansible venv if it's available and setup to use
env = dict(os.environ.items())
if settings.ANSIBLE_USE_VENV:
env['VIRTUAL_ENV'] = settings.ANSIBLE_VENV_PATH
env['PATH'] = os.path.join(settings.ANSIBLE_VENV_PATH, "bin") + ":" + env['PATH']
venv_libdir = os.path.join(settings.ANSIBLE_VENV_PATH, "lib")
env.pop('PYTHONPATH', None) # default to none if no python_ver matches
for python_ver in ["python2.7", "python2.6"]:
if os.path.isdir(os.path.join(venv_libdir, python_ver)):
env['PYTHONPATH'] = os.path.join(venv_libdir, python_ver, "site-packages") + ":"
break
return env
def get_base_args(self):
# get ansible-inventory absolute path for running in bubblewrap/proot, in Popen
for path in os.environ["PATH"].split(os.pathsep):
potential_path = os.path.join(path.strip('"'), 'ansible-inventory')
if os.path.isfile(potential_path) and os.access(potential_path, os.X_OK):
return [potential_path, '-i', self.source]
# ansible-inventory was not found, look for backported module
abs_module_path = os.path.abspath(os.path.join(
os.path.dirname(__file__), '..', '..', '..', 'plugins',
'ansible_inventory', 'backport.py'))
self.method = 'ansible-inventory backport'
if not os.path.exists(abs_module_path):
raise ImproperlyConfigured('Can not find inventory module')
return [abs_module_path, '-i', self.source]
def get_proot_args(self, cmd, env):
source_dir = functioning_dir(self.source)
cwd = os.getcwd()
if not check_proot_installed():
raise RuntimeError("proot is not installed but is configured for use")
kwargs = {}
if self.is_custom:
# use source's tmp dir for proot, task manager will delete folder
logger.debug("Using provided directory '{}' for isolation.".format(source_dir))
kwargs['proot_temp_dir'] = source_dir
cwd = source_dir
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
host = None
if host_name not in self.all_group.all_hosts:
host = MemHost(host_name, self.source_dir, port)
self.all_group.all_hosts[host_name] = host
return self.all_group.all_hosts[host_name]
# we can not safely store tmp data in source dir or trust script contents
if env['AWX_PRIVATE_DATA_DIR']:
# If this is non-blank, file credentials are being used and we need access
private_data_dir = functioning_dir(env['AWX_PRIVATE_DATA_DIR'])
logger.debug("Using private credential data in '{}'.".format(private_data_dir))
kwargs['private_data_dir'] = private_data_dir
self.tmp_private_dir = build_proot_temp_dir()
logger.debug("Using fresh temporary directory '{}' for isolation.".format(self.tmp_private_dir))
kwargs['proot_temp_dir'] = self.tmp_private_dir
# Run from source's location so that custom script contents are in `show_paths`
cwd = functioning_dir(self.source)
logger.debug("Running from `{}` working directory.".format(cwd))
def get_hosts(self, name):
'''
Return iterator over one or more MemHost instances from host name or
host pattern.
'''
def iternest(*args):
if args:
for i in args[0]:
for j in iternest(*args[1:]):
yield ''.join([str(i), j])
else:
yield ''
if self.ipv6_port_re.match(name):
yield self.get_host(name)
return
pattern_re = re.compile(r'(\[(?:(?:\d+\:\d+)|(?:[A-Za-z]\:[A-Za-z]))(?:\:\d+)??\])')
iters = []
for s in re.split(pattern_re, name):
if re.match(pattern_re, s):
start, end, step = (s[1:-1] + ':1').split(':')[:3]
mapfunc = str
if start in string.ascii_letters:
istart = string.ascii_letters.index(start)
iend = string.ascii_letters.index(end) + 1
if istart >= iend:
raise ValueError('invalid host range specified')
seq = string.ascii_letters[istart:iend:int(step)]
else:
if start[0] == '0' and len(start) > 1:
if len(start) != len(end):
raise ValueError('invalid host range specified')
mapfunc = lambda x: str(x).zfill(len(start))
seq = xrange(int(start), int(end) + 1, int(step))
iters.append(map(mapfunc, seq))
elif re.search(r'[\[\]]', s):
raise ValueError('invalid host range specified')
elif s:
iters.append([s])
for iname in iternest(*iters):
yield self.get_host(iname)
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 == 'all':
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 = MemGroup(name, self.source_dir)
if not child:
all_group.add_child_group(group)
self.all_group.all_groups[name] = group
return self.all_group.all_groups[name]
def load(self):
raise NotImplementedError
class IniLoader(BaseLoader):
'''
Loader to read inventory from an INI-formatted text file.
'''
def load(self):
logger.info('Reading INI source: %s', self.source)
group = self.all_group
input_mode = 'host'
for line in file(self.source, 'r'):
line = line.split('#')[0].strip()
if not line:
continue
elif line.startswith('[') and line.endswith(']'):
# Mode change, possible new group name
line = line[1:-1].strip()
if line.endswith(':vars'):
input_mode = 'vars'
line = line[:-5]
elif line.endswith(':children'):
input_mode = 'children'
line = line[:-9]
else:
input_mode = 'host'
group = self.get_group(line)
elif group:
# If group is None, we are skipping this group and shouldn't
# capture any children/variables/hosts under it.
# Add hosts with inline variables, or variables/children to
# an existing group.
tokens = shlex.split(line)
if input_mode == 'host':
for host in self.get_hosts(tokens[0]):
if not host:
continue
if len(tokens) > 1:
for t in tokens[1:]:
k,v = t.split('=', 1)
host.variables[k] = v
group.add_host(host)
elif input_mode == 'children':
group.child_group_by_name(line, self)
elif input_mode == 'vars':
for t in tokens:
k, v = t.split('=', 1)
group.variables[k] = v
# TODO: expansion patterns are probably not going to be supported. YES THEY ARE!
# from API documentation:
#
# if called with --list, inventory outputs like so:
#
# {
# "databases" : {
# "hosts" : [ "host1.example.com", "host2.example.com" ],
# "vars" : {
# "a" : true
# }
# },
# "webservers" : [ "host2.example.com", "host3.example.com" ],
# "atlanta" : {
# "hosts" : [ "host1.example.com", "host4.example.com", "host5.example.com" ],
# "vars" : {
# "b" : false
# },
# "children": [ "marietta", "5points" ],
# },
# "marietta" : [ "host6.example.com" ],
# "5points" : [ "host7.example.com" ]
# }
#
# if called with --host <host_record_name> outputs JSON for that host
class ExecutableJsonLoader(BaseLoader):
return wrap_args_with_proot(cmd, cwd, **kwargs)
def command_to_json(self, cmd):
data = {}
stdout, stderr = '', ''
try:
if self.is_custom and getattr(settings, 'AWX_PROOT_ENABLED', False):
if not check_proot_installed():
raise RuntimeError("proot is not installed but is configured for use")
kwargs = {'proot_temp_dir': self.source_dir} # TODO: Remove proot dir
cmd = wrap_args_with_proot(cmd, self.source_dir, **kwargs)
# Use ansible venv if it's available and setup to use
env = dict(os.environ.items())
if settings.ANSIBLE_USE_VENV:
env['VIRTUAL_ENV'] = settings.ANSIBLE_VENV_PATH
env['PATH'] = os.path.join(settings.ANSIBLE_VENV_PATH, "bin") + ":" + env['PATH']
venv_libdir = os.path.join(settings.ANSIBLE_VENV_PATH, "lib")
env.pop('PYTHONPATH', None) # default to none if no python_ver matches
for python_ver in ["python2.7", "python2.6"]:
if os.path.isdir(os.path.join(venv_libdir, python_ver)):
env['PYTHONPATH'] = os.path.join(venv_libdir, python_ver, "site-packages") + ":"
break
env = self.build_env()
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
stdout, stderr = proc.communicate()
if proc.returncode != 0:
raise RuntimeError('%r failed (rc=%d) with output: %s' % (cmd, proc.returncode, stderr))
try:
data = json.loads(stdout)
except ValueError:
if ((self.is_custom or 'AWX_PRIVATE_DATA_DIR' in env) and
getattr(settings, 'AWX_PROOT_ENABLED', False)):
cmd = self.get_proot_args(cmd, env)
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
stdout, stderr = proc.communicate()
if self.tmp_private_dir:
shutil.rmtree(self.tmp_private_dir, True)
if proc.returncode != 0:
raise RuntimeError('%s failed (rc=%d) with output:\n%s' % (self.method, proc.returncode, stderr))
elif 'file not found' in stderr:
# File not visible to inventory module due proot (exit code 0, Ansible behavior)
raise IOError('Inventory module failed to find source {} with output:\n{}.'.format(self.source, stderr))
try:
data = json.loads(stdout)
if not isinstance(data, dict):
raise TypeError('Returned JSON must be a dictionary, got %s instead' % str(type(data)))
except:
logger.error('Failed to load JSON from: %s', stdout)
@@ -388,93 +163,21 @@ class ExecutableJsonLoader(BaseLoader):
return data
def load(self):
logger.info('Reading executable JSON source: %s', self.source)
data = self.command_to_json([self.source, '--list'])
_meta = data.pop('_meta', {})
base_args = self.get_base_args()
logger.info('Reading Ansible inventory source: %s', self.source)
data = self.command_to_json(base_args + ['--list'])
for k,v in data.iteritems():
group = self.get_group(k)
if not group:
continue
logger.info('Processing JSON output...')
inventory = MemInventory(
group_filter_re=self.group_filter_re, host_filter_re=self.host_filter_re)
inventory = dict_to_mem_data(data, inventory=inventory)
# 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 = self.get_host(hk)
if not host:
continue
if isinstance(hv, dict):
host.variables.update(hv)
else:
self.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 = self.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:
self.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 = self.get_group(c, self.all_group, child=True)
if child:
group.add_child_group(child)
else:
self.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 = self.get_host(h)
if not host:
continue
group.add_host(host)
else:
logger.warning('')
self.logger.warning('Expected dict or list for group "%s", '
'got %s instead', k, str(type(v)))
if k != 'all':
self.all_group.add_child_group(group)
# Invoke the executable once for each host name we've built up
# to set their variables
for k,v in self.all_group.all_hosts.iteritems():
if 'hostvars' not in _meta:
data = self.command_to_json([self.source, '--host', k.encode("utf-8")])
else:
data = _meta['hostvars'].get(k, {})
if isinstance(data, dict):
v.variables.update(data)
else:
self.logger.warning('Expected dict of vars for '
'host "%s", got %s instead',
k, str(type(data)))
return inventory
def load_inventory_source(source, all_group=None, group_filter_re=None,
host_filter_re=None, exclude_empty_groups=False, is_custom=False):
def load_inventory_source(source, group_filter_re=None,
host_filter_re=None, exclude_empty_groups=False,
is_custom=False):
'''
Load inventory from given source directory or file.
'''
@@ -483,41 +186,25 @@ def load_inventory_source(source, all_group=None, group_filter_re=None,
source = source.replace('azure.py', 'windows_azure.py')
source = source.replace('satellite6.py', 'foreman.py')
source = source.replace('vmware.py', 'vmware_inventory.py')
logger.debug('Analyzing type of source: %s', source)
original_all_group = all_group
if not os.path.exists(source):
raise IOError('Source does not exist: %s' % source)
source = os.path.join(os.getcwd(), os.path.dirname(source),
os.path.basename(source))
source = os.path.normpath(os.path.abspath(source))
if os.path.isdir(source):
all_group = all_group or MemGroup('all', source)
for filename in glob.glob(os.path.join(source, '*')):
if filename.endswith(".ini") or os.path.isdir(filename):
continue
load_inventory_source(filename, all_group, group_filter_re,
host_filter_re, is_custom=is_custom)
else:
all_group = all_group or MemGroup('all', os.path.dirname(source))
if os.access(source, os.X_OK):
ExecutableJsonLoader(source, all_group, group_filter_re, host_filter_re, is_custom).load()
else:
IniLoader(source, all_group, group_filter_re, host_filter_re).load()
inventory = AnsibleInventoryLoader(
source=source,
group_filter_re=group_filter_re,
host_filter_re=host_filter_re,
is_custom=is_custom).load()
logger.debug('Finished loading from source: %s', source)
# Exclude groups that are completely empty.
if original_all_group is None and exclude_empty_groups:
for name, group in 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 all_group.all_groups[name]
if original_all_group is None:
logger.info('Loaded %d groups, %d hosts', len(all_group.all_groups),
len(all_group.all_hosts))
return all_group
if exclude_empty_groups:
inventory.delete_empty_groups()
logger.info('Loaded %d groups, %d hosts', len(inventory.all_group.all_groups),
len(inventory.all_group.all_hosts))
return inventory.all_group
class Command(NoArgsCommand):
@@ -573,22 +260,10 @@ class Command(NoArgsCommand):
'specified as "foo.bar" to traverse nested dicts.'),
)
def init_logging(self):
def set_logging_level(self):
log_levels = dict(enumerate([logging.WARNING, logging.INFO,
logging.DEBUG, 0]))
self.logger = logging.getLogger('awx.main.commands.inventory_import')
self.logger.setLevel(log_levels.get(self.verbosity, 0))
handler = logging.StreamHandler()
class Formatter(logging.Formatter):
def format(self, record):
record.relativeSeconds = record.relativeCreated / 1000.0
return logging.Formatter.format(self, record)
formatter = Formatter('%(relativeSeconds)9.3f %(levelname)-8s %(message)s')
handler.setFormatter(formatter)
self.logger.addHandler(handler)
self.logger.propagate = False
logger.setLevel(log_levels.get(self.verbosity, 0))
def _get_instance_id(self, from_dict, default=''):
'''
@@ -650,8 +325,8 @@ class Command(NoArgsCommand):
raise CommandError('Inventory with %s = %s cannot be found' % q.items()[0])
except Inventory.MultipleObjectsReturned:
raise CommandError('Inventory with %s = %s returned multiple results' % q.items()[0])
self.logger.info('Updating inventory %d: %s' % (self.inventory.pk,
self.inventory.name))
logger.info('Updating inventory %d: %s' % (self.inventory.pk,
self.inventory.name))
# Load inventory source if specified via environment variable (when
# inventory_import is called from an InventoryUpdate task).
@@ -727,8 +402,8 @@ class Command(NoArgsCommand):
for mem_host in self.all_group.all_hosts.values():
instance_id = self._get_instance_id(mem_host.variables)
if not instance_id:
self.logger.warning('Host "%s" has no "%s" variable',
mem_host.name, self.instance_id_var)
logger.warning('Host "%s" has no "%s" variable',
mem_host.name, self.instance_id_var)
continue
mem_host.instance_id = instance_id
self.mem_instance_id_map[instance_id] = mem_host.name
@@ -768,11 +443,11 @@ class Command(NoArgsCommand):
for host in hosts_qs.filter(pk__in=del_pks):
host_name = host.name
host.delete()
self.logger.info('Deleted host "%s"', host_name)
logger.info('Deleted host "%s"', host_name)
if settings.SQL_DEBUG:
self.logger.warning('host deletions took %d queries for %d hosts',
len(connection.queries) - queries_before,
len(all_del_pks))
logger.warning('host deletions took %d queries for %d hosts',
len(connection.queries) - queries_before,
len(all_del_pks))
def _delete_groups(self):
'''
@@ -799,11 +474,11 @@ class Command(NoArgsCommand):
group_name = group.name
with ignore_inventory_computed_fields():
group.delete()
self.logger.info('Group "%s" deleted', group_name)
logger.info('Group "%s" deleted', group_name)
if settings.SQL_DEBUG:
self.logger.warning('group deletions took %d queries for %d groups',
len(connection.queries) - queries_before,
len(all_del_pks))
logger.warning('group deletions took %d queries for %d groups',
len(connection.queries) - queries_before,
len(all_del_pks))
def _delete_group_children_and_hosts(self):
'''
@@ -831,8 +506,8 @@ class Command(NoArgsCommand):
for db_child in db_children.filter(pk__in=child_group_pks):
group_group_count += 1
db_group.children.remove(db_child)
self.logger.info('Group "%s" removed from group "%s"',
db_child.name, db_group.name)
logger.info('Group "%s" removed from group "%s"',
db_child.name, db_group.name)
# FIXME: Inventory source group relationships
# Delete group/host relationships not present in imported data.
db_hosts = db_group.hosts
@@ -859,12 +534,12 @@ class Command(NoArgsCommand):
if db_host not in db_group.hosts.all():
continue
db_group.hosts.remove(db_host)
self.logger.info('Host "%s" removed from group "%s"',
db_host.name, db_group.name)
logger.info('Host "%s" removed from group "%s"',
db_host.name, db_group.name)
if settings.SQL_DEBUG:
self.logger.warning('group-group and group-host deletions took %d queries for %d relationships',
len(connection.queries) - queries_before,
group_group_count + group_host_count)
logger.warning('group-group and group-host deletions took %d queries for %d relationships',
len(connection.queries) - queries_before,
group_group_count + group_host_count)
def _update_inventory(self):
'''
@@ -884,11 +559,11 @@ class Command(NoArgsCommand):
all_obj.variables = json.dumps(db_variables)
all_obj.save(update_fields=['variables'])
if self.overwrite_vars:
self.logger.info('%s variables replaced from "all" group', all_name.capitalize())
logger.info('%s variables replaced from "all" group', all_name.capitalize())
else:
self.logger.info('%s variables updated from "all" group', all_name.capitalize())
logger.info('%s variables updated from "all" group', all_name.capitalize())
else:
self.logger.info('%s variables unmodified', all_name.capitalize())
logger.info('%s variables unmodified', all_name.capitalize())
def _create_update_groups(self):
'''
@@ -920,11 +595,11 @@ class Command(NoArgsCommand):
group.variables = json.dumps(db_variables)
group.save(update_fields=['variables'])
if self.overwrite_vars:
self.logger.info('Group "%s" variables replaced', group.name)
logger.info('Group "%s" variables replaced', group.name)
else:
self.logger.info('Group "%s" variables updated', group.name)
logger.info('Group "%s" variables updated', group.name)
else:
self.logger.info('Group "%s" variables unmodified', group.name)
logger.info('Group "%s" variables unmodified', group.name)
existing_group_names.add(group.name)
self._batch_add_m2m(self.inventory_source.groups, group)
for group_name in all_group_names:
@@ -932,13 +607,13 @@ class Command(NoArgsCommand):
continue
mem_group = self.all_group.all_groups[group_name]
group = self.inventory.groups.create(name=group_name, variables=json.dumps(mem_group.variables), description='imported')
self.logger.info('Group "%s" added', group.name)
logger.info('Group "%s" added', group.name)
self._batch_add_m2m(self.inventory_source.groups, group)
self._batch_add_m2m(self.inventory_source.groups, flush=True)
if settings.SQL_DEBUG:
self.logger.warning('group updates took %d queries for %d groups',
len(connection.queries) - queries_before,
len(self.all_group.all_groups))
logger.warning('group updates took %d queries for %d groups',
len(connection.queries) - queries_before,
len(self.all_group.all_groups))
def _update_db_host_from_mem_host(self, db_host, mem_host):
# Update host variables.
@@ -971,24 +646,24 @@ class Command(NoArgsCommand):
if update_fields:
db_host.save(update_fields=update_fields)
if 'name' in update_fields:
self.logger.info('Host renamed from "%s" to "%s"', old_name, mem_host.name)
logger.info('Host renamed from "%s" to "%s"', old_name, mem_host.name)
if 'instance_id' in update_fields:
if old_instance_id:
self.logger.info('Host "%s" instance_id updated', mem_host.name)
logger.info('Host "%s" instance_id updated', mem_host.name)
else:
self.logger.info('Host "%s" instance_id added', mem_host.name)
logger.info('Host "%s" instance_id added', mem_host.name)
if 'variables' in update_fields:
if self.overwrite_vars:
self.logger.info('Host "%s" variables replaced', mem_host.name)
logger.info('Host "%s" variables replaced', mem_host.name)
else:
self.logger.info('Host "%s" variables updated', mem_host.name)
logger.info('Host "%s" variables updated', mem_host.name)
else:
self.logger.info('Host "%s" variables unmodified', mem_host.name)
logger.info('Host "%s" variables unmodified', mem_host.name)
if 'enabled' in update_fields:
if enabled:
self.logger.info('Host "%s" is now enabled', mem_host.name)
logger.info('Host "%s" is now enabled', mem_host.name)
else:
self.logger.info('Host "%s" is now disabled', mem_host.name)
logger.info('Host "%s" is now disabled', mem_host.name)
self._batch_add_m2m(self.inventory_source.hosts, db_host)
def _create_update_hosts(self):
@@ -1062,17 +737,17 @@ class Command(NoArgsCommand):
host_attrs['instance_id'] = instance_id
db_host = self.inventory.hosts.create(**host_attrs)
if enabled is False:
self.logger.info('Host "%s" added (disabled)', mem_host_name)
logger.info('Host "%s" added (disabled)', mem_host_name)
else:
self.logger.info('Host "%s" added', mem_host_name)
logger.info('Host "%s" added', mem_host_name)
self._batch_add_m2m(self.inventory_source.hosts, db_host)
self._batch_add_m2m(self.inventory_source.hosts, flush=True)
if settings.SQL_DEBUG:
self.logger.warning('host updates took %d queries for %d hosts',
len(connection.queries) - queries_before,
len(self.all_group.all_hosts))
logger.warning('host updates took %d queries for %d hosts',
len(connection.queries) - queries_before,
len(self.all_group.all_hosts))
def _create_update_group_children(self):
'''
@@ -1092,14 +767,14 @@ class Command(NoArgsCommand):
child_names = all_child_names[offset2:(offset2 + self._batch_size)]
db_children_qs = self.inventory.groups.filter(name__in=child_names)
for db_child in db_children_qs.filter(children__id=db_group.id):
self.logger.info('Group "%s" already child of group "%s"', db_child.name, db_group.name)
logger.info('Group "%s" already child of group "%s"', db_child.name, db_group.name)
for db_child in db_children_qs.exclude(children__id=db_group.id):
self._batch_add_m2m(db_group.children, db_child)
self.logger.info('Group "%s" added as child of "%s"', db_child.name, db_group.name)
logger.info('Group "%s" added as child of "%s"', db_child.name, db_group.name)
self._batch_add_m2m(db_group.children, flush=True)
if settings.SQL_DEBUG:
self.logger.warning('Group-group updates took %d queries for %d group-group relationships',
len(connection.queries) - queries_before, group_group_count)
logger.warning('Group-group updates took %d queries for %d group-group relationships',
len(connection.queries) - queries_before, group_group_count)
def _create_update_group_hosts(self):
# For each host in a mem group, add it to the parent(s) to which it
@@ -1118,23 +793,23 @@ class Command(NoArgsCommand):
host_names = all_host_names[offset2:(offset2 + self._batch_size)]
db_hosts_qs = self.inventory.hosts.filter(name__in=host_names)
for db_host in db_hosts_qs.filter(groups__id=db_group.id):
self.logger.info('Host "%s" already in group "%s"', db_host.name, db_group.name)
logger.info('Host "%s" already in group "%s"', db_host.name, db_group.name)
for db_host in db_hosts_qs.exclude(groups__id=db_group.id):
self._batch_add_m2m(db_group.hosts, db_host)
self.logger.info('Host "%s" added to group "%s"', db_host.name, db_group.name)
logger.info('Host "%s" added to group "%s"', db_host.name, db_group.name)
all_instance_ids = sorted([h.instance_id for h in mem_group.hosts if h.instance_id])
for offset2 in xrange(0, len(all_instance_ids), self._batch_size):
instance_ids = all_instance_ids[offset2:(offset2 + self._batch_size)]
db_hosts_qs = self.inventory.hosts.filter(instance_id__in=instance_ids)
for db_host in db_hosts_qs.filter(groups__id=db_group.id):
self.logger.info('Host "%s" already in group "%s"', db_host.name, db_group.name)
logger.info('Host "%s" already in group "%s"', db_host.name, db_group.name)
for db_host in db_hosts_qs.exclude(groups__id=db_group.id):
self._batch_add_m2m(db_group.hosts, db_host)
self.logger.info('Host "%s" added to group "%s"', db_host.name, db_group.name)
logger.info('Host "%s" added to group "%s"', db_host.name, db_group.name)
self._batch_add_m2m(db_group.hosts, flush=True)
if settings.SQL_DEBUG:
self.logger.warning('Group-host updates took %d queries for %d group-host relationships',
len(connection.queries) - queries_before, group_host_count)
logger.warning('Group-host updates took %d queries for %d group-host relationships',
len(connection.queries) - queries_before, group_host_count)
def load_into_database(self):
'''
@@ -1159,14 +834,14 @@ class Command(NoArgsCommand):
def check_license(self):
license_info = TaskEnhancer().validate_enhancements()
if license_info.get('license_key', 'UNLICENSED') == 'UNLICENSED':
self.logger.error(LICENSE_NON_EXISTANT_MESSAGE)
logger.error(LICENSE_NON_EXISTANT_MESSAGE)
raise CommandError('No Tower license found!')
available_instances = license_info.get('available_instances', 0)
free_instances = license_info.get('free_instances', 0)
time_remaining = license_info.get('time_remaining', 0)
new_count = Host.objects.active_count()
if time_remaining <= 0 and not license_info.get('demo', False):
self.logger.error(LICENSE_EXPIRED_MESSAGE)
logger.error(LICENSE_EXPIRED_MESSAGE)
raise CommandError("License has expired!")
if free_instances < 0:
d = {
@@ -1174,9 +849,9 @@ class Command(NoArgsCommand):
'available_instances': available_instances,
}
if license_info.get('demo', False):
self.logger.error(DEMO_LICENSE_MESSAGE % d)
logger.error(DEMO_LICENSE_MESSAGE % d)
else:
self.logger.error(LICENSE_MESSAGE % d)
logger.error(LICENSE_MESSAGE % d)
raise CommandError('License count exceeded!')
def mark_license_failure(self, save=True):
@@ -1185,7 +860,7 @@ class Command(NoArgsCommand):
def handle_noargs(self, **options):
self.verbosity = int(options.get('verbosity', 1))
self.init_logging()
self.set_logging_level()
self.inventory_name = options.get('inventory_name', None)
self.inventory_id = options.get('inventory_id', None)
self.overwrite = bool(options.get('overwrite', False))
@@ -1224,7 +899,7 @@ class Command(NoArgsCommand):
TODO: Remove this deprecation when we remove support for rax.py
'''
if self.source == "rax.py":
self.logger.info("Rackspace inventory sync is Deprecated in Tower 3.1.0 and support for Rackspace will be removed in a future release.")
logger.info("Rackspace inventory sync is Deprecated in Tower 3.1.0 and support for Rackspace will be removed in a future release.")
begin = time.time()
self.load_inventory_from_database()
@@ -1249,7 +924,7 @@ class Command(NoArgsCommand):
self.inventory_update.save()
# Load inventory from source.
self.all_group = load_inventory_source(self.source, None,
self.all_group = load_inventory_source(self.source,
self.group_filter_re,
self.host_filter_re,
self.exclude_empty_groups,
@@ -1262,7 +937,7 @@ class Command(NoArgsCommand):
with transaction.atomic():
# Merge/overwrite inventory into database.
if settings.SQL_DEBUG:
self.logger.warning('loading into database...')
logger.warning('loading into database...')
with ignore_inventory_computed_fields():
if getattr(settings, 'ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC', True):
self.load_into_database()
@@ -1273,8 +948,8 @@ class Command(NoArgsCommand):
queries_before2 = len(connection.queries)
self.inventory.update_computed_fields()
if settings.SQL_DEBUG:
self.logger.warning('update computed fields took %d queries',
len(connection.queries) - queries_before2)
logger.warning('update computed fields took %d queries',
len(connection.queries) - queries_before2)
try:
self.check_license()
except CommandError as e:
@@ -1282,11 +957,11 @@ class Command(NoArgsCommand):
raise e
if settings.SQL_DEBUG:
self.logger.warning('Inventory import completed for %s in %0.1fs',
self.inventory_source.name, time.time() - begin)
logger.warning('Inventory import completed for %s in %0.1fs',
self.inventory_source.name, time.time() - begin)
else:
self.logger.info('Inventory import completed for %s in %0.1fs',
self.inventory_source.name, time.time() - begin)
logger.info('Inventory import completed for %s in %0.1fs',
self.inventory_source.name, time.time() - begin)
status = 'successful'
# If we're in debug mode, then log the queries and time
@@ -1294,9 +969,9 @@ class Command(NoArgsCommand):
if settings.SQL_DEBUG:
queries_this_import = connection.queries[queries_before:]
sqltime = sum(float(x['time']) for x in queries_this_import)
self.logger.warning('Inventory import required %d queries '
'taking %0.3fs', len(queries_this_import),
sqltime)
logger.warning('Inventory import required %d queries '
'taking %0.3fs', len(queries_this_import),
sqltime)
except Exception as e:
if isinstance(e, KeyboardInterrupt):
status = 'canceled'

View File

@@ -97,12 +97,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='inventorysource',
name='source',
field=models.CharField(default=b'', max_length=32, blank=True, choices=[(b'', 'Manual'), (b'file', 'File, Directory or Script Locally or in Project'), (b'rax', 'Rackspace Cloud Servers'), (b'ec2', 'Amazon EC2'), (b'gce', 'Google Compute Engine'), (b'azure', 'Microsoft Azure Classic (deprecated)'), (b'azure_rm', 'Microsoft Azure Resource Manager'), (b'vmware', 'VMware vCenter'), (b'satellite6', 'Red Hat Satellite 6'), (b'cloudforms', 'Red Hat CloudForms'), (b'openstack', 'OpenStack'), (b'custom', 'Custom Script')]),
field=models.CharField(default=b'', max_length=32, blank=True, choices=[(b'', 'Manual'), (b'file', 'File, Directory or Script'), (b'scm', 'Sourced from a project in Tower'), (b'rax', 'Rackspace Cloud Servers'), (b'ec2', 'Amazon EC2'), (b'gce', 'Google Compute Engine'), (b'azure', 'Microsoft Azure Classic (deprecated)'), (b'azure_rm', 'Microsoft Azure Resource Manager'), (b'vmware', 'VMware vCenter'), (b'satellite6', 'Red Hat Satellite 6'), (b'cloudforms', 'Red Hat CloudForms'), (b'openstack', 'OpenStack'), (b'custom', 'Custom Script')]),
),
migrations.AlterField(
model_name='inventoryupdate',
name='source',
field=models.CharField(default=b'', max_length=32, blank=True, choices=[(b'', 'Manual'), (b'file', 'File, Directory or Script Locally or in Project'), (b'rax', 'Rackspace Cloud Servers'), (b'ec2', 'Amazon EC2'), (b'gce', 'Google Compute Engine'), (b'azure', 'Microsoft Azure Classic (deprecated)'), (b'azure_rm', 'Microsoft Azure Resource Manager'), (b'vmware', 'VMware vCenter'), (b'satellite6', 'Red Hat Satellite 6'), (b'cloudforms', 'Red Hat CloudForms'), (b'openstack', 'OpenStack'), (b'custom', 'Custom Script')]),
field=models.CharField(default=b'', max_length=32, blank=True, choices=[(b'', 'Manual'), (b'file', 'File, Directory or Script'), (b'scm', 'Sourced from a project in Tower'), (b'rax', 'Rackspace Cloud Servers'), (b'ec2', 'Amazon EC2'), (b'gce', 'Google Compute Engine'), (b'azure', 'Microsoft Azure Classic (deprecated)'), (b'azure_rm', 'Microsoft Azure Resource Manager'), (b'vmware', 'VMware vCenter'), (b'satellite6', 'Red Hat Satellite 6'), (b'cloudforms', 'Red Hat CloudForms'), (b'openstack', 'OpenStack'), (b'custom', 'Custom Script')]),
),
migrations.AlterField(
model_name='inventorysource',
@@ -135,4 +135,16 @@ class Migration(migrations.Migration):
name='notificationtemplate',
unique_together=set([('organization', 'name')]),
),
# Add verbosity option to inventory updates
migrations.AddField(
model_name='inventorysource',
name='verbosity',
field=models.PositiveIntegerField(default=1, blank=True, choices=[(0, b'0 (WARNING)'), (1, b'1 (INFO)'), (2, b'2 (DEBUG)')]),
),
migrations.AddField(
model_name='inventoryupdate',
name='verbosity',
field=models.PositiveIntegerField(default=1, blank=True, choices=[(0, b'0 (WARNING)'), (1, b'1 (INFO)'), (2, b'2 (DEBUG)')]),
),
]

View File

@@ -743,7 +743,8 @@ class InventorySourceOptions(BaseModel):
SOURCE_CHOICES = [
('', _('Manual')),
('file', _('File, Directory or Script Locally or in Project')),
('file', _('File, Directory or Script')),
('scm', _('Sourced from a project in Tower')),
('rax', _('Rackspace Cloud Servers')),
('ec2', _('Amazon EC2')),
('gce', _('Google Compute Engine')),
@@ -756,6 +757,13 @@ class InventorySourceOptions(BaseModel):
('custom', _('Custom Script')),
]
# From the options of the Django management base command
INVENTORY_UPDATE_VERBOSITY_CHOICES = [
(0, '0 (WARNING)'),
(1, '1 (INFO)'),
(2, '2 (DEBUG)'),
]
# Use tools/scripts/get_ec2_filter_names.py to build this list.
INSTANCE_FILTER_NAMES = [
"architecture",
@@ -902,6 +910,11 @@ class InventorySourceOptions(BaseModel):
blank=True,
default=0,
)
verbosity = models.PositiveIntegerField(
choices=INVENTORY_UPDATE_VERBOSITY_CHOICES,
blank=True,
default=1,
)
@classmethod
def get_ec2_region_choices(cls):
@@ -1002,7 +1015,7 @@ class InventorySourceOptions(BaseModel):
if not self.source:
return None
cred = self.credential
if cred and self.source != 'custom':
if cred and self.source not in ('custom', 'scm'):
# If a credential was provided, it's important that it matches
# the actual inventory source being used (Amazon requires Amazon
# credentials; Rackspace requires Rackspace credentials; etc...)
@@ -1139,14 +1152,14 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
def _get_unified_job_field_names(cls):
return ['name', 'description', 'source', 'source_path', 'source_script', 'source_vars', 'schedule',
'credential', 'source_regions', 'instance_filters', 'group_by', 'overwrite', 'overwrite_vars',
'timeout', 'launch_type', 'scm_project_update',]
'timeout', 'verbosity', 'launch_type', 'scm_project_update',]
def save(self, *args, **kwargs):
# If update_fields has been specified, add our field names to it,
# if it hasn't been specified, then we're just doing a normal save.
update_fields = kwargs.get('update_fields', [])
is_new_instance = not bool(self.pk)
is_scm_type = self.scm_project_id is not None
is_scm_type = self.scm_project_id is not None and self.source == 'scm'
# Set name automatically. Include PK (or placeholder) to make sure the names are always unique.
replace_text = '__replace_%s__' % now()
@@ -1347,6 +1360,8 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin):
if (self.source not in ('custom', 'ec2') and
not (self.credential)):
return False
elif self.source in ('file', 'scm'):
return False
return True
'''

View File

@@ -1689,13 +1689,17 @@ class RunInventoryUpdate(BaseTask):
env['FOREMAN_INI_PATH'] = cloud_credential
elif inventory_update.source == 'cloudforms':
env['CLOUDFORMS_INI_PATH'] = cloud_credential
elif inventory_update.source == 'file':
elif inventory_update.source == 'scm':
# Parse source_vars to dict, update env.
env.update(parse_yaml_or_json(inventory_update.source_vars))
elif inventory_update.source == 'custom':
for env_k in inventory_update.source_vars_dict:
if str(env_k) not in env and str(env_k) not in settings.INV_ENV_VARIABLE_BLACKLIST:
env[str(env_k)] = unicode(inventory_update.source_vars_dict[env_k])
elif inventory_update.source == 'file':
raise NotImplementedError('Can not update file sources through the task system.')
# add private_data_files
env['AWX_PRIVATE_DATA_DIR'] = kwargs.get('private_data_dir', '')
return env
def build_args(self, inventory_update, **kwargs):
@@ -1759,7 +1763,7 @@ class RunInventoryUpdate(BaseTask):
getattr(settings, '%s_INSTANCE_ID_VAR' % src.upper()),
])
elif inventory_update.source == 'file':
elif inventory_update.source == 'scm':
args.append(inventory_update.get_actual_source_path())
elif inventory_update.source == 'custom':
runpath = tempfile.mkdtemp(prefix='ansible_tower_launch_')
@@ -1770,11 +1774,10 @@ class RunInventoryUpdate(BaseTask):
f.write(inventory_update.source_script.script.encode('utf-8'))
f.close()
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
args.append(runpath)
args.append(path)
args.append("--custom")
self.custom_dir_path.append(runpath)
verbosity = getattr(settings, 'INVENTORY_UPDATE_VERBOSITY', 1)
args.append('-v%d' % verbosity)
args.append('-v%d' % inventory_update.verbosity)
if settings.DEBUG:
args.append('--traceback')
return args

View File

@@ -1,890 +0,0 @@
#!/usr/bin/env python
# Python
import json
import optparse
inv_list = {
"ansible1.axialmarket.com": [
"ec2-54-226-227-106.compute-1.amazonaws.com"
],
"ansible2.axialmarket.com": [
"ec2-54-227-113-75.compute-1.amazonaws.com"
],
"app1new.axialmarket.com": [
"ec2-54-235-143-131.compute-1.amazonaws.com"
],
"app2new.axialmarket.com": [
"ec2-54-235-143-132.compute-1.amazonaws.com"
],
"app2t.axialmarket.com": [
"ec2-23-23-168-208.compute-1.amazonaws.com"
],
"app2t.dev.axialmarket.com": [
"ec2-23-23-168-208.compute-1.amazonaws.com"
],
"awx.axialmarket.com": [
"ec2-54-211-252-32.compute-1.amazonaws.com"
],
"axtdev2.axialmarket.com": [
"ec2-54-234-3-7.compute-1.amazonaws.com"
],
"backup1.axialmarket.com": [
"ec2-23-23-170-30.compute-1.amazonaws.com"
],
"bah.axialmarket.com": [
"ec2-107-20-176-139.compute-1.amazonaws.com"
],
"bennew.axialmarket.com": [
"ec2-54-243-146-75.compute-1.amazonaws.com"
],
"build0.axialmarket.com": [
"ec2-54-226-244-191.compute-1.amazonaws.com"
],
"cburke0.axialmarket.com": [
"ec2-54-226-100-117.compute-1.amazonaws.com"
],
"dabnew.axialmarket.com": [
"ec2-107-22-248-113.compute-1.amazonaws.com"
],
"dannew.axialmarket.com": [
"ec2-107-22-247-88.compute-1.amazonaws.com"
],
"de1-intenv.axialmarket.com": [
"ec2-54-224-92-80.compute-1.amazonaws.com"
],
"dev11-20120311": [
"dev11-20120311.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"dev11-20130828": [
"dev11-20130828.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"dev11-20130903-dab": [
"dev11-20130903-dab.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"firecrow.axialmarket.com": [
"ec2-54-227-30-105.compute-1.amazonaws.com"
],
"herby0.axialmarket.com": [
"ec2-174-129-140-30.compute-1.amazonaws.com"
],
"i-02966c7a": [
"ec2-23-21-57-109.compute-1.amazonaws.com"
],
"i-0485b47c": [
"ec2-23-23-168-208.compute-1.amazonaws.com"
],
"i-0805a578": [
"ec2-107-22-234-22.compute-1.amazonaws.com"
],
"i-0a1e4777": [
"ec2-75-101-129-169.compute-1.amazonaws.com"
],
"i-0e05a57e": [
"ec2-107-22-234-180.compute-1.amazonaws.com"
],
"i-116f5861": [
"ec2-54-235-143-162.compute-1.amazonaws.com"
],
"i-197edf79": [
"ec2-54-226-244-191.compute-1.amazonaws.com"
],
"i-26008355": [
"ec2-75-101-157-248.compute-1.amazonaws.com"
],
"i-2ff6135e": [
"ec2-54-242-36-133.compute-1.amazonaws.com"
],
"i-3cbc6d50": [
"ec2-54-234-233-19.compute-1.amazonaws.com"
],
"i-3e9a7f5b": [
"ec2-54-224-92-80.compute-1.amazonaws.com"
],
"i-43f6a533": [
"ec2-54-235-143-131.compute-1.amazonaws.com"
],
"i-45906822": [
"ec2-23-21-100-222.compute-1.amazonaws.com"
],
"i-508c1923": [
"ec2-23-23-130-201.compute-1.amazonaws.com"
],
"i-52970021": [
"ec2-23-23-169-133.compute-1.amazonaws.com"
],
"i-57cc2c25": [
"ec2-54-225-229-159.compute-1.amazonaws.com"
],
"i-59f23536": [
"ec2-75-101-128-47.compute-1.amazonaws.com"
],
"i-7012b200": [
"ec2-107-22-249-212.compute-1.amazonaws.com"
],
"i-73fead03": [
"ec2-54-235-143-132.compute-1.amazonaws.com"
],
"i-75faa905": [
"ec2-54-235-143-133.compute-1.amazonaws.com"
],
"i-76e49b0e": [
"ec2-75-101-128-224.compute-1.amazonaws.com"
],
"i-78c9450b": [
"ec2-54-225-88-116.compute-1.amazonaws.com"
],
"i-7aa18911": [
"ec2-54-211-252-32.compute-1.amazonaws.com"
],
"i-7dfdae0d": [
"ec2-54-235-143-134.compute-1.amazonaws.com"
],
"i-8559d6fa": [
"ec2-23-21-224-105.compute-1.amazonaws.com"
],
"i-899768e4": [
"ec2-54-234-3-7.compute-1.amazonaws.com"
],
"i-918130fb": [
"ec2-174-129-171-101.compute-1.amazonaws.com"
],
"i-99ce0ceb": [
"ec2-107-22-234-92.compute-1.amazonaws.com"
],
"i-9a450df8": [
"ec2-50-19-184-148.compute-1.amazonaws.com"
],
"i-9fce0ced": [
"ec2-107-20-176-139.compute-1.amazonaws.com"
],
"i-a80682c4": [
"ec2-54-235-65-26.compute-1.amazonaws.com"
],
"i-b43ab5df": [
"ec2-174-129-140-30.compute-1.amazonaws.com"
],
"i-baa893c2": [
"ec2-23-23-170-30.compute-1.amazonaws.com"
],
"i-bc23a0cf": [
"ec2-75-101-159-82.compute-1.amazonaws.com"
],
"i-bed948cd": [
"ec2-54-235-112-3.compute-1.amazonaws.com"
],
"i-c200c4a8": [
"ec2-54-227-30-105.compute-1.amazonaws.com"
],
"i-c69ae2be": [
"ec2-23-21-133-17.compute-1.amazonaws.com"
],
"i-c6d33fa3": [
"ec2-54-226-100-117.compute-1.amazonaws.com"
],
"i-cc4d2abf": [
"ec2-107-20-160-49.compute-1.amazonaws.com"
],
"i-cc9c3fbc": [
"ec2-54-243-146-75.compute-1.amazonaws.com"
],
"i-d01dacb3": [
"ec2-54-234-218-33.compute-1.amazonaws.com"
],
"i-da6631b3": [
"ec2-54-226-227-106.compute-1.amazonaws.com"
],
"i-dc6631b5": [
"ec2-54-227-113-75.compute-1.amazonaws.com"
],
"i-f005a580": [
"ec2-107-22-241-13.compute-1.amazonaws.com"
],
"i-f605a586": [
"ec2-107-22-247-88.compute-1.amazonaws.com"
],
"i-f805a588": [
"ec2-107-22-248-113.compute-1.amazonaws.com"
],
"i-f9829894": [
"ec2-54-225-172-84.compute-1.amazonaws.com"
],
"inf.axialmarket.com": [
"ec2-54-225-229-159.compute-1.amazonaws.com"
],
"jeffnew.axialmarket.com": [
"ec2-107-22-234-180.compute-1.amazonaws.com"
],
"jenkins.axialmarket.com": [
"ec2-23-21-224-105.compute-1.amazonaws.com"
],
"jump.axialmarket.com": [
"ec2-23-23-169-133.compute-1.amazonaws.com"
],
"key_Dana_Spiegel": [
"ec2-50-19-184-148.compute-1.amazonaws.com"
],
"key_bah-20130614": [
"ec2-54-234-218-33.compute-1.amazonaws.com",
"ec2-54-226-244-191.compute-1.amazonaws.com"
],
"key_herby-axial-20130903": [
"ec2-54-224-92-80.compute-1.amazonaws.com"
],
"key_herbyg-axial-201308": [
"ec2-54-211-252-32.compute-1.amazonaws.com",
"ec2-54-234-3-7.compute-1.amazonaws.com"
],
"key_ike-20120322": [
"ec2-23-21-100-222.compute-1.amazonaws.com",
"ec2-23-21-57-109.compute-1.amazonaws.com",
"ec2-75-101-128-224.compute-1.amazonaws.com",
"ec2-23-21-133-17.compute-1.amazonaws.com",
"ec2-23-23-168-208.compute-1.amazonaws.com",
"ec2-23-23-170-30.compute-1.amazonaws.com",
"ec2-75-101-129-169.compute-1.amazonaws.com",
"ec2-23-21-224-105.compute-1.amazonaws.com",
"ec2-54-242-36-133.compute-1.amazonaws.com",
"ec2-107-22-234-22.compute-1.amazonaws.com",
"ec2-107-22-234-180.compute-1.amazonaws.com",
"ec2-107-22-241-13.compute-1.amazonaws.com",
"ec2-107-22-247-88.compute-1.amazonaws.com",
"ec2-107-22-248-113.compute-1.amazonaws.com",
"ec2-107-22-249-212.compute-1.amazonaws.com",
"ec2-54-243-146-75.compute-1.amazonaws.com",
"ec2-54-235-143-131.compute-1.amazonaws.com",
"ec2-54-235-143-133.compute-1.amazonaws.com",
"ec2-54-235-143-132.compute-1.amazonaws.com",
"ec2-54-235-143-134.compute-1.amazonaws.com",
"ec2-54-235-143-162.compute-1.amazonaws.com",
"ec2-75-101-157-248.compute-1.amazonaws.com",
"ec2-75-101-159-82.compute-1.amazonaws.com",
"ec2-54-225-88-116.compute-1.amazonaws.com",
"ec2-23-23-169-133.compute-1.amazonaws.com",
"ec2-54-235-112-3.compute-1.amazonaws.com",
"ec2-54-225-229-159.compute-1.amazonaws.com",
"ec2-107-22-234-92.compute-1.amazonaws.com",
"ec2-107-20-176-139.compute-1.amazonaws.com",
"ec2-54-225-172-84.compute-1.amazonaws.com"
],
"key_matt-20120423": [
"ec2-54-226-227-106.compute-1.amazonaws.com",
"ec2-54-227-113-75.compute-1.amazonaws.com",
"ec2-54-235-65-26.compute-1.amazonaws.com",
"ec2-174-129-171-101.compute-1.amazonaws.com",
"ec2-54-234-233-19.compute-1.amazonaws.com",
"ec2-174-129-140-30.compute-1.amazonaws.com",
"ec2-54-227-30-105.compute-1.amazonaws.com",
"ec2-54-226-100-117.compute-1.amazonaws.com"
],
"key_mike-20121126": [
"ec2-75-101-128-47.compute-1.amazonaws.com",
"ec2-23-23-130-201.compute-1.amazonaws.com",
"ec2-107-20-160-49.compute-1.amazonaws.com"
],
"logstore1.axialmarket.com": [
"ec2-75-101-129-169.compute-1.amazonaws.com"
],
"logstore2.axialmarket.com": [
"ec2-54-235-112-3.compute-1.amazonaws.com"
],
"mattnew.axialmarket.com": [
"ec2-107-22-241-13.compute-1.amazonaws.com"
],
"monitor0.axialmarket.com": [
"ec2-54-235-65-26.compute-1.amazonaws.com"
],
"mx0.axialmarket.com": [
"ec2-23-21-57-109.compute-1.amazonaws.com"
],
"mx0a.axialmarket.com": [
"ec2-23-21-224-105.compute-1.amazonaws.com"
],
"mx1.axialmarket.com": [
"ec2-75-101-128-47.compute-1.amazonaws.com"
],
"mx2.axialmarket.com": [
"ec2-75-101-128-224.compute-1.amazonaws.com"
],
"mx5.axialmarket.com": [
"ec2-75-101-129-169.compute-1.amazonaws.com"
],
"pak.axialmarket.com": [
"ec2-54-242-36-133.compute-1.amazonaws.com"
],
"pak0.axialmarket.com": [
"ec2-54-242-36-133.compute-1.amazonaws.com"
],
"poundtest1.axialmarket.com": [
"ec2-107-20-160-49.compute-1.amazonaws.com"
],
"production-db7": [
"production-db7.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"production-db7-rdssnap-p4hsx77hy8l5zqj": [
"production-db7-rdssnap-p4hsx77hy8l5zqj.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"production-readonly-db7": [
"production-readonly-db7.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"rabbit.axialmarket.com": [
"ec2-50-19-184-148.compute-1.amazonaws.com"
],
"rds_mysql": [
"dev11-20120311.co735munpzcw.us-east-1.rds.amazonaws.com",
"dev11-20130828.co735munpzcw.us-east-1.rds.amazonaws.com",
"dev11-20130903-dab.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-db7.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-db7-rdssnap-p4hsx77hy8l5zqj.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-readonly-db7.co735munpzcw.us-east-1.rds.amazonaws.com",
"web-mktg-1.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"rds_parameter_group_axialmarket-5-5": [
"dev11-20120311.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-db7.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-readonly-db7.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"rds_parameter_group_default_mysql5_1": [
"web-mktg-1.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"rds_parameter_group_default_mysql5_5": [
"dev11-20130828.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"rds_parameter_group_mysqldump": [
"dev11-20130903-dab.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-db7-rdssnap-p4hsx77hy8l5zqj.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"releng0.axialmarket.com": [
"ec2-23-21-100-222.compute-1.amazonaws.com"
],
"releng1.axialmarket.com": [
"ec2-23-21-133-17.compute-1.amazonaws.com"
],
"rexnew.axialmarket.com": [
"ec2-54-235-143-162.compute-1.amazonaws.com"
],
"rollupy0.axialmarket.com": [
"ec2-54-225-172-84.compute-1.amazonaws.com"
],
"security_group_MTA": [
"ec2-75-101-128-47.compute-1.amazonaws.com",
"ec2-23-21-57-109.compute-1.amazonaws.com",
"ec2-75-101-128-224.compute-1.amazonaws.com",
"ec2-23-21-224-105.compute-1.amazonaws.com"
],
"security_group_WWW-PROD-2013": [
"ec2-75-101-157-248.compute-1.amazonaws.com",
"ec2-75-101-159-82.compute-1.amazonaws.com"
],
"security_group_backup2012": [
"ec2-23-23-170-30.compute-1.amazonaws.com"
],
"security_group_dataeng-test": [
"ec2-54-224-92-80.compute-1.amazonaws.com"
],
"security_group_development-2013-Jan": [
"ec2-54-226-227-106.compute-1.amazonaws.com",
"ec2-54-227-113-75.compute-1.amazonaws.com",
"ec2-174-129-171-101.compute-1.amazonaws.com",
"ec2-54-234-233-19.compute-1.amazonaws.com",
"ec2-54-234-218-33.compute-1.amazonaws.com",
"ec2-54-226-244-191.compute-1.amazonaws.com",
"ec2-174-129-140-30.compute-1.amazonaws.com",
"ec2-54-227-30-105.compute-1.amazonaws.com",
"ec2-54-226-100-117.compute-1.amazonaws.com",
"ec2-54-234-3-7.compute-1.amazonaws.com",
"ec2-107-22-234-22.compute-1.amazonaws.com",
"ec2-107-22-234-180.compute-1.amazonaws.com",
"ec2-107-22-241-13.compute-1.amazonaws.com",
"ec2-107-22-247-88.compute-1.amazonaws.com",
"ec2-107-22-248-113.compute-1.amazonaws.com",
"ec2-107-22-249-212.compute-1.amazonaws.com",
"ec2-54-243-146-75.compute-1.amazonaws.com",
"ec2-54-235-143-162.compute-1.amazonaws.com",
"ec2-54-225-88-116.compute-1.amazonaws.com",
"ec2-23-23-130-201.compute-1.amazonaws.com",
"ec2-107-20-160-49.compute-1.amazonaws.com",
"ec2-107-22-234-92.compute-1.amazonaws.com",
"ec2-107-20-176-139.compute-1.amazonaws.com"
],
"security_group_development-summer2012": [
"dev11-20120311.co735munpzcw.us-east-1.rds.amazonaws.com",
"dev11-20130828.co735munpzcw.us-east-1.rds.amazonaws.com",
"dev11-20130903-dab.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-db7-rdssnap-p4hsx77hy8l5zqj.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-readonly-db7.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"security_group_development2012July": [
"ec2-23-23-168-208.compute-1.amazonaws.com"
],
"security_group_inf-mgmt-2013": [
"ec2-54-225-229-159.compute-1.amazonaws.com"
],
"security_group_jump": [
"ec2-23-23-169-133.compute-1.amazonaws.com"
],
"security_group_monitor-GOD-2013": [
"ec2-54-235-65-26.compute-1.amazonaws.com"
],
"security_group_pak-internal": [
"ec2-54-242-36-133.compute-1.amazonaws.com"
],
"security_group_production": [
"ec2-50-19-184-148.compute-1.amazonaws.com",
"production-db7.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"security_group_production-NEWWORLD-201202": [
"ec2-54-235-143-131.compute-1.amazonaws.com",
"ec2-54-235-143-133.compute-1.amazonaws.com",
"ec2-54-235-143-132.compute-1.amazonaws.com",
"ec2-54-235-143-134.compute-1.amazonaws.com",
"ec2-54-225-172-84.compute-1.amazonaws.com"
],
"security_group_production-awx": [
"ec2-54-211-252-32.compute-1.amazonaws.com"
],
"security_group_releng20120404": [
"ec2-23-21-100-222.compute-1.amazonaws.com",
"ec2-23-21-133-17.compute-1.amazonaws.com"
],
"security_group_util-20121011": [
"ec2-75-101-129-169.compute-1.amazonaws.com",
"ec2-54-235-112-3.compute-1.amazonaws.com"
],
"security_group_www-mktg": [
"web-mktg-1.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"stevenew.axialmarket.com": [
"ec2-107-22-234-92.compute-1.amazonaws.com"
],
"tag_Environment_Production": [
"ec2-50-19-184-148.compute-1.amazonaws.com"
],
"tag_Name_INF-umgmt1": [
"ec2-54-225-229-159.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-PROD-app1": [
"ec2-54-235-143-131.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-PROD-app2": [
"ec2-54-235-143-132.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-PROD-worker1": [
"ec2-54-235-143-133.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-PROD-worker2": [
"ec2-54-235-143-134.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-bah": [
"ec2-107-20-176-139.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-bennew": [
"ec2-54-243-146-75.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-dabnew": [
"ec2-107-22-248-113.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-dannew": [
"ec2-107-22-247-88.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-jeffnew": [
"ec2-107-22-234-180.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-jumphost-2": [
"ec2-23-23-169-133.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-mattnew": [
"ec2-107-22-241-13.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-poundtest1": [
"ec2-107-20-160-49.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-poundtest1_": [
"ec2-107-20-160-49.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-rexnew": [
"ec2-54-235-143-162.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-stevenew-replace": [
"ec2-107-22-234-92.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-tannernew": [
"ec2-23-23-130-201.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-thomasnew-2": [
"ec2-54-225-88-116.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-willnew": [
"ec2-107-22-234-22.compute-1.amazonaws.com"
],
"tag_Name_NEWWORLD-worker1devnew": [
"ec2-107-22-249-212.compute-1.amazonaws.com"
],
"tag_Name_WWW-TEST": [
"ec2-54-234-233-19.compute-1.amazonaws.com"
],
"tag_Name_WWW1-MKTG": [
"ec2-75-101-157-248.compute-1.amazonaws.com"
],
"tag_Name_WWW2-MKTG": [
"ec2-75-101-159-82.compute-1.amazonaws.com"
],
"tag_Name_ansible": [
"ec2-54-226-227-106.compute-1.amazonaws.com",
"ec2-54-227-113-75.compute-1.amazonaws.com"
],
"tag_Name_app2t_development_axialmarket_com": [
"ec2-23-23-168-208.compute-1.amazonaws.com"
],
"tag_Name_awx": [
"ec2-54-211-252-32.compute-1.amazonaws.com"
],
"tag_Name_axtdev2": [
"ec2-54-234-3-7.compute-1.amazonaws.com"
],
"tag_Name_backup1": [
"ec2-23-23-170-30.compute-1.amazonaws.com"
],
"tag_Name_build_server": [
"ec2-54-226-244-191.compute-1.amazonaws.com"
],
"tag_Name_cburke0": [
"ec2-54-226-100-117.compute-1.amazonaws.com"
],
"tag_Name_dataeng_test1": [
"ec2-54-224-92-80.compute-1.amazonaws.com"
],
"tag_Name_firecrow-dev": [
"ec2-54-227-30-105.compute-1.amazonaws.com"
],
"tag_Name_herby0": [
"ec2-174-129-140-30.compute-1.amazonaws.com"
],
"tag_Name_logstore1": [
"ec2-75-101-129-169.compute-1.amazonaws.com"
],
"tag_Name_logstore2": [
"ec2-54-235-112-3.compute-1.amazonaws.com"
],
"tag_Name_mx0": [
"ec2-23-21-57-109.compute-1.amazonaws.com"
],
"tag_Name_mx0a": [
"ec2-23-21-224-105.compute-1.amazonaws.com"
],
"tag_Name_mx1_new": [
"ec2-75-101-128-47.compute-1.amazonaws.com"
],
"tag_Name_mx2": [
"ec2-75-101-128-224.compute-1.amazonaws.com"
],
"tag_Name_new-testapp1": [
"ec2-174-129-171-101.compute-1.amazonaws.com"
],
"tag_Name_pak0_axialmarket_com": [
"ec2-54-242-36-133.compute-1.amazonaws.com"
],
"tag_Name_rabbit_axialmarket_com": [
"ec2-50-19-184-148.compute-1.amazonaws.com"
],
"tag_Name_releng0": [
"ec2-23-21-100-222.compute-1.amazonaws.com"
],
"tag_Name_releng1": [
"ec2-23-21-133-17.compute-1.amazonaws.com"
],
"tag_Name_rollupy0-PROD": [
"ec2-54-225-172-84.compute-1.amazonaws.com"
],
"tag_Name_tannernew_": [
"ec2-23-23-130-201.compute-1.amazonaws.com"
],
"tag_Name_testapp1": [
"ec2-54-234-218-33.compute-1.amazonaws.com"
],
"tag_Name_zabbix-upgrade": [
"ec2-54-235-65-26.compute-1.amazonaws.com"
],
"tag_Use_RabbitMQ__celerycam__celerybeat__celeryd__postfix": [
"ec2-50-19-184-148.compute-1.amazonaws.com"
],
"tag_environment_dev": [
"ec2-54-234-3-7.compute-1.amazonaws.com"
],
"tag_environment_production": [
"ec2-54-211-252-32.compute-1.amazonaws.com"
],
"tag_id_awx": [
"ec2-54-211-252-32.compute-1.amazonaws.com"
],
"tag_id_axtdev2": [
"ec2-54-234-3-7.compute-1.amazonaws.com"
],
"tag_os_ubuntu": [
"ec2-54-211-252-32.compute-1.amazonaws.com",
"ec2-54-234-3-7.compute-1.amazonaws.com"
],
"tag_primary_role_awx": [
"ec2-54-211-252-32.compute-1.amazonaws.com"
],
"tag_primary_role_dev": [
"ec2-54-234-3-7.compute-1.amazonaws.com"
],
"tag_purpose_syscleanup": [
"ec2-23-21-100-222.compute-1.amazonaws.com"
],
"tag_role_awx_": [
"ec2-54-211-252-32.compute-1.amazonaws.com"
],
"tag_role_dev_": [
"ec2-54-234-3-7.compute-1.amazonaws.com"
],
"tannernew.axialmarket.com": [
"ec2-23-23-130-201.compute-1.amazonaws.com"
],
"testapp1.axialmarket.com": [
"ec2-174-129-171-101.compute-1.amazonaws.com"
],
"testapp2.axialmarket.com": [
"ec2-54-234-218-33.compute-1.amazonaws.com"
],
"testnoelb.axialmarket.com": [
"ec2-107-20-160-49.compute-1.amazonaws.com"
],
"testworker1.axialmarket.com": [
"ec2-107-22-249-212.compute-1.amazonaws.com"
],
"thomasnew.axialmarket.com": [
"ec2-54-225-88-116.compute-1.amazonaws.com"
],
"type_db_m1_medium": [
"web-mktg-1.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"type_db_m1_xlarge": [
"dev11-20120311.co735munpzcw.us-east-1.rds.amazonaws.com",
"dev11-20130828.co735munpzcw.us-east-1.rds.amazonaws.com",
"dev11-20130903-dab.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-db7.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-db7-rdssnap-p4hsx77hy8l5zqj.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-readonly-db7.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"type_m1_large": [
"ec2-54-235-65-26.compute-1.amazonaws.com",
"ec2-174-129-171-101.compute-1.amazonaws.com",
"ec2-54-234-218-33.compute-1.amazonaws.com",
"ec2-50-19-184-148.compute-1.amazonaws.com",
"ec2-174-129-140-30.compute-1.amazonaws.com",
"ec2-54-227-30-105.compute-1.amazonaws.com",
"ec2-54-226-100-117.compute-1.amazonaws.com",
"ec2-54-224-92-80.compute-1.amazonaws.com",
"ec2-23-23-168-208.compute-1.amazonaws.com",
"ec2-54-234-3-7.compute-1.amazonaws.com",
"ec2-107-22-234-22.compute-1.amazonaws.com",
"ec2-107-22-234-180.compute-1.amazonaws.com",
"ec2-107-22-241-13.compute-1.amazonaws.com",
"ec2-107-22-247-88.compute-1.amazonaws.com",
"ec2-107-22-248-113.compute-1.amazonaws.com",
"ec2-107-22-249-212.compute-1.amazonaws.com",
"ec2-54-243-146-75.compute-1.amazonaws.com",
"ec2-54-235-143-131.compute-1.amazonaws.com",
"ec2-54-235-143-132.compute-1.amazonaws.com",
"ec2-54-235-143-162.compute-1.amazonaws.com",
"ec2-23-23-130-201.compute-1.amazonaws.com",
"ec2-107-22-234-92.compute-1.amazonaws.com",
"ec2-107-20-176-139.compute-1.amazonaws.com"
],
"type_m1_medium": [
"ec2-54-226-227-106.compute-1.amazonaws.com",
"ec2-54-227-113-75.compute-1.amazonaws.com",
"ec2-54-234-233-19.compute-1.amazonaws.com",
"ec2-54-226-244-191.compute-1.amazonaws.com",
"ec2-23-21-100-222.compute-1.amazonaws.com",
"ec2-23-21-133-17.compute-1.amazonaws.com",
"ec2-54-211-252-32.compute-1.amazonaws.com",
"ec2-54-242-36-133.compute-1.amazonaws.com",
"ec2-75-101-157-248.compute-1.amazonaws.com",
"ec2-75-101-159-82.compute-1.amazonaws.com",
"ec2-54-225-88-116.compute-1.amazonaws.com",
"ec2-23-23-169-133.compute-1.amazonaws.com"
],
"type_m1_small": [
"ec2-75-101-129-169.compute-1.amazonaws.com",
"ec2-107-20-160-49.compute-1.amazonaws.com"
],
"type_m1_xlarge": [
"ec2-54-235-143-133.compute-1.amazonaws.com",
"ec2-54-235-143-134.compute-1.amazonaws.com",
"ec2-54-235-112-3.compute-1.amazonaws.com",
"ec2-54-225-172-84.compute-1.amazonaws.com"
],
"type_m2_2xlarge": [
"ec2-23-23-170-30.compute-1.amazonaws.com"
],
"type_t1_micro": [
"ec2-75-101-128-47.compute-1.amazonaws.com",
"ec2-23-21-57-109.compute-1.amazonaws.com",
"ec2-75-101-128-224.compute-1.amazonaws.com",
"ec2-23-21-224-105.compute-1.amazonaws.com",
"ec2-54-225-229-159.compute-1.amazonaws.com"
],
"us-east-1": [
"ec2-54-226-227-106.compute-1.amazonaws.com",
"ec2-54-227-113-75.compute-1.amazonaws.com",
"ec2-54-235-65-26.compute-1.amazonaws.com",
"ec2-174-129-171-101.compute-1.amazonaws.com",
"ec2-54-234-233-19.compute-1.amazonaws.com",
"ec2-75-101-128-47.compute-1.amazonaws.com",
"ec2-54-234-218-33.compute-1.amazonaws.com",
"ec2-54-226-244-191.compute-1.amazonaws.com",
"ec2-50-19-184-148.compute-1.amazonaws.com",
"ec2-174-129-140-30.compute-1.amazonaws.com",
"ec2-54-227-30-105.compute-1.amazonaws.com",
"ec2-23-21-100-222.compute-1.amazonaws.com",
"ec2-54-226-100-117.compute-1.amazonaws.com",
"ec2-54-224-92-80.compute-1.amazonaws.com",
"ec2-23-21-57-109.compute-1.amazonaws.com",
"ec2-75-101-128-224.compute-1.amazonaws.com",
"ec2-23-21-133-17.compute-1.amazonaws.com",
"ec2-23-23-168-208.compute-1.amazonaws.com",
"ec2-23-23-170-30.compute-1.amazonaws.com",
"ec2-54-211-252-32.compute-1.amazonaws.com",
"ec2-54-234-3-7.compute-1.amazonaws.com",
"ec2-75-101-129-169.compute-1.amazonaws.com",
"ec2-23-21-224-105.compute-1.amazonaws.com",
"ec2-54-242-36-133.compute-1.amazonaws.com",
"ec2-107-22-234-22.compute-1.amazonaws.com",
"ec2-107-22-234-180.compute-1.amazonaws.com",
"ec2-107-22-241-13.compute-1.amazonaws.com",
"ec2-107-22-247-88.compute-1.amazonaws.com",
"ec2-107-22-248-113.compute-1.amazonaws.com",
"ec2-107-22-249-212.compute-1.amazonaws.com",
"ec2-54-243-146-75.compute-1.amazonaws.com",
"ec2-54-235-143-131.compute-1.amazonaws.com",
"ec2-54-235-143-133.compute-1.amazonaws.com",
"ec2-54-235-143-132.compute-1.amazonaws.com",
"ec2-54-235-143-134.compute-1.amazonaws.com",
"ec2-54-235-143-162.compute-1.amazonaws.com",
"ec2-75-101-157-248.compute-1.amazonaws.com",
"ec2-75-101-159-82.compute-1.amazonaws.com",
"ec2-54-225-88-116.compute-1.amazonaws.com",
"ec2-23-23-130-201.compute-1.amazonaws.com",
"ec2-23-23-169-133.compute-1.amazonaws.com",
"ec2-54-235-112-3.compute-1.amazonaws.com",
"ec2-107-20-160-49.compute-1.amazonaws.com",
"ec2-54-225-229-159.compute-1.amazonaws.com",
"ec2-107-22-234-92.compute-1.amazonaws.com",
"ec2-107-20-176-139.compute-1.amazonaws.com",
"ec2-54-225-172-84.compute-1.amazonaws.com",
"dev11-20120311.co735munpzcw.us-east-1.rds.amazonaws.com",
"dev11-20130828.co735munpzcw.us-east-1.rds.amazonaws.com",
"dev11-20130903-dab.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-db7.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-db7-rdssnap-p4hsx77hy8l5zqj.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-readonly-db7.co735munpzcw.us-east-1.rds.amazonaws.com",
"web-mktg-1.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"us-east-1c": [
"ec2-23-21-100-222.compute-1.amazonaws.com",
"ec2-23-23-168-208.compute-1.amazonaws.com",
"ec2-75-101-129-169.compute-1.amazonaws.com",
"ec2-107-22-249-212.compute-1.amazonaws.com",
"ec2-54-235-143-132.compute-1.amazonaws.com",
"ec2-54-235-143-134.compute-1.amazonaws.com",
"ec2-75-101-157-248.compute-1.amazonaws.com",
"ec2-54-235-112-3.compute-1.amazonaws.com",
"ec2-107-20-160-49.compute-1.amazonaws.com",
"ec2-54-225-172-84.compute-1.amazonaws.com",
"dev11-20130828.co735munpzcw.us-east-1.rds.amazonaws.com",
"dev11-20130903-dab.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-db7-rdssnap-p4hsx77hy8l5zqj.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"us-east-1d": [
"ec2-54-226-227-106.compute-1.amazonaws.com",
"ec2-54-227-113-75.compute-1.amazonaws.com",
"ec2-54-235-65-26.compute-1.amazonaws.com",
"ec2-174-129-171-101.compute-1.amazonaws.com",
"ec2-54-234-233-19.compute-1.amazonaws.com",
"ec2-75-101-128-47.compute-1.amazonaws.com",
"ec2-54-234-218-33.compute-1.amazonaws.com",
"ec2-54-226-244-191.compute-1.amazonaws.com",
"ec2-50-19-184-148.compute-1.amazonaws.com",
"ec2-174-129-140-30.compute-1.amazonaws.com",
"ec2-54-227-30-105.compute-1.amazonaws.com",
"ec2-54-226-100-117.compute-1.amazonaws.com",
"ec2-54-224-92-80.compute-1.amazonaws.com",
"ec2-23-21-57-109.compute-1.amazonaws.com",
"ec2-75-101-128-224.compute-1.amazonaws.com",
"ec2-23-21-133-17.compute-1.amazonaws.com",
"ec2-54-211-252-32.compute-1.amazonaws.com",
"ec2-54-234-3-7.compute-1.amazonaws.com",
"ec2-23-21-224-105.compute-1.amazonaws.com",
"ec2-54-242-36-133.compute-1.amazonaws.com",
"ec2-107-22-234-22.compute-1.amazonaws.com",
"ec2-107-22-234-180.compute-1.amazonaws.com",
"ec2-107-22-241-13.compute-1.amazonaws.com",
"ec2-107-22-247-88.compute-1.amazonaws.com",
"ec2-107-22-248-113.compute-1.amazonaws.com",
"ec2-54-243-146-75.compute-1.amazonaws.com",
"ec2-54-235-143-131.compute-1.amazonaws.com",
"ec2-54-235-143-133.compute-1.amazonaws.com",
"ec2-54-235-143-162.compute-1.amazonaws.com",
"ec2-75-101-159-82.compute-1.amazonaws.com",
"ec2-54-225-88-116.compute-1.amazonaws.com",
"ec2-23-23-130-201.compute-1.amazonaws.com",
"ec2-23-23-169-133.compute-1.amazonaws.com",
"ec2-54-225-229-159.compute-1.amazonaws.com",
"ec2-107-22-234-92.compute-1.amazonaws.com",
"ec2-107-20-176-139.compute-1.amazonaws.com",
"dev11-20120311.co735munpzcw.us-east-1.rds.amazonaws.com",
"web-mktg-1.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"us-east-1e": [
"ec2-23-23-170-30.compute-1.amazonaws.com",
"production-db7.co735munpzcw.us-east-1.rds.amazonaws.com",
"production-readonly-db7.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"web-mktg-1": [
"web-mktg-1.co735munpzcw.us-east-1.rds.amazonaws.com"
],
"web1.axialmarket.com": [
"ec2-75-101-157-248.compute-1.amazonaws.com"
],
"web2.axialmarket.com": [
"ec2-75-101-159-82.compute-1.amazonaws.com"
],
"willnew.axialmarket.com": [
"ec2-107-22-234-22.compute-1.amazonaws.com"
],
"worker1new.axialmarket.com": [
"ec2-54-235-143-133.compute-1.amazonaws.com"
],
"worker1newdev.axialmarket.com": [
"ec2-107-22-249-212.compute-1.amazonaws.com"
],
"worker2new.axialmarket.com": [
"ec2-54-235-143-134.compute-1.amazonaws.com"
],
"www-test.axialmarket.com": [
"ec2-54-234-233-19.compute-1.amazonaws.com"
],
'_meta': {
'hostvars': {}
}
}
host_vars = {
}
if __name__ == '__main__':
parser = optparse.OptionParser()
parser.add_option('--list', action='store_true', dest='list')
parser.add_option('--host', dest='hostname', default='')
options, args = parser.parse_args()
if options.list:
print json.dumps(inv_list, indent=4)
elif options.hostname:
print json.dumps(host_vars, indent=4)
else:
print json.dumps({}, indent=4)

View File

@@ -1,63 +0,0 @@
#!/usr/bin/env python
# Python
import json
import optparse
import os
nhosts = int(os.environ.get('NHOSTS', 100))
inv_list = {
'_meta': {
'hostvars': {},
},
}
for n in xrange(nhosts):
hostname = 'host-%08d.example.com' % n
group_evens_odds = 'evens.example.com' if n % 2 == 0 else 'odds.example.com'
group_threes = 'threes.example.com' if n % 3 == 0 else ''
group_fours = 'fours.example.com' if n % 4 == 0 else ''
group_fives = 'fives.example.com' if n % 5 == 0 else ''
group_sixes = 'sixes.example.com' if n % 6 == 0 else ''
group_sevens = 'sevens.example.com' if n % 7 == 0 else ''
group_eights = 'eights.example.com' if n % 8 == 0 else ''
group_nines = 'nines.example.com' if n % 9 == 0 else ''
group_tens = 'tens.example.com' if n % 10 == 0 else ''
group_by_10s = 'group-%07dX.example.com' % (n / 10)
group_by_100s = 'group-%06dXX.example.com' % (n / 100)
group_by_1000s = 'group-%05dXXX.example.com' % (n / 1000)
for group in [group_evens_odds, group_threes, group_fours, group_fives, group_sixes, group_sevens, group_eights, group_nines, group_tens, group_by_10s]:
if not group:
continue
if group in inv_list:
inv_list[group]['hosts'].append(hostname)
else:
inv_list[group] = {'hosts': [hostname], 'children': [], 'vars': {'group_prefix': group.split('.')[0]}}
if group_by_1000s not in inv_list:
inv_list[group_by_1000s] = {'hosts': [], 'children': [], 'vars': {'group_prefix': group_by_1000s.split('.')[0]}}
if group_by_100s not in inv_list:
inv_list[group_by_100s] = {'hosts': [], 'children': [], 'vars': {'group_prefix': group_by_100s.split('.')[0]}}
if group_by_100s not in inv_list[group_by_1000s]['children']:
inv_list[group_by_1000s]['children'].append(group_by_100s)
if group_by_10s not in inv_list[group_by_100s]['children']:
inv_list[group_by_100s]['children'].append(group_by_10s)
inv_list['_meta']['hostvars'][hostname] = {
'ansible_ssh_user': 'example',
'ansible_connection': 'local',
'host_prefix': hostname.split('.')[0],
'host_id': n,
}
if __name__ == '__main__':
parser = optparse.OptionParser()
parser.add_option('--list', action='store_true', dest='list')
parser.add_option('--host', dest='hostname', default='')
options, args = parser.parse_args()
if options.list:
print json.dumps(inv_list, indent=4)
elif options.hostname:
print json.dumps(inv_list['_meta']['hostvars'][options.hostname], indent=4)
else:
print json.dumps({}, indent=4)

View File

@@ -9,68 +9,86 @@ import mock
from django.core.management.base import CommandError
# AWX
from awx.main.management.commands.inventory_import import (
Command
)
from awx.main.management.commands import inventory_import
from awx.main.models import Inventory, Host, Group
from awx.main.utils.mem_inventory import dict_to_mem_data
TEST_INVENTORY_INI = '''\
# Some comment about blah blah blah...
[webservers]
web1.example.com ansible_ssh_host=w1.example.net
web2.example.com
web3.example.com:1022
[webservers:vars] # Comment on a section
webvar=blah # Comment on an option
[dbservers]
db1.example.com
db2.example.com
[dbservers:vars]
dbvar=ugh
[servers:children]
webservers
dbservers
[servers:vars]
varb=B
[all:vars]
vara=A
[others]
10.11.12.13
10.12.14.16:8022
fe80::1610:9fff:fedd:654b
[fe80::1610:9fff:fedd:b654]:1022
::1
'''
TEST_INVENTORY_CONTENT = {
"_meta": {
"hostvars": {}
},
"all": {
"children": [
"others",
"servers",
"ungrouped"
],
"vars": {
"vara": "A"
}
},
"dbservers": {
"hosts": [
"db1.example.com",
"db2.example.com"
],
"vars": {
"dbvar": "ugh"
}
},
"others": {
"hosts": {
"10.11.12.13": {},
"10.12.14.16": {"ansible_port": 8022},
"::1": {},
"fe80::1610:9fff:fedd:654b": {},
"fe80::1610:9fff:fedd:b654": {"ansible_port": 1022}
}
},
"servers": {
"children": [
"dbservers",
"webservers"
],
"vars": {
"varb": "B"
}
},
"ungrouped": {},
"webservers": {
"hosts": {
"web1.example.com": {
"ansible_ssh_host": "w1.example.net"
},
"web2.example.com": {},
"web3.example.com": {
"ansible_port": 1022
}
},
"vars": {
"webvar": "blah"
}
}
}
@pytest.fixture(scope='session')
def test_dir(tmpdir_factory):
return tmpdir_factory.mktemp('inv_files', numbered=False)
TEST_MEM_OBJECTS = dict_to_mem_data(TEST_INVENTORY_CONTENT)
@pytest.fixture(scope='session')
def ini_file(test_dir):
fn = test_dir.join('test_hosts')
fn.write(TEST_INVENTORY_INI)
return fn
def mock_logging(self):
pass
@pytest.mark.django_db
@mock.patch.object(Command, 'check_license', mock.MagicMock())
@pytest.mark.inventory_import
@mock.patch.object(inventory_import.Command, 'check_license', mock.MagicMock())
@mock.patch.object(inventory_import.Command, 'set_logging_level', mock_logging)
class TestInvalidOptionsFunctional:
def test_invalid_options_invalid_source(self, inventory):
# Give invalid file to the command
cmd = Command()
cmd = inventory_import.Command()
with mock.patch('django.db.transaction.rollback'):
with pytest.raises(IOError) as err:
cmd.handle_noargs(
@@ -78,28 +96,33 @@ class TestInvalidOptionsFunctional:
source='/tmp/pytest-of-root/pytest-7/inv_files0-invalid')
assert 'Source does not exist' in err.value.message
def test_invalid_inventory_id(self, ini_file):
cmd = Command()
def test_invalid_inventory_id(self):
cmd = inventory_import.Command()
with pytest.raises(CommandError) as err:
cmd.handle_noargs(inventory_id=42, source=ini_file.dirname)
cmd.handle_noargs(inventory_id=42, source='/notapath/shouldnotmatter')
assert 'id = 42' in err.value.message
assert 'cannot be found' in err.value.message
def test_invalid_inventory_name(self, ini_file):
cmd = Command()
def test_invalid_inventory_name(self):
cmd = inventory_import.Command()
with pytest.raises(CommandError) as err:
cmd.handle_noargs(inventory_name='fooservers', source=ini_file.dirname)
cmd.handle_noargs(inventory_name='fooservers', source='/notapath/shouldnotmatter')
assert 'name = fooservers' in err.value.message
assert 'cannot be found' in err.value.message
@pytest.mark.django_db
@mock.patch.object(Command, 'check_license', mock.MagicMock())
@pytest.mark.inventory_import
@mock.patch.object(inventory_import.Command, 'check_license', mock.MagicMock())
@mock.patch.object(inventory_import.Command, 'set_logging_level', mock_logging)
class TestINIImports:
def test_inventory_single_ini_import(self, inventory, ini_file, capsys):
cmd = Command()
r = cmd.handle_noargs(inventory_id=inventory.pk, source=ini_file.dirname)
@mock.patch.object(inventory_import.AnsibleInventoryLoader, 'load', mock.MagicMock(return_value=TEST_MEM_OBJECTS))
def test_inventory_single_ini_import(self, inventory, capsys):
cmd = inventory_import.Command()
r = cmd.handle_noargs(
inventory_id=inventory.pk, source=__file__,
method='backport')
out, err = capsys.readouterr()
assert r is None
assert out == ''
@@ -117,10 +140,12 @@ class TestINIImports:
reloaded_inv = Inventory.objects.get(pk=inventory.pk)
assert reloaded_inv.variables_dict == {'vara': 'A'}
# Groups vars are applied to host in the newer versions
assert Host.objects.get(name='web1.example.com').variables_dict == {'ansible_ssh_host': 'w1.example.net'}
assert Host.objects.get(name='web3.example.com').variables_dict == {'ansible_ssh_port': 1022}
assert Host.objects.get(name='fe80::1610:9fff:fedd:b654').variables_dict == {'ansible_ssh_port': 1022}
assert Host.objects.get(name='10.12.14.16').variables_dict == {'ansible_ssh_port': 8022}
# Old version uses `ansible_ssh_port` but new version uses `ansible_port`
assert Host.objects.get(name='web3.example.com').variables_dict == {'ansible_port': 1022}
assert Host.objects.get(name='fe80::1610:9fff:fedd:b654').variables_dict == {'ansible_port': 1022}
assert Host.objects.get(name='10.12.14.16').variables_dict == {'ansible_port': 8022}
servers = Group.objects.get(name='servers')
assert servers.variables_dict == {'varb': 'B'}
@@ -143,24 +168,53 @@ class TestINIImports:
assert invsrc.inventory_updates.count() == 1
assert invsrc.inventory_updates.first().status == 'successful'
def test_inventory_import_group_vars_file(self, inventory, ini_file, tmpdir_factory):
# Create an extra group_vars file for group webservers
gvarf = tmpdir_factory.mktemp('inv_files/group_vars', numbered=False).join('webservers')
gvarf.write('''webservers_only_variable: foobar\n''')
# Check creation of ad-hoc inventory source - this was not called with one specified
assert reloaded_inv.inventory_sources.count() == 1
assert reloaded_inv.inventory_sources.all()[0].source == 'file'
cmd = Command()
cmd.handle_noargs(inventory_id=inventory.pk, source=ini_file.dirname)
@mock.patch.object(
inventory_import, 'load_inventory_source', mock.MagicMock(
return_value=dict_to_mem_data(
{
"_meta": {
"hostvars": {"foo": {"some_hostvar": "foobar"}}
},
"all": {
"children": ["ungrouped"]
},
"ungrouped": {
"hosts": ["foo"]
}
}).all_group
)
)
def test_hostvars_are_saved(self, inventory):
cmd = inventory_import.Command()
cmd.handle_noargs(inventory_id=inventory.pk, source='doesnt matter')
assert inventory.hosts.count() == 1
h = inventory.hosts.all()[0]
assert h.name == 'foo'
assert h.variables_dict == {"some_hostvar": "foobar"}
servers = Group.objects.get(name='webservers')
assert servers.variables_dict == {'webvar': 'blah', 'webservers_only_variable': 'foobar'}
def test_inventory_import_host_vars_file(self, inventory, ini_file, tmpdir_factory):
# Create an extra host_vars file for one specific host
gvarf = tmpdir_factory.mktemp('inv_files/host_vars', numbered=False).join('web1.example.com')
gvarf.write('''host_only_variable: foobar\n''')
cmd = Command()
cmd.handle_noargs(inventory_id=inventory.pk, source=ini_file.dirname)
Host.objects.get(name='web1.example.com').variables_dict == {
'ansible_ssh_host': 'w1.example.net', 'host_only_variable': 'foobar'}
@mock.patch.object(
inventory_import, 'load_inventory_source', mock.MagicMock(
return_value=dict_to_mem_data(
{
"_meta": {
"hostvars": {}
},
"all": {
"children": ["fooland", "barland"]
},
"fooland": {
"children": ["barland"]
},
"barland": {
"children": ["fooland"]
}
}).all_group
)
)
def test_recursive_group_error(self, inventory):
cmd = inventory_import.Command()
cmd.handle_noargs(inventory_id=inventory.pk, source='doesnt matter')

View File

@@ -16,7 +16,8 @@ class TestSCMUpdateFeatures:
inv_src = InventorySource(
scm_project=project,
source_path='inventory_file',
inventory=inventory)
inventory=inventory,
source='scm')
with mock.patch.object(inv_src.scm_project, 'update') as mck_update:
inv_src.save()
mck_update.assert_called_once_with()

View File

@@ -5,16 +5,12 @@
import json
import os
import shutil
import string
import StringIO
import sys
import tempfile
import time
import urlparse
import unittest2 as unittest
# Django
from django.conf import settings
from django.core.management import call_command
from django.utils.timezone import now
from django.test.utils import override_settings
@@ -36,77 +32,6 @@ TEST_PLAYBOOK = '''- hosts: test-group
command: test 2 = 2
'''
TEST_INVENTORY_INI = '''\
# Some comment about blah blah blah...
[webservers]
web1.example.com ansible_ssh_host=w1.example.net
web2.example.com
web3.example.com:1022
[webservers:vars] # Comment on a section
webvar=blah # Comment on an option
[dbservers]
db1.example.com
db2.example.com
[dbservers:vars]
dbvar=ugh
[servers:children]
webservers
dbservers
[servers:vars]
varb=B
[all:vars]
vara=A
[others]
10.11.12.13
10.12.14.16:8022
fe80::1610:9fff:fedd:654b
[fe80::1610:9fff:fedd:b654]:1022
::1
'''
TEST_INVENTORY_INI_WITH_HOST_PATTERNS = '''\
[dotcom]
web[00:63].example.com ansible_ssh_user=example
dns.example.com
[dotnet]
db-[a:z].example.net
ns.example.net
[dotorg]
[A:F][0:9].example.org:1022 ansible_ssh_user=example
mx.example.org
[dotus]
lb[00:08:2].example.us even_odd=even
lb[01:09:2].example.us even_odd=odd
[dotcc]
media[0:9][0:9].example.cc
'''
TEST_INVENTORY_INI_WITH_RECURSIVE_GROUPS = '''\
[family:children]
parent
[parent:children]
child
[child:children]
grandchild
[grandchild:children]
parent
'''
class BaseCommandMixin(object):
'''
@@ -449,412 +374,3 @@ class CleanupActivityStreamTest(BaseCommandMixin, BaseTest):
self.assertFalse(count_after)
self.assertTrue(cleanup_elapsed < (create_elapsed / 4),
'create took %0.3fs, cleanup took %0.3fs, expected < %0.3fs' % (create_elapsed, cleanup_elapsed, create_elapsed / 4))
@unittest.skipIf(os.environ.get('SKIP_SLOW_TESTS', False), 'Skipping slow test')
class InventoryImportTest(BaseCommandMixin, BaseLiveServerTest):
'''
Test cases for inventory_import management command.
'''
def setUp(self):
super(InventoryImportTest, self).setUp()
self.start_rabbit()
self.setup_instances()
self.create_test_inventories()
self.create_test_ini()
self.create_test_license_file()
def tearDown(self):
super(InventoryImportTest, self).tearDown()
self.stop_rabbit()
def create_test_ini(self, inv_dir=None, ini_content=None):
ini_content = ini_content or TEST_INVENTORY_INI
handle, self.ini_path = tempfile.mkstemp(suffix='.txt', dir=inv_dir)
ini_file = os.fdopen(handle, 'w')
ini_file.write(ini_content)
ini_file.close()
self._temp_paths.append(self.ini_path)
def create_test_dir(self, host_names=None, group_names=None, suffix=''):
host_names = host_names or []
group_names = group_names or []
if 'all' not in group_names:
group_names.insert(0, 'all')
self.inv_dir = tempfile.mkdtemp()
self._temp_paths.append(self.inv_dir)
self.create_test_ini(self.inv_dir)
group_vars_dir = os.path.join(self.inv_dir, 'group_vars')
os.makedirs(group_vars_dir)
for group_name in group_names:
if suffix == '.json':
group_vars_content = '''{"test_group_name": "%s"}\n''' % group_name
else:
group_vars_content = '''test_group_name: %s\n''' % group_name
group_vars_file = os.path.join(group_vars_dir, '%s%s' % (group_name, suffix))
file(group_vars_file, 'wb').write(group_vars_content)
if host_names:
host_vars_dir = os.path.join(self.inv_dir, 'host_vars')
os.makedirs(host_vars_dir)
for host_name in host_names:
if suffix == '.json':
host_vars_content = '''{"test_host_name": "%s"}''' % host_name
else:
host_vars_content = '''test_host_name: %s''' % host_name
host_vars_file = os.path.join(host_vars_dir, '%s%s' % (host_name, suffix))
file(host_vars_file, 'wb').write(host_vars_content)
def check_adhoc_inventory_source(self, inventory, except_host_pks=None,
except_group_pks=None):
# Check that management command created a new inventory source and
# related inventory update.
inventory_sources = inventory.inventory_sources.filter(group=None)
self.assertEqual(inventory_sources.count(), 1)
inventory_source = inventory_sources[0]
self.assertEqual(inventory_source.source, 'file')
self.assertEqual(inventory_source.inventory_updates.count(), 1)
inventory_update = inventory_source.inventory_updates.all()[0]
self.assertEqual(inventory_update.status, 'successful')
for host in inventory.hosts.all():
if host.pk in (except_host_pks or []):
continue
source_pks = host.inventory_sources.values_list('pk', flat=True)
self.assertTrue(inventory_source.pk in source_pks)
for group in inventory.groups.all():
if group.pk in (except_group_pks or []):
continue
source_pks = group.inventory_sources.values_list('pk', flat=True)
self.assertTrue(inventory_source.pk in source_pks)
def test_dir_with_ini_file(self):
self.create_test_dir(host_names=['db1.example.com', 'db2.example.com'],
group_names=['dbservers'], suffix='')
self.test_ini_file(self.inv_dir)
self.create_test_dir(host_names=['db1.example.com', 'db2.example.com'],
group_names=['dbservers'], suffix='.yml')
self.test_ini_file(self.inv_dir)
self.create_test_dir(host_names=['db1.example.com', 'db2.example.com'],
group_names=['dbservers'], suffix='.yaml')
self.test_ini_file(self.inv_dir)
self.create_test_dir(host_names=['db1.example.com', 'db2.example.com'],
group_names=['dbservers'], suffix='.json')
self.test_ini_file(self.inv_dir)
def test_merge_from_ini_file(self, overwrite=False, overwrite_vars=False):
new_inv_vars = json.dumps({'varc': 'C'})
new_inv = self.organizations[0].inventories.create(name='inv123',
variables=new_inv_vars)
lb_host_vars = json.dumps({'lbvar': 'ni!'})
lb_host = new_inv.hosts.create(name='lb.example.com',
variables=lb_host_vars)
lb_group = new_inv.groups.create(name='lbservers')
servers_group_vars = json.dumps({'vard': 'D'})
servers_group = new_inv.groups.create(name='servers',
variables=servers_group_vars)
servers_group.children.add(lb_group)
lb_group.hosts.add(lb_host)
result, stdout, stderr = self.run_command('inventory_import',
inventory_id=new_inv.pk,
source=self.ini_path,
overwrite=overwrite,
overwrite_vars=overwrite_vars)
self.assertEqual(result, None, stdout + stderr)
# Check that inventory is populated as expected.
new_inv = Inventory.objects.get(pk=new_inv.pk)
expected_group_names = set(['servers', 'dbservers', 'webservers',
'lbservers', 'others'])
if overwrite:
expected_group_names.remove('lbservers')
group_names = set(new_inv.groups.values_list('name', flat=True))
self.assertEqual(expected_group_names, group_names)
expected_host_names = set(['web1.example.com', 'web2.example.com',
'web3.example.com', 'db1.example.com',
'db2.example.com', 'lb.example.com',
'10.11.12.13', '10.12.14.16',
'fe80::1610:9fff:fedd:654b',
'fe80::1610:9fff:fedd:b654', '::1'])
if overwrite:
expected_host_names.remove('lb.example.com')
host_names = set(new_inv.hosts.values_list('name', flat=True))
self.assertEqual(expected_host_names, host_names)
expected_inv_vars = {'vara': 'A', 'varc': 'C'}
if overwrite_vars:
expected_inv_vars.pop('varc')
self.assertEqual(new_inv.variables_dict, expected_inv_vars)
for host in new_inv.hosts.all():
if host.name == 'web1.example.com':
self.assertEqual(host.variables_dict,
{'ansible_ssh_host': 'w1.example.net'})
elif host.name in ('web3.example.com', 'fe80::1610:9fff:fedd:b654'):
self.assertEqual(host.variables_dict, {'ansible_ssh_port': 1022})
elif host.name == '10.12.14.16':
self.assertEqual(host.variables_dict, {'ansible_ssh_port': 8022})
elif host.name == 'lb.example.com':
self.assertEqual(host.variables_dict, {'lbvar': 'ni!'})
else:
self.assertEqual(host.variables_dict, {})
for group in new_inv.groups.all():
if group.name == 'servers':
expected_vars = {'varb': 'B', 'vard': 'D'}
if overwrite_vars:
expected_vars.pop('vard')
self.assertEqual(group.variables_dict, expected_vars)
children = set(group.children.values_list('name', flat=True))
expected_children = set(['dbservers', 'webservers', 'lbservers'])
if overwrite:
expected_children.remove('lbservers')
self.assertEqual(children, expected_children)
self.assertEqual(group.hosts.count(), 0)
elif group.name == 'dbservers':
self.assertEqual(group.variables_dict, {'dbvar': 'ugh'})
self.assertEqual(group.children.count(), 0)
hosts = set(group.hosts.values_list('name', flat=True))
host_names = set(['db1.example.com','db2.example.com'])
self.assertEqual(hosts, host_names)
elif group.name == 'webservers':
self.assertEqual(group.variables_dict, {'webvar': 'blah'})
self.assertEqual(group.children.count(), 0)
hosts = set(group.hosts.values_list('name', flat=True))
host_names = set(['web1.example.com','web2.example.com',
'web3.example.com'])
self.assertEqual(hosts, host_names)
elif group.name == 'lbservers':
self.assertEqual(group.variables_dict, {})
self.assertEqual(group.children.count(), 0)
hosts = set(group.hosts.values_list('name', flat=True))
host_names = set(['lb.example.com'])
self.assertEqual(hosts, host_names)
if overwrite:
except_host_pks = set()
except_group_pks = set()
else:
except_host_pks = set([lb_host.pk])
except_group_pks = set([lb_group.pk])
self.check_adhoc_inventory_source(new_inv, except_host_pks,
except_group_pks)
def test_overwrite_vars_from_ini_file(self):
self.test_merge_from_ini_file(overwrite_vars=True)
def test_overwrite_from_ini_file(self):
self.test_merge_from_ini_file(overwrite=True)
def test_ini_file_with_host_patterns(self):
self.create_test_ini(ini_content=TEST_INVENTORY_INI_WITH_HOST_PATTERNS)
# New empty inventory.
new_inv = self.organizations[0].inventories.create(name='newb')
self.assertEqual(new_inv.hosts.count(), 0)
self.assertEqual(new_inv.groups.count(), 0)
result, stdout, stderr = self.run_command('inventory_import',
inventory_id=new_inv.pk,
source=self.ini_path)
self.assertEqual(result, None, stdout + stderr)
# Check that inventory is populated as expected.
new_inv = Inventory.objects.get(pk=new_inv.pk)
expected_group_names = set(['dotcom', 'dotnet', 'dotorg', 'dotus', 'dotcc'])
group_names = set(new_inv.groups.values_list('name', flat=True))
self.assertEqual(expected_group_names, group_names)
# Check that all host ranges are expanded into host names.
expected_host_names = set()
expected_host_names.update(['web%02d.example.com' % x for x in xrange(64)])
expected_host_names.add('dns.example.com')
expected_host_names.update(['db-%s.example.net' % x for x in string.ascii_lowercase])
expected_host_names.add('ns.example.net')
for x in 'ABCDEF':
for y in xrange(10):
expected_host_names.add('%s%d.example.org' % (x, y))
expected_host_names.add('mx.example.org')
expected_host_names.update(['lb%02d.example.us' % x for x in xrange(10)])
expected_host_names.update(['media%02d.example.cc' % x for x in xrange(100)])
host_names = set(new_inv.hosts.values_list('name', flat=True))
self.assertEqual(expected_host_names, host_names)
# Check hosts in dotcom group.
group = new_inv.groups.get(name='dotcom')
self.assertEqual(group.hosts.count(), 65)
for host in group.hosts.filter( name__startswith='web'):
self.assertEqual(host.variables_dict.get('ansible_ssh_user', ''), 'example')
# Check hosts in dotnet group.
group = new_inv.groups.get(name='dotnet')
self.assertEqual(group.hosts.count(), 27)
# Check hosts in dotorg group.
group = new_inv.groups.get(name='dotorg')
self.assertEqual(group.hosts.count(), 61)
for host in group.hosts.all():
if host.name.startswith('mx.'):
continue
self.assertEqual(host.variables_dict.get('ansible_ssh_user', ''), 'example')
self.assertEqual(host.variables_dict.get('ansible_ssh_port', 22), 1022)
# Check hosts in dotus group.
group = new_inv.groups.get(name='dotus')
self.assertEqual(group.hosts.count(), 10)
for host in group.hosts.all():
if int(host.name[2:4]) % 2 == 0:
self.assertEqual(host.variables_dict.get('even_odd', ''), 'even')
else:
self.assertEqual(host.variables_dict.get('even_odd', ''), 'odd')
# Check hosts in dotcc group.
group = new_inv.groups.get(name='dotcc')
self.assertEqual(group.hosts.count(), 100)
# Check inventory source/update after running command.
self.check_adhoc_inventory_source(new_inv)
# Test with invalid host pattern -- alpha begin > end.
self.create_test_ini(ini_content='[invalid]\nhost[X:P]')
result, stdout, stderr = self.run_command('inventory_import',
inventory_id=new_inv.pk,
source=self.ini_path)
self.assertTrue(isinstance(result, ValueError), result)
# Test with invalid host pattern -- different numeric pattern lengths.
self.create_test_ini(ini_content='[invalid]\nhost[001:08]')
result, stdout, stderr = self.run_command('inventory_import',
inventory_id=new_inv.pk,
source=self.ini_path)
self.assertTrue(isinstance(result, ValueError), result)
# Test with invalid host pattern -- invalid range/slice spec.
self.create_test_ini(ini_content='[invalid]\nhost[1:2:3:4]')
result, stdout, stderr = self.run_command('inventory_import',
inventory_id=new_inv.pk,
source=self.ini_path)
self.assertTrue(isinstance(result, ValueError), result)
# Test with invalid host pattern -- no begin.
self.create_test_ini(ini_content='[invalid]\nhost[:9]')
result, stdout, stderr = self.run_command('inventory_import',
inventory_id=new_inv.pk,
source=self.ini_path)
self.assertTrue(isinstance(result, ValueError), result)
# Test with invalid host pattern -- no end.
self.create_test_ini(ini_content='[invalid]\nhost[0:]')
result, stdout, stderr = self.run_command('inventory_import',
inventory_id=new_inv.pk,
source=self.ini_path)
self.assertTrue(isinstance(result, ValueError), result)
# Test with invalid host pattern -- invalid slice.
self.create_test_ini(ini_content='[invalid]\nhost[0:9:Q]')
result, stdout, stderr = self.run_command('inventory_import',
inventory_id=new_inv.pk,
source=self.ini_path)
self.assertTrue(isinstance(result, ValueError), result)
def test_ini_file_with_recursive_groups(self):
self.create_test_ini(ini_content=TEST_INVENTORY_INI_WITH_RECURSIVE_GROUPS)
new_inv = self.organizations[0].inventories.create(name='new')
self.assertEqual(new_inv.hosts.count(), 0)
self.assertEqual(new_inv.groups.count(), 0)
result, stdout, stderr = self.run_command('inventory_import',
inventory_id=new_inv.pk,
source=self.ini_path)
self.assertEqual(result, None, stdout + stderr)
def test_executable_file(self):
# Use existing inventory as source.
old_inv = self.inventories[1]
# Modify host name to contain brackets (AC-1295).
old_host = old_inv.hosts.all()[0]
old_host.name = '[hey look some brackets]'
old_host.save()
# New empty inventory.
new_inv = self.organizations[0].inventories.create(name='newb')
self.assertEqual(new_inv.hosts.count(), 0)
self.assertEqual(new_inv.groups.count(), 0)
# Use our own inventory script as executable file.
rest_api_url = self.live_server_url
parts = urlparse.urlsplit(rest_api_url)
username, password = self.get_super_credentials()
netloc = '%s:%s@%s' % (username, password, parts.netloc)
rest_api_url = urlparse.urlunsplit([parts.scheme, netloc, parts.path,
parts.query, parts.fragment])
os.environ.setdefault('REST_API_URL', rest_api_url)
os.environ['INVENTORY_ID'] = str(old_inv.pk)
source = os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', 'plugins',
'inventory', 'awxrest.py')
result, stdout, stderr = self.run_command('inventory_import',
inventory_id=new_inv.pk,
source=source)
self.assertEqual(result, None, stdout + stderr)
# Check that inventory is populated as expected.
new_inv = Inventory.objects.get(pk=new_inv.pk)
self.assertEqual(old_inv.variables_dict, new_inv.variables_dict)
old_groups = set(old_inv.groups.values_list('name', flat=True))
new_groups = set(new_inv.groups.values_list('name', flat=True))
self.assertEqual(old_groups, new_groups)
old_hosts = set(old_inv.hosts.values_list('name', flat=True))
new_hosts = set(new_inv.hosts.values_list('name', flat=True))
self.assertEqual(old_hosts, new_hosts)
for new_host in new_inv.hosts.all():
old_host = old_inv.hosts.get(name=new_host.name)
self.assertEqual(old_host.variables_dict, new_host.variables_dict)
for new_group in new_inv.groups.all():
old_group = old_inv.groups.get(name=new_group.name)
self.assertEqual(old_group.variables_dict, new_group.variables_dict)
old_children = set(old_group.children.values_list('name', flat=True))
new_children = set(new_group.children.values_list('name', flat=True))
self.assertEqual(old_children, new_children)
old_hosts = set(old_group.hosts.values_list('name', flat=True))
new_hosts = set(new_group.hosts.values_list('name', flat=True))
self.assertEqual(old_hosts, new_hosts)
self.check_adhoc_inventory_source(new_inv)
def test_executable_file_with_meta_hostvars(self):
os.environ['INVENTORY_HOSTVARS'] = '1'
self.test_executable_file()
def test_large_executable_file(self):
new_inv = self.organizations[0].inventories.create(name='newec2')
self.assertEqual(new_inv.hosts.count(), 0)
self.assertEqual(new_inv.groups.count(), 0)
os.chdir(os.path.join(os.path.dirname(__file__), '..', '..', 'data'))
inv_file = 'large_ec2_inventory.py'
result, stdout, stderr = self.run_command('inventory_import',
inventory_id=new_inv.pk,
source=inv_file)
self.assertEqual(result, None, stdout + stderr)
# Check that inventory is populated as expected within a reasonable
# amount of time. Computed fields should also be updated.
new_inv = Inventory.objects.get(pk=new_inv.pk)
self.assertNotEqual(new_inv.hosts.count(), 0)
self.assertNotEqual(new_inv.groups.count(), 0)
self.assertNotEqual(new_inv.total_hosts, 0)
self.assertNotEqual(new_inv.total_groups, 0)
self.assertElapsedLessThan(60)
def _get_ngroups_for_nhosts(self, n):
if n > 0:
return min(n, 10) + ((n - 1) / 10 + 1) + ((n - 1) / 100 + 1) + ((n - 1) / 1000 + 1)
else:
return 0
def _check_largeinv_import(self, new_inv, nhosts):
self._start_time = time.time()
inv_file = os.path.join(os.path.dirname(__file__), '..', '..', 'data', 'largeinv.py')
ngroups = self._get_ngroups_for_nhosts(nhosts)
os.environ['NHOSTS'] = str(nhosts)
result, stdout, stderr = self.run_command('inventory_import',
inventory_id=new_inv.pk,
source=inv_file,
overwrite=True, verbosity=0)
self.assertEqual(result, None, stdout + stderr)
# Check that inventory is populated as expected within a reasonable
# amount of time. Computed fields should also be updated.
new_inv = Inventory.objects.get(pk=new_inv.pk)
self.assertEqual(new_inv.hosts.count(), nhosts)
self.assertEqual(new_inv.groups.count(), ngroups)
self.assertEqual(new_inv.total_hosts, nhosts)
self.assertEqual(new_inv.total_groups, ngroups)
self.assertElapsedLessThan(120)
@unittest.skipIf(getattr(settings, 'LOCAL_DEVELOPMENT', False),
'Skip this test in local development environments, '
'which may vary widely on memory.')
def test_large_inventory_file(self):
new_inv = self.organizations[0].inventories.create(name='largeinv')
self.assertEqual(new_inv.hosts.count(), 0)
self.assertEqual(new_inv.groups.count(), 0)
nhosts = 2000
# Test initial import into empty inventory.
self._check_largeinv_import(new_inv, nhosts)
# Test re-importing and overwriting.
self._check_largeinv_import(new_inv, nhosts)
# Test re-importing with only half as many hosts.
self._check_largeinv_import(new_inv, nhosts / 2)
# Test re-importing that clears all hosts.
self._check_largeinv_import(new_inv, 0)

View File

@@ -13,6 +13,7 @@ from awx.main.management.commands.inventory_import import (
)
@pytest.mark.inventory_import
class TestInvalidOptions:
def test_invalid_options_no_options_specified(self):

View File

@@ -79,6 +79,7 @@ def test_job_args_unredacted_passwords(job):
assert extra_vars['secret_key'] == 'my_password'
@pytest.mark.survey
def test_update_kwargs_survey_invalid_default(survey_spec_factory):
spec = survey_spec_factory('var2')
spec['spec'][0]['required'] = False
@@ -91,6 +92,7 @@ def test_update_kwargs_survey_invalid_default(survey_spec_factory):
assert json.loads(defaulted_extra_vars['extra_vars'])['var2'] == 2
@pytest.mark.survey
@pytest.mark.parametrize("question_type,default,expect_use,expect_value", [
("multiplechoice", "", False, 'N/A'), # historical bug
("multiplechoice", "zeb", False, 'N/A'), # zeb not in choices
@@ -125,6 +127,7 @@ def test_optional_survey_question_defaults(
assert 'c' not in defaulted_extra_vars['extra_vars']
@pytest.mark.survey
class TestWorkflowSurveys:
def test_update_kwargs_survey_defaults(self, survey_spec_factory):
"Assure that the survey default over-rides a JT variable"

View File

@@ -0,0 +1,128 @@
# AWX utils
from awx.main.utils.mem_inventory import (
MemInventory,
mem_data_to_dict, dict_to_mem_data
)
import pytest
import json
@pytest.fixture
def memory_inventory():
inventory = MemInventory()
h = inventory.get_host('my_host')
h.variables = {'foo': 'bar'}
g = inventory.get_group('my_group')
g.variables = {'foobar': 'barfoo'}
h2 = inventory.get_host('group_host')
g.add_host(h2)
return inventory
@pytest.fixture
def JSON_of_inv():
# Implemented as fixture becuase it may be change inside of tests
return {
"_meta": {
"hostvars": {
"group_host": {},
"my_host": {"foo": "bar"}
}
},
"all": {"children": ["my_group", "ungrouped"]},
"my_group": {
"hosts": ["group_host"],
"vars": {"foobar": "barfoo"}
},
"ungrouped": {"hosts": ["my_host"]}
}
# Structure mentioned in official docs
# https://docs.ansible.com/ansible/dev_guide/developing_inventory.html
@pytest.fixture
def JSON_with_lists():
docs_example = '''{
"databases" : {
"hosts" : [ "host1.example.com", "host2.example.com" ],
"vars" : {
"a" : true
}
},
"webservers" : [ "host2.example.com", "host3.example.com" ],
"atlanta" : {
"hosts" : [ "host1.example.com", "host4.example.com", "host5.example.com" ],
"vars" : {
"b" : false
},
"children": [ "marietta", "5points" ]
},
"marietta" : [ "host6.example.com" ],
"5points" : [ "host7.example.com" ]
}'''
return json.loads(docs_example)
# MemObject basic operations tests
@pytest.mark.inventory_import
def test_inventory_create_all_group():
inventory = MemInventory()
assert inventory.all_group.name == 'all'
@pytest.mark.inventory_import
def test_create_child_group():
inventory = MemInventory()
g1 = inventory.get_group('g1')
# Create new group by name as child of g1
g2 = inventory.get_group('g2', g1)
# Check that child is in the children of the parent group
assert g1.children == [g2]
# Check that _only_ the parent group is listed as a root group
assert inventory.all_group.children == [g1]
# Check that _both_ are tracked by the global `all_groups` dict
assert set(inventory.all_group.all_groups.values()) == set([g1, g2])
@pytest.mark.inventory_import
def test_ungrouped_mechanics():
# ansible-inventory returns a group called `ungrouped`
# we can safely treat this the same as the `all_group`
inventory = MemInventory()
ug = inventory.get_group('ungrouped')
assert ug is inventory.all_group
# MemObject --> JSON tests
@pytest.mark.inventory_import
def test_convert_memory_to_JSON_with_vars(memory_inventory):
data = mem_data_to_dict(memory_inventory)
# Assertions about the variables on the objects
assert data['_meta']['hostvars']['my_host'] == {'foo': 'bar'}
assert data['my_group']['vars'] == {'foobar': 'barfoo'}
# Orphan host should be found in ungrouped false group
assert data['ungrouped']['hosts'] == ['my_host']
# JSON --> MemObject tests
@pytest.mark.inventory_import
def test_convert_JSON_to_memory_with_vars(JSON_of_inv):
inventory = dict_to_mem_data(JSON_of_inv)
# Assertions about the variables on the objects
assert inventory.get_host('my_host').variables == {'foo': 'bar'}
assert inventory.get_group('my_group').variables == {'foobar': 'barfoo'}
# Host should be child of group
assert inventory.get_host('group_host') in inventory.get_group('my_group').hosts
@pytest.mark.inventory_import
def test_host_lists_accepted(JSON_with_lists):
inventory = dict_to_mem_data(JSON_with_lists)
assert inventory.get_group('marietta').name == 'marietta'
# Check that marietta's hosts was saved
h = inventory.get_host('host6.example.com')
assert h.name == 'host6.example.com'

View File

@@ -5,6 +5,16 @@ from logstash.formatter import LogstashFormatterVersion1
from copy import copy
import json
import time
import logging
class TimeFormatter(logging.Formatter):
'''
Custom log formatter used for inventory imports
'''
def format(self, record):
record.relativeSeconds = record.relativeCreated / 1000.0
return logging.Formatter.format(self, record)
class LogstashFormatter(LogstashFormatterVersion1):

View File

@@ -0,0 +1,315 @@
# 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

View File

@@ -0,0 +1,326 @@
#!/usr/bin/env python
# (c) 2017, Brian Coca <bcoca@ansible.com>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import optparse
from operator import attrgetter
from ansible import constants as C
from ansible.cli import CLI
from ansible.errors import AnsibleOptionsError
from ansible.inventory import Inventory
from ansible.parsing.dataloader import DataLoader
from ansible.vars import VariableManager
try:
from __main__ import display
except ImportError:
from ansible.utils.display import Display
display = Display()
INTERNAL_VARS = frozenset([ 'ansible_facts',
'ansible_version',
'ansible_playbook_python',
'inventory_dir',
'inventory_file',
'inventory_hostname',
'inventory_hostname_short',
'groups',
'group_names',
'omit',
'playbook_dir',
])
class InventoryCLI(CLI):
''' used to display or dump the configured inventory as Ansible sees it '''
ARGUMENTS = { 'host': 'The name of a host to match in the inventory, relevant when using --list',
'group': 'The name of a group in the inventory, relevant when using --graph',
}
def __init__(self, args):
super(InventoryCLI, self).__init__(args)
self.vm = None
self.loader = None
def parse(self):
self.parser = CLI.base_parser(
usage='usage: %prog [options] [host|group]',
epilog='Show Ansible inventory information, by default it uses the inventory script JSON format',
inventory_opts=True,
vault_opts=True
)
# Actions
action_group = optparse.OptionGroup(self.parser, "Actions", "One of following must be used on invocation, ONLY ONE!")
action_group.add_option("--list", action="store_true", default=False, dest='list', help='Output all hosts info, works as inventory script')
action_group.add_option("--host", action="store", default=None, dest='host', help='Output specific host info, works as inventory script')
action_group.add_option("--graph", action="store_true", default=False, dest='graph',
help='create inventory graph, if supplying pattern it must be a valid group name')
self.parser.add_option_group(action_group)
# Options
self.parser.add_option("-y", "--yaml", action="store_true", default=False, dest='yaml',
help='Use YAML format instead of default JSON, ignored for --graph')
self.parser.add_option("--vars", action="store_true", default=False, dest='show_vars',
help='Add vars to graph display, ignored unless used with --graph')
try:
super(InventoryCLI, self).parse()
except Exception as e:
if 'Need to implement!' not in e.args[0]:
raise
# --- Start of 2.3+ super(InventoryCLI, self).parse() ---
self.options, self.args = self.parser.parse_args(self.args[1:])
if hasattr(self.options, 'tags') and not self.options.tags:
# optparse defaults does not do what's expected
self.options.tags = ['all']
if hasattr(self.options, 'tags') and self.options.tags:
if not C.MERGE_MULTIPLE_CLI_TAGS:
if len(self.options.tags) > 1:
display.deprecated('Specifying --tags multiple times on the command line currently uses the last specified value. In 2.4, values will be merged instead. Set merge_multiple_cli_tags=True in ansible.cfg to get this behavior now.', version=2.5, removed=False)
self.options.tags = [self.options.tags[-1]]
tags = set()
for tag_set in self.options.tags:
for tag in tag_set.split(u','):
tags.add(tag.strip())
self.options.tags = list(tags)
if hasattr(self.options, 'skip_tags') and self.options.skip_tags:
if not C.MERGE_MULTIPLE_CLI_TAGS:
if len(self.options.skip_tags) > 1:
display.deprecated('Specifying --skip-tags multiple times on the command line currently uses the last specified value. In 2.4, values will be merged instead. Set merge_multiple_cli_tags=True in ansible.cfg to get this behavior now.', version=2.5, removed=False)
self.options.skip_tags = [self.options.skip_tags[-1]]
skip_tags = set()
for tag_set in self.options.skip_tags:
for tag in tag_set.split(u','):
skip_tags.add(tag.strip())
self.options.skip_tags = list(skip_tags)
# --- End of 2.3+ super(InventoryCLI, self).parse() ---
display.verbosity = self.options.verbosity
self.validate_conflicts(vault_opts=True)
# there can be only one! and, at least, one!
used = 0
for opt in (self.options.list, self.options.host, self.options.graph):
if opt:
used += 1
if used == 0:
raise AnsibleOptionsError("No action selected, at least one of --host, --graph or --list needs to be specified.")
elif used > 1:
raise AnsibleOptionsError("Conflicting options used, only one of --host, --graph or --list can be used at the same time.")
# set host pattern to default if not supplied
if len(self.args) > 0:
self.options.pattern = self.args[0]
else:
self.options.pattern = 'all'
def run(self):
results = None
super(InventoryCLI, self).run()
# Initialize needed objects
self.loader = DataLoader()
self.vm = VariableManager()
# use vault if needed
if self.options.vault_password_file:
vault_pass = CLI.read_vault_password_file(self.options.vault_password_file, loader=self.loader)
elif self.options.ask_vault_pass:
vault_pass = self.ask_vault_passwords()
else:
vault_pass = None
if vault_pass:
self.loader.set_vault_password(vault_pass)
# actually get inventory and vars
self.inventory = Inventory(loader=self.loader, variable_manager=self.vm, host_list=self.options.inventory)
self.vm.set_inventory(self.inventory)
if self.options.host:
hosts = self.inventory.get_hosts(self.options.host)
if len(hosts) != 1:
raise AnsibleOptionsError("You must pass a single valid host to --hosts parameter")
myvars = self.vm.get_vars(self.loader, host=hosts[0])
self._remove_internal(myvars)
# FIXME: should we template first?
results = self.dump(myvars)
elif self.options.graph:
results = self.inventory_graph()
elif self.options.list:
top = self.inventory.get_group('all')
if self.options.yaml:
results = self.yaml_inventory(top)
else:
results = self.json_inventory(top)
results = self.dump(results)
if results:
display.display(results)
exit(0)
exit(1)
def dump(self, stuff):
if self.options.yaml:
import yaml
from ansible.parsing.yaml.dumper import AnsibleDumper
results = yaml.dump(stuff, Dumper=AnsibleDumper, default_flow_style=False)
else:
import json
results = json.dumps(stuff, sort_keys=True, indent=4)
return results
def _remove_internal(self, dump):
for internal in INTERNAL_VARS:
if internal in dump:
del dump[internal]
def _remove_empty(self, dump):
# remove empty keys
for x in ('hosts', 'vars', 'children'):
if x in dump and not dump[x]:
del dump[x]
def _show_vars(self, dump, depth):
result = []
self._remove_internal(dump)
if self.options.show_vars:
for (name, val) in sorted(dump.items()):
result.append(self._graph_name('{%s = %s}' % (name, val), depth + 1))
return result
def _graph_name(self, name, depth=0):
if depth:
name = " |" * (depth) + "--%s" % name
return name
def _graph_group(self, group, depth=0):
result = [self._graph_name('@%s:' % group.name, depth)]
depth = depth + 1
for kid in sorted(group.child_groups, key=attrgetter('name')):
result.extend(self._graph_group(kid, depth))
if group.name != 'all':
for host in sorted(group.hosts, key=attrgetter('name')):
result.append(self._graph_name(host.name, depth))
result.extend(self._show_vars(host.get_vars(), depth))
result.extend(self._show_vars(group.get_vars(), depth))
return result
def inventory_graph(self):
start_at = self.inventory.get_group(self.options.pattern)
if start_at:
return '\n'.join(self._graph_group(start_at))
else:
raise AnsibleOptionsError("Pattern must be valid group name when using --graph")
def json_inventory(self, top):
def format_group(group):
results = {}
results[group.name] = {}
if group.name != 'all':
results[group.name]['hosts'] = [h.name for h in sorted(group.hosts, key=attrgetter('name'))]
results[group.name]['vars'] = group.get_vars()
results[group.name]['children'] = []
for subgroup in sorted(group.child_groups, key=attrgetter('name')):
results[group.name]['children'].append(subgroup.name)
results.update(format_group(subgroup))
self._remove_empty(results[group.name])
return results
results = format_group(top)
# populate meta
results['_meta'] = {'hostvars': {}}
hosts = self.inventory.get_hosts()
for host in hosts:
results['_meta']['hostvars'][host.name] = self.vm.get_vars(self.loader, host=host)
self._remove_internal(results['_meta']['hostvars'][host.name])
return results
def yaml_inventory(self, top):
seen = []
def format_group(group):
results = {}
# initialize group + vars
results[group.name] = {}
results[group.name]['vars'] = group.get_vars()
# subgroups
results[group.name]['children'] = {}
for subgroup in sorted(group.child_groups, key=attrgetter('name')):
if subgroup.name != 'all':
results[group.name]['children'].update(format_group(subgroup))
# hosts for group
results[group.name]['hosts'] = {}
if group.name != 'all':
for h in sorted(group.hosts, key=attrgetter('name')):
myvars = {}
if h.name not in seen: # avoid defining host vars more than once
seen.append(h.name)
myvars = self.vm.get_vars(self.loader, host=h)
self._remove_internal(myvars)
results[group.name]['hosts'][h.name] = myvars
self._remove_empty(results[group.name])
return results
return format_group(top)
if __name__ == '__main__':
import imp
import subprocess
import sys
with open(__file__) as f:
imp.load_source('ansible.cli.inventory', __file__ + '.py', f)
ansible_path = subprocess.check_output(['which', 'ansible']).strip()
sys.argv[0] = 'ansible-inventory'
execfile(ansible_path)

View File

@@ -907,6 +907,10 @@ LOGGING = {
},
'json': {
'()': 'awx.main.utils.formatters.LogstashFormatter'
},
'timed_import': {
'()': 'awx.main.utils.formatters.TimeFormatter',
'format': '%(relativeSeconds)9.3f %(levelname)-8s %(message)s'
}
},
'handlers': {
@@ -958,6 +962,11 @@ LOGGING = {
'backupCount': 5,
'formatter':'simple',
},
'inventory_import': {
'level': 'DEBUG',
'class':'logging.StreamHandler',
'formatter': 'timed_import',
},
'task_system': {
'level': 'INFO',
'class':'logging.handlers.RotatingFileHandler',
@@ -1029,6 +1038,10 @@ LOGGING = {
'awx.main.commands.run_callback_receiver': {
'handlers': ['callback_receiver'],
},
'awx.main.commands.inventory_import': {
'handlers': ['inventory_import'],
'propagate': False
},
'awx.main.tasks': {
'handlers': ['task_system'],
},