mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-05-02 15:21:25 -07:00
enabled initial support for password prompt on become
- moved check prompt/password functions to connection, make more senes there - TODO: consider moving make_become to connection from connection_info - removed executable param that was never overriden outside of connection info
This commit is contained in:
parent
bac35ae773
commit
580993fef7
5 changed files with 37 additions and 38 deletions
|
@ -24,7 +24,6 @@ __metaclass__ = type
|
||||||
import pipes
|
import pipes
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import gettext
|
|
||||||
|
|
||||||
from ansible import constants as C
|
from ansible import constants as C
|
||||||
from ansible.template import Templar
|
from ansible.template import Templar
|
||||||
|
@ -298,7 +297,7 @@ class ConnectionInformation:
|
||||||
|
|
||||||
return new_info
|
return new_info
|
||||||
|
|
||||||
def make_become_cmd(self, cmd, executable ):
|
def make_become_cmd(self, cmd, executable='/bin/sh'):
|
||||||
""" helper function to create privilege escalation commands """
|
""" helper function to create privilege escalation commands """
|
||||||
|
|
||||||
prompt = None
|
prompt = None
|
||||||
|
@ -356,19 +355,6 @@ class ConnectionInformation:
|
||||||
|
|
||||||
return (cmd, prompt, success_key)
|
return (cmd, prompt, success_key)
|
||||||
|
|
||||||
def check_become_success(self, output, success_key):
|
|
||||||
return success_key in output
|
|
||||||
|
|
||||||
def check_password_prompt(self, output, prompt):
|
|
||||||
if isinstance(prompt, basestring):
|
|
||||||
return output.endswith(prompt)
|
|
||||||
else:
|
|
||||||
return prompt(output)
|
|
||||||
|
|
||||||
def check_incorrect_password(self, output, prompt):
|
|
||||||
incorrect_password = gettext.dgettext(self.become_method, "Sorry, try again.")
|
|
||||||
return output.endswith(incorrect_password)
|
|
||||||
|
|
||||||
def _get_fields(self):
|
def _get_fields(self):
|
||||||
return [i for i in self.__dict__.keys() if i[:1] != '_']
|
return [i for i in self.__dict__.keys() if i[:1] != '_']
|
||||||
|
|
||||||
|
|
|
@ -425,7 +425,7 @@ class ActionBase:
|
||||||
debug("done with _execute_module (%s, %s)" % (module_name, module_args))
|
debug("done with _execute_module (%s, %s)" % (module_name, module_args))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def _low_level_execute_command(self, cmd, tmp, executable=None, sudoable=True, in_data=None):
|
def _low_level_execute_command(self, cmd, tmp, sudoable=True, in_data=None):
|
||||||
'''
|
'''
|
||||||
This is the function which executes the low level shell command, which
|
This is the function which executes the low level shell command, which
|
||||||
may be commands to create/remove directories for temporary files, or to
|
may be commands to create/remove directories for temporary files, or to
|
||||||
|
@ -438,17 +438,15 @@ class ActionBase:
|
||||||
debug("no command, exiting _low_level_execute_command()")
|
debug("no command, exiting _low_level_execute_command()")
|
||||||
return dict(stdout='', stderr='')
|
return dict(stdout='', stderr='')
|
||||||
|
|
||||||
if executable is None:
|
#FIXME: disabled as this should happen in the connection plugin, verify before removing
|
||||||
executable = C.DEFAULT_EXECUTABLE
|
#prompt = None
|
||||||
|
#success_key = None
|
||||||
prompt = None
|
#
|
||||||
success_key = None
|
#if sudoable:
|
||||||
|
# cmd, prompt, success_key = self._connection_info.make_become_cmd(cmd)
|
||||||
if sudoable:
|
|
||||||
cmd, prompt, success_key = self._connection_info.make_become_cmd(cmd, executable)
|
|
||||||
|
|
||||||
debug("executing the command %s through the connection" % cmd)
|
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, in_data=in_data, sudoable=sudoable)
|
||||||
debug("command execution done")
|
debug("command execution done")
|
||||||
|
|
||||||
if not isinstance(stdout, basestring):
|
if not isinstance(stdout, basestring):
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
from __future__ import (absolute_import, division, print_function)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import gettext
|
||||||
from abc import ABCMeta, abstractmethod, abstractproperty
|
from abc import ABCMeta, abstractmethod, abstractproperty
|
||||||
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
@ -97,7 +98,7 @@ class ConnectionBase(with_metaclass(ABCMeta, object)):
|
||||||
|
|
||||||
@ensure_connect
|
@ensure_connect
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def exec_command(self, cmd, tmp_path, executable=None, in_data=None, sudoable=True):
|
def exec_command(self, cmd, tmp_path, in_data=None, sudoable=True):
|
||||||
"""Run a command on the remote host"""
|
"""Run a command on the remote host"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -117,3 +118,17 @@ class ConnectionBase(with_metaclass(ABCMeta, object)):
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Terminate the connection"""
|
"""Terminate the connection"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def check_become_success(self, output, success_key):
|
||||||
|
return success_key in output
|
||||||
|
|
||||||
|
def check_password_prompt(self, output, prompt):
|
||||||
|
if isinstance(prompt, basestring):
|
||||||
|
return output.endswith(prompt)
|
||||||
|
else:
|
||||||
|
return prompt(output)
|
||||||
|
|
||||||
|
def check_incorrect_password(self, output, prompt):
|
||||||
|
incorrect_password = gettext.dgettext(self._connection_info.become_method, "Sorry, try again.")
|
||||||
|
return output.endswith(incorrect_password)
|
||||||
|
|
||||||
|
|
|
@ -46,10 +46,10 @@ class Connection(ConnectionBase):
|
||||||
self._connected = True
|
self._connected = True
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def exec_command(self, cmd, tmp_path, executable='/bin/sh', in_data=None):
|
def exec_command(self, cmd, tmp_path, in_data=None):
|
||||||
''' run a command on the local host '''
|
''' run a command on the local host '''
|
||||||
|
|
||||||
super(Connection, self).exec_command(cmd, tmp_path, executable=executable, in_data=in_data)
|
super(Connection, self).exec_command(cmd, tmp_path, in_data=in_data)
|
||||||
|
|
||||||
debug("in local.exec_command()")
|
debug("in local.exec_command()")
|
||||||
# su requires to be run from a terminal, and therefore isn't supported here (yet?)
|
# su requires to be run from a terminal, and therefore isn't supported here (yet?)
|
||||||
|
@ -59,7 +59,7 @@ class Connection(ConnectionBase):
|
||||||
if in_data:
|
if in_data:
|
||||||
raise AnsibleError("Internal Error: this module does not support optimized module pipelining")
|
raise AnsibleError("Internal Error: this module does not support optimized module pipelining")
|
||||||
|
|
||||||
executable = executable.split()[0] if executable else None
|
executable = self._connection_info.executable.split()[0] if self._connection_info.executable else None
|
||||||
|
|
||||||
self._display.vvv("{0} EXEC {1}".format(self._connection_info.remote_addr, cmd))
|
self._display.vvv("{0} EXEC {1}".format(self._connection_info.remote_addr, 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
|
||||||
|
|
|
@ -174,10 +174,10 @@ class Connection(ConnectionBase):
|
||||||
# fail early if the become password is wrong
|
# fail early if the become password is wrong
|
||||||
if self._connection_info.become and sudoable:
|
if self._connection_info.become and sudoable:
|
||||||
if self._connection_info.become_pass:
|
if self._connection_info.become_pass:
|
||||||
if self._connection_info.check_incorrect_password(stdout, prompt):
|
if self.check_incorrect_password(stdout, prompt):
|
||||||
raise AnsibleError('Incorrect %s password', self._connection_info.become_method)
|
raise AnsibleError('Incorrect %s password', self._connection_info.become_method)
|
||||||
|
|
||||||
elif self._connection_info.check_password_prompt(stdout, prompt):
|
elif self.check_password_prompt(stdout, prompt):
|
||||||
raise AnsibleError('Missing %s password', self._connection_info.become_method)
|
raise AnsibleError('Missing %s password', self._connection_info.become_method)
|
||||||
|
|
||||||
if p.stdout in rfd:
|
if p.stdout in rfd:
|
||||||
|
@ -260,10 +260,10 @@ class Connection(ConnectionBase):
|
||||||
self._display.vvv("EXEC previous known host file not found for {0}".format(host))
|
self._display.vvv("EXEC previous known host file not found for {0}".format(host))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def exec_command(self, cmd, tmp_path, executable='/bin/sh', in_data=None, sudoable=True):
|
def exec_command(self, cmd, tmp_path, in_data=None, sudoable=True):
|
||||||
''' run a command on the remote host '''
|
''' run a command on the remote host '''
|
||||||
|
|
||||||
super(Connection, self).exec_command(cmd, tmp_path, executable=executable, in_data=in_data, sudoable=sudoable)
|
super(Connection, self).exec_command(cmd, tmp_path, in_data=in_data, sudoable=sudoable)
|
||||||
|
|
||||||
host = self._connection_info.remote_addr
|
host = self._connection_info.remote_addr
|
||||||
|
|
||||||
|
@ -287,7 +287,7 @@ class Connection(ConnectionBase):
|
||||||
prompt = None
|
prompt = None
|
||||||
success_key = ''
|
success_key = ''
|
||||||
if sudoable:
|
if sudoable:
|
||||||
cmd, prompt, success_key = self._connection_info.make_become_cmd(cmd, executable)
|
cmd, prompt, success_key = self._connection_info.make_become_cmd(cmd)
|
||||||
|
|
||||||
ssh_cmd.append(cmd)
|
ssh_cmd.append(cmd)
|
||||||
self._display.vvv("EXEC {0}".format(' '.join(ssh_cmd)), host=host)
|
self._display.vvv("EXEC {0}".format(' '.join(ssh_cmd)), host=host)
|
||||||
|
@ -323,8 +323,8 @@ class Connection(ConnectionBase):
|
||||||
become_errput = ''
|
become_errput = ''
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if self._connection_info.check_become_success(become_output, success_key) or \
|
if self.check_become_success(become_output, success_key) or \
|
||||||
self._connection_info.check_password_prompt(become_output, prompt ):
|
self.check_password_prompt(become_output, prompt ):
|
||||||
break
|
break
|
||||||
rfd, wfd, efd = select.select([p.stdout, p.stderr], [], [p.stdout], self._connection_info.timeout)
|
rfd, wfd, efd = select.select([p.stdout, p.stderr], [], [p.stdout], self._connection_info.timeout)
|
||||||
if p.stderr in rfd:
|
if p.stderr in rfd:
|
||||||
|
@ -333,7 +333,7 @@ class Connection(ConnectionBase):
|
||||||
raise AnsibleError('ssh connection closed waiting for privilege escalation password prompt')
|
raise AnsibleError('ssh connection closed waiting for privilege escalation password prompt')
|
||||||
become_errput += chunk
|
become_errput += chunk
|
||||||
|
|
||||||
if self._connection_info.check_incorrect_password(become_errput, prompt):
|
if self.check_incorrect_password(become_errput, prompt):
|
||||||
raise AnsibleError('Incorrect %s password', self._connection_info.become_method)
|
raise AnsibleError('Incorrect %s password', self._connection_info.become_method)
|
||||||
|
|
||||||
if p.stdout in rfd:
|
if p.stdout in rfd:
|
||||||
|
@ -347,7 +347,7 @@ class Connection(ConnectionBase):
|
||||||
stdout = p.communicate()
|
stdout = p.communicate()
|
||||||
raise AnsibleError('ssh connection error waiting for sudo or su password prompt')
|
raise AnsibleError('ssh connection error waiting for sudo or su password prompt')
|
||||||
|
|
||||||
if not self._connection_info.check_become_success(become_output, success_key):
|
if not self.check_become_success(become_output, success_key):
|
||||||
if sudoable:
|
if sudoable:
|
||||||
stdin.write(self._connection_info.become_pass + '\n')
|
stdin.write(self._connection_info.become_pass + '\n')
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue