mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Sudo support operational in both playbooks and main program. Implementation could use some cleanup.
This commit is contained in:
parent
81e3496037
commit
2372a3b734
4 changed files with 53 additions and 33 deletions
|
@ -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
|
||||||
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):
|
||||||
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, '')
|
|
||||||
|
|
||||||
def _expect_like(self, msg):
|
# now load the results of the JSON execution...
|
||||||
''' hack to make invoke_shell more or less work for sudo usage '''
|
# FIXME: really need some timeout logic here
|
||||||
left = msg.rindex("START==>")
|
sftp = self.ssh.open_sftp()
|
||||||
right = msg.rindex("<==STOP")
|
while True:
|
||||||
return msg[left+8:right].lstrip().rstrip()
|
# print "waiting on %s" % result_file
|
||||||
|
time.sleep(1)
|
||||||
|
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):
|
||||||
# *****************************************************
|
# *****************************************************
|
||||||
|
|
||||||
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):
|
||||||
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):
|
||||||
# *****************************************************
|
# *****************************************************
|
||||||
|
|
||||||
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):
|
||||||
# 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):
|
||||||
|
|
||||||
# *****************************************************
|
# *****************************************************
|
||||||
|
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
''' 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):
|
||||||
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):
|
||||||
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):
|
||||||
|
|
||||||
# *****************************************************
|
# *****************************************************
|
||||||
|
|
||||||
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):
|
||||||
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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue