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.
|
- Optional text to use for the prompt message.
|
||||||
required: false
|
required: false
|
||||||
default: null
|
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)"
|
author: "Tim Bielawa (@tbielawa)"
|
||||||
notes:
|
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.
|
- 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.
|
- 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 = '''
|
EXAMPLES = '''
|
||||||
|
@ -57,6 +66,11 @@ EXAMPLES = '''
|
||||||
# A helpful reminder of what to look out for post-update.
|
# A helpful reminder of what to look out for post-update.
|
||||||
- pause:
|
- pause:
|
||||||
prompt: "Make sure org.foo.FooOverload exception is not present"
|
prompt: "Make sure org.foo.FooOverload exception is not present"
|
||||||
|
|
||||||
|
# Pause to get some sensitive input.
|
||||||
|
- pause:
|
||||||
|
prompt: "Enter a secret"
|
||||||
|
echo: no
|
||||||
'''
|
'''
|
||||||
|
|
||||||
RETURN = '''
|
RETURN = '''
|
||||||
|
@ -85,4 +99,9 @@ stdout:
|
||||||
returned: always
|
returned: always
|
||||||
type: string
|
type: string
|
||||||
sample: Paused for 0.04 minutes
|
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):
|
class ActionModule(ActionBase):
|
||||||
''' pauses execution for a length or time, or until input is received '''
|
''' 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
|
BYPASS_HOST_LOOP = True
|
||||||
|
|
||||||
def run(self, tmp=None, task_vars=None):
|
def run(self, tmp=None, task_vars=None):
|
||||||
|
@ -60,6 +60,8 @@ class ActionModule(ActionBase):
|
||||||
duration_unit = 'minutes'
|
duration_unit = 'minutes'
|
||||||
prompt = None
|
prompt = None
|
||||||
seconds = None
|
seconds = None
|
||||||
|
echo = True
|
||||||
|
echo_prompt = ''
|
||||||
result.update(dict(
|
result.update(dict(
|
||||||
changed=False,
|
changed=False,
|
||||||
rc=0,
|
rc=0,
|
||||||
|
@ -68,14 +70,35 @@ class ActionModule(ActionBase):
|
||||||
start=None,
|
start=None,
|
||||||
stop=None,
|
stop=None,
|
||||||
delta=None,
|
delta=None,
|
||||||
|
echo=echo
|
||||||
))
|
))
|
||||||
|
|
||||||
# Is 'args' empty, then this is the default prompted pause
|
if not set(self._task.args.keys()) <= set(self.PAUSE_TYPES):
|
||||||
if self._task.args is None or len(self._task.args.keys()) == 0:
|
result['failed'] = True
|
||||||
prompt = "[%s]\nPress enter to continue:" % self._task.get_name().strip()
|
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'?
|
# 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:
|
try:
|
||||||
if 'minutes' in self._task.args:
|
if 'minutes' in self._task.args:
|
||||||
# The time() command operates in seconds so we need to
|
# 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)
|
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'?
|
|
||||||
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!
|
# Begin the hard work!
|
||||||
|
|
||||||
|
@ -113,12 +126,19 @@ class ActionModule(ActionBase):
|
||||||
if seconds is not None:
|
if seconds is not None:
|
||||||
if seconds < 1:
|
if seconds < 1:
|
||||||
seconds = 1
|
seconds = 1
|
||||||
|
|
||||||
# setup the alarm handler
|
# setup the alarm handler
|
||||||
signal.signal(signal.SIGALRM, timeout_handler)
|
signal.signal(signal.SIGALRM, timeout_handler)
|
||||||
signal.alarm(seconds)
|
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"),
|
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:
|
else:
|
||||||
display.display(prompt)
|
display.display(prompt)
|
||||||
|
|
||||||
|
@ -144,16 +164,19 @@ class ActionModule(ActionBase):
|
||||||
# ICANON -> Allows characters to be deleted and hides things like ^M.
|
# ICANON -> Allows characters to be deleted and hides things like ^M.
|
||||||
# ICRNL -> Makes the return key work when ICANON is enabled, otherwise
|
# 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.
|
# 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
|
# See man termios for details on these flags
|
||||||
if not seconds:
|
if not seconds:
|
||||||
new_settings = termios.tcgetattr(fd)
|
new_settings = termios.tcgetattr(fd)
|
||||||
new_settings[0] = new_settings[0] | termios.ICRNL
|
new_settings[0] = new_settings[0] | termios.ICRNL
|
||||||
new_settings[3] = new_settings[3] | (termios.ICANON | termios.ECHO)
|
new_settings[3] = new_settings[3] | termios.ICANON
|
||||||
termios.tcsetattr(fd, termios.TCSANOW, new_settings)
|
|
||||||
termios.tcsetattr(fd, termios.TCSANOW, new_settings)
|
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
|
# flush the buffer to make sure no previous key presses
|
||||||
# are read in below
|
# are read in below
|
||||||
termios.tcflush(stdin, termios.TCIFLUSH)
|
termios.tcflush(stdin, termios.TCIFLUSH)
|
||||||
|
@ -161,8 +184,10 @@ class ActionModule(ActionBase):
|
||||||
try:
|
try:
|
||||||
if fd is not None:
|
if fd is not None:
|
||||||
key_pressed = stdin.read(1)
|
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 not seconds:
|
||||||
if fd is None or not isatty(fd):
|
if fd is None or not isatty(fd):
|
||||||
|
|
Loading…
Reference in a new issue