mirror of
https://github.com/ansible/awx.git
synced 2026-05-24 00:57:48 -02:30
move script injection logic to inventory file
This commit is contained in:
@@ -14,6 +14,7 @@ import yaml
|
|||||||
import configparser
|
import configparser
|
||||||
import stat
|
import stat
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from io import StringIO
|
||||||
from distutils.version import LooseVersion as Version
|
from distutils.version import LooseVersion as Version
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
@@ -57,7 +58,7 @@ from awx.main.models.notifications import (
|
|||||||
NotificationTemplate,
|
NotificationTemplate,
|
||||||
JobNotificationMixin,
|
JobNotificationMixin,
|
||||||
)
|
)
|
||||||
from awx.main.utils import _inventory_updates, region_sorting
|
from awx.main.utils import _inventory_updates, region_sorting, get_licenser
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate',
|
__all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate',
|
||||||
@@ -1314,9 +1315,9 @@ class InventorySourceOptions(BaseModel):
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_inventory_plugin_name(self):
|
def get_inventory_plugin_name(self, ansible_version):
|
||||||
if self.source in InventorySourceOptions.injectors:
|
if self.source in InventorySourceOptions.injectors:
|
||||||
return InventorySourceOptions.injectors[self.source].plugin_name
|
return InventorySourceOptions.injectors[self.source](ansible_version).use_plugin_name()
|
||||||
if self.source in CLOUD_PROVIDERS or self.source == 'custom':
|
if self.source in CLOUD_PROVIDERS or self.source == 'custom':
|
||||||
# TODO: today, all vendored sources are scripts
|
# TODO: today, all vendored sources are scripts
|
||||||
# in future release inventory plugins will replace these
|
# in future release inventory plugins will replace these
|
||||||
@@ -1822,8 +1823,9 @@ class CustomInventoryScript(CommonModelNameNotUnique, ResourceMixin):
|
|||||||
|
|
||||||
# TODO: move these to their own file somewhere?
|
# TODO: move these to their own file somewhere?
|
||||||
class PluginFileInjector(object):
|
class PluginFileInjector(object):
|
||||||
plugin_name = None
|
plugin_name = None # Ansible core name used to reference plugin
|
||||||
initial_version = None
|
initial_version = None # at what version do we switch to the plugin
|
||||||
|
ini_env_reference = None # env var name that points to old ini config file
|
||||||
|
|
||||||
def __init__(self, ansible_version):
|
def __init__(self, ansible_version):
|
||||||
# This is InventoryOptions instance, could be source or inventory update
|
# This is InventoryOptions instance, could be source or inventory update
|
||||||
@@ -1842,6 +1844,13 @@ class PluginFileInjector(object):
|
|||||||
Version(self.ansible_version) >= Version(self.initial_version)
|
Version(self.ansible_version) >= Version(self.initial_version)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def use_plugin_name(self):
|
||||||
|
if self.should_use_plugin() and self.plugin_name is not None:
|
||||||
|
return self.plugin_name
|
||||||
|
else:
|
||||||
|
# By default, if the plugin cannot be used, then we use old vendored scripts
|
||||||
|
return 'script'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_builtin_injector(source):
|
def get_builtin_injector(source):
|
||||||
from awx.main.models.credential import injectors as builtin_injectors
|
from awx.main.models.credential import injectors as builtin_injectors
|
||||||
@@ -1850,30 +1859,35 @@ class PluginFileInjector(object):
|
|||||||
return None
|
return None
|
||||||
return getattr(builtin_injectors, cred_kind)
|
return getattr(builtin_injectors, cred_kind)
|
||||||
|
|
||||||
def build_env(self, inventory_update, env, private_data_dir):
|
def build_env(self, inventory_update, env, private_data_dir, private_data_files):
|
||||||
if self.should_use_plugin():
|
if self.should_use_plugin():
|
||||||
injector_env = self.get_plugin_env(inventory_update, private_data_dir)
|
injector_env = self.get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
||||||
else:
|
else:
|
||||||
injector_env = self.get_script_env(inventory_update, private_data_dir)
|
injector_env = self.get_script_env(inventory_update, private_data_dir, private_data_files)
|
||||||
env.update(injector_env)
|
env.update(injector_env)
|
||||||
return env
|
return env
|
||||||
|
|
||||||
def get_plugin_env(self, inventory_update, private_data_dir, safe=False):
|
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files, safe=False):
|
||||||
return self.get_script_env(inventory_update, private_data_dir, safe)
|
return self.get_script_env(inventory_update, private_data_dir, private_data_files, safe)
|
||||||
|
|
||||||
def get_script_env(self, inventory_update, private_data_dir, safe=False):
|
def get_script_env(self, inventory_update, private_data_dir, private_data_files, safe=False):
|
||||||
"""By default, we will apply the standard managed_by_tower injectors
|
"""By default, we will apply the standard managed_by_tower injectors
|
||||||
for the script injection
|
for the script injection
|
||||||
"""
|
"""
|
||||||
injected_env = {}
|
injected_env = {}
|
||||||
credential = inventory_update.get_cloud_credential()
|
credential = inventory_update.get_cloud_credential()
|
||||||
builtin_injector = self.get_builtin_injector(inventory_update.source)
|
builtin_injector = self.get_builtin_injector(inventory_update.source)
|
||||||
if builtin_injector is None:
|
if builtin_injector is not None:
|
||||||
return {}
|
builtin_injector(credential, injected_env, private_data_dir)
|
||||||
builtin_injector(credential, injected_env, private_data_dir)
|
if safe:
|
||||||
if safe:
|
from awx.main.models.credential import build_safe_env
|
||||||
from awx.main.models.credential import build_safe_env
|
injected_env = build_safe_env(injected_env)
|
||||||
injected_env = build_safe_env(injected_env)
|
|
||||||
|
# Put in env var reference to private data files, if relevant
|
||||||
|
if self.ini_env_reference:
|
||||||
|
cred_data = private_data_files.get('credentials', '')
|
||||||
|
injected_env[self.ini_env_reference] = cred_data[credential]
|
||||||
|
|
||||||
return injected_env
|
return injected_env
|
||||||
|
|
||||||
def build_private_data(self, inventory_update, private_data_dir):
|
def build_private_data(self, inventory_update, private_data_dir):
|
||||||
@@ -1888,13 +1902,90 @@ class PluginFileInjector(object):
|
|||||||
def build_plugin_private_data(self, inventory_update, private_data_dir):
|
def build_plugin_private_data(self, inventory_update, private_data_dir):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def dump_cp(cp, credential):
|
||||||
|
"""Dump config parser data and return it as a string.
|
||||||
|
Helper method intended for use by build_script_private_data
|
||||||
|
"""
|
||||||
|
if cp.sections():
|
||||||
|
f = StringIO()
|
||||||
|
cp.write(f)
|
||||||
|
private_data = private_data = {'credentials': {}}
|
||||||
|
private_data['credentials'][credential] = f.getvalue()
|
||||||
|
return private_data
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class azure_rm(PluginFileInjector):
|
||||||
|
ini_env_reference = 'AZURE_INI_PATH'
|
||||||
|
|
||||||
|
def build_script_private_data(self, inventory_update, private_data_dir):
|
||||||
|
cp = configparser.RawConfigParser()
|
||||||
|
section = 'azure'
|
||||||
|
cp.add_section(section)
|
||||||
|
cp.set(section, 'include_powerstate', 'yes')
|
||||||
|
cp.set(section, 'group_by_resource_group', 'yes')
|
||||||
|
cp.set(section, 'group_by_location', 'yes')
|
||||||
|
cp.set(section, 'group_by_tag', 'yes')
|
||||||
|
|
||||||
|
if inventory_update.source_regions and 'all' not in inventory_update.source_regions:
|
||||||
|
cp.set(
|
||||||
|
section, 'locations',
|
||||||
|
','.join([x.strip() for x in inventory_update.source_regions.split(',')])
|
||||||
|
)
|
||||||
|
|
||||||
|
azure_rm_opts = dict(inventory_update.source_vars_dict.items())
|
||||||
|
for k, v in azure_rm_opts.items():
|
||||||
|
cp.set(section, k, str(v))
|
||||||
|
return self.dump_cp(cp, inventory_update.get_cloud_credential())
|
||||||
|
|
||||||
|
|
||||||
|
class ec2(PluginFileInjector):
|
||||||
|
ini_env_reference = 'EC2_INI_PATH'
|
||||||
|
|
||||||
|
def build_script_private_data(self, inventory_update, private_data_dir):
|
||||||
|
cp = configparser.RawConfigParser()
|
||||||
|
# Build custom ec2.ini for ec2 inventory script to use.
|
||||||
|
section = 'ec2'
|
||||||
|
cp.add_section(section)
|
||||||
|
ec2_opts = dict(inventory_update.source_vars_dict.items())
|
||||||
|
regions = inventory_update.source_regions or 'all'
|
||||||
|
regions = ','.join([x.strip() for x in regions.split(',')])
|
||||||
|
regions_blacklist = ','.join(settings.EC2_REGIONS_BLACKLIST)
|
||||||
|
ec2_opts['regions'] = regions
|
||||||
|
ec2_opts.setdefault('regions_exclude', regions_blacklist)
|
||||||
|
ec2_opts.setdefault('destination_variable', 'public_dns_name')
|
||||||
|
ec2_opts.setdefault('vpc_destination_variable', 'ip_address')
|
||||||
|
ec2_opts.setdefault('route53', 'False')
|
||||||
|
ec2_opts.setdefault('all_instances', 'True')
|
||||||
|
ec2_opts.setdefault('all_rds_instances', 'False')
|
||||||
|
ec2_opts.setdefault('include_rds_clusters', 'False')
|
||||||
|
ec2_opts.setdefault('rds', 'False')
|
||||||
|
ec2_opts.setdefault('nested_groups', 'True')
|
||||||
|
ec2_opts.setdefault('elasticache', 'False')
|
||||||
|
ec2_opts.setdefault('stack_filters', 'False')
|
||||||
|
if inventory_update.instance_filters:
|
||||||
|
ec2_opts.setdefault('instance_filters', inventory_update.instance_filters)
|
||||||
|
group_by = [x.strip().lower() for x in inventory_update.group_by.split(',') if x.strip()]
|
||||||
|
for choice in inventory_update.get_ec2_group_by_choices():
|
||||||
|
value = bool((group_by and choice[0] in group_by) or (not group_by and choice[0] != 'instance_id'))
|
||||||
|
ec2_opts.setdefault('group_by_%s' % choice[0], str(value))
|
||||||
|
if 'cache_path' not in ec2_opts:
|
||||||
|
cache_path = tempfile.mkdtemp(prefix='ec2_cache', dir=private_data_dir)
|
||||||
|
ec2_opts['cache_path'] = cache_path
|
||||||
|
ec2_opts.setdefault('cache_max_age', '300')
|
||||||
|
for k, v in ec2_opts.items():
|
||||||
|
cp.set(section, k, str(v))
|
||||||
|
return self.dump_cp(cp, inventory_update.get_cloud_credential())
|
||||||
|
|
||||||
|
|
||||||
class gce(PluginFileInjector):
|
class gce(PluginFileInjector):
|
||||||
plugin_name = 'gcp_compute'
|
plugin_name = 'gcp_compute'
|
||||||
initial_version = '2.6'
|
initial_version = '2.6'
|
||||||
|
|
||||||
def get_script_env(self, inventory_update, private_data_dir):
|
def get_script_env(self, inventory_update, private_data_dir, private_data_files):
|
||||||
env = super(gce, self).get_script_env(inventory_update, private_data_dir)
|
env = super(gce, self).get_script_env(inventory_update, private_data_dir, private_data_files)
|
||||||
env['GCE_ZONE'] = inventory_update.source_regions if inventory_update.source_regions != 'all' else '' # noqa
|
env['GCE_ZONE'] = inventory_update.source_regions if inventory_update.source_regions != 'all' else '' # noqa
|
||||||
|
|
||||||
# by default, the GCE inventory source caches results on disk for
|
# by default, the GCE inventory source caches results on disk for
|
||||||
@@ -1926,14 +2017,94 @@ class gce(PluginFileInjector):
|
|||||||
ret['zones'] = inventory_update.source_regions.split(',')
|
ret['zones'] = inventory_update.source_regions.split(',')
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_plugin_env(self, inventory_update, private_data_dir, safe=False):
|
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files, safe=False):
|
||||||
# gce wants everything defined in inventory & cred files
|
# gce wants everything defined in inventory & cred files
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
class vmware(PluginFileInjector):
|
||||||
|
ini_env_reference = 'VMWARE_INI_PATH'
|
||||||
|
|
||||||
|
def build_script_private_data(self, inventory_update, private_data_dir):
|
||||||
|
cp = configparser.RawConfigParser()
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
|
||||||
|
# Allow custom options to vmware inventory script.
|
||||||
|
section = 'vmware'
|
||||||
|
cp.add_section(section)
|
||||||
|
cp.set('vmware', 'cache_max_age', '0')
|
||||||
|
cp.set('vmware', 'validate_certs', str(settings.VMWARE_VALIDATE_CERTS))
|
||||||
|
cp.set('vmware', 'username', credential.get_input('username', default=''))
|
||||||
|
cp.set('vmware', 'password', credential.get_input('password', default=''))
|
||||||
|
cp.set('vmware', 'server', credential.get_input('host', default=''))
|
||||||
|
|
||||||
|
vmware_opts = dict(inventory_update.source_vars_dict.items())
|
||||||
|
if inventory_update.instance_filters:
|
||||||
|
vmware_opts.setdefault('host_filters', inventory_update.instance_filters)
|
||||||
|
if inventory_update.group_by:
|
||||||
|
vmware_opts.setdefault('groupby_patterns', inventory_update.group_by)
|
||||||
|
|
||||||
|
for k, v in vmware_opts.items():
|
||||||
|
cp.set(section, k, str(v))
|
||||||
|
|
||||||
|
return self.dump_cp(cp, credential)
|
||||||
|
|
||||||
|
|
||||||
|
class openstack(PluginFileInjector):
|
||||||
|
ini_env_reference = 'OS_CLIENT_CONFIG_FILE'
|
||||||
|
|
||||||
|
def build_script_private_data(self, inventory_update, private_data_dir):
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
private_data = {'credentials': {}}
|
||||||
|
|
||||||
|
openstack_auth = dict(auth_url=credential.get_input('host', default=''),
|
||||||
|
username=credential.get_input('username', default=''),
|
||||||
|
password=credential.get_input('password', default=''),
|
||||||
|
project_name=credential.get_input('project', default=''))
|
||||||
|
if credential.has_input('domain'):
|
||||||
|
openstack_auth['domain_name'] = credential.get_input('domain', default='')
|
||||||
|
|
||||||
|
private_state = inventory_update.source_vars_dict.get('private', True)
|
||||||
|
verify_state = credential.get_input('verify_ssl', default=True)
|
||||||
|
# Retrieve cache path from inventory update vars if available,
|
||||||
|
# otherwise create a temporary cache path only for this update.
|
||||||
|
cache = inventory_update.source_vars_dict.get('cache', {})
|
||||||
|
if not isinstance(cache, dict):
|
||||||
|
cache = {}
|
||||||
|
if not cache.get('path', ''):
|
||||||
|
cache_path = tempfile.mkdtemp(prefix='openstack_cache', dir=private_data_dir)
|
||||||
|
cache['path'] = cache_path
|
||||||
|
openstack_data = {
|
||||||
|
'clouds': {
|
||||||
|
'devstack': {
|
||||||
|
'private': private_state,
|
||||||
|
'verify': verify_state,
|
||||||
|
'auth': openstack_auth,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'cache': cache,
|
||||||
|
}
|
||||||
|
ansible_variables = {
|
||||||
|
'use_hostnames': True,
|
||||||
|
'expand_hostvars': False,
|
||||||
|
'fail_on_errors': True,
|
||||||
|
}
|
||||||
|
provided_count = 0
|
||||||
|
for var_name in ansible_variables:
|
||||||
|
if var_name in inventory_update.source_vars_dict:
|
||||||
|
ansible_variables[var_name] = inventory_update.source_vars_dict[var_name]
|
||||||
|
provided_count += 1
|
||||||
|
if provided_count:
|
||||||
|
openstack_data['ansible'] = ansible_variables
|
||||||
|
private_data['credentials'][credential] = yaml.safe_dump(
|
||||||
|
openstack_data, default_flow_style=False, allow_unicode=True
|
||||||
|
)
|
||||||
|
return private_data
|
||||||
|
|
||||||
|
|
||||||
class rhv(PluginFileInjector):
|
class rhv(PluginFileInjector):
|
||||||
|
|
||||||
def get_script_env(self, inventory_update, private_data_dir):
|
def get_script_env(self, inventory_update, private_data_dir, private_data_files):
|
||||||
"""Unlike the others, ovirt uses the custom credential templating
|
"""Unlike the others, ovirt uses the custom credential templating
|
||||||
"""
|
"""
|
||||||
env = {'INVENTORY_UPDATE_ID': inventory_update.pk}
|
env = {'INVENTORY_UPDATE_ID': inventory_update.pk}
|
||||||
@@ -1947,5 +2118,92 @@ class rhv(PluginFileInjector):
|
|||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
class satellite6(PluginFileInjector):
|
||||||
|
ini_env_reference = 'FOREMAN_INI_PATH'
|
||||||
|
|
||||||
|
def build_script_private_data(self, inventory_update, private_data_dir):
|
||||||
|
cp = configparser.RawConfigParser()
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
|
||||||
|
section = 'foreman'
|
||||||
|
cp.add_section(section)
|
||||||
|
|
||||||
|
group_patterns = '[]'
|
||||||
|
group_prefix = 'foreman_'
|
||||||
|
want_hostcollections = 'False'
|
||||||
|
foreman_opts = dict(inventory_update.source_vars_dict.items())
|
||||||
|
foreman_opts.setdefault('ssl_verify', 'False')
|
||||||
|
for k, v in foreman_opts.items():
|
||||||
|
if k == 'satellite6_group_patterns' and isinstance(v, str):
|
||||||
|
group_patterns = v
|
||||||
|
elif k == 'satellite6_group_prefix' and isinstance(v, str):
|
||||||
|
group_prefix = v
|
||||||
|
elif k == 'satellite6_want_hostcollections' and isinstance(v, bool):
|
||||||
|
want_hostcollections = v
|
||||||
|
else:
|
||||||
|
cp.set(section, k, str(v))
|
||||||
|
|
||||||
|
if credential:
|
||||||
|
cp.set(section, 'url', credential.get_input('host', default=''))
|
||||||
|
cp.set(section, 'user', credential.get_input('username', default=''))
|
||||||
|
cp.set(section, 'password', credential.get_input('password', default=''))
|
||||||
|
|
||||||
|
section = 'ansible'
|
||||||
|
cp.add_section(section)
|
||||||
|
cp.set(section, 'group_patterns', group_patterns)
|
||||||
|
cp.set(section, 'want_facts', 'True')
|
||||||
|
cp.set(section, 'want_hostcollections', str(want_hostcollections))
|
||||||
|
cp.set(section, 'group_prefix', group_prefix)
|
||||||
|
|
||||||
|
section = 'cache'
|
||||||
|
cp.add_section(section)
|
||||||
|
cp.set(section, 'path', '/tmp')
|
||||||
|
cp.set(section, 'max_age', '0')
|
||||||
|
|
||||||
|
return self.dump_cp(cp, credential)
|
||||||
|
|
||||||
|
|
||||||
|
class cloudforms(PluginFileInjector):
|
||||||
|
ini_env_reference = 'CLOUDFORMS_INI_PATH'
|
||||||
|
|
||||||
|
def build_script_private_data(self, inventory_update, private_data_dir):
|
||||||
|
cp = configparser.RawConfigParser()
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
|
||||||
|
section = 'cloudforms'
|
||||||
|
cp.add_section(section)
|
||||||
|
|
||||||
|
if credential:
|
||||||
|
cp.set(section, 'url', credential.get_input('host', default=''))
|
||||||
|
cp.set(section, 'username', credential.get_input('username', default=''))
|
||||||
|
cp.set(section, 'password', credential.get_input('password', default=''))
|
||||||
|
cp.set(section, 'ssl_verify', "false")
|
||||||
|
|
||||||
|
cloudforms_opts = dict(inventory_update.source_vars_dict.items())
|
||||||
|
for opt in ['version', 'purge_actions', 'clean_group_keys', 'nest_tags', 'suffix', 'prefer_ipv4']:
|
||||||
|
if opt in cloudforms_opts:
|
||||||
|
cp.set(section, opt, str(cloudforms_opts[opt]))
|
||||||
|
|
||||||
|
section = 'cache'
|
||||||
|
cp.add_section(section)
|
||||||
|
cp.set(section, 'max_age', "0")
|
||||||
|
cache_path = tempfile.mkdtemp(
|
||||||
|
prefix='cloudforms_cache',
|
||||||
|
dir=private_data_dir
|
||||||
|
)
|
||||||
|
cp.set(section, 'path', cache_path)
|
||||||
|
|
||||||
|
return self.dump_cp(cp, credential)
|
||||||
|
|
||||||
|
|
||||||
|
class tower(PluginFileInjector):
|
||||||
|
|
||||||
|
def get_script_env(self, inventory_update, private_data_dir, private_data_files):
|
||||||
|
env = super(tower, self).get_script_env(inventory_update, private_data_dir, private_data_files)
|
||||||
|
env['TOWER_INVENTORY'] = inventory_update.instance_filters
|
||||||
|
env['TOWER_LICENSE_TYPE'] = get_licenser().validate().get('license_type', 'unlicensed')
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
for cls in PluginFileInjector.__subclasses__():
|
for cls in PluginFileInjector.__subclasses__():
|
||||||
InventorySourceOptions.injectors[cls.__name__] = cls
|
InventorySourceOptions.injectors[cls.__name__] = cls
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
# Python
|
# Python
|
||||||
from collections import OrderedDict, namedtuple
|
from collections import OrderedDict, namedtuple
|
||||||
import configparser
|
|
||||||
import errno
|
import errno
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import functools
|
import functools
|
||||||
@@ -24,7 +23,6 @@ try:
|
|||||||
import psutil
|
import psutil
|
||||||
except Exception:
|
except Exception:
|
||||||
psutil = None
|
psutil = None
|
||||||
from io import StringIO
|
|
||||||
import urllib.parse as urlparse
|
import urllib.parse as urlparse
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
@@ -1940,194 +1938,8 @@ class RunInventoryUpdate(BaseTask):
|
|||||||
If no private data is needed, return None.
|
If no private data is needed, return None.
|
||||||
"""
|
"""
|
||||||
if inventory_update.source in InventorySource.injectors:
|
if inventory_update.source in InventorySource.injectors:
|
||||||
injector = InventorySource.injectors[inventory_update.source](kwargs['ansible_version'])
|
injector = InventorySource.injectors[inventory_update.source](self.get_ansible_version(inventory_update))
|
||||||
return injector.build_private_data(inventory_update, kwargs.get('private_data_dir', None))
|
return injector.build_private_data(inventory_update, private_data_dir)
|
||||||
|
|
||||||
private_data = {'credentials': {}}
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
|
|
||||||
if inventory_update.source == 'openstack':
|
|
||||||
openstack_auth = dict(auth_url=credential.get_input('host', default=''),
|
|
||||||
username=credential.get_input('username', default=''),
|
|
||||||
password=credential.get_input('password', default=''),
|
|
||||||
project_name=credential.get_input('project', default=''))
|
|
||||||
if credential.has_input('domain'):
|
|
||||||
openstack_auth['domain_name'] = credential.get_input('domain', default='')
|
|
||||||
|
|
||||||
private_state = inventory_update.source_vars_dict.get('private', True)
|
|
||||||
verify_state = credential.get_input('verify_ssl', default=True)
|
|
||||||
# Retrieve cache path from inventory update vars if available,
|
|
||||||
# otherwise create a temporary cache path only for this update.
|
|
||||||
cache = inventory_update.source_vars_dict.get('cache', {})
|
|
||||||
if not isinstance(cache, dict):
|
|
||||||
cache = {}
|
|
||||||
if not cache.get('path', ''):
|
|
||||||
cache_path = tempfile.mkdtemp(prefix='openstack_cache', dir=private_data_dir)
|
|
||||||
cache['path'] = cache_path
|
|
||||||
openstack_data = {
|
|
||||||
'clouds': {
|
|
||||||
'devstack': {
|
|
||||||
'private': private_state,
|
|
||||||
'verify': verify_state,
|
|
||||||
'auth': openstack_auth,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'cache': cache,
|
|
||||||
}
|
|
||||||
ansible_variables = {
|
|
||||||
'use_hostnames': True,
|
|
||||||
'expand_hostvars': False,
|
|
||||||
'fail_on_errors': True,
|
|
||||||
}
|
|
||||||
provided_count = 0
|
|
||||||
for var_name in ansible_variables:
|
|
||||||
if var_name in inventory_update.source_vars_dict:
|
|
||||||
ansible_variables[var_name] = inventory_update.source_vars_dict[var_name]
|
|
||||||
provided_count += 1
|
|
||||||
if provided_count:
|
|
||||||
openstack_data['ansible'] = ansible_variables
|
|
||||||
private_data['credentials'][credential] = yaml.safe_dump(
|
|
||||||
openstack_data, default_flow_style=False, allow_unicode=True
|
|
||||||
)
|
|
||||||
return private_data
|
|
||||||
|
|
||||||
cp = configparser.RawConfigParser()
|
|
||||||
# Build custom ec2.ini for ec2 inventory script to use.
|
|
||||||
if inventory_update.source == 'ec2':
|
|
||||||
section = 'ec2'
|
|
||||||
cp.add_section(section)
|
|
||||||
ec2_opts = dict(inventory_update.source_vars_dict.items())
|
|
||||||
regions = inventory_update.source_regions or 'all'
|
|
||||||
regions = ','.join([x.strip() for x in regions.split(',')])
|
|
||||||
regions_blacklist = ','.join(settings.EC2_REGIONS_BLACKLIST)
|
|
||||||
ec2_opts['regions'] = regions
|
|
||||||
ec2_opts.setdefault('regions_exclude', regions_blacklist)
|
|
||||||
ec2_opts.setdefault('destination_variable', 'public_dns_name')
|
|
||||||
ec2_opts.setdefault('vpc_destination_variable', 'ip_address')
|
|
||||||
ec2_opts.setdefault('route53', 'False')
|
|
||||||
ec2_opts.setdefault('all_instances', 'True')
|
|
||||||
ec2_opts.setdefault('all_rds_instances', 'False')
|
|
||||||
ec2_opts.setdefault('include_rds_clusters', 'False')
|
|
||||||
ec2_opts.setdefault('rds', 'False')
|
|
||||||
ec2_opts.setdefault('nested_groups', 'True')
|
|
||||||
ec2_opts.setdefault('elasticache', 'False')
|
|
||||||
ec2_opts.setdefault('stack_filters', 'False')
|
|
||||||
if inventory_update.instance_filters:
|
|
||||||
ec2_opts.setdefault('instance_filters', inventory_update.instance_filters)
|
|
||||||
group_by = [x.strip().lower() for x in inventory_update.group_by.split(',') if x.strip()]
|
|
||||||
for choice in inventory_update.get_ec2_group_by_choices():
|
|
||||||
value = bool((group_by and choice[0] in group_by) or (not group_by and choice[0] != 'instance_id'))
|
|
||||||
ec2_opts.setdefault('group_by_%s' % choice[0], str(value))
|
|
||||||
if 'cache_path' not in ec2_opts:
|
|
||||||
cache_path = tempfile.mkdtemp(prefix='ec2_cache', dir=private_data_dir)
|
|
||||||
ec2_opts['cache_path'] = cache_path
|
|
||||||
ec2_opts.setdefault('cache_max_age', '300')
|
|
||||||
for k, v in ec2_opts.items():
|
|
||||||
cp.set(section, k, str(v))
|
|
||||||
# Allow custom options to vmware inventory script.
|
|
||||||
elif inventory_update.source == 'vmware':
|
|
||||||
|
|
||||||
section = 'vmware'
|
|
||||||
cp.add_section(section)
|
|
||||||
cp.set('vmware', 'cache_max_age', '0')
|
|
||||||
cp.set('vmware', 'validate_certs', str(settings.VMWARE_VALIDATE_CERTS))
|
|
||||||
cp.set('vmware', 'username', credential.get_input('username', default=''))
|
|
||||||
cp.set('vmware', 'password', credential.get_input('password', default=''))
|
|
||||||
cp.set('vmware', 'server', credential.get_input('host', default=''))
|
|
||||||
|
|
||||||
vmware_opts = dict(inventory_update.source_vars_dict.items())
|
|
||||||
if inventory_update.instance_filters:
|
|
||||||
vmware_opts.setdefault('host_filters', inventory_update.instance_filters)
|
|
||||||
if inventory_update.group_by:
|
|
||||||
vmware_opts.setdefault('groupby_patterns', inventory_update.group_by)
|
|
||||||
|
|
||||||
for k, v in vmware_opts.items():
|
|
||||||
cp.set(section, k, str(v))
|
|
||||||
|
|
||||||
elif inventory_update.source == 'satellite6':
|
|
||||||
section = 'foreman'
|
|
||||||
cp.add_section(section)
|
|
||||||
|
|
||||||
group_patterns = '[]'
|
|
||||||
group_prefix = 'foreman_'
|
|
||||||
want_hostcollections = 'False'
|
|
||||||
foreman_opts = dict(inventory_update.source_vars_dict.items())
|
|
||||||
foreman_opts.setdefault('ssl_verify', 'False')
|
|
||||||
for k, v in foreman_opts.items():
|
|
||||||
if k == 'satellite6_group_patterns' and isinstance(v, str):
|
|
||||||
group_patterns = v
|
|
||||||
elif k == 'satellite6_group_prefix' and isinstance(v, str):
|
|
||||||
group_prefix = v
|
|
||||||
elif k == 'satellite6_want_hostcollections' and isinstance(v, bool):
|
|
||||||
want_hostcollections = v
|
|
||||||
else:
|
|
||||||
cp.set(section, k, str(v))
|
|
||||||
|
|
||||||
if credential:
|
|
||||||
cp.set(section, 'url', credential.get_input('host', default=''))
|
|
||||||
cp.set(section, 'user', credential.get_input('username', default=''))
|
|
||||||
cp.set(section, 'password', credential.get_input('password', default=''))
|
|
||||||
|
|
||||||
section = 'ansible'
|
|
||||||
cp.add_section(section)
|
|
||||||
cp.set(section, 'group_patterns', group_patterns)
|
|
||||||
cp.set(section, 'want_facts', 'True')
|
|
||||||
cp.set(section, 'want_hostcollections', str(want_hostcollections))
|
|
||||||
cp.set(section, 'group_prefix', group_prefix)
|
|
||||||
|
|
||||||
section = 'cache'
|
|
||||||
cp.add_section(section)
|
|
||||||
cp.set(section, 'path', '/tmp')
|
|
||||||
cp.set(section, 'max_age', '0')
|
|
||||||
|
|
||||||
elif inventory_update.source == 'cloudforms':
|
|
||||||
section = 'cloudforms'
|
|
||||||
cp.add_section(section)
|
|
||||||
|
|
||||||
if credential:
|
|
||||||
cp.set(section, 'url', credential.get_input('host', default=''))
|
|
||||||
cp.set(section, 'username', credential.get_input('username', default=''))
|
|
||||||
cp.set(section, 'password', credential.get_input('password', default=''))
|
|
||||||
cp.set(section, 'ssl_verify', "false")
|
|
||||||
|
|
||||||
cloudforms_opts = dict(inventory_update.source_vars_dict.items())
|
|
||||||
for opt in ['version', 'purge_actions', 'clean_group_keys', 'nest_tags', 'suffix', 'prefer_ipv4']:
|
|
||||||
if opt in cloudforms_opts:
|
|
||||||
cp.set(section, opt, str(cloudforms_opts[opt]))
|
|
||||||
|
|
||||||
section = 'cache'
|
|
||||||
cp.add_section(section)
|
|
||||||
cp.set(section, 'max_age', "0")
|
|
||||||
cache_path = tempfile.mkdtemp(
|
|
||||||
prefix='cloudforms_cache',
|
|
||||||
dir=private_data_dir
|
|
||||||
)
|
|
||||||
cp.set(section, 'path', cache_path)
|
|
||||||
|
|
||||||
elif inventory_update.source == 'azure_rm':
|
|
||||||
section = 'azure'
|
|
||||||
cp.add_section(section)
|
|
||||||
cp.set(section, 'include_powerstate', 'yes')
|
|
||||||
cp.set(section, 'group_by_resource_group', 'yes')
|
|
||||||
cp.set(section, 'group_by_location', 'yes')
|
|
||||||
cp.set(section, 'group_by_tag', 'yes')
|
|
||||||
|
|
||||||
if inventory_update.source_regions and 'all' not in inventory_update.source_regions:
|
|
||||||
cp.set(
|
|
||||||
section, 'locations',
|
|
||||||
','.join([x.strip() for x in inventory_update.source_regions.split(',')])
|
|
||||||
)
|
|
||||||
|
|
||||||
azure_rm_opts = dict(inventory_update.source_vars_dict.items())
|
|
||||||
for k, v in azure_rm_opts.items():
|
|
||||||
cp.set(section, k, str(v))
|
|
||||||
|
|
||||||
# Return INI content.
|
|
||||||
if cp.sections():
|
|
||||||
f = StringIO()
|
|
||||||
cp.write(f)
|
|
||||||
private_data['credentials'][credential] = f.getvalue()
|
|
||||||
return private_data
|
|
||||||
|
|
||||||
def build_passwords(self, inventory_update, runtime_passwords):
|
def build_passwords(self, inventory_update, runtime_passwords):
|
||||||
"""Build a dictionary of authentication/credential information for
|
"""Build a dictionary of authentication/credential information for
|
||||||
@@ -2171,41 +1983,13 @@ class RunInventoryUpdate(BaseTask):
|
|||||||
env['INVENTORY_SOURCE_ID'] = str(inventory_update.inventory_source_id)
|
env['INVENTORY_SOURCE_ID'] = str(inventory_update.inventory_source_id)
|
||||||
env['INVENTORY_UPDATE_ID'] = str(inventory_update.pk)
|
env['INVENTORY_UPDATE_ID'] = str(inventory_update.pk)
|
||||||
env.update(STANDARD_INVENTORY_UPDATE_ENV)
|
env.update(STANDARD_INVENTORY_UPDATE_ENV)
|
||||||
plugin_name = inventory_update.get_inventory_plugin_name()
|
plugin_name = inventory_update.get_inventory_plugin_name(self.get_ansible_version(inventory_update))
|
||||||
if plugin_name is not None:
|
if plugin_name is not None:
|
||||||
env['ANSIBLE_INVENTORY_ENABLED'] = plugin_name
|
env['ANSIBLE_INVENTORY_ENABLED'] = plugin_name
|
||||||
|
|
||||||
# Set environment variables specific to each source.
|
|
||||||
#
|
|
||||||
# These are set here and then read in by the various Ansible inventory
|
|
||||||
# modules, which will actually do the inventory sync.
|
|
||||||
#
|
|
||||||
# The inventory modules are vendored in AWX in the
|
|
||||||
# `awx/plugins/inventory` directory; those files should be kept in
|
|
||||||
# sync with those in Ansible core at all times.
|
|
||||||
|
|
||||||
ini_mapping = {
|
|
||||||
'ec2': 'EC2_INI_PATH',
|
|
||||||
'vmware': 'VMWARE_INI_PATH',
|
|
||||||
'azure_rm': 'AZURE_INI_PATH',
|
|
||||||
'openstack': 'OS_CLIENT_CONFIG_FILE',
|
|
||||||
'satellite6': 'FOREMAN_INI_PATH',
|
|
||||||
'cloudforms': 'CLOUDFORMS_INI_PATH'
|
|
||||||
}
|
|
||||||
if inventory_update.source in ini_mapping:
|
|
||||||
cred_data = private_data_files.get('credentials', {})
|
|
||||||
env[ini_mapping[inventory_update.source]] = cred_data.get(
|
|
||||||
inventory_update.get_cloud_credential(), ''
|
|
||||||
)
|
|
||||||
|
|
||||||
if inventory_update.source in InventorySource.injectors:
|
if inventory_update.source in InventorySource.injectors:
|
||||||
# TODO: mapping from credential.kind to inventory_source.source
|
|
||||||
injector = InventorySource.injectors[inventory_update.source](self.get_ansible_version(inventory_update))
|
injector = InventorySource.injectors[inventory_update.source](self.get_ansible_version(inventory_update))
|
||||||
env = injector.build_env(inventory_update, env, private_data_dir)
|
env = injector.build_env(inventory_update, env, private_data_dir, private_data_files)
|
||||||
|
|
||||||
if inventory_update.source == 'tower':
|
|
||||||
env['TOWER_INVENTORY'] = inventory_update.instance_filters
|
|
||||||
env['TOWER_LICENSE_TYPE'] = get_licenser().validate()['license_type']
|
|
||||||
|
|
||||||
if inventory_update.source in ['scm', 'custom']:
|
if inventory_update.source in ['scm', 'custom']:
|
||||||
for env_k in inventory_update.source_vars_dict:
|
for env_k in inventory_update.source_vars_dict:
|
||||||
@@ -2289,7 +2073,7 @@ class RunInventoryUpdate(BaseTask):
|
|||||||
if src in InventorySource.injectors:
|
if src in InventorySource.injectors:
|
||||||
injector = InventorySource.injectors[inventory_update.source](self.get_ansible_version(inventory_update))
|
injector = InventorySource.injectors[inventory_update.source](self.get_ansible_version(inventory_update))
|
||||||
if injector.should_use_plugin():
|
if injector.should_use_plugin():
|
||||||
content = injector.inventory_contents(inventory_update, kwargs['private_data_dir'])
|
content = injector.inventory_contents(inventory_update, private_data_dir)
|
||||||
# must be a statically named file
|
# must be a statically named file
|
||||||
inventory_path = os.path.join(private_data_dir, injector.filename)
|
inventory_path = os.path.join(private_data_dir, injector.filename)
|
||||||
with open(inventory_path, 'w') as f:
|
with open(inventory_path, 'w') as f:
|
||||||
|
|||||||
@@ -117,9 +117,9 @@ def read_content(private_data_dir, env, inventory_update):
|
|||||||
return a dictionary `content` with file contents, keyed off environment variable
|
return a dictionary `content` with file contents, keyed off environment variable
|
||||||
that references the file
|
that references the file
|
||||||
"""
|
"""
|
||||||
references = {}
|
inverse_env = {}
|
||||||
for key, value in env.items():
|
for key, value in env.items():
|
||||||
references[value] = key
|
inverse_env[value] = key
|
||||||
|
|
||||||
cache_file_regex = re.compile(r'/tmp/awx_{0}_[a-zA-Z0-9_]+/{1}_cache[a-zA-Z0-9_]+'.format(
|
cache_file_regex = re.compile(r'/tmp/awx_{0}_[a-zA-Z0-9_]+/{1}_cache[a-zA-Z0-9_]+'.format(
|
||||||
inventory_update.id, inventory_update.source)
|
inventory_update.id, inventory_update.source)
|
||||||
@@ -127,8 +127,11 @@ def read_content(private_data_dir, env, inventory_update):
|
|||||||
private_key_regex = re.compile(r'-----BEGIN ENCRYPTED PRIVATE KEY-----.*-----END ENCRYPTED PRIVATE KEY-----')
|
private_key_regex = re.compile(r'-----BEGIN ENCRYPTED PRIVATE KEY-----.*-----END ENCRYPTED PRIVATE KEY-----')
|
||||||
|
|
||||||
dir_contents = {}
|
dir_contents = {}
|
||||||
|
references = {}
|
||||||
for filename in os.listdir(private_data_dir):
|
for filename in os.listdir(private_data_dir):
|
||||||
abs_file_path = os.path.join(private_data_dir, filename)
|
abs_file_path = os.path.join(private_data_dir, filename)
|
||||||
|
if abs_file_path in inverse_env:
|
||||||
|
references[abs_file_path] = inverse_env[abs_file_path]
|
||||||
try:
|
try:
|
||||||
with open(abs_file_path, 'r') as f:
|
with open(abs_file_path, 'r') as f:
|
||||||
dir_contents[abs_file_path] = f.read()
|
dir_contents[abs_file_path] = f.read()
|
||||||
@@ -172,8 +175,8 @@ def read_content(private_data_dir, env, inventory_update):
|
|||||||
for abs_file_path, file_content in dir_contents.items():
|
for abs_file_path, file_content in dir_contents.items():
|
||||||
if abs_file_path not in references:
|
if abs_file_path not in references:
|
||||||
raise AssertionError(
|
raise AssertionError(
|
||||||
"File {} is not referenced by any other file or environment variable:\n{}\n{}".format(
|
"File {} is not referenced. References and files:\n{}\n{}".format(
|
||||||
abs_file_path, json.dumps(env, indent=4), json.dumps(dir_contents, indent=4)))
|
abs_file_path, json.dumps(references, indent=4), json.dumps(dir_contents, indent=4)))
|
||||||
reference_key = references[abs_file_path]
|
reference_key = references[abs_file_path]
|
||||||
file_content = private_key_regex.sub('{{private_key}}', file_content)
|
file_content = private_key_regex.sub('{{private_key}}', file_content)
|
||||||
content[reference_key] = file_content
|
content[reference_key] = file_content
|
||||||
@@ -228,7 +231,7 @@ def test_inventory_script_structure(this_kind, script_or_plugin, inventory):
|
|||||||
base_dir = os.path.join(DATA, script_or_plugin)
|
base_dir = os.path.join(DATA, script_or_plugin)
|
||||||
if not os.path.exists(base_dir):
|
if not os.path.exists(base_dir):
|
||||||
os.mkdir(base_dir)
|
os.mkdir(base_dir)
|
||||||
ref_dir = os.path.join(base_dir, this_kind)
|
ref_dir = os.path.join(base_dir, this_kind) # this_kind is a global
|
||||||
if set_files:
|
if set_files:
|
||||||
create_reference_data(ref_dir, content)
|
create_reference_data(ref_dir, content)
|
||||||
pytest.skip('You set MAKE_INVENTORY_REFERENCE_FILES, so this created files, unset to run actual test.')
|
pytest.skip('You set MAKE_INVENTORY_REFERENCE_FILES, so this created files, unset to run actual test.')
|
||||||
@@ -250,5 +253,7 @@ def test_inventory_script_structure(this_kind, script_or_plugin, inventory):
|
|||||||
with mock.patch('awx.main.models.inventory.PluginFileInjector.should_use_plugin', return_value=use_plugin):
|
with mock.patch('awx.main.models.inventory.PluginFileInjector.should_use_plugin', return_value=use_plugin):
|
||||||
# Also do not send websocket status updates
|
# Also do not send websocket status updates
|
||||||
with mock.patch.object(UnifiedJob, 'websocket_emit_status', mock.Mock()):
|
with mock.patch.object(UnifiedJob, 'websocket_emit_status', mock.Mock()):
|
||||||
|
# The point of this test is that we replace run_pexpect with assertions
|
||||||
with mock.patch('awx.main.expect.run.run_pexpect', substitute_run):
|
with mock.patch('awx.main.expect.run.run_pexpect', substitute_run):
|
||||||
|
# so this sets up everything for a run and then yields control over to substitute_run
|
||||||
task.run(inventory_update.pk)
|
task.run(inventory_update.pk)
|
||||||
|
|||||||
Reference in New Issue
Block a user