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.

add stdin argument to command
fixes #14380

##### SUMMARY
This change add an 'stdin' argument to 'command'. The value is directly passed to the command stdin using <code>data</code> parameter in <code>module_utils.basic.run_command</code>.

Change include 2 simple tests.


##### ISSUE TYPE
 - Feature Pull Request

##### COMPONENT NAME
lib/ansible/modules/commands/command.py

##### ANSIBLE VERSION
```
ansible 2.4.0 (stdin-command-shell 0f4eaa6d32) last updated 2017/04/08 02:18:13 (GMT +200)
python version = 3.6.0 (default, Jan 16 2017, 12:12:55) [GCC 6.3.1 20170109]
```




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 --->
Here is another example of why this would be a good addition. `smbpasswd` can take a password (repeated twice separated by newline) on stdin. This is what I currently have to do:

```
- name: 'create users'
  shell: '(echo "{{ item.password|csh_escape }}"; echo "{{ item.password|csh_escape }}") | smbpasswd -s -a "{{ item.username }}"'
  with_items: '{{ users }}'
  register: 'create_user_result'
  changed_when: '"Added user" in create_user_result.stdout'
```

If the command module could take stdin, there would be no need to rely on shell just to pipe data, which is a massive program to load for such a simple task, and there would be no need for a custom `csh_escape` filter, which is only needed because I am forced to use a shell. This would make the task less brittle, and more portable.

Bash is not the only shell, and not all shells are equal. (For example csh expands `!` even inside single quotes). It should not be necessary to load a shell just to input data into a program. Using the shell module should be a deliberate choice.


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 == 'b1a4df888d8d261876dc33ded211ba3ebe803010  -'"
+
 ##
 ## shell
 ##