Add echo option to pause module (#32205)

* Enable ECHO in prompt module

Fixes #14160

* Add option for controlling echo behavior with pause module

* Improve option logic

Allow all options to be used in varying combinations, rather than being mutually exclusive.
Always capture output and return it, even when a time limit is set.

* Add version_added to docs

* Improve behavior of echo output

Set a few more flags to allow interactive deletion and hide control characters.
Do not capture or echo input when a time is set. Tried to get this working nicely, but ran into too many issues/oddities to keep it. Maybe in the future if there is demand for capturing/echoing input when a time is set I'll take another pass at it.
This commit is contained in:
Sam Doran 2017-11-03 13:32:36 -04:00 committed by Brian Coca
parent 6ce3972f21
commit e65045f51f
2 changed files with 67 additions and 23 deletions

View file

@ -40,10 +40,19 @@ options:
- Optional text to use for the prompt message.
required: false
default: null
echo:
description:
- Contols whether or not keyboard input is shown when typing.
- Has no effect if 'seconds' or 'minutes' is set.
required: false
default: 'yes'
choices: ['yes', 'no']
version_added: 2.5
author: "Tim Bielawa (@tbielawa)"
notes:
- Starting in 2.2, if you specify 0 or negative for minutes or seconds, it will wait for 1 second, previously it would wait indefinitely.
- This module is also supported for Windows targets.
- User input is not captured or echoed, regardless of echo setting, when minutes or seconds is specified.
'''
EXAMPLES = '''
@ -57,6 +66,11 @@ EXAMPLES = '''
# A helpful reminder of what to look out for post-update.
- pause:
prompt: "Make sure org.foo.FooOverload exception is not present"
# Pause to get some sensitive input.
- pause:
prompt: "Enter a secret"
echo: no
'''
RETURN = '''
@ -85,4 +99,9 @@ stdout:
returned: always
type: string
sample: Paused for 0.04 minutes
echo:
description: Value of echo setting
returned: always
type: bool
sample: true
'''

View file

@ -47,7 +47,7 @@ def timeout_handler(signum, frame):
class ActionModule(ActionBase):
''' pauses execution for a length or time, or until input is received '''
PAUSE_TYPES = ['seconds', 'minutes', 'prompt', '']
PAUSE_TYPES = ['seconds', 'minutes', 'prompt', 'echo', '']
BYPASS_HOST_LOOP = True
def run(self, tmp=None, task_vars=None):
@ -60,6 +60,8 @@ class ActionModule(ActionBase):
duration_unit = 'minutes'
prompt = None
seconds = None
echo = True
echo_prompt = ''
result.update(dict(
changed=False,
rc=0,
@ -68,14 +70,35 @@ class ActionModule(ActionBase):
start=None,
stop=None,
delta=None,
echo=echo
))
# Is 'args' empty, then this is the default prompted pause
if self._task.args is None or len(self._task.args.keys()) == 0:
prompt = "[%s]\nPress enter to continue:" % self._task.get_name().strip()
if not set(self._task.args.keys()) <= set(self.PAUSE_TYPES):
result['failed'] = True
result['msg'] = "Invalid argument given. Must be one of: %s" % ", ".join(self.PAUSE_TYPES)
return result
# Should keystrokes be echoed to stdout?
if 'echo' in self._task.args:
echo = self._task.args['echo']
if not type(echo) == bool:
result['failed'] = True
result['msg'] = "'%s' is not a valid setting for 'echo'." % self._task.args['echo']
return result
# Add a note saying the output is hidden if echo is disabled
if not echo:
echo_prompt = ' (output is hidden)'
# Is 'prompt' a key in 'args'?
if 'prompt' in self._task.args:
prompt = "[%s]\n%s%s:" % (self._task.get_name().strip(), self._task.args['prompt'], echo_prompt)
else:
# If no custom prompt is specified, set a default prompt
prompt = "[%s]\n%s%s:" % (self._task.get_name().strip(), 'Press enter to continue', echo_prompt)
# Are 'minutes' or 'seconds' keys that exist in 'args'?
elif 'minutes' in self._task.args or 'seconds' in self._task.args:
if 'minutes' in self._task.args or 'seconds' in self._task.args:
try:
if 'minutes' in self._task.args:
# The time() command operates in seconds so we need to
@ -90,16 +113,6 @@ class ActionModule(ActionBase):
result['msg'] = u"non-integer value given for prompt duration:\n%s" % to_text(e)
return result
# Is 'prompt' a key in 'args'?
elif 'prompt' in self._task.args:
prompt = "[%s]\n%s:" % (self._task.get_name().strip(), self._task.args['prompt'])
else:
# I have no idea what you're trying to do. But it's so wrong.
result['failed'] = True
result['msg'] = "invalid pause type given. must be one of: %s" % ", ".join(self.PAUSE_TYPES)
return result
########################################################################
# Begin the hard work!
@ -113,12 +126,19 @@ class ActionModule(ActionBase):
if seconds is not None:
if seconds < 1:
seconds = 1
# setup the alarm handler
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(seconds)
# show the prompt
display.display("Pausing for %d seconds" % seconds)
# show the timer and control prompts
display.display("Pausing for %d seconds%s" % (seconds, echo_prompt))
display.display("(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)\r"),
# show the prompt specified in the task
if 'prompt' in self._task.args:
display.display(prompt)
else:
display.display(prompt)
@ -144,16 +164,19 @@ class ActionModule(ActionBase):
# ICANON -> Allows characters to be deleted and hides things like ^M.
# ICRNL -> Makes the return key work when ICANON is enabled, otherwise
# you get stuck at the prompt with no way to get out of it.
# ECHO -> Echos input back to stdout
#
# See man termios for details on these flags
if not seconds:
new_settings = termios.tcgetattr(fd)
new_settings[0] = new_settings[0] | termios.ICRNL
new_settings[3] = new_settings[3] | (termios.ICANON | termios.ECHO)
termios.tcsetattr(fd, termios.TCSANOW, new_settings)
new_settings[3] = new_settings[3] | termios.ICANON
termios.tcsetattr(fd, termios.TCSANOW, new_settings)
if echo:
# Enable ECHO since tty.setraw() disables it
new_settings = termios.tcgetattr(fd)
new_settings[3] = new_settings[3] | termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, new_settings)
# flush the buffer to make sure no previous key presses
# are read in below
termios.tcflush(stdin, termios.TCIFLUSH)
@ -161,8 +184,10 @@ class ActionModule(ActionBase):
try:
if fd is not None:
key_pressed = stdin.read(1)
if key_pressed == b'\x03':
raise KeyboardInterrupt
if seconds:
if key_pressed == b'\x03':
raise KeyboardInterrupt
if not seconds:
if fd is None or not isatty(fd):