diff --git a/changelogs/fragments/command-support-argv.yaml b/changelogs/fragments/command-support-argv.yaml new file mode 100644 index 0000000000..c38d8064d6 --- /dev/null +++ b/changelogs/fragments/command-support-argv.yaml @@ -0,0 +1,4 @@ +--- +features: + - command module - Added argv option to allow command to be specified as a list vs. a string + (https://github.com/ansible/ansible/issues/19392) diff --git a/lib/ansible/modules/commands/command.py b/lib/ansible/modules/commands/command.py index ec18aba684..288a1e1017 100644 --- a/lib/ansible/modules/commands/command.py +++ b/lib/ansible/modules/commands/command.py @@ -32,6 +32,11 @@ options: - 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 @@ EXAMPLES = ''' 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) diff --git a/test/integration/targets/command_shell/tasks/main.yml b/test/integration/targets/command_shell/tasks/main.yml index 858f07d1a8..02ddb0afcf 100644 --- 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