Fixing many bugs in v2

* delegate_to rudimentary support (still needs much more work)
* lots of other things
This commit is contained in:
James Cammarata 2015-02-09 16:54:44 -06:00
parent 402a6d0533
commit 31dd75de59
17 changed files with 144 additions and 124 deletions

View file

@ -43,9 +43,11 @@ class ConnectionInformation:
# various different auth escalation methods (becomes, etc.) # various different auth escalation methods (becomes, etc.)
self.connection = C.DEFAULT_TRANSPORT self.connection = C.DEFAULT_TRANSPORT
self.remote_addr = None
self.remote_user = 'root' self.remote_user = 'root'
self.password = '' self.password = ''
self.port = 22 self.port = 22
self.private_key_file = None
self.su = False self.su = False
self.su_user = '' self.su_user = ''
self.su_pass = '' self.su_pass = ''
@ -65,6 +67,14 @@ class ConnectionInformation:
if options: if options:
self.set_options(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): def set_play(self, play):
''' '''
Configures this connection information instance with data from Configures this connection information instance with data from
@ -128,26 +138,6 @@ class ConnectionInformation:
when merging in data from task overrides. 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(): for field in self._get_fields():
value = getattr(ci, field, None) value = getattr(ci, field, None)
if isinstance(value, dict): if isinstance(value, dict):

View file

@ -172,7 +172,7 @@ class TaskExecutor:
self._connection_info.post_validate(variables=variables, loader=self._loader) self._connection_info.post_validate(variables=variables, loader=self._loader)
# get the connection and the handler for this execution # 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) self._handler = self._get_action_handler(connection=self._connection)
# Evaluate the conditional (if any) for this task, which we do before running # 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 # with the registered variable value later on when testing conditions
vars_copy = variables.copy() vars_copy = variables.copy()
debug("starting attempt loop") debug("starting attempt loop")
result = None result = None
for attempt in range(retries): for attempt in range(retries):
@ -301,7 +302,7 @@ class TaskExecutor:
else: else:
return async_result return async_result
def _get_connection(self): def _get_connection(self, variables):
''' '''
Reads the connection property for the host, and returns the Reads the connection property for the host, and returns the
correct connection object from the list of connection plugins correct connection object from the list of connection plugins
@ -310,13 +311,17 @@ class TaskExecutor:
# FIXME: delegate_to calculation should be done here # FIXME: delegate_to calculation should be done here
# FIXME: calculation of connection params/auth stuff 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, # FIXME: add all port/connection type munging here (accelerated mode,
# fixing up options for ssh, etc.)? and 'smart' conversion # fixing up options for ssh, etc.)? and 'smart' conversion
conn_type = self._connection_info.connection conn_type = self._connection_info.connection
if conn_type == 'smart': if conn_type == 'smart':
conn_type = 'ssh' 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: if not connection:
raise AnsibleError("the connection plugin '%s' was not found" % conn_type) 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) raise AnsibleError("the handler '%s' was not found" % handler_name)
return handler 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]

@ -1 +1 @@
Subproject commit 095f8681dbdfd2e9247446822e953287c9bca66c Subproject commit 34784b7a617aa35d3b994c9f0795567afc6fb0b0

View file

@ -219,7 +219,7 @@ class ModuleArgsParser:
thing = None thing = None
action = None action = None
delegate_to = None delegate_to = self._task_ds.get('delegate_to', None)
args = dict() args = dict()
@ -236,15 +236,12 @@ class ModuleArgsParser:
# action # action
if 'action' in self._task_ds: if 'action' in self._task_ds:
# an old school 'action' statement # an old school 'action' statement
thing = self._task_ds['action'] thing = self._task_ds['action']
delegate_to = None
action, args = self._normalize_parameters(thing, additional_args=additional_args) action, args = self._normalize_parameters(thing, additional_args=additional_args)
# local_action # local_action
if 'local_action' in self._task_ds: if 'local_action' in self._task_ds:
# local_action is similar but also implies a delegate_to # local_action is similar but also implies a delegate_to
if action is not None: if action is not None:
raise AnsibleParserError("action and local_action are mutually exclusive", obj=self._task_ds) raise AnsibleParserError("action and local_action are mutually exclusive", obj=self._task_ds)

View file

@ -66,6 +66,7 @@ class Conditional:
evaluation. evaluation.
''' '''
original = conditional
if conditional is None or conditional == '': if conditional is None or conditional == '':
return True return True

View file

