Add shell_plugins to abstract shell-specific functions out of runner, add winrm connection plugin, add initial Windows modules.

This commit is contained in:
Chris Church
2014-06-17 15:15:18 -05:00
committed by Matt Martz
parent 627ff30a6f
commit 5dcaa30476
25 changed files with 757 additions and 103 deletions

View File

@@ -167,7 +167,7 @@ class Runner(object):
self.module_vars = utils.default(module_vars, lambda: {})
self.default_vars = utils.default(default_vars, lambda: {})
self.always_run = None
self.connector = connection.Connection(self)
self.connector = connection.Connector(self)
self.conditional = conditional
self.module_name = module_name
self.forks = int(forks)
@@ -198,7 +198,7 @@ class Runner(object):
self.vault_pass = vault_pass
self.no_log = no_log
if self.transport == 'smart':
if self.transport == 'smart': # FIXME
# if the transport is 'smart' see if SSH can support ControlPersist if not use paramiko
# 'smart' is the default since 1.2.1/1.3
cmd = subprocess.Popen(['ssh','-o','ControlPersist'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -275,7 +275,7 @@ class Runner(object):
afo.flush()
afo.close()
remote = os.path.join(tmp, name)
remote = conn.shell.join_path(tmp, name)
try:
conn.put_file(afile, remote)
finally:
@@ -284,32 +284,17 @@ class Runner(object):
# *****************************************************
def _compute_environment_string(self, inject=None):
def _compute_environment_string(self, conn, inject=None): # CCTODO: Changed this method signature
''' what environment variables to use when running the command? '''
shell_type = inject.get('ansible_shell_type')
if not shell_type:
shell_type = os.path.basename(C.DEFAULT_EXECUTABLE)
default_environment = dict(
LANG = C.DEFAULT_MODULE_LANG,
LC_CTYPE = C.DEFAULT_MODULE_LANG,
)
enviro = {}
if self.environment:
enviro = template.template(self.basedir, self.environment, inject, convert_bare=True)
enviro = utils.safe_eval(enviro)
if type(enviro) != dict:
raise errors.AnsibleError("environment must be a dictionary, received %s" % enviro)
default_environment.update(enviro)
result = ""
for (k,v) in default_environment.iteritems():
if shell_type in ('csh', 'fish'):
result = "env %s=%s %s" % (k, pipes.quote(unicode(v)), result)
else:
result = "%s=%s %s" % (k, pipes.quote(unicode(v)), result)
return result
return conn.shell.env_prefix(**enviro)
# *****************************************************
@@ -425,7 +410,7 @@ class Runner(object):
if self._late_needs_tmp_path(conn, tmp, module_style):
tmp = self._make_tmp_path(conn)
remote_module_path = os.path.join(tmp, module_name)
remote_module_path = conn.shell.join_path(tmp, module_name)
if (module_style != 'new'
or async_jid is not None
@@ -435,12 +420,11 @@ class Runner(object):
or self.su):
self._transfer_str(conn, tmp, module_name, module_data)
environment_string = self._compute_environment_string(inject)
environment_string = self._compute_environment_string(conn, inject)
if "tmp" in tmp and ((self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root')):
# deal with possible umask issues once sudo'ed to other user
cmd_chmod = "chmod a+r %s" % remote_module_path
self._low_level_exec_command(conn, cmd_chmod, tmp, sudoable=False)
self._remote_chmod(conn, 'a+r', remote_module_path)
cmd = ""
in_data = None
@@ -468,8 +452,7 @@ class Runner(object):
if (self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root'):
# deal with possible umask issues once sudo'ed to other user
cmd_args_chmod = "chmod a+r %s" % argsfile
self._low_level_exec_command(conn, cmd_args_chmod, tmp, sudoable=False)
self._remote_chmod(conn, 'a+r', argsfile)
if async_jid is None:
cmd = "%s %s" % (remote_module_path, argsfile)
@@ -487,14 +470,14 @@ class Runner(object):
if not shebang:
raise errors.AnsibleError("module is missing interpreter line")
cmd = " ".join([environment_string.strip(), shebang.replace("#!","").strip(), cmd])
cmd = cmd.strip()
rm_tmp = None
if "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files and delete_remote_tmp:
if not self.sudo or self.su or self.sudo_user == 'root' or self.su_user == 'root':
# not sudoing or sudoing to root, so can cleanup files in the same step
cmd = cmd + "; rm -rf %s >/dev/null 2>&1" % tmp
rm_tmp = tmp
cmd = conn.shell.build_module_command(environment_string, shebang, cmd, rm_tmp)
cmd = cmd.strip()
sudoable = True
if module_name == "accelerate":
@@ -511,7 +494,7 @@ class Runner(object):
if (self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root'):
# not sudoing to root, so maybe can't delete files as that other user
# have to clean up temp files as original user in a second step
cmd2 = "rm -rf %s >/dev/null 2>&1" % tmp
cmd2 = conn.shell.remove(tmp, recurse=True)
self._low_level_exec_command(conn, cmd2, tmp, sudoable=False)
data = utils.parse_json(res['stdout'])
@@ -776,7 +759,8 @@ class Runner(object):
if not self.accelerate_port:
self.accelerate_port = C.ACCELERATE_PORT
if actual_transport in [ 'paramiko', 'ssh', 'accelerate' ]:
# CCTODO: Any reason not to do this regardless of connection type, and let the connection plugin ignore it?
if True:#actual_transport in [ 'paramiko', 'ssh', 'accelerate', 'winrm' ]:
actual_port = inject.get('ansible_ssh_port', port)
# the delegated host may have different SSH port configured, etc
@@ -818,6 +802,18 @@ class Runner(object):
if delegate_to or host != actual_host:
conn.delegate = host
default_shell = getattr(conn, 'default_shell', '')
shell_type = inject.get('ansible_shell_type')
if not shell_type:
if default_shell:
shell_type = default_shell
else:
shell_type = os.path.basename(C.DEFAULT_EXECUTABLE)
shell_plugin = utils.plugins.shell_loader.get(shell_type)
if shell_plugin is None:
shell_plugin = utils.plugins.shell_loader.get('sh')
conn.shell = shell_plugin
except errors.AnsibleConnectionFailed, e:
result = dict(failed=True, msg="FAILED: %s" % str(e))
@@ -947,6 +943,9 @@ class Runner(object):
executable=None, su=False, in_data=None):
''' execute a command string over SSH, return the output '''
if not cmd:
return dict(stdout='', stderr='')
if executable is None:
executable = C.DEFAULT_EXECUTABLE
@@ -954,16 +953,11 @@ class Runner(object):
su_user = self.su_user
# compare connection user to (su|sudo)_user and disable if the same
if hasattr(conn, 'user'):
if (not su and conn.user == sudo_user) or (su and conn.user == su_user):
sudoable = False
su = False
else:
# assume connection type is local if no user attribute
this_user = getpass.getuser()
if (not su and this_user == sudo_user) or (su and this_user == su_user):
sudoable = False
su = False
# assume connection type is local if no user attribute
this_user = getattr(conn, 'user', getpass.getuser())
if (not su and this_user == sudo_user) or (su and this_user == su_user):
sudoable = False
su = False
if su:
rc, stdin, stdout, stderr = conn.exec_command(cmd,
@@ -997,26 +991,16 @@ class Runner(object):
# *****************************************************
def _remote_chmod(self, conn, mode, path, tmp, sudoable=False, su=False):
''' issue a remote chmod command '''
cmd = conn.shell.chmod(mode, path)
return self._low_level_exec_command(conn, cmd, tmp, sudoable=sudoable, su=su)
# *****************************************************
def _remote_md5(self, conn, tmp, path):
''' takes a remote md5sum without requiring python, and returns 1 if no file '''
path = pipes.quote(path)
# The following test needs to be SH-compliant. BASH-isms will
# not work if /bin/sh points to a non-BASH shell.
test = "rc=0; [ -r \"%s\" ] || rc=2; [ -f \"%s\" ] || rc=1; [ -d \"%s\" ] && echo 3 && exit 0" % ((path,) * 3)
md5s = [
"(/usr/bin/md5sum %s 2>/dev/null)" % path, # Linux
"(/sbin/md5sum -q %s 2>/dev/null)" % path, # ?
"(/usr/bin/digest -a md5 %s 2>/dev/null)" % path, # Solaris 10+
"(/sbin/md5 -q %s 2>/dev/null)" % path, # Freebsd
"(/usr/bin/md5 -n %s 2>/dev/null)" % path, # Netbsd
"(/bin/md5 -q %s 2>/dev/null)" % path, # Openbsd
"(/usr/bin/csum -h MD5 %s 2>/dev/null)" % path, # AIX
"(/bin/csum -h MD5 %s 2>/dev/null)" % path # AIX also
]
cmd = " || ".join(md5s)
cmd = "%s; %s || (echo \"${rc} %s\")" % (test, cmd, path)
cmd = conn.shell.md5(path)
data = self._low_level_exec_command(conn, cmd, tmp, sudoable=True)
data2 = utils.last_non_blank_line(data['stdout'])
try:
@@ -1039,17 +1023,16 @@ class Runner(object):
def _make_tmp_path(self, conn):
''' make and return a temporary path on a remote box '''
basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48))
basetmp = os.path.join(C.DEFAULT_REMOTE_TMP, basefile)
if (self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root') and basetmp.startswith('$HOME'):
basetmp = os.path.join('/tmp', basefile)
use_system_tmp = False
if (self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root'):
use_system_tmp = True
cmd = 'mkdir -p %s' % basetmp
tmp_mode = None
if self.remote_user != 'root' or ((self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root')):
cmd += ' && chmod a+rx %s' % basetmp
cmd += ' && echo %s' % basetmp
tmp_mode = 'a+rx'
cmd = conn.shell.mkdtemp(basefile, use_system_tmp, tmp_mode)
result = self._low_level_exec_command(conn, cmd, None, sudoable=False)
# error handling on this seems a little aggressive?
@@ -1078,9 +1061,8 @@ class Runner(object):
def _remove_tmp_path(self, conn, tmp_path):
''' Remove a tmp_path. '''
if "-tmp-" in tmp_path:
cmd = "rm -rf %s >/dev/null 2>&1" % tmp_path
cmd = conn.shell.remove(tmp_path, recurse=True)
self._low_level_exec_command(conn, cmd, None, sudoable=False)
# If we have gotten here we have a working ssh configuration.
# If ssh breaks we could leave tmp directories out on the remote system.
@@ -1094,7 +1076,7 @@ class Runner(object):
module_shebang,
module_data
) = self._configure_module(conn, module_name, module_args, inject, complex_args)
module_remote_path = os.path.join(tmp, module_name)
module_remote_path = conn.shell.join_path(tmp, module_name)
self._transfer_str(conn, tmp, module_name, module_data)
@@ -1106,7 +1088,8 @@ class Runner(object):
''' find module and configure it '''
# Search module path(s) for named module.
module_path = utils.plugins.module_finder.find_plugin(module_name)
module_suffixes = getattr(conn, 'default_suffixes', None)
module_path = utils.plugins.module_finder.find_plugin(module_name, module_suffixes)
if module_path is None:
raise errors.AnsibleFileNotFound("module %s not found in %s" % (module_name, utils.plugins.module_finder.print_paths()))