1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00
community.general/plugins/modules/packaging/language/composer.py
George Angelopoulos eb455c69a2
composer: --no-interaction when discovering available options (#2348)
The composer module always uses the no-interaction option if it
discovers it _after_ calling "composer help ..." but not on the help
call itself. The lack of this option caused composer to not exit when
called through the ansible module.

The same example command when ran interactively does not prompt for user
interaction and exits immediately. It is therefore currently unknown why
the same command hangs when called through the ansible composer module
or even directly with the command module.

Example command which hangs:
php /usr/local/bin/composer help install --format=json
2021-05-01 18:23:14 +02:00

300 lines
12 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2014, Dimitrios Tydeas Mengidis <tydeas.dr@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: composer
author:
- "Dimitrios Tydeas Mengidis (@dmtrs)"
- "René Moser (@resmo)"
short_description: Dependency Manager for PHP
description:
- >
Composer is a tool for dependency management in PHP. It allows you to
declare the dependent libraries your project needs and it will install
them in your project for you.
options:
command:
type: str
description:
- Composer command like "install", "update" and so on.
default: install
arguments:
type: str
description:
- Composer arguments like required package, version and so on.
executable:
type: path
description:
- Path to PHP Executable on the remote host, if PHP is not in PATH.
aliases: [ php_path ]
working_dir:
type: path
description:
- Directory of your project (see --working-dir). This is required when
the command is not run globally.
- Will be ignored if C(global_command=true).
- Alias C(working-dir) has been deprecated and will be removed in community.general 5.0.0.
aliases: [ working-dir ]
global_command:
description:
- Runs the specified command globally.
- Alias C(global-command) has been deprecated and will be removed in community.general 5.0.0.
type: bool
default: false
aliases: [ global-command ]
prefer_source:
description:
- Forces installation from package sources when possible (see --prefer-source).
- Alias C(prefer-source) has been deprecated and will be removed in community.general 5.0.0.
default: false
type: bool
aliases: [ prefer-source ]
prefer_dist:
description:
- Forces installation from package dist even for dev versions (see --prefer-dist).
- Alias C(prefer-dist) has been deprecated and will be removed in community.general 5.0.0.
default: false
type: bool
aliases: [ prefer-dist ]
no_dev:
description:
- Disables installation of require-dev packages (see --no-dev).
- Alias C(no-dev) has been deprecated and will be removed in community.general 5.0.0.
default: true
type: bool
aliases: [ no-dev ]
no_scripts:
description:
- Skips the execution of all scripts defined in composer.json (see --no-scripts).
- Alias C(no-scripts) has been deprecated and will be removed in community.general 5.0.0.
default: false
type: bool
aliases: [ no-scripts ]
no_plugins:
description:
- Disables all plugins ( see --no-plugins ).
- Alias C(no-plugins) has been deprecated and will be removed in community.general 5.0.0.
default: false
type: bool
aliases: [ no-plugins ]
optimize_autoloader:
description:
- Optimize autoloader during autoloader dump (see --optimize-autoloader).
- Convert PSR-0/4 autoloading to classmap to get a faster autoloader.
- Recommended especially for production, but can take a bit of time to run.
- Alias C(optimize-autoloader) has been deprecated and will be removed in community.general 5.0.0.
default: true
type: bool
aliases: [ optimize-autoloader ]
classmap_authoritative:
description:
- Autoload classes from classmap only.
- Implicitely enable optimize_autoloader.
- Recommended especially for production, but can take a bit of time to run.
- Alias C(classmap-authoritative) has been deprecated and will be removed in community.general 5.0.0.
default: false
type: bool
aliases: [ classmap-authoritative ]
apcu_autoloader:
description:
- Uses APCu to cache found/not-found classes
- Alias C(apcu-autoloader) has been deprecated and will be removed in community.general 5.0.0.
default: false
type: bool
aliases: [ apcu-autoloader ]
ignore_platform_reqs:
description:
- Ignore php, hhvm, lib-* and ext-* requirements and force the installation even if the local machine does not fulfill these.
- Alias C(ignore-platform-reqs) has been deprecated and will be removed in community.general 5.0.0.
default: false
type: bool
aliases: [ ignore-platform-reqs ]
requirements:
- php
- composer installed in bin path (recommended /usr/local/bin)
notes:
- Default options that are always appended in each execution are --no-ansi, --no-interaction and --no-progress if available.
- We received reports about issues on macOS if composer was installed by Homebrew. Please use the official install method to avoid issues.
'''
EXAMPLES = '''
- name: Download and installs all libs and dependencies outlined in the /path/to/project/composer.lock
community.general.composer:
command: install
working_dir: /path/to/project
- name: Install a new package
community.general.composer:
command: require
arguments: my/package
working_dir: /path/to/project
- name: Clone and install a project with all dependencies
community.general.composer:
command: create-project
arguments: package/package /path/to/project ~1.0
working_dir: /path/to/project
prefer_dist: yes
- name: Install a package globally
community.general.composer:
command: require
global_command: yes
arguments: my/package
'''
import re
from ansible.module_utils.basic import AnsibleModule
def parse_out(string):
return re.sub(r"\s+", " ", string).strip()
def has_changed(string):
for no_change in ["Nothing to install or update", "Nothing to install, update or remove"]:
if no_change in string:
return False
return True
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")
if rc != 0:
output = parse_out(err)
module.fail_json(msg=output)
command_help_json = module.from_json(out)
return command_help_json['definition']['options']
def composer_command(module, command, arguments="", options=None, global_command=False):
if options is None:
options = []
if module.params['executable'] is None:
php_path = module.get_bin_path("php", True, ["/usr/local/bin"])
else:
php_path = module.params['executable']
composer_path = module.get_bin_path("composer", True, ["/usr/local/bin"])
cmd = "%s %s %s %s %s %s" % (php_path, composer_path, "global" if global_command else "", command, " ".join(options), arguments)
return module.run_command(cmd)
def main():
module = AnsibleModule(
argument_spec=dict(
command=dict(default="install", type="str"),
arguments=dict(default="", type="str"),
executable=dict(type="path", aliases=["php_path"]),
working_dir=dict(
type="path", aliases=["working-dir"],
deprecated_aliases=[dict(name='working-dir', version='5.0.0', collection_name='community.general')]),
global_command=dict(
default=False, type="bool", aliases=["global-command"],
deprecated_aliases=[dict(name='global-command', version='5.0.0', collection_name='community.general')]),
prefer_source=dict(
default=False, type="bool", aliases=["prefer-source"],
deprecated_aliases=[dict(name='prefer-source', version='5.0.0', collection_name='community.general')]),
prefer_dist=dict(
default=False, type="bool", aliases=["prefer-dist"],
deprecated_aliases=[dict(name='prefer-dist', version='5.0.0', collection_name='community.general')]),
no_dev=dict(
default=True, type="bool", aliases=["no-dev"],
deprecated_aliases=[dict(name='no-dev', version='5.0.0', collection_name='community.general')]),
no_scripts=dict(
default=False, type="bool", aliases=["no-scripts"],
deprecated_aliases=[dict(name='no-scripts', version='5.0.0', collection_name='community.general')]),
no_plugins=dict(
default=False, type="bool", aliases=["no-plugins"],
deprecated_aliases=[dict(name='no-plugins', version='5.0.0', collection_name='community.general')]),
apcu_autoloader=dict(
default=False, type="bool", aliases=["apcu-autoloader"],
deprecated_aliases=[dict(name='apcu-autoloader', version='5.0.0', collection_name='community.general')]),
optimize_autoloader=dict(
default=True, type="bool", aliases=["optimize-autoloader"],
deprecated_aliases=[dict(name='optimize-autoloader', version='5.0.0', collection_name='community.general')]),
classmap_authoritative=dict(
default=False, type="bool", aliases=["classmap-authoritative"],
deprecated_aliases=[dict(name='classmap-authoritative', version='5.0.0', collection_name='community.general')]),
ignore_platform_reqs=dict(
default=False, type="bool", aliases=["ignore-platform-reqs"],
deprecated_aliases=[dict(name='ignore-platform-reqs', version='5.0.0', collection_name='community.general')]),
),
required_if=[('global_command', False, ['working_dir'])],
supports_check_mode=True
)
# Get composer command with fallback to default
command = module.params['command']
if re.search(r"\s", command):
module.fail_json(msg="Use the 'arguments' param for passing arguments with the 'command'")
arguments = module.params['arguments']
global_command = module.params['global_command']
available_options = get_available_options(module=module, command=command)
options = []
# Default options
default_options = [
'no-ansi',
'no-interaction',
'no-progress',
]
for option in default_options:
if option in available_options:
option = "--%s" % option
options.append(option)
if not global_command:
options.extend(['--working-dir', "'%s'" % module.params['working_dir']])
option_params = {
'prefer_source': 'prefer-source',
'prefer_dist': 'prefer-dist',
'no_dev': 'no-dev',
'no_scripts': 'no-scripts',
'no_plugins': 'no-plugins',
'apcu_autoloader': 'acpu-autoloader',
'optimize_autoloader': 'optimize-autoloader',
'classmap_authoritative': 'classmap-authoritative',
'ignore_platform_reqs': 'ignore-platform-reqs',
}
for param, option in option_params.items():
if module.params.get(param) and option in available_options:
option = "--%s" % option
options.append(option)
if module.check_mode:
if 'dry-run' in available_options:
options.append('--dry-run')
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, global_command)
if rc != 0:
output = parse_out(err)
module.fail_json(msg=output, stdout=err)
else:
# Composer version > 1.0.0-alpha9 now use stderr for standard notification messages
output = parse_out(out + err)
module.exit_json(changed=has_changed(output), msg=output, stdout=out + err)
if __name__ == '__main__':
main()