diff --git a/CHANGELOG.md b/CHANGELOG.md index 8028f6ea1f..4417b3cbef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,6 +111,7 @@ Ansible Changes By Release * reverted implicit localhost getting vars from 'all' group * Fix the ping module documentation to reference win_ping instead of itself: https://github.com/ansible/ansible/pull/31444 * Fix for ec2_win_password to allow blank key_passphrase again (https://github.com/ansible/ansible/pull/28791) +* added toggle for vars_plugin behaviour to execute relative to playbook, set default to revert to previous way. diff --git a/docs/docsite/rst/porting_guide_2.4.rst b/docs/docsite/rst/porting_guide_2.4.rst index 4a922aa451..35ee0530ca 100644 --- a/docs/docsite/rst/porting_guide_2.4.rst +++ b/docs/docsite/rst/porting_guide_2.4.rst @@ -99,7 +99,10 @@ There have been many changes to the implementation of vars plugins, but both use The most notable difference to users is that vars plugins now get invoked on demand instead of at inventory build time. This should make them more efficient for large inventories, especially when using a subset of the hosts. -.. note:: This also creates a difference with group/host_vars when using them adjacent to playbooks. Before, the 'first' playbook loaded determined the variables; now the 'current' playbook does. We are looking to fix this soon, since 'all playbooks' in the path should be considered for variable loading. + +.. note:: + - This also creates a difference with group/host_vars when using them adjacent to playbooks. Before, the 'first' playbook loaded determined the variables; now the 'current' playbook does. We are looking to fix this soon, since 'all playbooks' in the path should be considered for variable loading. + - In 2.4.1 we added a toggle to allow you to control this behaviour, 'top' will be the pre 2.4, 'bottom' will use the current playbook hosting the task and 'all' will use them all from top to bottom. Inventory plugins diff --git a/lib/ansible/config/base.yml b/lib/ansible/config/base.yml index 13469650ae..6d9939cbd9 100644 --- a/lib/ansible/config/base.yml +++ b/lib/ansible/config/base.yml @@ -1479,6 +1479,19 @@ PERSISTENT_COMMAND_TIMEOUT: ini: - {key: command_timeout, section: persistent_connection} type: int +PLAYBOOK_VARS_ROOT: + name: playbook vars files root + default: top + version_added: "2.4.1" + description: + - This sets which playbook dirs will be used as a root to process vars plugins, which includes finding host_vars/group_vars + - The ``top`` option follows the traditional behaviour of using the top playbook in the chain to find the root directory. + - The ``bottom`` option follows the 2.4.0 behaviour of using the current playbook to find the root directory. + - The ``all`` option examines from the first parent to the current playbook. + env: [{name: ANSIBLE_PLAYBOOK_VARS_ROOT}] + ini: + - {key: playbook_vars_root, section: defaults} + choices: [ top, bottom, all ] RETRY_FILES_ENABLED: name: Retry files default: True diff --git a/lib/ansible/vars/manager.py b/lib/ansible/vars/manager.py index 0a50261a88..fac7d92fea 100644 --- a/lib/ansible/vars/manager.py +++ b/lib/ansible/vars/manager.py @@ -234,16 +234,26 @@ class VariableManager: for role in play.get_roles(): all_vars = combine_vars(all_vars, role.get_default_vars()) - # if we have a task in this context, and that task has a role, make - # sure it sees its defaults above any other roles, as we previously - # (v1) made sure each task had a copy of its roles default vars - if task and task._role is not None and (play or task.action == 'include_role'): - all_vars = combine_vars(all_vars, task._role.get_default_vars(dep_chain=task.get_dep_chain())) + basedirs = [] + if task: + # set basedirs + if C.PLAYBOOK_VARS_ROOT == 'all': # should be default + basedirs = task.get_search_path() + elif C.PLAYBOOK_VARS_ROOT == 'top': # only option pre 2.3 + basedirs = [self._loader.get_basedir()] + elif C.PLAYBOOK_VARS_ROOT in ('bottom', 'playbook_dir'): # only option in 2.4.0 + basedirs = [task.get_search_path()[0]] + else: + raise AnsibleError('Unkown playbook vars logic: %s' % C.PLAYBOOK_VARS_ROOT) + + # if we have a task in this context, and that task has a role, make + # sure it sees its defaults above any other roles, as we previously + # (v1) made sure each task had a copy of its roles default vars + if task._role is not None and (play or task.action == 'include_role'): + all_vars = combine_vars(all_vars, task._role.get_default_vars(dep_chain=task.get_dep_chain())) if host: - # INIT WORK (use unsafe as we are going to copy/merge vars, no need to x2 copy) - # basedir, THE 'all' group and the rest of groups for a host, used below - basedir = self._loader.get_basedir() + # THE 'all' group and the rest of groups for a host, used below all_group = self._inventory.groups.get('all') host_groups = sort_groups([g for g in host.get_groups() if g.name not in ['all']]) @@ -283,7 +293,8 @@ class VariableManager: ''' merges all entities adjacent to play ''' data = {} for plugin in vars_loader.all(): - data = combine_vars(data, _get_plugin_vars(plugin, basedir, entities)) + for path in basedirs: + data = combine_vars(data, _get_plugin_vars(plugin, path, entities)) return data # configurable functions that are sortable via config, rememer to add to _ALLOWED if expanding this list