mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
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:
parent
6ce3972f21
commit
e65045f51f
2 changed files with 67 additions and 23 deletions
|
@ -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
|
||||
'''
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in a new issue