From 31dd75de59676d20e1e062c55b0680a2d197e93f Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Mon, 9 Feb 2015 16:54:44 -0600 Subject: [PATCH] Fixing many bugs in v2 * delegate_to rudimentary support (still needs much more work) * lots of other things --- v2/ansible/executor/connection_info.py | 30 ++++------- v2/ansible/executor/task_executor.py | 45 ++++++++++++++-- v2/ansible/modules/core | 2 +- v2/ansible/parsing/mod_args.py | 5 +- v2/ansible/playbook/conditional.py | 1 + v2/ansible/plugins/action/__init__.py | 35 +++++++++---- v2/ansible/plugins/action/copy.py | 26 +++------- v2/ansible/plugins/action/fetch.py | 2 +- v2/ansible/plugins/connections/__init__.py | 3 +- v2/ansible/plugins/connections/local.py | 6 +-- v2/ansible/plugins/connections/ssh.py | 60 ++++------------------ v2/ansible/plugins/lookup/first_found.py | 9 +++- v2/ansible/plugins/strategies/__init__.py | 14 ++--- v2/ansible/utils/display.py | 2 +- v2/ansible/utils/hashing.py | 2 + v2/ansible/utils/listify.py | 9 ++-- v2/samples/test_block.yml | 17 ++++++ 17 files changed, 144 insertions(+), 124 deletions(-) create mode 100644 v2/samples/test_block.yml diff --git a/v2/ansible/executor/connection_info.py b/v2/ansible/executor/connection_info.py index 5c48ff0089..7522ac210c 100644 --- a/v2/ansible/executor/connection_info.py +++ b/v2/ansible/executor/connection_info.py @@ -43,9 +43,11 @@ class ConnectionInformation: # various different auth escalation methods (becomes, etc.) self.connection = C.DEFAULT_TRANSPORT + self.remote_addr = None self.remote_user = 'root' self.password = '' self.port = 22 + self.private_key_file = None self.su = False self.su_user = '' self.su_pass = '' @@ -65,6 +67,14 @@ class ConnectionInformation: if options: self.set_options(options) + def __repr__(self): + value = "CONNECTION INFO:\n" + fields = self._get_fields() + fields.sort() + for field in fields: + value += "%20s : %s\n" % (field, getattr(self, field)) + return value + def set_play(self, play): ''' Configures this connection information instance with data from @@ -128,26 +138,6 @@ class ConnectionInformation: when merging in data from task overrides. ''' - #self.connection = ci.connection - #self.remote_user = ci.remote_user - #self.password = ci.password - #self.port = ci.port - #self.su = ci.su - #self.su_user = ci.su_user - #self.su_pass = ci.su_pass - #self.sudo = ci.sudo - #self.sudo_user = ci.sudo_user - #self.sudo_pass = ci.sudo_pass - #self.verbosity = ci.verbosity - - # other - #self.no_log = ci.no_log - #self.environment = ci.environment - - # requested tags - #self.only_tags = ci.only_tags.copy() - #self.skip_tags = ci.skip_tags.copy() - for field in self._get_fields(): value = getattr(ci, field, None) if isinstance(value, dict): diff --git a/v2/ansible/executor/task_executor.py b/v2/ansible/executor/task_executor.py index 5bd9d51842..91631aebb5 100644 --- a/v2/ansible/executor/task_executor.py +++ b/v2/ansible/executor/task_executor.py @@ -172,7 +172,7 @@ class TaskExecutor: self._connection_info.post_validate(variables=variables, loader=self._loader) # get the connection and the handler for this execution - self._connection = self._get_connection() + self._connection = self._get_connection(variables) self._handler = self._get_action_handler(connection=self._connection) # Evaluate the conditional (if any) for this task, which we do before running @@ -204,6 +204,7 @@ class TaskExecutor: # with the registered variable value later on when testing conditions vars_copy = variables.copy() + debug("starting attempt loop") result = None for attempt in range(retries): @@ -301,7 +302,7 @@ class TaskExecutor: else: return async_result - def _get_connection(self): + def _get_connection(self, variables): ''' Reads the connection property for the host, and returns the correct connection object from the list of connection plugins @@ -310,13 +311,17 @@ class TaskExecutor: # FIXME: delegate_to calculation should be done here # FIXME: calculation of connection params/auth stuff should be done here + self._connection_info.remote_addr = self._host.ipv4_address + if self._task.delegate_to is not None: + self._compute_delegate(variables) + # FIXME: add all port/connection type munging here (accelerated mode, # fixing up options for ssh, etc.)? and 'smart' conversion conn_type = self._connection_info.connection if conn_type == 'smart': conn_type = 'ssh' - connection = connection_loader.get(conn_type, self._host, self._connection_info) + connection = connection_loader.get(conn_type, self._connection_info) if not connection: raise AnsibleError("the connection plugin '%s' was not found" % conn_type) @@ -350,3 +355,37 @@ class TaskExecutor: raise AnsibleError("the handler '%s' was not found" % handler_name) return handler + + def _compute_delegate(self, variables): + + # get the vars for the delegate by its name + try: + this_info = variables['hostvars'][self._task.delegate_to] + except: + # make sure the inject is empty for non-inventory hosts + this_info = {} + + # get the real ssh_address for the delegate and allow ansible_ssh_host to be templated + #self._connection_info.remote_user = self._compute_delegate_user(self.delegate_to, delegate['inject']) + self._connection_info.remote_addr = this_info.get('ansible_ssh_host', self._task.delegate_to) + self._connection_info.port = this_info.get('ansible_ssh_port', self._connection_info.port) + self._connection_info.password = this_info.get('ansible_ssh_pass', self._connection_info.password) + self._connection_info.private_key_file = this_info.get('ansible_ssh_private_key_file', self._connection_info.private_key_file) + self._connection_info.connection = this_info.get('ansible_connection', self._connection_info.connection) + self._connection_info.sudo_pass = this_info.get('ansible_sudo_pass', self._connection_info.sudo_pass) + + if self._connection_info.remote_addr in ('127.0.0.1', 'localhost'): + self._connection_info.connection = 'local' + + # Last chance to get private_key_file from global variables. + # this is useful if delegated host is not defined in the inventory + #if delegate['private_key_file'] is None: + # delegate['private_key_file'] = remote_inject.get('ansible_ssh_private_key_file', None) + + #if delegate['private_key_file'] is not None: + # delegate['private_key_file'] = os.path.expanduser(delegate['private_key_file']) + + for i in this_info: + if i.startswith("ansible_") and i.endswith("_interpreter"): + variables[i] = this_info[i] + diff --git a/v2/ansible/modules/core b/v2/ansible/modules/core index 095f8681db..34784b7a61 160000 --- a/v2/ansible/modules/core +++ b/v2/ansible/modules/core @@ -1 +1 @@ -Subproject commit 095f8681dbdfd2e9247446822e953287c9bca66c +Subproject commit 34784b7a617aa35d3b994c9f0795567afc6fb0b0 diff --git a/v2/ansible/parsing/mod_args.py b/v2/ansible/parsing/mod_args.py index eddc093ef3..85e45cc35f 100644 --- a/v2/ansible/parsing/mod_args.py +++ b/v2/ansible/parsing/mod_args.py @@ -219,7 +219,7 @@ class ModuleArgsParser: thing = None action = None - delegate_to = None + delegate_to = self._task_ds.get('delegate_to', None) args = dict() @@ -236,15 +236,12 @@ class ModuleArgsParser: # action if 'action' in self._task_ds: - # an old school 'action' statement thing = self._task_ds['action'] - delegate_to = None action, args = self._normalize_parameters(thing, additional_args=additional_args) # local_action if 'local_action' in self._task_ds: - # local_action is similar but also implies a delegate_to if action is not None: raise AnsibleParserError("action and local_action are mutually exclusive", obj=self._task_ds) diff --git a/v2/ansible/playbook/conditional.py b/v2/ansible/playbook/conditional.py index 2d8db78bba..7ddba2b3d4 100644 --- a/v2/ansible/playbook/conditional.py +++ b/v2/ansible/playbook/conditional.py @@ -66,6 +66,7 @@ class Conditional: evaluation. ''' + original = conditional if conditional is None or conditional == '': return True diff --git a/v2/ansible/plugins/action/__init__.py b/v2/ansible/plugins/action/__init__.py index 6eb69e45d6..2c0ad5bcd8 100644 --- a/v2/ansible/plugins/action/__init__.py +++ b/v2/ansible/plugins/action/__init__.py @@ -161,7 +161,9 @@ class ActionBase: tmp_mode = 'a+rx' cmd = self._shell.mkdtemp(basefile, use_system_tmp, tmp_mode) + debug("executing _low_level_execute_command to create the tmp path") result = self._low_level_execute_command(cmd, None, sudoable=False) + debug("done with creation of tmp path") # error handling on this seems a little aggressive? if result['rc'] != 0: @@ -196,11 +198,13 @@ class ActionBase: def _remove_tmp_path(self, tmp_path): '''Remove a temporary path we created. ''' - if "-tmp-" in tmp_path: + if tmp_path and "-tmp-" in tmp_path: cmd = self._shell.remove(tmp_path, recurse=True) # If we have gotten here we have a working ssh configuration. # If ssh breaks we could leave tmp directories out on the remote system. + debug("calling _low_level_execute_command to remove the tmp path") self._low_level_execute_command(cmd, None, sudoable=False) + debug("done removing the tmp path") def _transfer_data(self, remote_path, data): ''' @@ -213,14 +217,16 @@ class ActionBase: afd, afile = tempfile.mkstemp() afo = os.fdopen(afd, 'w') try: - if not isinstance(data, unicode): - #ensure the data is valid UTF-8 - data = data.decode('utf-8') - else: - data = data.encode('utf-8') + # FIXME: is this still necessary? + #if not isinstance(data, unicode): + # #ensure the data is valid UTF-8 + # data = data.decode('utf-8') + #else: + # data = data.encode('utf-8') afo.write(data) except Exception, e: - raise AnsibleError("failure encoding into utf-8: %s" % str(e)) + #raise AnsibleError("failure encoding into utf-8: %s" % str(e)) + raise AnsibleError("failure writing module data to temporary file for transfer: %s" % str(e)) afo.flush() afo.close() @@ -238,7 +244,10 @@ class ActionBase: ''' cmd = self._shell.chmod(mode, path) - return self._low_level_execute_command(cmd, tmp, sudoable=sudoable) + debug("calling _low_level_execute_command to chmod the remote path") + res = self._low_level_execute_command(cmd, tmp, sudoable=sudoable) + debug("done with chmod call") + return res def _remote_checksum(self, tmp, path): ''' @@ -250,7 +259,9 @@ class ActionBase: #python_interp = inject['hostvars'][inject['inventory_hostname']].get('ansible_python_interpreter', 'python') python_interp = 'python' cmd = self._shell.checksum(path, python_interp) + debug("calling _low_level_execute_command to get the remote checksum") data = self._low_level_execute_command(cmd, tmp, sudoable=True) + debug("done getting the remote checksum") # FIXME: implement this function? #data2 = utils.last_non_blank_line(data['stdout']) try: @@ -286,7 +297,9 @@ class ActionBase: expand_path = '~%s' % self._connection_info.su_user cmd = self._shell.expand_user(expand_path) + debug("calling _low_level_execute_command to expand the remote user path") data = self._low_level_execute_command(cmd, tmp, sudoable=False) + debug("done expanding the remote user path") #initial_fragment = utils.last_non_blank_line(data['stdout']) initial_fragment = data['stdout'].strip().splitlines()[-1] @@ -354,7 +367,9 @@ class ActionBase: # FIXME: async stuff here? #if (module_style != 'new' or async_jid is not None or not self._connection._has_pipelining or not C.ANSIBLE_SSH_PIPELINING or C.DEFAULT_KEEP_REMOTE_FILES): if remote_module_path: + debug("transfering module to remote") self._transfer_data(remote_module_path, module_data) + debug("done transfering module to remote") environment_string = self._compute_environment_string() @@ -389,7 +404,9 @@ class ActionBase: # specified in the play, not the sudo_user sudoable = False + debug("calling _low_level_execute_command() for command %s" % cmd) res = self._low_level_execute_command(cmd, tmp, sudoable=sudoable, in_data=in_data) + debug("_low_level_execute_command returned ok") if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files and delete_remote_tmp: if (self._connection_info.sudo and self._connection_info.sudo_user != 'root') or (self._connection_info.su and self._connection_info.su_user != 'root'): @@ -446,7 +463,7 @@ class ActionBase: # FIXME: hard-coded sudo_exe here cmd, prompt, success_key = self._connection_info.make_sudo_cmd('/usr/bin/sudo', executable, cmd) - debug("executing the command through the connection") + debug("executing the command %s through the connection" % cmd) rc, stdin, stdout, stderr = self._connection.exec_command(cmd, tmp, executable=executable, in_data=in_data) debug("command execution done") diff --git a/v2/ansible/plugins/action/copy.py b/v2/ansible/plugins/action/copy.py index 6975bff1bf..46cb895502 100644 --- a/v2/ansible/plugins/action/copy.py +++ b/v2/ansible/plugins/action/copy.py @@ -251,12 +251,6 @@ class ActionModule(ActionBase): ) ) - # FIXME: checkmode and no_log stuff - #if self.runner.noop_on_check(inject): - # new_module_args['CHECKMODE'] = True - #if self.runner.no_log: - # new_module_args['NO_LOG'] = True - module_return = self._execute_module(module_name='copy', module_args=new_module_args, delete_remote_tmp=delete_remote_tmp) module_executed = True @@ -279,11 +273,6 @@ class ActionModule(ActionBase): original_basename=source_rel ) ) - # FIXME: checkmode and no_log stuff - #if self.runner.noop_on_check(inject): - # new_module_args['CHECKMODE'] = True - #if self.runner.no_log: - # new_module_args['NO_LOG'] = True # Execute the file module. module_return = self._execute_module(module_name='file', module_args=new_module_args, delete_remote_tmp=delete_remote_tmp) @@ -296,18 +285,17 @@ class ActionModule(ActionBase): if module_return.get('changed') == True: changed = True + # the file module returns the file path as 'path', but + # the copy module uses 'dest', so add it if it's not there + if 'path' in module_return and 'dest' not in module_return: + module_return['dest'] = module_return['path'] + # Delete tmp path if we were recursive or if we did not execute a module. - if (not C.DEFAULT_KEEP_REMOTE_FILES and not delete_remote_tmp) \ - or (not C.DEFAULT_KEEP_REMOTE_FILES and delete_remote_tmp and not module_executed): + if (not C.DEFAULT_KEEP_REMOTE_FILES and not delete_remote_tmp) or (not C.DEFAULT_KEEP_REMOTE_FILES and delete_remote_tmp and not module_executed): self._remove_tmp_path(tmp) - # the file module returns the file path as 'path', but - # the copy module uses 'dest', so add it if it's not there - if 'path' in module_return and 'dest' not in module_return: - module_return['dest'] = module_return['path'] - # TODO: Support detailed status/diff for multiple files - if len(source_files) == 1: + if module_executed and len(source_files) == 1: result = module_return else: result = dict(dest=dest, src=source, changed=changed) diff --git a/v2/ansible/plugins/action/fetch.py b/v2/ansible/plugins/action/fetch.py index 0ce33c650f..9bd73136b4 100644 --- a/v2/ansible/plugins/action/fetch.py +++ b/v2/ansible/plugins/action/fetch.py @@ -92,7 +92,7 @@ class ActionModule(ActionBase): dest = self._loader.path_dwim(dest) else: # files are saved in dest dir, with a subdir for each host, then the filename - dest = "%s/%s/%s" % (self._loader.path_dwim(dest), self._connection._host, source_local) + dest = "%s/%s/%s" % (self._loader.path_dwim(dest), self._connection_info.remote_addr, source_local) dest = dest.replace("//","/") diff --git a/v2/ansible/plugins/connections/__init__.py b/v2/ansible/plugins/connections/__init__.py index 8dbd808191..aad19b7764 100644 --- a/v2/ansible/plugins/connections/__init__.py +++ b/v2/ansible/plugins/connections/__init__.py @@ -34,8 +34,7 @@ class ConnectionBase: A base class for connections to contain common code. ''' - def __init__(self, host, connection_info, *args, **kwargs): - self._host = host + def __init__(self, connection_info, *args, **kwargs): self._connection_info = connection_info self._has_pipelining = False self._display = Display(connection_info) diff --git a/v2/ansible/plugins/connections/local.py b/v2/ansible/plugins/connections/local.py index 58e8a20a2e..963c8c2d4e 100644 --- a/v2/ansible/plugins/connections/local.py +++ b/v2/ansible/plugins/connections/local.py @@ -65,7 +65,7 @@ class Connection(ConnectionBase): executable = executable.split()[0] if executable else None - self._display.vvv("%s EXEC %s" % (self._host, local_cmd)) + self._display.vvv("%s EXEC %s" % (self._connection_info.remote_addr, local_cmd)) # FIXME: cwd= needs to be set to the basedir of the playbook debug("opening command with Popen()") p = subprocess.Popen( @@ -115,7 +115,7 @@ class Connection(ConnectionBase): ''' transfer a file from local to local ''' #vvv("PUT %s TO %s" % (in_path, out_path), host=self.host) - self._display.vvv("%s PUT %s TO %s" % (self._host, in_path, out_path)) + self._display.vvv("%s PUT %s TO %s" % (self._connection_info.remote_addr, in_path, out_path)) if not os.path.exists(in_path): #raise AnsibleFileNotFound("file or module does not exist: %s" % in_path) raise AnsibleError("file or module does not exist: %s" % in_path) @@ -130,7 +130,7 @@ class Connection(ConnectionBase): def fetch_file(self, in_path, out_path): #vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host) - self._display.vvv("%s FETCH %s TO %s" % (self._host, in_path, out_path)) + self._display.vvv("%s FETCH %s TO %s" % (self._connection_info.remote_addr, in_path, out_path)) ''' fetch a file from local to local -- for copatibility ''' self.put_file(in_path, out_path) diff --git a/v2/ansible/plugins/connections/ssh.py b/v2/ansible/plugins/connections/ssh.py index b8bbc5c46f..a79187e535 100644 --- a/v2/ansible/plugins/connections/ssh.py +++ b/v2/ansible/plugins/connections/ssh.py @@ -37,8 +37,8 @@ from ansible.plugins.connections import ConnectionBase class Connection(ConnectionBase): ''' ssh based connections ''' - def __init__(self, host, connection_info, *args, **kwargs): - super(Connection, self).__init__(host, connection_info) + def __init__(self, connection_info, *args, **kwargs): + super(Connection, self).__init__(connection_info) # SSH connection specific init stuff self.HASHED_KEY_MAGIC = "|1|" @@ -57,7 +57,7 @@ class Connection(ConnectionBase): def connect(self): ''' connect to the remote host ''' - self._display.vvv("ESTABLISH CONNECTION FOR USER: %s" % self._connection_info.remote_user, host=self._host) + self._display.vvv("ESTABLISH CONNECTION FOR USER: %s" % self._connection_info.remote_user, host=self._connection_info.remote_addr) self._common_args = [] extra_args = C.ANSIBLE_SSH_ARGS @@ -277,7 +277,7 @@ class Connection(ConnectionBase): # not sure if it's all working yet so this remains commented out #if self._ipv6: # ssh_cmd += ['-6'] - ssh_cmd += [self._host.ipv4_address] + ssh_cmd += [self._connection_info.remote_addr] if not (self._connection_info.sudo or self._connection_info.su): prompt = None @@ -293,9 +293,9 @@ class Connection(ConnectionBase): sudo_cmd, prompt, success_key = self._connection_info.make_sudo_cmd('/usr/bin/sudo', executable, cmd) ssh_cmd.append(sudo_cmd) - self._display.vvv("EXEC %s" % ' '.join(ssh_cmd), host=self._host) + self._display.vvv("EXEC %s" % ' '.join(ssh_cmd), host=self._connection_info.remote_addr) - not_in_host_file = self.not_in_host_file(self._host.get_name()) + not_in_host_file = self.not_in_host_file(self._connection_info.remote_addr) # FIXME: move the locations of these lock files, same as init above #if C.HOST_KEY_CHECKING and not_in_host_file: @@ -309,44 +309,6 @@ class Connection(ConnectionBase): self._send_password() - no_prompt_out = '' - no_prompt_err = '' - # FIXME: su/sudo stuff - #if (self.runner.sudo and sudoable and self.runner.sudo_pass) or \ - # (self.runner.su and su and self.runner.su_pass): - # # several cases are handled for sudo privileges with password - # # * NOPASSWD (tty & no-tty): detect success_key on stdout - # # * without NOPASSWD: - # # * detect prompt on stdout (tty) - # # * detect prompt on stderr (no-tty) - # fcntl.fcntl(p.stdout, fcntl.F_SETFL, - # fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK) - # fcntl.fcntl(p.stderr, fcntl.F_SETFL, - # fcntl.fcntl(p.stderr, fcntl.F_GETFL) | os.O_NONBLOCK) - # sudo_output = '' - # sudo_errput = '' - # - # while True: - # if success_key in sudo_output or \ - # (self.runner.sudo_pass and sudo_output.endswith(prompt)) or \ - # (self.runner.su_pass and utils.su_prompts.check_su_prompt(sudo_output)): - # break - self._display.vvv("EXEC %s" % ' '.join(ssh_cmd), host=self._host) - - not_in_host_file = self.not_in_host_file(self._host.get_name()) - - # FIXME: file locations - #if C.HOST_KEY_CHECKING and not_in_host_file: - # # lock around the initial SSH connectivity so the user prompt about whether to add - # # the host to known hosts is not intermingled with multiprocess output. - # fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX) - # fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_EX) - - # create process - (p, stdin) = self._run(ssh_cmd, in_data) - - self._send_password() - no_prompt_out = '' no_prompt_err = '' # FIXME: su/sudo stuff @@ -429,13 +391,13 @@ class Connection(ConnectionBase): def put_file(self, in_path, out_path): ''' transfer a file from local to remote ''' - self._display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._host) + self._display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._connection_info.remote_addr) if not os.path.exists(in_path): raise AnsibleFileNotFound("file or module does not exist: %s" % in_path) cmd = self._password_cmd() # FIXME: make a function, used in all 3 methods EXEC/PUT/FETCH - host = self._host.ipv4_address + host = self._connection_info.remote_addr # FIXME: ipv6 stuff needs to be figured out. It's in the connection info, however # not sure if it's all working yet so this remains commented out @@ -461,16 +423,16 @@ class Connection(ConnectionBase): def fetch_file(self, in_path, out_path): ''' fetch a file from remote to local ''' - self._display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._host) + self._display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._connection_info.remote_addr) cmd = self._password_cmd() # FIXME: make a function, used in all 3 methods EXEC/PUT/FETCH - host = self._host.ipv4_address + host = self._connection_info.remote_addr # FIXME: ipv6 stuff needs to be figured out. It's in the connection info, however # not sure if it's all working yet so this remains commented out #if self._ipv6: - # host = '[%s]' % self._host + # host = '[%s]' % self._connection_info.remote_addr if C.DEFAULT_SCP_IF_SSH: cmd += ["scp"] + self._common_args diff --git a/v2/ansible/plugins/lookup/first_found.py b/v2/ansible/plugins/lookup/first_found.py index ea43e13c4d..0ed2688015 100644 --- a/v2/ansible/plugins/lookup/first_found.py +++ b/v2/ansible/plugins/lookup/first_found.py @@ -119,6 +119,9 @@ import os +from jinja2.exceptions import UndefinedError + +from ansible.errors import AnsibleUndefinedVariable from ansible.plugins.lookup import LookupBase from ansible.template import Templar from ansible.utils.boolean import boolean @@ -172,7 +175,11 @@ class LookupModule(LookupBase): templar = Templar(loader=self._loader, variables=variables) roledir = variables.get('roledir') for fn in total_search: - fn = templar.template(fn) + try: + fn = templar.template(fn) + except (AnsibleUndefinedVariable, UndefinedError), e: + continue + if os.path.isabs(fn) and os.path.exists(fn): return [fn] else: diff --git a/v2/ansible/plugins/strategies/__init__.py b/v2/ansible/plugins/strategies/__init__.py index b8ae6ffa85..c26b155873 100644 --- a/v2/ansible/plugins/strategies/__init__.py +++ b/v2/ansible/plugins/strategies/__init__.py @@ -85,8 +85,8 @@ class StrategyBase: def get_hosts_remaining(self, play): return [host for host in self._inventory.get_hosts(play.hosts) if host.name not in self._tqm._failed_hosts and host.get_name() not in self._tqm._unreachable_hosts] - def get_failed_hosts(self): - return [host for host in self._inventory.get_hosts() if host.name in self._tqm._failed_hosts] + def get_failed_hosts(self, play): + return [host for host in self._inventory.get_hosts(play.hosts) if host.name in self._tqm._failed_hosts] def _queue_task(self, host, task, task_vars, connection_info): ''' handles queueing the task up to be sent to a worker ''' @@ -129,6 +129,7 @@ class StrategyBase: task = task_result._task if result[0] == 'host_task_failed': if not task.ignore_errors: + debug("marking %s as failed" % host.get_name()) self._tqm._failed_hosts[host.get_name()] = True self._callback.runner_on_failed(task, task_result) elif result[0] == 'host_unreachable': @@ -284,7 +285,7 @@ class StrategyBase: result = True debug("getting failed hosts") - failed_hosts = self.get_failed_hosts() + failed_hosts = self.get_failed_hosts(iterator._play) if len(failed_hosts) == 0: debug("there are no failed hosts") return result @@ -317,8 +318,9 @@ class StrategyBase: # pop the task, mark the host blocked, and queue it self._blocked_hosts[host_name] = True task = iterator.get_next_task_for_host(host) + task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=host, task=task) self._callback.playbook_on_cleanup_task_start(task.get_name()) - self._queue_task(iterator._play, host, task, connection_info) + self._queue_task(host, task, task_vars, connection_info) self._process_pending_results() @@ -352,8 +354,8 @@ class StrategyBase: self._callback.playbook_on_handler_task_start(handler_name) for host in self._notified_handlers[handler_name]: if not handler.has_triggered(host): - temp_data = handler.serialize() - self._queue_task(iterator._play, host, handler, connection_info) + task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=host, task=handler) + self._queue_task(host, handler, task_vars, connection_info) handler.flag_for_host(host) self._process_pending_results() diff --git a/v2/ansible/utils/display.py b/v2/ansible/utils/display.py index 085d52b2c8..3976198703 100644 --- a/v2/ansible/utils/display.py +++ b/v2/ansible/utils/display.py @@ -76,7 +76,7 @@ class Display: if host is None: self.display(msg, color='blue') else: - self.display("<%s> %s" % (host.name, msg), color='blue') + self.display("<%s> %s" % (host, msg), color='blue') def deprecated(self, msg, version, removed=False): ''' used to print out a deprecation message.''' diff --git a/v2/ansible/utils/hashing.py b/v2/ansible/utils/hashing.py index a7d142e5bd..0b2edd434b 100644 --- a/v2/ansible/utils/hashing.py +++ b/v2/ansible/utils/hashing.py @@ -43,6 +43,8 @@ def secure_hash_s(data, hash_func=sha1): digest = hash_func() try: + if not isinstance(data, basestring): + data = "%s" % data digest.update(data) except UnicodeEncodeError: digest.update(data.encode('utf-8')) diff --git a/v2/ansible/utils/listify.py b/v2/ansible/utils/listify.py index 800b99b8ec..a26b4b9829 100644 --- a/v2/ansible/utils/listify.py +++ b/v2/ansible/utils/listify.py @@ -39,14 +39,11 @@ def listify_lookup_plugin_terms(terms, variables, loader): # with_items: {{ alist }} stripped = terms.strip() - if not (stripped.startswith('{') or stripped.startswith('[')) and \ - not stripped.startswith("/") and \ - not stripped.startswith('set([') and \ - not LOOKUP_REGEX.search(terms): + templar = Templar(loader=loader, variables=variables) + if not (stripped.startswith('{') or stripped.startswith('[')) and not stripped.startswith("/") and not stripped.startswith('set([') and not LOOKUP_REGEX.search(terms): # if not already a list, get ready to evaluate with Jinja2 # not sure why the "/" is in above code :) try: - templar = Templar(loader=loader, variables=variables) new_terms = templar.template("{{ %s }}" % terms) if isinstance(new_terms, basestring) and "{{" in new_terms: pass @@ -54,6 +51,8 @@ def listify_lookup_plugin_terms(terms, variables, loader): terms = new_terms except: pass + else: + terms = templar.template(terms) if '{' in terms or '[' in terms: # Jinja2 already evaluated a variable to a list. diff --git a/v2/samples/test_block.yml b/v2/samples/test_block.yml new file mode 100644 index 0000000000..ae1f222444 --- /dev/null +++ b/v2/samples/test_block.yml @@ -0,0 +1,17 @@ +- hosts: localhost + connection: local + gather_facts: no + tasks: + - block: + - command: /bin/false + - debug: msg="you shouldn't see me" + rescue: + - debug: msg="this is the rescue" + - command: /bin/false + - debug: msg="you shouldn't see this either" + always: + - debug: msg="this is the always block, it will always be seen" + when: foo|default('') != "some value" + tags: + - foo + - bar