From 13bd4b5d82edc00a6abc3810ed64c1487a1b0372 Mon Sep 17 00:00:00 2001 From: Alexei Znamensky <103110+russoz@users.noreply.github.com> Date: Sun, 17 Aug 2025 22:43:29 +1200 Subject: [PATCH] composer: fix command args as list rather than string (#10669) --- plugins/modules/composer.py | 22 +++++++++------ tests/unit/plugins/modules/test_composer.py | 17 ++++++++++++ tests/unit/plugins/modules/test_composer.yaml | 27 +++++++++++++++++++ tests/unit/plugins/modules/uthelper.py | 2 +- 4 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 tests/unit/plugins/modules/test_composer.py create mode 100644 tests/unit/plugins/modules/test_composer.yaml diff --git a/plugins/modules/composer.py b/plugins/modules/composer.py index d932a5a060..cf8c1dfeff 100644 --- a/plugins/modules/composer.py +++ b/plugins/modules/composer.py @@ -97,7 +97,7 @@ options: type: bool ignore_platform_reqs: description: - - Ignore php, hhvm, lib-* and ext-* requirements and force the installation even if the local machine does not fulfill + - Ignore C(php), C(hhvm), C(lib-*) and C(ext-*) requirements and force the installation even if the local machine does not fulfill these. default: false type: bool @@ -143,6 +143,7 @@ EXAMPLES = r""" """ import re +import shlex from ansible.module_utils.basic import AnsibleModule @@ -160,7 +161,7 @@ def has_changed(string): def get_available_options(module, command='install'): # get all available options from a composer command using composer help to json - rc, out, err = composer_command(module, "help %s" % command, arguments="--no-interaction --format=json") + rc, out, err = composer_command(module, ["help", command], arguments=["--no-interaction", "--format=json"]) if rc != 0: output = parse_out(err) module.fail_json(msg=output) @@ -169,14 +170,19 @@ def get_available_options(module, command='install'): return command_help_json['definition']['options'] -def composer_command(module, command, arguments="", options=None): +def composer_command(module, command, arguments=None, options=None): if options is None: options = [] + if arguments is None: + arguments = [] global_command = module.params['global_command'] - if not global_command: - options.extend(['--working-dir', "'%s'" % module.params['working_dir']]) + if global_command: + global_arg = ["global"] + else: + global_arg = [] + options.extend(['--working-dir', module.params['working_dir']]) if module.params['executable'] is None: php_path = module.get_bin_path("php", True, ["/usr/local/bin"]) @@ -188,7 +194,7 @@ def composer_command(module, command, arguments="", options=None): else: composer_path = module.params['composer_executable'] - cmd = [php_path, composer_path, "global" if global_command else "", command] + options + [arguments] + cmd = [php_path, composer_path] + global_arg + command + options + arguments return module.run_command(cmd) @@ -220,7 +226,7 @@ def main(): if re.search(r"\s", command): module.fail_json(msg="Use the 'arguments' param for passing arguments with the 'command'") - arguments = module.params['arguments'] + arguments = shlex.split(module.params['arguments']) available_options = get_available_options(module=module, command=command) options = [] @@ -260,7 +266,7 @@ def main(): else: module.exit_json(skipped=True, msg="command '%s' does not support check mode, skipping" % command) - rc, out, err = composer_command(module, command, arguments, options) + rc, out, err = composer_command(module, [command], arguments, options) if rc != 0: output = parse_out(err) diff --git a/tests/unit/plugins/modules/test_composer.py b/tests/unit/plugins/modules/test_composer.py new file mode 100644 index 0000000000..efcae50d5e --- /dev/null +++ b/tests/unit/plugins/modules/test_composer.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Author: Alexei Znamensky (russoz@gmail.com) +# +# Copyright (c) Alexei Znamensky (russoz@gmail.com) +# +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +from ansible_collections.community.general.plugins.modules import composer +from .uthelper import UTHelper, RunCommandMock + + +UTHelper.from_module(composer, __name__, mocks=[RunCommandMock]) diff --git a/tests/unit/plugins/modules/test_composer.yaml b/tests/unit/plugins/modules/test_composer.yaml new file mode 100644 index 0000000000..2e509ba733 --- /dev/null +++ b/tests/unit/plugins/modules/test_composer.yaml @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Alexei Znamensky (russoz@gmail.com) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +--- +anchors: + environ_true: &env-def-true {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true} + environ_false: &env-def-false {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false} +test_cases: + - id: composer + input: + command: install + working_dir: "/var/www/foo" + output: + changed: true + mocks: + run_command: + - command: ["/testbin/php", "/testbin/composer", "help", "install", "--working-dir", "/var/www/foo", "--no-interaction", "--format=json"] + rc: 0 + out: | + {"definition": {"options": ["a", "b", "c"]}} + err: '' + - command: ["/testbin/php", "/testbin/composer", "install", "--working-dir", "/var/www/foo"] + rc: 0 + out: '' + err: '' diff --git a/tests/unit/plugins/modules/uthelper.py b/tests/unit/plugins/modules/uthelper.py index 7aea76581b..5fef13e8de 100644 --- a/tests/unit/plugins/modules/uthelper.py +++ b/tests/unit/plugins/modules/uthelper.py @@ -253,7 +253,7 @@ class RunCommandMock(TestCaseMock): def check(self, test_case, results): call_args_list = [(item[0][0], item[1]) for item in self.mock_run_cmd.call_args_list] - expected_call_args_list = [(item['command'], item['environ']) for item in self.mock_specs] + expected_call_args_list = [(item['command'], item.get('environ', {})) for item in self.mock_specs] print("call args list =\n%s" % call_args_list) print("expected args list =\n%s" % expected_call_args_list)