145 lines
5.3 KiB
Python
145 lines
5.3 KiB
Python
# Copyright (c) 2018 Ansible Project
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
from functools import lru_cache
|
|
|
|
from ansible import constants as C
|
|
from ansible.errors import AnsibleError
|
|
from ansible.inventory.group import InventoryObjectType
|
|
from ansible.plugins.loader import vars_loader
|
|
from ansible.utils.display import Display
|
|
from ansible.utils.vars import combine_vars
|
|
|
|
display = Display()
|
|
|
|
cached_vars_plugin_order = None
|
|
|
|
|
|
def _load_vars_plugins_order():
|
|
# find 3rd party legacy vars plugins once, and look them up by name subsequently
|
|
auto = []
|
|
for auto_run_plugin in vars_loader.all(class_only=True):
|
|
needs_enabled = False
|
|
if hasattr(auto_run_plugin, 'REQUIRES_ENABLED'):
|
|
needs_enabled = auto_run_plugin.REQUIRES_ENABLED
|
|
elif hasattr(auto_run_plugin, 'REQUIRES_WHITELIST'):
|
|
needs_enabled = auto_run_plugin.REQUIRES_WHITELIST
|
|
display.deprecated("The VarsModule class variable 'REQUIRES_WHITELIST' is deprecated. "
|
|
"Use 'REQUIRES_ENABLED' instead.", version=2.18)
|
|
if needs_enabled:
|
|
continue
|
|
auto.append(auto_run_plugin._load_name)
|
|
|
|
# find enabled plugins once so we can look them up by resolved fqcn subsequently
|
|
enabled = []
|
|
for plugin_name in C.VARIABLE_PLUGINS_ENABLED:
|
|
if (plugin := vars_loader.get(plugin_name)) is None:
|
|
enabled.append(plugin_name)
|
|
else:
|
|
collection = '.' in plugin.ansible_name and not plugin.ansible_name.startswith('ansible.builtin.')
|
|
# Warn if a collection plugin has REQUIRES_ENABLED because it has no effect.
|
|
if collection and (hasattr(plugin, 'REQUIRES_ENABLED') or hasattr(plugin, 'REQUIRES_WHITELIST')):
|
|
display.warning(
|
|
"Vars plugins in collections must be enabled to be loaded, REQUIRES_ENABLED is not supported. "
|
|
"This should be removed from the plugin %s." % plugin.ansible_name
|
|
)
|
|
enabled.append(plugin.ansible_name)
|
|
|
|
global cached_vars_plugin_order
|
|
cached_vars_plugin_order = auto + enabled
|
|
|
|
|
|
def get_plugin_vars(loader, plugin, path, entities):
|
|
|
|
data = {}
|
|
try:
|
|
data = plugin.get_vars(loader, path, entities)
|
|
except AttributeError:
|
|
if hasattr(plugin, 'get_host_vars') or hasattr(plugin, 'get_group_vars'):
|
|
display.deprecated(
|
|
f"The vars plugin {plugin.ansible_name} from {plugin._original_path} is relying "
|
|
"on the deprecated entrypoints 'get_host_vars' and 'get_group_vars'. "
|
|
"This plugin should be updated to inherit from BaseVarsPlugin and define "
|
|
"a 'get_vars' method as the main entrypoint instead.",
|
|
version="2.20",
|
|
)
|
|
try:
|
|
for entity in entities:
|
|
if entity.base_type is InventoryObjectType.HOST:
|
|
data |= plugin.get_host_vars(entity.name)
|
|
else:
|
|
data |= plugin.get_group_vars(entity.name)
|
|
except AttributeError:
|
|
if hasattr(plugin, 'run'):
|
|
raise AnsibleError("Cannot use v1 type vars plugin %s from %s" % (plugin._load_name, plugin._original_path))
|
|
else:
|
|
raise AnsibleError("Invalid vars plugin %s from %s" % (plugin._load_name, plugin._original_path))
|
|
return data
|
|
|
|
|
|
# optimized for stateless plugins; non-stateless plugin instances will fall out quickly
|
|
@lru_cache(maxsize=10)
|
|
def _plugin_should_run(plugin, stage):
|
|
# if a plugin-specific setting has not been provided, use the global setting
|
|
# older/non shipped plugins that don't support the plugin-specific setting should also use the global setting
|
|
allowed_stages = None
|
|
|
|
try:
|
|
allowed_stages = plugin.get_option('stage')
|
|
except (AttributeError, KeyError):
|
|
pass
|
|
|
|
if allowed_stages:
|
|
return allowed_stages in ('all', stage)
|
|
|
|
# plugin didn't declare a preference; consult global config
|
|
config_stage_override = C.RUN_VARS_PLUGINS
|
|
if config_stage_override == 'demand' and stage == 'inventory':
|
|
return False
|
|
elif config_stage_override == 'start' and stage == 'task':
|
|
return False
|
|
return True
|
|
|
|
|
|
def get_vars_from_path(loader, path, entities, stage):
|
|
|
|
data = {}
|
|
|
|
if cached_vars_plugin_order is None:
|
|
_load_vars_plugins_order()
|
|
|
|
for plugin_name in cached_vars_plugin_order:
|
|
if (plugin := vars_loader.get(plugin_name)) is None:
|
|
continue
|
|
|
|
if not _plugin_should_run(plugin, stage):
|
|
continue
|
|
|
|
if (new_vars := get_plugin_vars(loader, plugin, path, entities)) != {}:
|
|
data = combine_vars(data, new_vars)
|
|
|
|
return data
|
|
|
|
|
|
def get_vars_from_inventory_sources(loader, sources, entities, stage):
|
|
|
|
data = {}
|
|
for path in sources:
|
|
|
|
if path is None:
|
|
continue
|
|
if ',' in path and not os.path.exists(path): # skip host lists
|
|
continue
|
|
elif not os.path.isdir(path):
|
|
# always pass the directory of the inventory source file
|
|
path = os.path.dirname(path)
|
|
|
|
if (new_vars := get_vars_from_path(loader, path, entities, stage)) != {}:
|
|
data = combine_vars(data, new_vars)
|
|
|
|
return data
|