Commit 2372a3b7 by Michael DeHaan

Sudo support operational in both playbooks and main program. Implementation…

Sudo support operational in both playbooks and main program.   Implementation could use some cleanup.
parent 81e34960
...@@ -10,6 +10,10 @@ ...@@ -10,6 +10,10 @@
- hosts: all - hosts: all
user: root user: root
# could have also have done:
# user: mdehaan
# sudo: True
# make these variables available inside of templates # make these variables available inside of templates
# for when we use the 'template' action/module later on... # for when we use the 'template' action/module later on...
......
...@@ -22,6 +22,7 @@ import paramiko ...@@ -22,6 +22,7 @@ import paramiko
import traceback import traceback
import os import os
import time import time
import random
from ansible import errors from ansible import errors
################################################ ################################################
...@@ -80,36 +81,47 @@ class ParamikoConnection(object): ...@@ -80,36 +81,47 @@ class ParamikoConnection(object):
self.ssh = self._get_conn() self.ssh = self._get_conn()
return self return self
def exec_command(self, cmd, sudoable=True): def exec_command(self, cmd, tmp_path, sudoable=False):
''' run a command on the remote host ''' ''' run a command on the remote host '''
if not self.runner.sudo or not sudoable: if not self.runner.sudo or not sudoable:
stdin, stdout, stderr = self.ssh.exec_command(cmd) stdin, stdout, stderr = self.ssh.exec_command(cmd)
return (stdin, stdout, stderr) return (stdin, stdout, stderr)
else: else:
# percalculated tmp_path is ONLY required for sudo usage
if tmp_path is None:
raise Exception("expecting tmp_path")
r = random.randint(0,99999)
# invoke command using a new connection over sudo
result_file = os.path.join(tmp_path, "sudo_result.%s" % r)
self.ssh.close() self.ssh.close()
ssh_sudo = self._get_conn() ssh_sudo = self._get_conn()
sudo_chan = ssh_sudo.invoke_shell() sudo_chan = ssh_sudo.invoke_shell()
sudo_chan.send("sudo -s\n") sudo_chan.send("sudo -s\n")
sudo_chan.send("echo 'START==>';%s;echo '<==STOP'\n" % cmd)
timeout = 60 # make configurable? # to avoid ssh expect logic, redirect output to file and move the
# file when we are done with it...
sudo_chan.send("(%s >%s_pre 2>/dev/null ; mv %s_pre %s) &\n" % (cmd, result_file, result_file, result_file))
time.sleep(1) time.sleep(1)
while not sudo_chan.recv_ready():
time.sleep(1)
timeout -= 1
if timeout < 0:
return (None, json.dumps(dict(failed=True, msg="sudo timeout")), '')
out = sudo_chan.recv(2058)
sudo_chan.close() sudo_chan.close()
self.ssh = self._get_conn() self.ssh = self._get_conn()
out = self._expect_like(out)
return (None, out, '') # now load the results of the JSON execution...
# FIXME: really need some timeout logic here
def _expect_like(self, msg): sftp = self.ssh.open_sftp()
''' hack to make invoke_shell more or less work for sudo usage ''' while True:
left = msg.rindex("START==>") # print "waiting on %s" % result_file
right = msg.rindex("<==STOP") time.sleep(1)
return msg[left+8:right].lstrip().rstrip() try:
stat = sftp.stat(result_file)
break
except IOError:
pass
sftp.close()
# TODO: see if there's a SFTP way to just get the file contents w/o saving
# to disk vs this hack...
stdin, stdout, stderr = self.ssh.exec_command("cat %s" % result_file)
return (stdin, stdout, stderr)
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 '''
......
...@@ -255,7 +255,7 @@ class PlayBook(object): ...@@ -255,7 +255,7 @@ class PlayBook(object):
# ***************************************************** # *****************************************************
def _run_module(self, pattern, host_list, module, args, remote_user, def _run_module(self, pattern, host_list, module, args, remote_user,
async_seconds, async_poll_interval, only_if): async_seconds, async_poll_interval, only_if, sudo):
''' run a particular module step in a playbook ''' ''' run a particular module step in a playbook '''
hosts = [ h for h in host_list if (h not in self.stats.failures) and (h not in self.stats.dark)] hosts = [ h for h in host_list if (h not in self.stats.failures) and (h not in self.stats.dark)]
...@@ -268,6 +268,7 @@ class PlayBook(object): ...@@ -268,6 +268,7 @@ class PlayBook(object):
remote_port=self.remote_port, remote_port=self.remote_port,
setup_cache=SETUP_CACHE, basedir=self.basedir, setup_cache=SETUP_CACHE, basedir=self.basedir,
conditional=only_if, callbacks=self.runner_callbacks, conditional=only_if, callbacks=self.runner_callbacks,
sudo=sudo
) )
if async_seconds == 0: if async_seconds == 0:
...@@ -278,7 +279,7 @@ class PlayBook(object): ...@@ -278,7 +279,7 @@ class PlayBook(object):
# ***************************************************** # *****************************************************
def _run_task(self, pattern=None, host_list=None, task=None, def _run_task(self, pattern=None, host_list=None, task=None,
remote_user=None, handlers=None, conditional=False): remote_user=None, handlers=None, conditional=False, sudo=False):
''' run a single task in the playbook and recursively run any subtasks. ''' ''' run a single task in the playbook and recursively run any subtasks. '''
# load the module name and parameters from the task entry # load the module name and parameters from the task entry
...@@ -307,7 +308,7 @@ class PlayBook(object): ...@@ -307,7 +308,7 @@ class PlayBook(object):
# run the task in parallel # run the task in parallel
results = self._run_module(pattern, host_list, module_name, results = self._run_module(pattern, host_list, module_name,
module_args, remote_user, async_seconds, module_args, remote_user, async_seconds,
async_poll_interval, only_if) async_poll_interval, only_if, sudo)
self.stats.compute(results) self.stats.compute(results)
...@@ -402,7 +403,7 @@ class PlayBook(object): ...@@ -402,7 +403,7 @@ class PlayBook(object):
# ***************************************************** # *****************************************************
def _do_setup_step(self, pattern, vars, user, port, vars_files=None): def _do_setup_step(self, pattern, vars, user, port, sudo, vars_files=None):
''' push variables down to the systems and get variables+facts back up ''' ''' push variables down to the systems and get variables+facts back up '''
# this enables conditional includes like $facter_os.yml and is only done # this enables conditional includes like $facter_os.yml and is only done
...@@ -432,7 +433,7 @@ class PlayBook(object): ...@@ -432,7 +433,7 @@ class PlayBook(object):
timeout=self.timeout, remote_user=user, timeout=self.timeout, remote_user=user,
remote_pass=self.remote_pass, remote_port=self.remote_port, remote_pass=self.remote_pass, remote_port=self.remote_port,
setup_cache=SETUP_CACHE, setup_cache=SETUP_CACHE,
callbacks=self.runner_callbacks, callbacks=self.runner_callbacks, sudo=sudo,
).run() ).run()
self.stats.compute(setup_results, setup=True) self.stats.compute(setup_results, setup=True)
...@@ -464,15 +465,16 @@ class PlayBook(object): ...@@ -464,15 +465,16 @@ class PlayBook(object):
handlers = pg.get('handlers', []) handlers = pg.get('handlers', [])
user = pg.get('user', C.DEFAULT_REMOTE_USER) user = pg.get('user', C.DEFAULT_REMOTE_USER)
port = pg.get('port', C.DEFAULT_REMOTE_PORT) port = pg.get('port', C.DEFAULT_REMOTE_PORT)
sudo = pg.get('sudo', False)
self.callbacks.on_play_start(pattern) self.callbacks.on_play_start(pattern)
# push any variables down to the system # and get facts/ohai/other data back up # push any variables down to the system # and get facts/ohai/other data back up
self._do_setup_step(pattern, vars, user, port, None) self._do_setup_step(pattern, vars, user, port, sudo, None)
# now with that data, handle contentional variable file imports! # now with that data, handle contentional variable file imports!
if len(vars_files) > 0: if len(vars_files) > 0:
self._do_setup_step(pattern, vars, user, port, vars_files) self._do_setup_step(pattern, vars, user, port, sudo, vars_files)
# run all the top level tasks, these get run on every node # run all the top level tasks, these get run on every node
for task in tasks: for task in tasks:
...@@ -482,6 +484,7 @@ class PlayBook(object): ...@@ -482,6 +484,7 @@ class PlayBook(object):
task=task, task=task,
handlers=handlers, handlers=handlers,
remote_user=user, remote_user=user,
sudo=sudo
) )
# handlers only run on certain nodes, they are flagged by _flag_handlers # handlers only run on certain nodes, they are flagged by _flag_handlers
...@@ -499,7 +502,8 @@ class PlayBook(object): ...@@ -499,7 +502,8 @@ class PlayBook(object):
handlers=[], handlers=[],
host_list=triggered_by, host_list=triggered_by,
conditional=True, conditional=True,
remote_user=user remote_user=user,
sudo=sudo
) )
# end of execution for this particular pattern. Multiple patterns # end of execution for this particular pattern. Multiple patterns
......
...@@ -241,7 +241,7 @@ class Runner(object): ...@@ -241,7 +241,7 @@ class Runner(object):
for filename in files: for filename in files:
if not filename.startswith('/tmp/'): if not filename.startswith('/tmp/'):
raise Exception("not going to happen") raise Exception("not going to happen")
self._exec_command(conn, "rm -rf %s" % filename) self._exec_command(conn, "rm -rf %s" % filename, None)
# ***************************************************** # *****************************************************
...@@ -249,7 +249,7 @@ class Runner(object): ...@@ -249,7 +249,7 @@ class Runner(object):
''' transfers a module file to the remote side to execute it, but does not execute it yet ''' ''' transfers a module file to the remote side to execute it, but does not execute it yet '''
outpath = self._copy_module(conn, tmp, module) outpath = self._copy_module(conn, tmp, module)
self._exec_command(conn, "chmod +x %s" % outpath) self._exec_command(conn, "chmod +x %s" % outpath, tmp)
return outpath return outpath
# ***************************************************** # *****************************************************
...@@ -313,7 +313,7 @@ class Runner(object): ...@@ -313,7 +313,7 @@ class Runner(object):
if self.remote_user == 'root': if self.remote_user == 'root':
args = "%s metadata=/etc/ansible/setup" % args args = "%s metadata=/etc/ansible/setup" % args
else: else:
args = "%s metadata=~/.ansible/setup" % args args = "%s metadata=/home/%s/.ansible/setup" % (args, self.remote_user)
return args return args
# ***************************************************** # *****************************************************
...@@ -356,7 +356,7 @@ class Runner(object): ...@@ -356,7 +356,7 @@ class Runner(object):
cmd = "%s %s" % (remote_module_path, argsfile) cmd = "%s %s" % (remote_module_path, argsfile)
else: else:
cmd = " ".join([str(x) for x in [remote_module_path, async_jid, async_limit, async_module, argsfile]]) cmd = " ".join([str(x) for x in [remote_module_path, async_jid, async_limit, async_module, argsfile]])
return [ self._exec_command(conn, cmd, sudoable=True), client_executed_str ] return [ self._exec_command(conn, cmd, tmp, sudoable=True), client_executed_str ]
# ***************************************************** # *****************************************************
...@@ -555,14 +555,14 @@ class Runner(object): ...@@ -555,14 +555,14 @@ class Runner(object):
# ***************************************************** # *****************************************************
def _exec_command(self, conn, cmd, sudoable=False): def _exec_command(self, conn, cmd, tmp, sudoable=False):
''' execute a command string over SSH, return the output ''' ''' execute a command string over SSH, return the output '''
msg = '%s: %s' % (self.module_name, cmd) msg = '%s: %s' % (self.module_name, cmd)
# log remote command execution # log remote command execution
conn.exec_command('/usr/bin/logger -t ansible -p auth.info "%s"' % msg) conn.exec_command('/usr/bin/logger -t ansible -p auth.info "%s"' % msg, None)
# now run actual command # now run actual command
stdin, stdout, stderr = conn.exec_command(cmd, sudoable=sudoable) stdin, stdout, stderr = conn.exec_command(cmd, tmp, sudoable=sudoable)
if type(stdout) != str: if type(stdout) != str:
return "\n".join(stdout.readlines()) return "\n".join(stdout.readlines())
else: else:
...@@ -573,7 +573,7 @@ class Runner(object): ...@@ -573,7 +573,7 @@ class Runner(object):
def _get_tmp_path(self, conn): def _get_tmp_path(self, conn):
''' gets a temporary path on a remote box ''' ''' gets a temporary path on a remote box '''
result = self._exec_command(conn, "mktemp -d /tmp/ansible.XXXXXX", sudoable=False) result = self._exec_command(conn, "mktemp -d /tmp/ansible.XXXXXX", None, sudoable=False)
cleaned = result.split("\n")[0].strip() + '/' cleaned = result.split("\n")[0].strip() + '/'
return cleaned return cleaned
......
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