Commit 1f5584aa by James Cammarata

Refactoring delegate_to code

Now, instead of relying on hostvars on the executor side, we compile
the vars for the delegated to host in a special internal variable and
have the PlayContext object look for things there when applying task/
var overrides, which is much cleaner and takes advantage of the code
already dealing with all of the magic variable variations.

Fixes #12127
Fixes #12079
parent fa69e8eb
...@@ -474,7 +474,18 @@ class TaskExecutor: ...@@ -474,7 +474,18 @@ class TaskExecutor:
self._play_context.remote_addr = self._host.address self._play_context.remote_addr = self._host.address
if self._task.delegate_to is not None: if self._task.delegate_to is not None:
self._compute_delegate(variables) # since we're delegating, we don't want to use interpreter values
# which would have been set for the original target host
for i in variables.keys():
if i.startswith('ansible_') and i.endswith('_interpreter'):
del variables[i]
# now replace the interpreter values with those that may have come
# from the delegated-to host
delegated_vars = variables.get('ansible_delegated_vars', dict())
if isinstance(delegated_vars, dict):
for i in delegated_vars:
if i.startswith("ansible_") and i.endswith("_interpreter"):
variables[i] = delegated_vars[i]
conn_type = self._play_context.connection conn_type = self._play_context.connection
if conn_type == 'smart': if conn_type == 'smart':
...@@ -529,74 +540,3 @@ class TaskExecutor: ...@@ -529,74 +540,3 @@ class TaskExecutor:
return handler return handler
def _compute_delegate(self, variables):
# get the vars for the delegate by its name
try:
self._display.debug("Delegating to %s" % self._task.delegate_to)
if self._task.delegate_to in C.LOCALHOST and self._task.delegate_to not in variables['hostvars']:
this_info = dict(ansible_connection="local")
for alt_local in C.LOCALHOST:
if alt_local in variables['hostvars']:
this_info = variables['hostvars'][self._task.delegate_to]
if this_info == Undefined:
this_info = dict(ansible_connection="local")
break
else:
this_info = variables['hostvars'][self._task.delegate_to]
except Exception as e:
# make sure the inject is empty for non-inventory hosts
this_info = {}
self._display.debug("Delegate to lookup failed due to: %s" % str(e))
conn = this_info.get('ansible_connection')
if conn:
self._play_context.connection = conn
if conn in ('smart', 'paramiko'):
# smart and paramiko connections will be using some kind of ssh,
# so use 'ssh' for the string to check connection variables
conn_test = 'ssh'
else:
conn_test = conn
else:
# default to ssh for the connection variable test, as
# that's the historical default
conn_test = 'ssh'
# get the real ssh_address for the delegate and allow ansible_ssh_host to be templated
self._play_context.remote_addr = this_info.get('ansible_%s_host' % conn_test, this_info.get('ansible_host', self._task.delegate_to))
self._play_context.remote_user = this_info.get('ansible_%s_user' % conn_test, this_info.get('ansible_user', self._play_context.remote_user))
self._play_context.port = this_info.get('ansible_%s_port' % conn_test, this_info.get('ansible_port', self._play_context.port))
self._play_context.password = this_info.get('ansible_%s_pass' % conn_test, this_info.get('ansible_pass', self._play_context.password))
self._play_context.private_key_file = this_info.get('ansible_%s_private_key_file' % conn_test, self._play_context.private_key_file)
# because of the switch from su/sudo -> become, the become pass for the
# delegated-to host may be in one of several fields, so try each until
# (maybe) one is found.
for become_pass in ('ansible_become_password', 'ansible_become_pass', 'ansible_sudo_password', 'ansible_sudo_pass'):
if become_pass in this_info:
self._play_context.become_pass = this_info[become_pass]
break
# Last chance to get private_key_file from global variables.
# this is useful if delegated host is not defined in the inventory
if self._play_context.private_key_file is None:
self._play_context.private_key_file = this_info.get('ansible_ssh_private_key_file', None)
if self._play_context.private_key_file is None:
key = this_info.get('private_key_file', None)
if key:
self._play_context.private_key_file = os.path.expanduser(key)
# since we're delegating, we don't want to use interpreter values
# which would have been set for the original target host
for i in variables.keys():
if i.startswith('ansible_') and i.endswith('_interpreter'):
del variables[i]
# now replace the interpreter values with those that may have come
# from the delegated-to host
for i in this_info:
if i.startswith("ansible_") and i.endswith("_interpreter"):
variables[i] = this_info[i]
...@@ -279,10 +279,16 @@ class PlayContext(Base): ...@@ -279,10 +279,16 @@ class PlayContext(Base):
setattr(new_info, attr, attr_val) setattr(new_info, attr, attr_val)
# next, use the MAGIC_VARIABLE_MAPPING dictionary to update this # next, use the MAGIC_VARIABLE_MAPPING dictionary to update this
# connection info object with 'magic' variables from the variable list # connection info object with 'magic' variables from the variable list.
# If the value 'ansible_delegated_vars' is in the variables, it means
# we have a delegated-to host, so we check there first before looking
# at the variables in general
delegated_vars = variables.get('ansible_delegated_vars', dict())
for (attr, variable_names) in iteritems(MAGIC_VARIABLE_MAPPING): for (attr, variable_names) in iteritems(MAGIC_VARIABLE_MAPPING):
for variable_name in variable_names: for variable_name in variable_names:
if variable_name in variables: if isinstance(delegated_vars, dict) and variable_name in delegated_vars:
setattr(new_info, attr, delegated_vars[variable_name])
elif variable_name in variables:
setattr(new_info, attr, variables[variable_name]) setattr(new_info, attr, variables[variable_name])
# make sure we get port defaults if needed # make sure we get port defaults if needed
...@@ -296,6 +302,7 @@ class PlayContext(Base): ...@@ -296,6 +302,7 @@ class PlayContext(Base):
elif new_info.become_method == 'su' and new_info.su_pass: elif new_info.become_method == 'su' and new_info.su_pass:
setattr(new_info, 'become_pass', new_info.su_pass) setattr(new_info, 'become_pass', new_info.su_pass)
# finally, in the special instance that the task was specified # finally, in the special instance that the task was specified
# as a local action, override the connection in case it was changed # as a local action, override the connection in case it was changed
# during some other step in the process # during some other step in the process
......
...@@ -34,6 +34,7 @@ except ImportError: ...@@ -34,6 +34,7 @@ except ImportError:
from ansible import constants as C from ansible import constants as C
from ansible.cli import CLI from ansible.cli import CLI
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.inventory.host import Host
from ansible.parsing import DataLoader from ansible.parsing import DataLoader
from ansible.plugins.cache import FactCache from ansible.plugins.cache import FactCache
from ansible.template import Templar from ansible.template import Templar
...@@ -127,7 +128,7 @@ class VariableManager: ...@@ -127,7 +128,7 @@ class VariableManager:
return data return data
def get_vars(self, loader, play=None, host=None, task=None, include_hostvars=True, use_cache=True): def get_vars(self, loader, play=None, host=None, task=None, include_hostvars=True, include_delegate_to=True, use_cache=True):
''' '''
Returns the variables, with optional "context" given via the parameters Returns the variables, with optional "context" given via the parameters
for the play, host, and task (which could possibly result in different for the play, host, and task (which could possibly result in different
...@@ -276,6 +277,38 @@ class VariableManager: ...@@ -276,6 +277,38 @@ class VariableManager:
if task._role: if task._role:
all_vars['role_path'] = task._role._role_path all_vars['role_path'] = task._role._role_path
# if we have a task and we're delegating to another host, figure out the
# variables for that host now so we don't have to rely on hostvars later
if task.delegate_to is not None and include_delegate_to:
# we unfortunately need to template the delegate_to field here,
# as we're fetching vars before post_validate has been called on
# the task that has been passed in
templar = Templar(loader=loader, variables=all_vars)
delegated_host_name = templar.template(task.delegate_to)
# now try to find the delegated-to host in inventory, or failing that,
# create a new host on the fly so we can fetch variables for it
delegated_host = None
if self._inventory is not None:
delegated_host = self._inventory.get_host(delegated_host_name)
# try looking it up based on the address field, and finally
# fall back to creating a host on the fly to use for the var lookup
if delegated_host is None:
for h in self._inventory.get_hosts(ignore_limits_and_restrictions=True):
# check if the address matches, or if both the delegated_to host
# and the current host are in the list of localhost aliases
if h.address == delegated_host_name or h.name in C.LOCALHOST and delegated_host_name in C.LOCALHOST:
delegated_host = h
break
else:
delegated_host = Host(name=delegated_host_name)
else:
delegated_host = Host(name=delegated_host_name)
# now we go fetch the vars for the delegated-to host and save them in our
# master dictionary of variables to be used later in the TaskExecutor/PlayContext
all_vars['ansible_delegated_vars'] = self.get_vars(loader=loader, play=play, host=delegated_host, task=task, include_delegate_to=False, include_hostvars=False)
if self._inventory is not None: if self._inventory is not None:
all_vars['inventory_dir'] = self._inventory.basedir() all_vars['inventory_dir'] = self._inventory.basedir()
if play: if play:
...@@ -287,10 +320,8 @@ class VariableManager: ...@@ -287,10 +320,8 @@ class VariableManager:
all_vars['play_hosts'] = host_list all_vars['play_hosts'] = host_list
all_vars['ansible_play_hosts'] = host_list all_vars['ansible_play_hosts'] = host_list
# the 'omit' value alows params to be left out if the variable they are based on is undefined # the 'omit' value alows params to be left out if the variable they are based on is undefined
all_vars['omit'] = self._omit_token all_vars['omit'] = self._omit_token
all_vars['ansible_version'] = CLI.version_info(gitinfo=False) all_vars['ansible_version'] = CLI.version_info(gitinfo=False)
if 'hostvars' in all_vars and host: if 'hostvars' in all_vars and host:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment