Repository: ansible/ansible

Issue:
Pause Prompt keeps input hidden in v2
In tasks I prompt for an AWS AMI and get the user input using register. Before ansible v2, I was able to see what I was typing in at the prompt. After v2 I am not longer able to see what I am typing. 

It seems it now defaults to private, but it there is no way to disable it.



Discussion:
+1
Please add private option to ansible 2.0 or revert old ansible 1.9 behavior.
Thank you

@bcoca any ETA on when this will get fixed? Saw it assigned to the stable 2.0 milestone but I don't see this resolved in the 2.1.0.0 release I'm using. 

+1
I have the same issue with Ansible 2.
I can not see my input with private no or without private option.

fix is not easy as we had to rewrite all the terminal input code, the old one used raw_input which allows echo, the new one sets the tty to 'raw' mode which does not.

@mwhite14 Greetings! Thanks for taking the time to open this issue. In order for the community to handle your issue effectively, we need a bit more information. 

Here are the items we could not find in your description:
- issue type
- ansible version
- component name

Please set the description of this issue with this template:
https://raw.githubusercontent.com/ansible/ansible/devel/.github/ISSUE_TEMPLATE.md

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: issue_missing_data --->
Any update on this issue? Without echoing the input the prompting in pause module looks unusable to us.
@soumyadipDe I'm digging into this. Not a simple fix but doing my best to find a solution.

Resolving patch:
diff --git a/lib/ansible/modules/utilities/logic/pause.py b/lib/ansible/modules/utilities/logic/pause.py
--- a/lib/ansible/modules/utilities/logic/pause.py
+++ b/lib/ansible/modules/utilities/logic/pause.py
@@ -40,10 +40,19 @@
       - 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 @@
 # 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 @@
   returned: always
   type: string
   sample: Paused for 0.04 minutes
+echo:
+  description: Value of echo setting
+  returned: always
+  type: bool
+  sample: true
 '''
diff --git a/lib/ansible/plugins/action/pause.py b/lib/ansible/plugins/action/pause.py
--- a/lib/ansible/plugins/action/pause.py
+++ b/lib/ansible/plugins/action/pause.py
@@ -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 @@ def run(self, tmp=None, task_vars=None):
         duration_unit = 'minutes'
         prompt = None
         seconds = None
+        echo = True
+        echo_prompt = ''
         result.update(dict(
             changed=False,
             rc=0,
@@ -68,14 +70,35 @@ def run(self, tmp=None, task_vars=None):
             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 @@ def run(self, tmp=None, task_vars=None):
                 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 @@ def run(self, tmp=None, task_vars=None):
             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 @@ def run(self, tmp=None, task_vars=None):
                     # 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 @@ def run(self, tmp=None, task_vars=None):
                 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):


Test patch: