Repository: ansible/ansible

Issue:
Command should be able to accept argv rather than trying to split arguments itself.
##### ISSUE TYPE
 - Feature Idea

##### COMPONENT NAME
command
https://github.com/ansible/ansible-modules-core/blob/devel/commands/command.py

##### ANSIBLE VERSION
```
ansible 2.2.0.0
  config file = /home/max/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```

##### CONFIGURATION

##### OS / ENVIRONMENT
Debian 8

##### SUMMARY
Command tries to split a command string into arguments.  Sometimes it fails.  When it does so it can be extremely frustrating, trying to figure out how to escape a command only for it to be parsed correctly.  If it can accept an array of arguments that would take the guesswork out, reduce pain, increase reliability and trust.

##### STEPS TO REPRODUCE

One option:
```
  command: ["executable", "this", "is", "an", "array", "of", "arguments", "{{ lookup('file', 'stuff') | string }}"]
```
In the code for command this is a trivial change: Make `shlex.split(args)` conditional on args being a string.

Another option:
```
  command: "executable"
  argv: ["some", "stuff", "{{ lookup('file', 'stuff') | string }}"]
```
##### EXPECTED RESULTS

I would like command to execute without trying to split the array of arguments.

##### ACTUAL RESULTS
N/A


Discussion:
I am happy to provide a merge request if you show a preference between the above forms.  Or two merge requests and you can choose between them. :-)  Progress is better than pain.
cc @dharmabumstead
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

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
@@ -32,6 +32,11 @@
       - The command module takes a free form command to run.  There is no parameter actually named 'free form'.
         See the examples!
     required: yes
+  argv:
+    description:
+      - Allows the user to provide the command as a list vs. a string.  Only the string or the list form can be
+        provided, not both.  One or the other must be provided.
+    version_added: "2.6"
   creates:
     description:
       - A filename or (since 2.0) glob pattern, when it already exists, this step will B(not) be run.
@@ -81,6 +86,13 @@
     chdir: somedir/
     creates: /path/to/database
 
+- name: use argv to send the command as a list.  Be sure to leave command empty
+  command:
+  args:
+    argv:
+      - echo
+      - testing
+
 - name: safely use templated variable to run command. Always use the quote filter to avoid injection issues.
   command: cat {{ myfile|quote }}
   register: myoutput
@@ -129,7 +141,11 @@ def check_command(module, commandline):
                 'tar': 'unarchive', 'unzip': 'unarchive', 'sed': 'replace, lineinfile or template',
                 'dnf': 'dnf', 'zypper': 'zypper'}
     become = ['sudo', 'su', 'pbrun', 'pfexec', 'runas', 'pmrun']
-    command = os.path.basename(commandline.split()[0])
+    if isinstance(commandline, list):
+        command = commandline[0]
+    else:
+        command = commandline.split()[0]
+    command = os.path.basename(command)
 
     disable_suffix = "If you need to use command because {mod} is insufficient you can add" \
                      " warn=False to this command task or set command_warnings=False in" \
@@ -159,6 +175,7 @@ def main():
         argument_spec=dict(
             _raw_params=dict(),
             _uses_shell=dict(type='bool', default=False),
+            argv=dict(type='list'),
             chdir=dict(type='path'),
             executable=dict(),
             creates=dict(type='path'),
@@ -168,11 +185,11 @@ def main():
             stdin=dict(required=False),
         )
     )
-
     shell = module.params['_uses_shell']
     chdir = module.params['chdir']
     executable = module.params['executable']
     args = module.params['_raw_params']
+    argv = module.params['argv']
     creates = module.params['creates']
     removes = module.params['removes']
     warn = module.params['warn']
@@ -182,9 +199,17 @@ def main():
         module.warn("As of Ansible 2.4, the parameter 'executable' is no longer supported with the 'command' module. Not using '%s'." % executable)
         executable = None
 
-    if not args or args.strip() == '':
+    if (not args or args.strip() == '') and not argv:
         module.fail_json(rc=256, msg="no command given")
 
+    if args and argv:
+        module.fail_json(rc=256, msg="only command or argv can be given, not both")
+
+    if not shell and args:
+        args = shlex.split(args)
+
+    args = args or argv
+
     if chdir:
         chdir = os.path.abspath(chdir)
         os.chdir(chdir)
@@ -216,8 +241,6 @@ def main():
     if warn:
         check_command(module, args)
 
-    if not shell:
-        args = shlex.split(args)
     startd = datetime.datetime.now()
 
     rc, out, err = module.run_command(args, executable=executable, use_unsafe_shell=shell, encoding=None, data=stdin)


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
@@ -79,6 +79,35 @@
       - "no_command.msg == 'no command given'"
       - "no_command.rc == 256"
 
+- name: use argv
+  command:
+    argv:
+      - echo
+      - testing
+  register: argv_command
+  ignore_errors: true
+
+- name: assert executable works with argv
+  assert:
+    that:
+      - "argv_command.stdout == 'testing'"
+
+- name: use argv and command string
+  command: echo testing
+  args:
+    argv:
+      - echo
+      - testing
+  register: argv_and_string_command
+  ignore_errors: true
+
+- name: assert executable fails with both argv and command string
+  assert:
+    that:
+      - "argv_and_string_command.failed == true"
+      - "argv_and_string_command.msg == 'only command or argv can be given, not both'"
+      - "argv_and_string_command.rc == 256"
+
 - set_fact: output_dir_test={{output_dir}}/test_command_shell
 
 - name: make sure our testing sub-directory does not exist