Repository: ansible/ansible

Issue:
RFE: permit passing stdin to commands/shells
I want to be able to provide data to a command via stdin in my ansible playbooks.  For example, I have a playbook that uses `sfdisk` to handle partition manipulation, this:

```
- command: sfdisk -N2
  data: |
    label: dos
    /tmp/archlinux.img1 : start=        2048, size=      204800, type=c
    /tmp/archlinux.img2 : start=      206848, size=     1024000, type=83
```

Seems much more clear than:

```
- shell: >
    sfdisk -N2 < files/partitions
```

The second requires the `shell` module to perform input redirection,
and requires referencing another file just to find a small bit of
input.

We already have a `data` parameter in
`module_utils.basic.run_command`, so really this is just exposing it
via the `command` module.



Discussion:
do we really need more code?

```
vars:
   data:  |
     label: dos
     /tmp/archlinux.img1 : start=        2048, size=      204800, type=c
     /tmp/archlinux.img2 : start=      206848, size=     1024000, type=83
tasks:
  - shell: echo "{{data}}" |sfdisk -N2
```

@bcoca this is tricky to get right if data may contain quote characters.

There is an (important) case for this: **passing stdin securely to subsequent command/shell** (what is not possible as of today)

As talked in IRC today, passing stdin as an argument is not an option as soon as private (vault-originated) values are considered (ps faux would list them). Environment variable are sometimes an option, but not always (I'm unsure about their support in Ansible)

Every modules relying on a _freeform_ parameter just ends up doing:
`ssh host /bin/bash -c " < all the free form >"`
So freeform="`command <<< {{secret}}"` , freeform="`echo {{secret}}|command`", & al are **not** a secure way to pass variables.

The only form that I know of would be to pass stdin to the top-level process, ie :
Not: `ssh localhost bash -c "command <<<secret"``
Nor:``ssh localhost "command <<<secret"``(admitting /bin/bash is the default shell)
But rather``ssh localhost command <<<secret``
AFAICT the later is safe and could be run using Python I/O stdin-handling without having to pass stdin as an argument.

Adding a new (eg `stdin`) parameter  to _shell/command/raw_ modules could resolve this, and it would be backward-compatible: the inner-most stdin being always given highest priority:
- `ssh localhost cat <<< ext-stdin`
  vs
- `ssh localhost "/bin/bash -c 'cat<<<int-stdin'" <<< ext-stdin`

To echo what @drzraf noted, there are cases where stdin should be passed to the invoked command to avoid leaking sensitive info e.g. in the process table or a shell history file, another example being MySQL client tools' `-p` option which can prompt for the password from stdin instead of reading it as an argument on the command line. 

Could enable something like this?

```
- name: query tables from database
  command: mysqlshow -h {{ db_host }} -u {{ db_user }} -p {{ db_name }}
  args:
    stdin: "{{ db_password }}"
  register: db
```

> We already have a `data` parameter in `module_utils.basic.run_command`

Similarly, there is an inline `content` parameter in the [copy module](http://docs.ansible.com/ansible/copy_module.html)
@candlerb to re-iterate what @dspruell said it would be useful to be able to do this without the value from stdin touching the disk of the remote machine. So having it in `copy` is not so useful, although perhaps useful for some.
Sorry, I should have been clear - I'm fully in support of being able to pipe literal data to stdin without going via disk (and I have some use cases for this)

I was just pointing out that there is another precedent for an ansible module taking inlined literal text.
wonder why stdin is not suppoprted in ansible. ```ansible-vault``` is used here for this reason, passwords could be in vars in a file? But it would be great to have this for password based auth
@larsks 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 --->

Resolving patch:
diff --git a/lib/ansible/modules/commands/command.py b/lib/ansible/modules/commands/command.py
--- a/lib/ansible/modules/commands/command.py
+++ b/lib/ansible/modules/commands/command.py
@@ -49,6 +49,12 @@
     type: bool
     default: 'yes'
     version_added: "1.8"
+  stdin:
+    version_added: "2.4"
+    description:
+      - Set the stdin of the command directly to the specified value.
+    required: false
+    default: null
 notes:
     -  If you want to run a command through the shell (say you are using C(<), C(>), C(|), etc), you actually want the M(shell) module instead.
        The C(command) module is much more secure as it's not affected by the user's environment.
@@ -121,6 +127,7 @@ def main():
             creates=dict(type='path'),
             removes=dict(type='path'),
             warn=dict(type='bool', default=True),
+            stdin=dict(required=False),
         )
     )
 
@@ -131,6 +138,7 @@ def main():
     creates = module.params['creates']
     removes = module.params['removes']
     warn = module.params['warn']
+    stdin = module.params['stdin']
 
     if not shell and executable:
         module.warn("As of Ansible 2.4, the parameter 'executable' is no longer supported with the 'command' module. Not using '%s'." % executable)
@@ -174,7 +182,7 @@ def main():
         args = shlex.split(args)
     startd = datetime.datetime.now()
 
-    rc, out, err = module.run_command(args, executable=executable, use_unsafe_shell=shell, encoding=None)
+    rc, out, err = module.run_command(args, executable=executable, use_unsafe_shell=shell, encoding=None, data=stdin)
 
     endd = datetime.datetime.now()
     delta = endd - startd
diff --git a/lib/ansible/modules/commands/shell.py b/lib/ansible/modules/commands/shell.py
--- a/lib/ansible/modules/commands/shell.py
+++ b/lib/ansible/modules/commands/shell.py
@@ -60,6 +60,12 @@
     required: false
     default: True
     version_added: "1.8"
+  stdin:
+    version_added: "2.4"
+    description:
+      - Set the stdin of the command directly to the specified value.
+    required: false
+    default: null
 notes:
    -  If you want to execute a command securely and predictably, it may be
       better to use the M(command) module instead. Best practices when writing


Test patch:
diff --git a/test/integration/targets/command_shell/tasks/main.yml b/test/integration/targets/command_shell/tasks/main.yml
--- a/test/integration/targets/command_shell/tasks/main.yml
+++ b/test/integration/targets/command_shell/tasks/main.yml
@@ -121,6 +121,35 @@
     that:
       - "command_result4.changed != True"
 
+- name: pass stdin to cat via command
+  command: "cat"
+  args:
+    stdin: 'foobar'
+  register: command_result5
+
+- name: assert that stdin is passed
+  assert:
+    that:
+      - "command_result5.stdout == 'foobar'"
+
+- name: send to stdin literal multiline block
+  command: "{{ sha1sum.stdout }}"
+  args:
+    stdin: |-
+      this is the first line
+      this is the second line
+
+      this line is after an empty line
+      this line is the last line
+  register: command_result6
+
+- debug: var=command_result6
+
+- name: assert the multiline input was passed correctly
+  assert:
+    that:
+      - "command_result6.stdout == '9cd0697c6a9ff6689f0afb9136fa62e0b3fee903  -'"
+
 ##
 ## shell
 ##