@ -161,7 +161,9 @@ class ActionBase:
tmp_mode = 'a+rx' tmp_mode = 'a+rx'
cmd = self._shell.mkdtemp(basefile, use_system_tmp, tmp_mode) 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) 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? # error handling on this seems a little aggressive?
if result['rc'] != 0: if result['rc'] != 0:
@ -196,11 +198,13 @@ class ActionBase:
def _remove_tmp_path(self, tmp_path): def _remove_tmp_path(self, tmp_path):
'''Remove a temporary path we created. ''' '''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) cmd = self._shell.remove(tmp_path, recurse=True)
# If we have gotten here we have a working ssh configuration. # If we have gotten here we have a working ssh configuration.
# If ssh breaks we could leave tmp directories out on the remote system. # 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) self._low_level_execute_command(cmd, None, sudoable=False)
debug("done removing the tmp path")
def _transfer_data(self, remote_path, data): def _transfer_data(self, remote_path, data):
''' '''
@ -213,14 +217,16 @@ class ActionBase:
afd, afile = tempfile.mkstemp() afd, afile = tempfile.mkstemp()
afo = os.fdopen(afd, 'w') afo = os.fdopen(afd, 'w')
try: try:
if not isinstance(data, unicode): # FIXME: is this still necessary?
#ensure the data is valid UTF-8 #if not isinstance(data, unicode):
data = data.decode('utf-8') # #ensure the data is valid UTF-8
else: # data = data.decode('utf-8')
data = data.encode('utf-8') #else:
# data = data.encode('utf-8')
afo.write(data) afo.write(data)
except Exception, e: 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.flush()
afo.close() afo.close()
@ -238,7 +244,10 @@ class ActionBase:
''' '''
cmd = self._shell.chmod(mode, path) 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): 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 = inject['hostvars'][inject['inventory_hostname']].get('ansible_python_interpreter', 'python')
python_interp = 'python' python_interp = 'python'
cmd = self._shell.checksum(path, python_interp) 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) data = self._low_level_execute_command(cmd, tmp, sudoable=True)
debug("done getting the remote checksum")
# FIXME: implement this function? # FIXME: implement this function?
#data2 = utils.last_non_blank_line(data['stdout']) #data2 = utils.last_non_blank_line(data['stdout'])
try: try:
@ -286,7 +297,9 @@ class ActionBase:
expand_path = '~%s' % self._connection_info.su_user expand_path = '~%s' % self._connection_info.su_user
cmd = self._shell.expand_user(expand_path) 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) 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 = utils.last_non_blank_line(data['stdout'])
initial_fragment = data['stdout'].strip().splitlines()[-1] initial_fragment = data['stdout'].strip().splitlines()[-1]
@ -354,7 +367,9 @@ class ActionBase:
# FIXME: async stuff here? # 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 (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: if remote_module_path:
debug("transfering module to remote")
self._transfer_data(remote_module_path, module_data) self._transfer_data(remote_module_path, module_data)
debug("done transfering module to remote")
environment_string = self._compute_environment_string() environment_string = self._compute_environment_string()
@ -389,7 +404,9 @@ class ActionBase:
# specified in the play, not the sudo_user # specified in the play, not the sudo_user
sudoable = False 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) 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 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'): 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 # FIXME: hard-coded sudo_exe here
cmd, prompt, success_key = self._connection_info.make_sudo_cmd('/usr/bin/sudo', executable, cmd) 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) rc, stdin, stdout, stderr = self._connection.exec_command(cmd, tmp, executable=executable, in_data=in_data)
debug("command execution done") debug("command execution done")

View file

@ -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_return = self._execute_module(module_name='copy', module_args=new_module_args, delete_remote_tmp=delete_remote_tmp)
module_executed = True module_executed = True
@ -279,11 +273,6 @@ class ActionModule(ActionBase):
original_basename=source_rel 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. # Execute the file module.
module_return = self._execute_module(module_name='file', module_args=new_module_args, delete_remote_tmp=delete_remote_tmp) 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: if module_return.get('changed') == True:
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. # 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) \ 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):
or (not C.DEFAULT_KEEP_REMOTE_FILES and delete_remote_tmp and not module_executed):
self._remove_tmp_path(tmp) 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 # TODO: Support detailed status/diff for multiple files
if len(source_files) == 1: if module_executed and len(source_files) == 1:
result = module_return result = module_return
else: else:
result = dict(dest=dest, src=source, changed=changed) result = dict(dest=dest, src=source, changed=changed)

View file

@ -92,7 +92,7 @@ class ActionModule(ActionBase):
dest = self._loader.path_dwim(dest) dest = self._loader.path_dwim(dest)
else: else:
# files are saved in dest dir, with a subdir for each host, then the filename # 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("//","/") dest = dest.replace("//","/")

View file

@ -34,8 +34,7 @@ class ConnectionBase:
A base class for connections to contain common code. A base class for connections to contain common code.
''' '''
def __init__(self, host, connection_info, *args, **kwargs): def __init__(self, connection_info, *args, **kwargs):
self._host = host
self._connection_info = connection_info self._connection_info = connection_info
self._has_pipelining = False self._has_pipelining = False
self._display = Display(connection_info) self._display = Display(connection_info)

View file

@ -65,7 +65,7 @@ class Connection(ConnectionBase):
executable = executable.split()[0] if executable else None 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 # FIXME: cwd= needs to be set to the basedir of the playbook
debug("opening command with Popen()") debug("opening command with Popen()")
p = subprocess.Popen( p = subprocess.Popen(
@ -115,7 +115,7 @@ class Connection(ConnectionBase):
''' transfer a file from local to local ''' ''' transfer a file from local to local '''
#vvv("PUT %s TO %s" % (in_path, out_path), host=self.host) #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): if not os.path.exists(in_path):
#raise AnsibleFileNotFound("file or module does not exist: %s" % in_path) #raise AnsibleFileNotFound("file or module does not exist: %s" % in_path)
raise AnsibleError("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): def fetch_file(self, in_path, out_path):
#vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host) #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 ''' ''' fetch a file from local to local -- for copatibility '''
self.put_file(in_path, out_path) self.put_file(in_path, out_path)

View file

@ -37,8 +37,8 @@ from ansible.plugins.connections import ConnectionBase
class Connection(ConnectionBase): class Connection(ConnectionBase):
''' ssh based connections ''' ''' ssh based connections '''
def __init__(self, host, connection_info, *args, **kwargs): def __init__(self, connection_info, *args, **kwargs):
super(Connection, self).__init__(host, connection_info) super(Connection, self).__init__(connection_info)
# SSH connection specific init stuff # SSH connection specific init stuff
self.HASHED_KEY_MAGIC = "|1|" self.HASHED_KEY_MAGIC = "|1|"
@ -57,7 +57,7 @@ class Connection(ConnectionBase):
def connect(self): def connect(self):
''' connect to the remote host ''' ''' 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 = [] self._common_args = []
extra_args = C.ANSIBLE_SSH_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 # not sure if it's all working yet so this remains commented out
#if self._ipv6: #if self._ipv6:
# ssh_cmd += ['-6'] # 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): if not (self._connection_info.sudo or self._connection_info.su):
prompt = None 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) sudo_cmd, prompt, success_key = self._connection_info.make_sudo_cmd('/usr/bin/sudo', executable, cmd)
ssh_cmd.append(sudo_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 # FIXME: move the locations of these lock files, same as init above
#if C.HOST_KEY_CHECKING and not_in_host_file: #if C.HOST_KEY_CHECKING and not_in_host_file:
@ -309,44 +309,6 @@ class Connection(ConnectionBase):
self._send_password() 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_out = ''
no_prompt_err = '' no_prompt_err = ''
# FIXME: su/sudo stuff # FIXME: su/sudo stuff
@ -429,13 +391,13 @@ class Connection(ConnectionBase):
def put_file(self, in_path, out_path): def put_file(self, in_path, out_path):
''' transfer a file from local to remote ''' ''' 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): if not os.path.exists(in_path):
raise AnsibleFileNotFound("file or module does not exist: %s" % in_path) raise AnsibleFileNotFound("file or module does not exist: %s" % in_path)
cmd = self._password_cmd() cmd = self._password_cmd()
# FIXME: make a function, used in all 3 methods EXEC/PUT/FETCH # 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 # 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 # 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): def fetch_file(self, in_path, out_path):
''' fetch a file from remote to local ''' ''' 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() cmd = self._password_cmd()
# FIXME: make a function, used in all 3 methods EXEC/PUT/FETCH # 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 # 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 # not sure if it's all working yet so this remains commented out
#if self._ipv6: #if self._ipv6:
# host = '[%s]' % self._host # host = '[%s]' % self._connection_info.remote_addr
if C.DEFAULT_SCP_IF_SSH: if C.DEFAULT_SCP_IF_SSH:
cmd += ["scp"] + self._common_args cmd += ["scp"] + self._common_args

View file

@ -119,6 +119,9 @@
import os import os
from jinja2.exceptions import UndefinedError
from ansible.errors import AnsibleUndefinedVariable
from ansible.plugins.lookup import LookupBase from ansible.plugins.lookup import LookupBase
from ansible.template import Templar from ansible.template import Templar
from ansible.utils.boolean import boolean from ansible.utils.boolean import boolean
@ -172,7 +175,11 @@ class LookupModule(LookupBase):
templar = Templar(loader=self._loader, variables=variables) templar = Templar(loader=self._loader, variables=variables)
roledir = variables.get('roledir') roledir = variables.get('roledir')
for fn in total_search: 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): if os.path.isabs(fn) and os.path.exists(fn):
return [fn] return [fn]
else: else:

View file

@ -85,8 +85,8 @@ class StrategyBase:
def get_hosts_remaining(self, play): 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] 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): def get_failed_hosts(self, play):
return [host for host in self._inventory.get_hosts() if host.name in self._tqm._failed_hosts] 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): def _queue_task(self, host, task, task_vars, connection_info):
''' handles queueing the task up to be sent to a worker ''' ''' handles queueing the task up to be sent to a worker '''
@ -129,6 +129,7 @@ class StrategyBase:
task = task_result._task task = task_result._task
if result[0] == 'host_task_failed': if result[0] == 'host_task_failed':
if not task.ignore_errors: if not task.ignore_errors:
debug("marking %s as failed" % host.get_name())
self._tqm._failed_hosts[host.get_name()] = True self._tqm._failed_hosts[host.get_name()] = True
self._callback.runner_on_failed(task, task_result) self._callback.runner_on_failed(task, task_result)
elif result[0] == 'host_unreachable': elif result[0] == 'host_unreachable':
@ -284,7 +285,7 @@ class StrategyBase:
result = True result = True
debug("getting failed hosts") debug("getting failed hosts")
failed_hosts = self.get_failed_hosts() failed_hosts = self.get_failed_hosts(iterator._play)
if len(failed_hosts) == 0: if len(failed_hosts) == 0:
debug("there are no failed hosts") debug("there are no failed hosts")
return result return result
@ -317,8 +318,9 @@ class StrategyBase:
# pop the task, mark the host blocked, and queue it # pop the task, mark the host blocked, and queue it
self._blocked_hosts[host_name] = True self._blocked_hosts[host_name] = True
task = iterator.get_next_task_for_host(host) 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._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() self._process_pending_results()
@ -352,8 +354,8 @@ class StrategyBase:
self._callback.playbook_on_handler_task_start(handler_name) self._callback.playbook_on_handler_task_start(handler_name)
for host in self._notified_handlers[handler_name]: for host in self._notified_handlers[handler_name]:
if not handler.has_triggered(host): if not handler.has_triggered(host):
temp_data = handler.serialize() task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=host, task=handler)
self._queue_task(iterator._play, host, handler, connection_info) self._queue_task(host, handler, task_vars, connection_info)
handler.flag_for_host(host) handler.flag_for_host(host)
self._process_pending_results() self._process_pending_results()

View file

@ -76,7 +76,7 @@ class Display:
if host is None: if host is None:
self.display(msg, color='blue') self.display(msg, color='blue')
else: 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): def deprecated(self, msg, version, removed=False):
''' used to print out a deprecation message.''' ''' used to print out a deprecation message.'''

View file

@ -43,6 +43,8 @@ def secure_hash_s(data, hash_func=sha1):
digest = hash_func() digest = hash_func()
try: try:
if not isinstance(data, basestring):
data = "%s" % data
digest.update(data) digest.update(data)
except UnicodeEncodeError: except UnicodeEncodeError:
digest.update(data.encode('utf-8')) digest.update(data.encode('utf-8'))

View file

@ -39,14 +39,11 @@ def listify_lookup_plugin_terms(terms, variables, loader):
# with_items: {{ alist }} # with_items: {{ alist }}
stripped = terms.strip() stripped = terms.strip()
if not (stripped.startswith('{') or stripped.startswith('[')) and \ templar = Templar(loader=loader, variables=variables)
not stripped.startswith("/") and \ if not (stripped.startswith('{') or stripped.startswith('[')) and not stripped.startswith("/") and not stripped.startswith('set([') and not LOOKUP_REGEX.search(terms):
not stripped.startswith('set([') and \
not LOOKUP_REGEX.search(terms):
# if not already a list, get ready to evaluate with Jinja2 # if not already a list, get ready to evaluate with Jinja2
# not sure why the "/" is in above code :) # not sure why the "/" is in above code :)
try: try:
templar = Templar(loader=loader, variables=variables)
new_terms = templar.template("{{ %s }}" % terms) new_terms = templar.template("{{ %s }}" % terms)
if isinstance(new_terms, basestring) and "{{" in new_terms: if isinstance(new_terms, basestring) and "{{" in new_terms:
pass pass
@ -54,6 +51,8 @@ def listify_lookup_plugin_terms(terms, variables, loader):
terms = new_terms terms = new_terms
except: except:
pass pass
else:
terms = templar.template(terms)
if '{' in terms or '[' in terms: if '{' in terms or '[' in terms:
# Jinja2 already evaluated a variable to a list. # Jinja2 already evaluated a variable to a list.

17
v2/samples/test_block.yml Normal file
View file

@ -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