Fix ctrl+c in pause module and add tests (#40134)
* Fix all cases with pause and ctrl+c - naked: - pause: - with prompt - pause: prompt=hi - time wait - pause: seconds=60 - time wait with prompt - pause: seconds=60 prompt=hi Fixes #35372 * Use curses to control stdout * Use curses to clear lines on interactive input * Validate input for echo parameter and fail nicely if invalid * Add integration tests for pause module using pexpect * Use try except when trying to determine erase sequence to account for lack of TTY in containers in tests * Improve output validation for regular paus test * Accept two digit precision for pause length in test * Check for seconds when seconds is specificed, minutes when minutes is specified * Add test for no TTY mode Co-authored by: Toshio Kuratomi <a.badger@gmail.com> Co-authored by: Brian Coca <brian.coca+git@gmail.com>
This commit is contained in:
parent
39f9d3e4a6
commit
1c20029694
12 changed files with 475 additions and 28 deletions
2
changelogs/fragments/pause-ctrl-c.yaml
Normal file
2
changelogs/fragments/pause-ctrl-c.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
bugfixes:
|
||||||
|
- pause - ensure ctrl+c interrupt works in all cases (https://github.com/ansible/ansible/issues/35372)
|
|
@ -19,14 +19,16 @@ __metaclass__ = type
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import signal
|
import signal
|
||||||
|
import sys
|
||||||
import termios
|
import termios
|
||||||
import time
|
import time
|
||||||
import tty
|
import tty
|
||||||
|
|
||||||
from os import isatty
|
from os import isatty
|
||||||
from ansible.errors import AnsibleError
|
from ansible.errors import AnsibleError
|
||||||
|
from ansible.module_utils._text import to_text, to_native
|
||||||
|
from ansible.module_utils.parsing.convert_bool import boolean
|
||||||
from ansible.module_utils.six import PY3
|
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:
|
||||||
|
@ -35,6 +37,20 @@ except ImportError:
|
||||||
from ansible.utils.display import Display
|
from ansible.utils.display import Display
|
||||||
display = Display()
|
display = Display()
|
||||||
|
|
||||||
|
try:
|
||||||
|
import curses
|
||||||
|
curses.setupterm()
|
||||||
|
HAS_CURSES = True
|
||||||
|
except (ImportError, curses.error):
|
||||||
|
HAS_CURSES = False
|
||||||
|
|
||||||
|
if HAS_CURSES:
|
||||||
|
MOVE_TO_BOL = curses.tigetstr('cr')
|
||||||
|
CLEAR_TO_EOL = curses.tigetstr('el')
|
||||||
|
else:
|
||||||
|
MOVE_TO_BOL = b'\r'
|
||||||
|
CLEAR_TO_EOL = b'\x1b[K'
|
||||||
|
|
||||||
|
|
||||||
class AnsibleTimeoutExceeded(Exception):
|
class AnsibleTimeoutExceeded(Exception):
|
||||||
pass
|
pass
|
||||||
|
@ -44,6 +60,11 @@ def timeout_handler(signum, frame):
|
||||||
raise AnsibleTimeoutExceeded
|
raise AnsibleTimeoutExceeded
|
||||||
|
|
||||||
|
|
||||||
|
def clear_line(stdout):
|
||||||
|
stdout.write(b'\x1b[%s' % MOVE_TO_BOL)
|
||||||
|
stdout.write(b'\x1b[%s' % CLEAR_TO_EOL)
|
||||||
|
|
||||||
|
|
||||||
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 '''
|
||||||
|
|
||||||
|
@ -81,10 +102,11 @@ class ActionModule(ActionBase):
|
||||||
|
|
||||||
# Should keystrokes be echoed to stdout?
|
# Should keystrokes be echoed to stdout?
|
||||||
if 'echo' in self._task.args:
|
if 'echo' in self._task.args:
|
||||||
echo = self._task.args['echo']
|
try:
|
||||||
if not type(echo) == bool:
|
echo = boolean(self._task.args['echo'])
|
||||||
|
except TypeError as e:
|
||||||
result['failed'] = True
|
result['failed'] = True
|
||||||
result['msg'] = "'%s' is not a valid setting for 'echo'." % self._task.args['echo']
|
result['msg'] = to_native(e)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Add a note saying the output is hidden if echo is disabled
|
# Add a note saying the output is hidden if echo is disabled
|
||||||
|
@ -96,7 +118,7 @@ class ActionModule(ActionBase):
|
||||||
prompt = "[%s]\n%s%s:" % (self._task.get_name().strip(), self._task.args['prompt'], echo_prompt)
|
prompt = "[%s]\n%s%s:" % (self._task.get_name().strip(), self._task.args['prompt'], echo_prompt)
|
||||||
else:
|
else:
|
||||||
# If no custom prompt is specified, set a default prompt
|
# 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)
|
prompt = "[%s]\n%s%s:" % (self._task.get_name().strip(), 'Press enter to continue, Ctrl+C to interrupt', echo_prompt)
|
||||||
|
|
||||||
# Are 'minutes' or 'seconds' keys that exist in 'args'?
|
# Are 'minutes' or 'seconds' keys that exist in 'args'?
|
||||||
if 'minutes' in self._task.args or 'seconds' in self._task.args:
|
if 'minutes' in self._task.args or 'seconds' in self._task.args:
|
||||||
|
@ -149,31 +171,38 @@ class ActionModule(ActionBase):
|
||||||
try:
|
try:
|
||||||
if PY3:
|
if PY3:
|
||||||
stdin = self._connection._new_stdin.buffer
|
stdin = self._connection._new_stdin.buffer
|
||||||
|
stdout = sys.stdout.buffer
|
||||||
else:
|
else:
|
||||||
stdin = self._connection._new_stdin
|
stdin = self._connection._new_stdin
|
||||||
|
stdout = sys.stdout
|
||||||
fd = 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
|
||||||
stdin = None
|
stdin = None
|
||||||
|
|
||||||
if fd is not None:
|
if fd is not None:
|
||||||
if isatty(fd):
|
if isatty(fd):
|
||||||
|
|
||||||
|
# grab actual Ctrl+C sequence
|
||||||
|
try:
|
||||||
|
intr = termios.tcgetattr(fd)[6][termios.VINTR]
|
||||||
|
except Exception:
|
||||||
|
# unsupported/not present, use default
|
||||||
|
intr = b'\x03' # value for Ctrl+C
|
||||||
|
|
||||||
|
# get backspace sequences
|
||||||
|
try:
|
||||||
|
backspace = termios.tcgetattr(fd)[6][termios.VERASE]
|
||||||
|
except Exception:
|
||||||
|
backspace = [b'\x7f', b'\x08']
|
||||||
|
|
||||||
old_settings = termios.tcgetattr(fd)
|
old_settings = termios.tcgetattr(fd)
|
||||||
tty.setraw(fd)
|
tty.setraw(fd)
|
||||||
|
tty.setraw(stdout.fileno())
|
||||||
|
|
||||||
# Enable a few things turned off by tty.setraw()
|
# Only echo input if no timeout is specified
|
||||||
# ICANON -> Allows characters to be deleted and hides things like ^M.
|
if not seconds and echo:
|
||||||
# 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.
|
|
||||||
# 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.tcsetattr(fd, termios.TCSANOW, new_settings)
|
|
||||||
|
|
||||||
if echo:
|
|
||||||
# Enable ECHO since tty.setraw() disables it
|
|
||||||
new_settings = termios.tcgetattr(fd)
|
new_settings = termios.tcgetattr(fd)
|
||||||
new_settings[3] = new_settings[3] | termios.ECHO
|
new_settings[3] = new_settings[3] | termios.ECHO
|
||||||
termios.tcsetattr(fd, termios.TCSANOW, new_settings)
|
termios.tcsetattr(fd, termios.TCSANOW, new_settings)
|
||||||
|
@ -181,32 +210,43 @@ 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(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 = stdin.read(1)
|
key_pressed = stdin.read(1)
|
||||||
|
if key_pressed == intr: # value for Ctrl+C
|
||||||
if seconds:
|
clear_line(stdout)
|
||||||
if key_pressed == b'\x03':
|
|
||||||
raise KeyboardInterrupt
|
raise KeyboardInterrupt
|
||||||
|
|
||||||
if not seconds:
|
if not seconds:
|
||||||
if fd is None or not isatty(fd):
|
if fd is None or not isatty(fd):
|
||||||
display.warning("Not waiting from prompt as stdin is not interactive")
|
display.warning("Not waiting for response to prompt as stdin is not interactive")
|
||||||
break
|
break
|
||||||
|
|
||||||
# read key presses and act accordingly
|
# read key presses and act accordingly
|
||||||
if key_pressed in (b'\r', b'\n'):
|
if key_pressed in (b'\r', b'\n'):
|
||||||
|
clear_line(stdout)
|
||||||
break
|
break
|
||||||
|
elif key_pressed in backspace:
|
||||||
|
# delete a character if backspace is pressed
|
||||||
|
result['user_input'] = result['user_input'][:-1]
|
||||||
|
clear_line(stdout)
|
||||||
|
if echo:
|
||||||
|
stdout.write(result['user_input'])
|
||||||
|
stdout.flush()
|
||||||
else:
|
else:
|
||||||
result['user_input'] += key_pressed
|
result['user_input'] += key_pressed
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
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(stdin):
|
if self._c_or_a(stdin):
|
||||||
|
clear_line(stdout)
|
||||||
break
|
break
|
||||||
else:
|
|
||||||
|
clear_line(stdout)
|
||||||
|
|
||||||
raise AnsibleError('user requested abort!')
|
raise AnsibleError('user requested abort!')
|
||||||
|
|
||||||
except AnsibleTimeoutExceeded:
|
except AnsibleTimeoutExceeded:
|
||||||
|
|
1
test/integration/targets/pause/aliases
Normal file
1
test/integration/targets/pause/aliases
Normal file
|
@ -0,0 +1 @@
|
||||||
|
posix/ci/group2
|
11
test/integration/targets/pause/pause-1.yml
Normal file
11
test/integration/targets/pause/pause-1.yml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
- name: Test pause module in default state
|
||||||
|
hosts: testhost
|
||||||
|
become: no
|
||||||
|
gather_facts: no
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: EXPECTED FAILURE
|
||||||
|
pause:
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
msg: Task after pause
|
12
test/integration/targets/pause/pause-2.yml
Normal file
12
test/integration/targets/pause/pause-2.yml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
- name: Test pause module with custom prompt
|
||||||
|
hosts: testhost
|
||||||
|
become: no
|
||||||
|
gather_facts: no
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: EXPECTED FAILURE
|
||||||
|
pause:
|
||||||
|
prompt: Custom prompt
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
msg: Task after pause
|
12
test/integration/targets/pause/pause-3.yml
Normal file
12
test/integration/targets/pause/pause-3.yml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
- name: Test pause module with pause
|
||||||
|
hosts: testhost
|
||||||
|
become: no
|
||||||
|
gather_facts: no
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: EXPECTED FAILURE
|
||||||
|
pause:
|
||||||
|
seconds: 2
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
msg: Task after pause
|
13
test/integration/targets/pause/pause-4.yml
Normal file
13
test/integration/targets/pause/pause-4.yml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
- name: Test pause module with pause and custom prompt
|
||||||
|
hosts: testhost
|
||||||
|
become: no
|
||||||
|
gather_facts: no
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: EXPECTED FAILURE
|
||||||
|
pause:
|
||||||
|
seconds: 2
|
||||||
|
prompt: Waiting for two seconds
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
msg: Task after pause
|
35
test/integration/targets/pause/pause-5.yml
Normal file
35
test/integration/targets/pause/pause-5.yml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
- name: Test pause module echo output
|
||||||
|
hosts: testhost
|
||||||
|
become: no
|
||||||
|
gather_facts: no
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- pause:
|
||||||
|
echo: yes
|
||||||
|
prompt: Enter some text
|
||||||
|
register: results
|
||||||
|
|
||||||
|
- name: Ensure that input was captured
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- results.user_input == 'hello there'
|
||||||
|
|
||||||
|
- pause:
|
||||||
|
echo: yes
|
||||||
|
prompt: Enter some text to edit
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Ensure edited input was captured
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result.user_input == 'hello tommy boy'
|
||||||
|
|
||||||
|
- pause:
|
||||||
|
echo: no
|
||||||
|
prompt: Enter some text
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Ensure secret input was caputered
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result.user_input == 'supersecretpancakes'
|
23
test/integration/targets/pause/runme.sh
Executable file
23
test/integration/targets/pause/runme.sh
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
# Test pause module when no tty and non-interactive. This is to prevent playbooks
|
||||||
|
# from hanging in cron and Tower jobs.
|
||||||
|
/usr/bin/env bash << EOF
|
||||||
|
ansible-playbook test-pause-no-tty.yml -i ../../inventory 2>&1 | \
|
||||||
|
grep '\[WARNING\]: Not waiting for response to prompt as stdin is not interactive' && {
|
||||||
|
echo 'Successfully skipped pause in no TTY mode' >&2
|
||||||
|
exit 0
|
||||||
|
} || {
|
||||||
|
echo 'Failed to skip pause module' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Test pause with seconds and minutes specified
|
||||||
|
ansible-playbook test-pause.yml -i ../../inventory "$@"
|
||||||
|
|
||||||
|
# Interactively test pause
|
||||||
|
pip install pexpect
|
||||||
|
python test-pause.py -i ../../inventory "$@"
|
7
test/integration/targets/pause/test-pause-no-tty.yml
Normal file
7
test/integration/targets/pause/test-pause-no-tty.yml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
- name: Test pause
|
||||||
|
hosts: testhost
|
||||||
|
gather_facts: no
|
||||||
|
become: no
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- pause:
|
270
test/integration/targets/pause/test-pause.py
Executable file
270
test/integration/targets/pause/test-pause.py
Executable file
|
@ -0,0 +1,270 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pexpect
|
||||||
|
import sys
|
||||||
|
import termios
|
||||||
|
|
||||||
|
from ansible.module_utils.six import PY2
|
||||||
|
|
||||||
|
args = sys.argv[1:]
|
||||||
|
|
||||||
|
env_vars = {
|
||||||
|
'ANSIBLE_ROLES_PATH': './roles',
|
||||||
|
'ANSIBLE_NOCOLOR': 'True',
|
||||||
|
'ANSIBLE_RETRY_FILES_ENABLED': 'False'
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
backspace = termios.tcgetattr(sys.stdin.fileno())[6][termios.VERASE]
|
||||||
|
except Exception:
|
||||||
|
backspace = b'\x7f'
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
log_buffer = sys.stdout
|
||||||
|
else:
|
||||||
|
log_buffer = sys.stdout.buffer
|
||||||
|
|
||||||
|
os.environ.update(env_vars)
|
||||||
|
|
||||||
|
# -- Plain pause -- #
|
||||||
|
playbook = 'pause-1.yml'
|
||||||
|
|
||||||
|
# Case 1 - Contiune with enter
|
||||||
|
pause_test = pexpect.spawn(
|
||||||
|
'ansible-playbook',
|
||||||
|
args=[playbook] + args,
|
||||||
|
timeout=10,
|
||||||
|
env=os.environ
|
||||||
|
)
|
||||||
|
|
||||||
|
pause_test.logfile = log_buffer
|
||||||
|
pause_test.expect(r'Press enter to continue, Ctrl\+C to interrupt:')
|
||||||
|
pause_test.send('\r')
|
||||||
|
pause_test.expect('Task after pause')
|
||||||
|
pause_test.expect(pexpect.EOF)
|
||||||
|
pause_test.close()
|
||||||
|
|
||||||
|
|
||||||
|
# Case 2 - Continue with C
|
||||||
|
pause_test = pexpect.spawn(
|
||||||
|
'ansible-playbook',
|
||||||
|
args=[playbook] + args,
|
||||||
|
timeout=10,
|
||||||
|
env=os.environ
|
||||||
|
)
|
||||||
|
|
||||||
|
pause_test.logfile = log_buffer
|
||||||
|
pause_test.expect(r'Press enter to continue, Ctrl\+C to interrupt:')
|
||||||
|
pause_test.send('\x03')
|
||||||
|
pause_test.expect("Press 'C' to continue the play or 'A' to abort")
|
||||||
|
pause_test.send('C')
|
||||||
|
pause_test.expect('Task after pause')
|
||||||
|
pause_test.expect(pexpect.EOF)
|
||||||
|
pause_test.close()
|
||||||
|
|
||||||
|
|
||||||
|
# Case 3 - Abort with A
|
||||||
|
pause_test = pexpect.spawn(
|
||||||
|
'ansible-playbook',
|
||||||
|
args=[playbook] + args,
|
||||||
|
timeout=10,
|
||||||
|
env=os.environ
|
||||||
|
)
|
||||||
|
|
||||||
|
pause_test.logfile = log_buffer
|
||||||
|
pause_test.expect(r'Press enter to continue, Ctrl\+C to interrupt:')
|
||||||
|
pause_test.send('\x03')
|
||||||
|
pause_test.expect("Press 'C' to continue the play or 'A' to abort")
|
||||||
|
pause_test.send('A')
|
||||||
|
pause_test.expect('user requested abort!')
|
||||||
|
pause_test.expect(pexpect.EOF)
|
||||||
|
pause_test.close()
|
||||||
|
|
||||||
|
# -- Custom Prompt -- #
|
||||||
|
playbook = 'pause-2.yml'
|
||||||
|
|
||||||
|
# Case 1 - Contiune with enter
|
||||||
|
pause_test = pexpect.spawn(
|
||||||
|
'ansible-playbook',
|
||||||
|
args=[playbook] + args,
|
||||||
|
timeout=10,
|
||||||
|
env=os.environ
|
||||||
|
)
|
||||||
|
|
||||||
|
pause_test.logfile = log_buffer
|
||||||
|
pause_test.expect(r'Custom prompt:')
|
||||||
|
pause_test.send('\r')
|
||||||
|
pause_test.expect('Task after pause')
|
||||||
|
pause_test.expect(pexpect.EOF)
|
||||||
|
pause_test.close()
|
||||||
|
|
||||||
|
|
||||||
|
# Case 2 - Contiune with C
|
||||||
|
pause_test = pexpect.spawn(
|
||||||
|
'ansible-playbook',
|
||||||
|
args=[playbook] + args,
|
||||||
|
timeout=10,
|
||||||
|
env=os.environ
|
||||||
|
)
|
||||||
|
|
||||||
|
pause_test.logfile = log_buffer
|
||||||
|
pause_test.expect(r'Custom prompt:')
|
||||||
|
pause_test.send('\x03')
|
||||||
|
pause_test.expect("Press 'C' to continue the play or 'A' to abort")
|
||||||
|
pause_test.send('C')
|
||||||
|
pause_test.expect('Task after pause')
|
||||||
|
pause_test.expect(pexpect.EOF)
|
||||||
|
pause_test.close()
|
||||||
|
|
||||||
|
|
||||||
|
# Case 3 - Abort with A
|
||||||
|
pause_test = pexpect.spawn(
|
||||||
|
'ansible-playbook',
|
||||||
|
args=[playbook] + args,
|
||||||
|
timeout=10,
|
||||||
|
env=os.environ
|
||||||
|
)
|
||||||
|
|
||||||
|
pause_test.logfile = log_buffer
|
||||||
|
pause_test.expect(r'Custom prompt:')
|
||||||
|
pause_test.send('\x03')
|
||||||
|
pause_test.expect("Press 'C' to continue the play or 'A' to abort")
|
||||||
|
pause_test.send('A')
|
||||||
|
pause_test.expect('user requested abort!')
|
||||||
|
pause_test.expect(pexpect.EOF)
|
||||||
|
pause_test.close()
|
||||||
|
|
||||||
|
# -- Pause for N seconds -- #
|
||||||
|
|
||||||
|
playbook = 'pause-3.yml'
|
||||||
|
|
||||||
|
# Case 1 - Wait for task to continue after timeout
|
||||||
|
pause_test = pexpect.spawn(
|
||||||
|
'ansible-playbook',
|
||||||
|
args=[playbook] + args,
|
||||||
|
timeout=10,
|
||||||
|
env=os.environ
|
||||||
|
)
|
||||||
|
|
||||||
|
pause_test.logfile = log_buffer
|
||||||
|
pause_test.expect(r'Pausing for \d+ seconds')
|
||||||
|
pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)")
|
||||||
|
pause_test.expect('Task after pause')
|
||||||
|
pause_test.expect(pexpect.EOF)
|
||||||
|
pause_test.close()
|
||||||
|
|
||||||
|
# Case 2 - Contiune with Ctrl + C, C
|
||||||
|
pause_test = pexpect.spawn(
|
||||||
|
'ansible-playbook',
|
||||||
|
args=[playbook] + args,
|
||||||
|
timeout=10,
|
||||||
|
env=os.environ
|
||||||
|
)
|
||||||
|
|
||||||
|
pause_test.logfile = log_buffer
|
||||||
|
pause_test.expect(r'Pausing for \d+ seconds')
|
||||||
|
pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)")
|
||||||
|
pause_test.send('\x03')
|
||||||
|
pause_test.send('C')
|
||||||
|
pause_test.expect('Task after pause')
|
||||||
|
pause_test.expect(pexpect.EOF)
|
||||||
|
pause_test.close()
|
||||||
|
|
||||||
|
|
||||||
|
# Case 3 - Abort with Ctrl + C, A
|
||||||
|
pause_test = pexpect.spawn(
|
||||||
|
'ansible-playbook',
|
||||||
|
args=[playbook] + args,
|
||||||
|
timeout=10,
|
||||||
|
env=os.environ
|
||||||
|
)
|
||||||
|
|
||||||
|
pause_test.logfile = log_buffer
|
||||||
|
pause_test.expect(r'Pausing for \d+ seconds')
|
||||||
|
pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)")
|
||||||
|
pause_test.send('\x03')
|
||||||
|
pause_test.send('A')
|
||||||
|
pause_test.expect('user requested abort!')
|
||||||
|
pause_test.expect(pexpect.EOF)
|
||||||
|
pause_test.close()
|
||||||
|
|
||||||
|
# -- Pause for N seconds with custom prompt -- #
|
||||||
|
|
||||||
|
playbook = 'pause-4.yml'
|
||||||
|
|
||||||
|
# Case 1 - Wait for task to continue after timeout
|
||||||
|
pause_test = pexpect.spawn(
|
||||||
|
'ansible-playbook',
|
||||||
|
args=[playbook] + args,
|
||||||
|
timeout=10,
|
||||||
|
env=os.environ
|
||||||
|
)
|
||||||
|
|
||||||
|
pause_test.logfile = log_buffer
|
||||||
|
pause_test.expect(r'Pausing for \d+ seconds')
|
||||||
|
pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)")
|
||||||
|
pause_test.expect(r"Waiting for two seconds:")
|
||||||
|
pause_test.expect('Task after pause')
|
||||||
|
pause_test.expect(pexpect.EOF)
|
||||||
|
pause_test.close()
|
||||||
|
|
||||||
|
# Case 2 - Contiune with Ctrl + C, C
|
||||||
|
pause_test = pexpect.spawn(
|
||||||
|
'ansible-playbook',
|
||||||
|
args=[playbook] + args,
|
||||||
|
timeout=10,
|
||||||
|
env=os.environ
|
||||||
|
)
|
||||||
|
|
||||||
|
pause_test.logfile = log_buffer
|
||||||
|
pause_test.expect(r'Pausing for \d+ seconds')
|
||||||
|
pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)")
|
||||||
|
pause_test.expect(r"Waiting for two seconds:")
|
||||||
|
pause_test.send('\x03')
|
||||||
|
pause_test.send('C')
|
||||||
|
pause_test.expect('Task after pause')
|
||||||
|
pause_test.expect(pexpect.EOF)
|
||||||
|
pause_test.close()
|
||||||
|
|
||||||
|
|
||||||
|
# Case 3 - Abort with Ctrl + C, A
|
||||||
|
pause_test = pexpect.spawn(
|
||||||
|
'ansible-playbook',
|
||||||
|
args=[playbook] + args,
|
||||||
|
timeout=10,
|
||||||
|
env=os.environ
|
||||||
|
)
|
||||||
|
|
||||||
|
pause_test.logfile = log_buffer
|
||||||
|
pause_test.expect(r'Pausing for \d+ seconds')
|
||||||
|
pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)")
|
||||||
|
pause_test.expect(r"Waiting for two seconds:")
|
||||||
|
pause_test.send('\x03')
|
||||||
|
pause_test.send('A')
|
||||||
|
pause_test.expect('user requested abort!')
|
||||||
|
pause_test.expect(pexpect.EOF)
|
||||||
|
pause_test.close()
|
||||||
|
|
||||||
|
# -- Enter input and ensure it's caputered, echoed, and can be edited -- #
|
||||||
|
|
||||||
|
playbook = 'pause-5.yml'
|
||||||
|
|
||||||
|
pause_test = pexpect.spawn(
|
||||||
|
'ansible-playbook',
|
||||||
|
args=[playbook] + args,
|
||||||
|
timeout=10,
|
||||||
|
env=os.environ
|
||||||
|
)
|
||||||
|
|
||||||
|
pause_test.logfile = log_buffer
|
||||||
|
pause_test.expect(r'Enter some text:')
|
||||||
|
pause_test.sendline('hello there')
|
||||||
|
pause_test.expect(r'Enter some text to edit:')
|
||||||
|
pause_test.send('hello there')
|
||||||
|
pause_test.send(backspace * 4)
|
||||||
|
pause_test.send('ommy boy\r')
|
||||||
|
pause_test.expect(r'Enter some text \(output is hidden\):')
|
||||||
|
pause_test.sendline('supersecretpancakes')
|
||||||
|
pause_test.expect(pexpect.EOF)
|
||||||
|
pause_test.close()
|
21
test/integration/targets/pause/test-pause.yml
Normal file
21
test/integration/targets/pause/test-pause.yml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
- name: Test pause
|
||||||
|
hosts: testhost
|
||||||
|
gather_facts: no
|
||||||
|
become: no
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- pause:
|
||||||
|
seconds: 1
|
||||||
|
register: results
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- results.stdout is search('Paused for \d+\.\d+ seconds')
|
||||||
|
|
||||||
|
- pause:
|
||||||
|
minutes: 1
|
||||||
|
register: results
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- results.stdout is search('Paused for \d+\.\d+ minutes')
|
Loading…
Reference in a new issue