Fix the pause module for python3

* On python3, stdin goes through a buffer that translates from raw bytes
  to text.  this interferes with pause as it (1) performs universal
  newline conversion and therefore '\r' is turned into '\n' and (2) the
  buffering prevents us from getting the typed characters immediately
  (possibly a python3 bug?)  Using the raw byte stream that's behind the
  text decoder fixes these problems.

Unrelated cleanups:
* Use to_text instead of str for conversion into strings to avoid possible tracebacks
* Use either \r or \n as the end of a line.

Fixes #26278
Resolves #26446
pull/26520/head
Toshio Kuratomi 7 years ago
parent a94156227d
commit 5ceabe939d

@ -25,6 +25,8 @@ import tty
from os import isatty from os import isatty
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.module_utils.six import PY3
from ansible.module_utils._text import to_text
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
try: try:
@ -85,7 +87,7 @@ class ActionModule(ActionBase):
except ValueError as e: except ValueError as e:
result['failed'] = True result['failed'] = True
result['msg'] = "non-integer value given for prompt duration:\n%s" % str(e) result['msg'] = u"non-integer value given for prompt duration:\n%s" % to_text(e)
return result return result
# Is 'prompt' a key in 'args'? # Is 'prompt' a key in 'args'?
@ -102,8 +104,8 @@ class ActionModule(ActionBase):
# Begin the hard work! # Begin the hard work!
start = time.time() start = time.time()
result['start'] = str(datetime.datetime.now()) result['start'] = to_text(datetime.datetime.now())
result['user_input'] = '' result['user_input'] = b''
fd = None fd = None
old_settings = None old_settings = None
@ -122,9 +124,13 @@ class ActionModule(ActionBase):
# save the attributes on the existing (duped) stdin so # save the attributes on the existing (duped) stdin so
# that we can restore them later after we set raw mode # that we can restore them later after we set raw mode
if PY3:
stdin = self._connection._new_stdin.buffer
else:
stdin = self._connection._new_stdin
fd = None fd = None
try: try:
fd = self._connection._new_stdin.fileno() fd = stdin.fileno()
except (ValueError, AttributeError): except (ValueError, AttributeError):
# ValueError: someone is using a closed file descriptor as stdin # ValueError: someone is using a closed file descriptor as stdin
# AttributeError: someone is using a null file descriptor as stdin on windoez # AttributeError: someone is using a null file descriptor as stdin on windoez
@ -136,12 +142,12 @@ class ActionModule(ActionBase):
# flush the buffer to make sure no previous key presses # flush the buffer to make sure no previous key presses
# are read in below # are read in below
termios.tcflush(self._connection._new_stdin, termios.TCIFLUSH) termios.tcflush(stdin, termios.TCIFLUSH)
while True: while True:
try: try:
if fd is not None: if fd is not None:
key_pressed = self._connection._new_stdin.read(1) key_pressed = stdin.read(1)
if key_pressed == '\x03': if key_pressed == b'\x03':
raise KeyboardInterrupt raise KeyboardInterrupt
if not seconds: if not seconds:
@ -149,7 +155,7 @@ class ActionModule(ActionBase):
display.warning("Not waiting from prompt as stdin is not interactive") display.warning("Not waiting from prompt as stdin is not interactive")
break break
# read key presses and act accordingly # read key presses and act accordingly
if key_pressed == '\r': if key_pressed in (b'\r', b'\n'):
break break
else: else:
result['user_input'] += key_pressed result['user_input'] += key_pressed
@ -158,7 +164,7 @@ class ActionModule(ActionBase):
if seconds is not None: if seconds is not None:
signal.alarm(0) signal.alarm(0)
display.display("Press 'C' to continue the play or 'A' to abort \r"), display.display("Press 'C' to continue the play or 'A' to abort \r"),
if self._c_or_a(): if self._c_or_a(stdin):
break break
else: else:
raise AnsibleError('user requested abort!') raise AnsibleError('user requested abort!')
@ -174,7 +180,7 @@ class ActionModule(ActionBase):
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
duration = time.time() - start duration = time.time() - start
result['stop'] = str(datetime.datetime.now()) result['stop'] = to_text(datetime.datetime.now())
result['delta'] = int(duration) result['delta'] = int(duration)
if duration_unit == 'minutes': if duration_unit == 'minutes':
@ -183,12 +189,13 @@ class ActionModule(ActionBase):
duration = round(duration, 2) duration = round(duration, 2)
result['stdout'] = "Paused for %s %s" % (duration, duration_unit) result['stdout'] = "Paused for %s %s" % (duration, duration_unit)
result['user_input'] = to_text(result['user_input'], errors='surrogate_or_strict')
return result return result
def _c_or_a(self): def _c_or_a(self, stdin):
while True: while True:
key_pressed = self._connection._new_stdin.read(1) key_pressed = stdin.read(1)
if key_pressed.lower() == 'a': if key_pressed.lower() == b'a':
return False return False
elif key_pressed.lower() == 'c': elif key_pressed.lower() == b'c':
return True return True

Loading…
Cancel
Save