initial become support to ssh plugin
- password prompt detection and incorrect passwrod detection to connection info - sudoable flag to avoid become on none pe'able commands
This commit is contained in:
parent
a267f93c83
commit
a248678518
3 changed files with 186 additions and 110 deletions
|
@ -1,3 +1,5 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
|
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of Ansible
|
# This file is part of Ansible
|
||||||
|
@ -21,6 +23,8 @@ __metaclass__ = type
|
||||||
|
|
||||||
import pipes
|
import pipes
|
||||||
import random
|
import random
|
||||||
|
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
|
||||||
|
@ -29,6 +33,40 @@ from ansible.errors import AnsibleError
|
||||||
|
|
||||||
__all__ = ['ConnectionInformation']
|
__all__ = ['ConnectionInformation']
|
||||||
|
|
||||||
|
SU_PROMPT_LOCALIZATIONS = [
|
||||||
|
'Password',
|
||||||
|
'암호',
|
||||||
|
'パスワード',
|
||||||
|
'Adgangskode',
|
||||||
|
'Contraseña',
|
||||||
|
'Contrasenya',
|
||||||
|
'Hasło',
|
||||||
|
'Heslo',
|
||||||
|
'Jelszó',
|
||||||
|
'Lösenord',
|
||||||
|
'Mật khẩu',
|
||||||
|
'Mot de passe',
|
||||||
|
'Parola',
|
||||||
|
'Parool',
|
||||||
|
'Pasahitza',
|
||||||
|
'Passord',
|
||||||
|
'Passwort',
|
||||||
|
'Salasana',
|
||||||
|
'Sandi',
|
||||||
|
'Senha',
|
||||||
|
'Wachtwoord',
|
||||||
|
'ססמה',
|
||||||
|
'Лозинка',
|
||||||
|
'Парола',
|
||||||
|
'Пароль',
|
||||||
|
'गुप्तशब्द',
|
||||||
|
'शब्दकूट',
|
||||||
|
'సంకేతపదము',
|
||||||
|
'හස්පදය',
|
||||||
|
'密码',
|
||||||
|
'密碼',
|
||||||
|
]
|
||||||
|
|
||||||
# the magic variable mapping dictionary below is used to translate
|
# the magic variable mapping dictionary below is used to translate
|
||||||
# host/inventory variables to fields in the ConnectionInformation
|
# host/inventory variables to fields in the ConnectionInformation
|
||||||
# object. The dictionary values are tuples, to account for aliases
|
# object. The dictionary values are tuples, to account for aliases
|
||||||
|
@ -44,6 +82,40 @@ MAGIC_VARIABLE_MAPPING = dict(
|
||||||
shell = ('ansible_shell_type',),
|
shell = ('ansible_shell_type',),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SU_PROMPT_LOCALIZATIONS = [
|
||||||
|
'Password',
|
||||||
|
'암호',
|
||||||
|
'パスワード',
|
||||||
|
'Adgangskode',
|
||||||
|
'Contraseña',
|
||||||
|
'Contrasenya',
|
||||||
|
'Hasło',
|
||||||
|
'Heslo',
|
||||||
|
'Jelszó',
|
||||||
|
'Lösenord',
|
||||||
|
'Mật khẩu',
|
||||||
|
'Mot de passe',
|
||||||
|
'Parola',
|
||||||
|
'Parool',
|
||||||
|
'Pasahitza',
|
||||||
|
'Passord',
|
||||||
|
'Passwort',
|
||||||
|
'Salasana',
|
||||||
|
'Sandi',
|
||||||
|
'Senha',
|
||||||
|
'Wachtwoord',
|
||||||
|
'ססמה',
|
||||||
|
'Лозинка',
|
||||||
|
'Парола',
|
||||||
|
'Пароль',
|
||||||
|
'गुप्तशब्द',
|
||||||
|
'शब्दकूट',
|
||||||
|
'సంకేతపదము',
|
||||||
|
'හස්පදය',
|
||||||
|
'密码',
|
||||||
|
'密碼',
|
||||||
|
]
|
||||||
|
|
||||||
class ConnectionInformation:
|
class ConnectionInformation:
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
@ -72,6 +144,14 @@ class ConnectionInformation:
|
||||||
self.become_method = None
|
self.become_method = None
|
||||||
self.become_user = None
|
self.become_user = None
|
||||||
self.become_pass = passwords.get('become_pass','')
|
self.become_pass = passwords.get('become_pass','')
|
||||||
|
self.become_exe = None
|
||||||
|
self.become_flags = None
|
||||||
|
|
||||||
|
# backwards compat
|
||||||
|
self.sudo_exe = None
|
||||||
|
self.sudo_flags = None
|
||||||
|
self.su_exe = None
|
||||||
|
self.su_flags = None
|
||||||
|
|
||||||
# general flags (should we move out?)
|
# general flags (should we move out?)
|
||||||
self.verbosity = 0
|
self.verbosity = 0
|
||||||
|
@ -202,25 +282,20 @@ class ConnectionInformation:
|
||||||
|
|
||||||
return new_info
|
return new_info
|
||||||
|
|
||||||
def make_become_cmd(self, cmd, executable, become_settings=None):
|
def make_become_cmd(self, cmd, executable ):
|
||||||
|
""" helper function to create privilege escalation commands """
|
||||||
|
|
||||||
"""
|
|
||||||
helper function to create privilege escalation commands
|
|
||||||
"""
|
|
||||||
|
|
||||||
# FIXME: become settings should probably be stored in the connection info itself
|
|
||||||
if become_settings is None:
|
|
||||||
become_settings = {}
|
|
||||||
|
|
||||||
randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32))
|
|
||||||
success_key = 'BECOME-SUCCESS-%s' % randbits
|
|
||||||
prompt = None
|
prompt = None
|
||||||
becomecmd = None
|
success_key = None
|
||||||
|
|
||||||
executable = executable or '$SHELL'
|
|
||||||
|
|
||||||
success_cmd = pipes.quote('echo %s; %s' % (success_key, cmd))
|
|
||||||
if self.become:
|
if self.become:
|
||||||
|
|
||||||
|
becomecmd = None
|
||||||
|
randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32))
|
||||||
|
success_key = 'BECOME-SUCCESS-%s' % randbits
|
||||||
|
executable = executable or '$SHELL'
|
||||||
|
success_cmd = pipes.quote('echo %s; %s' % (success_key, cmd))
|
||||||
|
|
||||||
if self.become_method == 'sudo':
|
if self.become_method == 'sudo':
|
||||||
# Rather than detect if sudo wants a password this time, -k makes sudo always ask for
|
# Rather than detect if sudo wants a password this time, -k makes sudo always ask for
|
||||||
# a password if one is required. Passing a quoted compound command to sudo (or sudo -s)
|
# a password if one is required. Passing a quoted compound command to sudo (or sudo -s)
|
||||||
|
@ -228,24 +303,33 @@ class ConnectionInformation:
|
||||||
# string to the user's shell. We loop reading output until we see the randomly-generated
|
# string to the user's shell. We loop reading output until we see the randomly-generated
|
||||||
# sudo prompt set with the -p option.
|
# sudo prompt set with the -p option.
|
||||||
prompt = '[sudo via ansible, key=%s] password: ' % randbits
|
prompt = '[sudo via ansible, key=%s] password: ' % randbits
|
||||||
exe = become_settings.get('sudo_exe', C.DEFAULT_SUDO_EXE)
|
exe = self.become_exe or self.sudo_exe or 'sudo'
|
||||||
flags = become_settings.get('sudo_flags', C.DEFAULT_SUDO_FLAGS)
|
flags = self.become_flags or self.sudo_flags or ''
|
||||||
becomecmd = '%s -k && %s %s -S -p "%s" -u %s %s -c %s' % \
|
becomecmd = '%s -k && %s %s -S -p "%s" -u %s %s -c %s' % \
|
||||||
(exe, exe, flags or C.DEFAULT_SUDO_FLAGS, prompt, self.become_user, executable, success_cmd)
|
(exe, exe, flags or C.DEFAULT_SUDO_FLAGS, prompt, self.become_user, executable, success_cmd)
|
||||||
|
|
||||||
elif self.become_method == 'su':
|
elif self.become_method == 'su':
|
||||||
exe = become_settings.get('su_exe', C.DEFAULT_SU_EXE)
|
|
||||||
flags = become_settings.get('su_flags', C.DEFAULT_SU_FLAGS)
|
def detect_su_prompt(data):
|
||||||
|
SU_PROMPT_LOCALIZATIONS_RE = re.compile("|".join(['(\w+\'s )?' + x + ' ?: ?' for x in SU_PROMPT_LOCALIZATIONS]), flags=re.IGNORECASE)
|
||||||
|
return bool(SU_PROMPT_LOCALIZATIONS_RE.match(data))
|
||||||
|
|
||||||
|
prompt = su_prompt()
|
||||||
|
exe = self.become_exe or self.su_exe or 'su'
|
||||||
|
flags = self.become_flags or self.su_flags or ''
|
||||||
becomecmd = '%s %s %s -c "%s -c %s"' % (exe, flags, self.become_user, executable, success_cmd)
|
becomecmd = '%s %s %s -c "%s -c %s"' % (exe, flags, self.become_user, executable, success_cmd)
|
||||||
|
|
||||||
elif self.become_method == 'pbrun':
|
elif self.become_method == 'pbrun':
|
||||||
exe = become_settings.get('pbrun_exe', 'pbrun')
|
|
||||||
flags = become_settings.get('pbrun_flags', '')
|
prompt='assword:'
|
||||||
|
exe = self.become_exe or 'pbrun'
|
||||||
|
flags = self.become_flags or ''
|
||||||
becomecmd = '%s -b -l %s -u %s %s' % (exe, flags, self.become_user, success_cmd)
|
becomecmd = '%s -b -l %s -u %s %s' % (exe, flags, self.become_user, success_cmd)
|
||||||
|
|
||||||
elif self.become_method == 'pfexec':
|
elif self.become_method == 'pfexec':
|
||||||
exe = become_settings.get('pfexec_exe', 'pbrun')
|
|
||||||
flags = become_settings.get('pfexec_flags', '')
|
exe = self.become_exe or 'pfexec'
|
||||||
|
flags = self.become_flags or ''
|
||||||
# No user as it uses it's own exec_attr to figure it out
|
# No user as it uses it's own exec_attr to figure it out
|
||||||
becomecmd = '%s %s "%s"' % (exe, flags, success_cmd)
|
becomecmd = '%s %s "%s"' % (exe, flags, success_cmd)
|
||||||
|
|
||||||
|
@ -254,11 +338,20 @@ class ConnectionInformation:
|
||||||
|
|
||||||
return (('%s -c ' % executable) + pipes.quote(becomecmd), prompt, success_key)
|
return (('%s -c ' % executable) + pipes.quote(becomecmd), prompt, success_key)
|
||||||
|
|
||||||
return (cmd, "", "")
|
return (cmd, prompt, success_key)
|
||||||
|
|
||||||
def check_become_success(self, output, become_settings):
|
def check_become_success(self, output, success_key):
|
||||||
#TODO: implement
|
return success_key in output
|
||||||
pass
|
|
||||||
|
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] != '_']
|
||||||
|
|
|
@ -94,7 +94,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):
|
def exec_command(self, cmd, tmp_path, executable=None, in_data=None, sudoable=True):
|
||||||
"""Run a command on the remote host"""
|
"""Run a command on the remote host"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -110,9 +110,7 @@ class Connection(ConnectionBase):
|
||||||
"-o", "PasswordAuthentication=no")
|
"-o", "PasswordAuthentication=no")
|
||||||
if self._connection_info.remote_user is not None and self._connection_info.remote_user != pwd.getpwuid(os.geteuid())[0]:
|
if self._connection_info.remote_user is not None and self._connection_info.remote_user != pwd.getpwuid(os.geteuid())[0]:
|
||||||
self._common_args += ("-o", "User={0}".format(self._connection_info.remote_user))
|
self._common_args += ("-o", "User={0}".format(self._connection_info.remote_user))
|
||||||
# FIXME: figure out where this goes
|
self._common_args += ("-o", "ConnectTimeout={0}".format(self._connection_info.timeout))
|
||||||
#self._common_args += ("-o", "ConnectTimeout={0}".format(self.runner.timeout))
|
|
||||||
self._common_args += ("-o", "ConnectTimeout=15")
|
|
||||||
|
|
||||||
self._connected = True
|
self._connected = True
|
||||||
|
|
||||||
|
@ -171,24 +169,14 @@ class Connection(ConnectionBase):
|
||||||
while True:
|
while True:
|
||||||
rfd, wfd, efd = select.select(rpipes, [], rpipes, 1)
|
rfd, wfd, efd = select.select(rpipes, [], rpipes, 1)
|
||||||
|
|
||||||
# FIXME: su/sudo stuff
|
# fail early if the become password is wrong
|
||||||
# fail early if the sudo/su password is wrong
|
if self._connection_info.become and sudoable:
|
||||||
#if self.runner.sudo and sudoable:
|
if self._connection_info.become_pass:
|
||||||
# if self.runner.sudo_pass:
|
if self._connection_info.check_incorrect_password(stdout, prompt):
|
||||||
# incorrect_password = gettext.dgettext(
|
raise AnsibleError('Incorrect %s password', self._connection_info.become_method)
|
||||||
# "sudo", "Sorry, try again.")
|
|
||||||
# if stdout.endswith("%s\r\n%s" % (incorrect_password,
|
elif self._connection_info.check_password_prompt(stdout, prompt):
|
||||||
# prompt)):
|
raise AnsibleError('Missing %s password', self._connection_info.become_method)
|
||||||
# raise AnsibleError('Incorrect sudo password')
|
|
||||||
#
|
|
||||||
# if stdout.endswith(prompt):
|
|
||||||
# raise AnsibleError('Missing sudo password')
|
|
||||||
#
|
|
||||||
#if self.runner.su and su and self.runner.su_pass:
|
|
||||||
# incorrect_password = gettext.dgettext(
|
|
||||||
# "su", "Sorry")
|
|
||||||
# if stdout.endswith("%s\r\n%s" % (incorrect_password, prompt)):
|
|
||||||
# raise AnsibleError('Incorrect su password')
|
|
||||||
|
|
||||||
if p.stdout in rfd:
|
if p.stdout in rfd:
|
||||||
dat = os.read(p.stdout.fileno(), 9000)
|
dat = os.read(p.stdout.fileno(), 9000)
|
||||||
|
@ -270,10 +258,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):
|
def exec_command(self, cmd, tmp_path, executable='/bin/sh', 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)
|
super(Connection, self).exec_command(cmd, tmp_path, executable=executable, in_data=in_data, sudoable=False)
|
||||||
|
|
||||||
host = self._connection_info.remote_addr
|
host = self._connection_info.remote_addr
|
||||||
|
|
||||||
|
@ -294,6 +282,11 @@ class Connection(ConnectionBase):
|
||||||
ssh_cmd += ['-6']
|
ssh_cmd += ['-6']
|
||||||
ssh_cmd.append(host)
|
ssh_cmd.append(host)
|
||||||
|
|
||||||
|
prompt = None
|
||||||
|
success_key = ''
|
||||||
|
if sudoable:
|
||||||
|
cmd, prompt, success_key = self._connection_info.make_become_cmd(cmd, executable)
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
@ -306,72 +299,62 @@ class Connection(ConnectionBase):
|
||||||
# fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX)
|
# fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX)
|
||||||
# fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_EX)
|
# fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_EX)
|
||||||
|
|
||||||
|
|
||||||
# create process
|
# create process
|
||||||
(p, stdin) = self._run(ssh_cmd, in_data)
|
(p, stdin) = self._run(ssh_cmd, in_data)
|
||||||
|
|
||||||
self._send_password()
|
if prompt:
|
||||||
|
self._send_password()
|
||||||
|
|
||||||
no_prompt_out = ''
|
no_prompt_out = ''
|
||||||
no_prompt_err = ''
|
no_prompt_err = ''
|
||||||
# FIXME: su/sudo stuff
|
q(self._connection_info.password)
|
||||||
#if (self.runner.sudo and sudoable and self.runner.sudo_pass) or \
|
if self._connection_info.become and sudoable and self._connection_info.password:
|
||||||
# (self.runner.su and su and self.runner.su_pass):
|
# several cases are handled for sudo privileges with password
|
||||||
# # several cases are handled for sudo privileges with password
|
# * NOPASSWD (tty & no-tty): detect success_key on stdout
|
||||||
# # * NOPASSWD (tty & no-tty): detect success_key on stdout
|
# * without NOPASSWD:
|
||||||
# # * without NOPASSWD:
|
# * detect prompt on stdout (tty)
|
||||||
# # * detect prompt on stdout (tty)
|
# * detect prompt on stderr (no-tty)
|
||||||
# # * detect prompt on stderr (no-tty)
|
fcntl.fcntl(p.stdout, fcntl.F_SETFL,
|
||||||
# fcntl.fcntl(p.stdout, fcntl.F_SETFL,
|
fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK)
|
||||||
# fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK)
|
fcntl.fcntl(p.stderr, fcntl.F_SETFL,
|
||||||
# fcntl.fcntl(p.stderr, fcntl.F_SETFL,
|
fcntl.fcntl(p.stderr, fcntl.F_GETFL) | os.O_NONBLOCK)
|
||||||
# fcntl.fcntl(p.stderr, fcntl.F_GETFL) | os.O_NONBLOCK)
|
become_output = ''
|
||||||
# sudo_output = ''
|
become_errput = ''
|
||||||
# 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
|
|
||||||
#
|
|
||||||
# rfd, wfd, efd = select.select([p.stdout, p.stderr], [],
|
|
||||||
# [p.stdout], self.runner.timeout)
|
|
||||||
# if p.stderr in rfd:
|
|
||||||
# chunk = p.stderr.read()
|
|
||||||
# if not chunk:
|
|
||||||
# raise AnsibleError('ssh connection closed waiting for sudo or su password prompt')
|
|
||||||
# sudo_errput += chunk
|
|
||||||
# incorrect_password = gettext.dgettext(
|
|
||||||
# "sudo", "Sorry, try again.")
|
|
||||||
# if sudo_errput.strip().endswith("%s%s" % (prompt, incorrect_password)):
|
|
||||||
# raise AnsibleError('Incorrect sudo password')
|
|
||||||
# elif sudo_errput.endswith(prompt):
|
|
||||||
# stdin.write(self.runner.sudo_pass + '\n')
|
|
||||||
#
|
|
||||||
# if p.stdout in rfd:
|
|
||||||
# chunk = p.stdout.read()
|
|
||||||
# if not chunk:
|
|
||||||
# raise AnsibleError('ssh connection closed waiting for sudo or su password prompt')
|
|
||||||
# sudo_output += chunk
|
|
||||||
#
|
|
||||||
# if not rfd:
|
|
||||||
# # timeout. wrap up process communication
|
|
||||||
# stdout = p.communicate()
|
|
||||||
# raise AnsibleError('ssh connection error waiting for sudo or su password prompt')
|
|
||||||
#
|
|
||||||
# if success_key not in sudo_output:
|
|
||||||
# if sudoable:
|
|
||||||
# stdin.write(self.runner.sudo_pass + '\n')
|
|
||||||
# elif su:
|
|
||||||
# stdin.write(self.runner.su_pass + '\n')
|
|
||||||
# else:
|
|
||||||
# no_prompt_out += sudo_output
|
|
||||||
# no_prompt_err += sudo_errput
|
|
||||||
|
|
||||||
#(returncode, stdout, stderr) = self._communicate(p, stdin, in_data, su=su, sudoable=sudoable, prompt=prompt)
|
while True:
|
||||||
# FIXME: the prompt won't be here anymore
|
if self._connection_info.check_become_success(become_output, success_key) or \
|
||||||
prompt=""
|
self._connection_info.check_password_prompt(become_output, prompt ):
|
||||||
(returncode, stdout, stderr) = self._communicate(p, stdin, in_data, prompt=prompt)
|
break
|
||||||
|
rfd, wfd, efd = select.select([p.stdout, p.stderr], [], [p.stdout], self._connection_info.timeout)
|
||||||
|
if p.stderr in rfd:
|
||||||
|
chunk = p.stderr.read()
|
||||||
|
if not chunk:
|
||||||
|
raise AnsibleError('ssh connection closed waiting for privilege escalation password prompt')
|
||||||
|
become_errput += chunk
|
||||||
|
|
||||||
|
if self._connection_info.check_incorrect_password(become_errput, prompt):
|
||||||
|
raise AnsibleError('Incorrect %s password', self._connection_info.become_method)
|
||||||
|
|
||||||
|
if p.stdout in rfd:
|
||||||
|
chunk = p.stdout.read()
|
||||||
|
if not chunk:
|
||||||
|
raise AnsibleError('ssh connection closed waiting for sudo or su password prompt')
|
||||||
|
become_output += chunk
|
||||||
|
|
||||||
|
if not rfd:
|
||||||
|
# timeout. wrap up process communication
|
||||||
|
stdout = p.communicate()
|
||||||
|
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 sudoable:
|
||||||
|
stdin.write(self._connection_info.password + '\n')
|
||||||
|
else:
|
||||||
|
no_prompt_out += become_output
|
||||||
|
no_prompt_err += become_errput
|
||||||
|
|
||||||
|
(returncode, stdout, stderr) = self._communicate(p, stdin, in_data, sudoable=sudoable, prompt=prompt)
|
||||||
|
|
||||||
#if C.HOST_KEY_CHECKING and not_in_host_file:
|
#if C.HOST_KEY_CHECKING and not_in_host_file:
|
||||||
# # lock around the initial SSH connectivity so the user prompt about whether to add
|
# # lock around the initial SSH connectivity so the user prompt about whether to add
|
||||||
|
|
Loading…
Reference in a new issue