1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

[PR #8675/5b2711bb backport][stable-9] pipx - add suffix parameter (#8721)

pipx - add suffix parameter (#8675)

* initial commit

* add changelog frag

* Add idempotency when using suffix

(cherry picked from commit 5b2711bbd3)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
This commit is contained in:
patchback[bot] 2024-08-07 15:12:40 +02:00 committed by GitHub
parent 9842b057b0
commit 7c9dd8d8ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 71 additions and 15 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- pipx - add parameter ``suffix`` to module (https://github.com/ansible-collections/community.general/pull/8675, https://github.com/ansible-collections/community.general/issues/8656).

View file

@ -28,7 +28,6 @@ def pipx_runner(module, command, **kwargs):
module, module,
command=command, command=command,
arg_formats=dict( arg_formats=dict(
state=fmt.as_map(_state_map), state=fmt.as_map(_state_map),
name=fmt.as_list(), name=fmt.as_list(),
name_source=fmt.as_func(fmt.unpack_args(lambda n, s: [s] if s else [n])), name_source=fmt.as_func(fmt.unpack_args(lambda n, s: [s] if s else [n])),
@ -43,6 +42,7 @@ def pipx_runner(module, command, **kwargs):
_list=fmt.as_fixed(['list', '--include-injected', '--json']), _list=fmt.as_fixed(['list', '--include-injected', '--json']),
editable=fmt.as_bool("--editable"), editable=fmt.as_bool("--editable"),
pip_args=fmt.as_opt_eq_val('--pip-args'), pip_args=fmt.as_opt_eq_val('--pip-args'),
suffix=fmt.as_opt_val('--suffix'),
), ),
environ_update={'USE_EMOJI': '0'}, environ_update={'USE_EMOJI': '0'},
check_rc=True, check_rc=True,

View file

@ -114,14 +114,20 @@ options:
- Arbitrary arguments to pass directly to C(pip). - Arbitrary arguments to pass directly to C(pip).
type: str type: str
version_added: 4.6.0 version_added: 4.6.0
suffix:
description:
- Optional suffix for virtual environment and executable names.
- "B(Warning): C(pipx) documentation states this is an B(experimental) feature subject to change."
type: str
version_added: 9.3.0
notes: notes:
- This module requires C(pipx) version 0.16.2.1 or above.
- Please note that C(pipx) requires Python 3.6 or above.
- This module does not install the C(pipx) python package, however that can be easily done with the module M(ansible.builtin.pip). - This module does not install the C(pipx) python package, however that can be easily done with the module M(ansible.builtin.pip).
- This module does not require C(pipx) to be in the shell C(PATH), but it must be loadable by Python as a module. - This module does not require C(pipx) to be in the shell C(PATH), but it must be loadable by Python as a module.
- > - >
This module will honor C(pipx) environment variables such as but not limited to C(PIPX_HOME) and C(PIPX_BIN_DIR) This module will honor C(pipx) environment variables such as but not limited to C(PIPX_HOME) and C(PIPX_BIN_DIR)
passed using the R(environment Ansible keyword, playbooks_environment). passed using the R(environment Ansible keyword, playbooks_environment).
- This module requires C(pipx) version 0.16.2.1 or above.
- Please note that C(pipx) requires Python 3.6 or above.
- > - >
This first implementation does not verify whether a specified version constraint has been installed or not. This first implementation does not verify whether a specified version constraint has been installed or not.
Hence, when using version operators, C(pipx) module will always try to execute the operation, Hence, when using version operators, C(pipx) module will always try to execute the operation,
@ -168,6 +174,10 @@ from ansible_collections.community.general.plugins.module_utils.pipx import pipx
from ansible.module_utils.facts.compat import ansible_facts from ansible.module_utils.facts.compat import ansible_facts
def _make_name(name, suffix):
return name if suffix is None else "{0}{1}".format(name, suffix)
class PipX(StateModuleHelper): class PipX(StateModuleHelper):
output_params = ['name', 'source', 'index_url', 'force', 'installdeps'] output_params = ['name', 'source', 'index_url', 'force', 'installdeps']
module = dict( module = dict(
@ -188,6 +198,7 @@ class PipX(StateModuleHelper):
executable=dict(type='path'), executable=dict(type='path'),
editable=dict(type='bool', default=False), editable=dict(type='bool', default=False),
pip_args=dict(type='str'), pip_args=dict(type='str'),
suffix=dict(type='str'),
), ),
required_if=[ required_if=[
('state', 'present', ['name']), ('state', 'present', ['name']),
@ -199,6 +210,9 @@ class PipX(StateModuleHelper):
('state', 'latest', ['name']), ('state', 'latest', ['name']),
('state', 'inject', ['name', 'inject_packages']), ('state', 'inject', ['name', 'inject_packages']),
], ],
required_by=dict(
suffix="name",
),
supports_check_mode=True, supports_check_mode=True,
) )
use_old_vardict = False use_old_vardict = False
@ -222,9 +236,10 @@ class PipX(StateModuleHelper):
installed = self.runner('_list', output_process=process_list).run(_list=1) installed = self.runner('_list', output_process=process_list).run(_list=1)
if self.vars.name is not None: if self.vars.name is not None:
app_list = installed.get(self.vars.name) name = _make_name(self.vars.name, self.vars.suffix)
app_list = installed.get(name)
if app_list: if app_list:
return {self.vars.name: app_list} return {name: app_list}
else: else:
return {} return {}
@ -253,45 +268,50 @@ class PipX(StateModuleHelper):
def state_install(self): def state_install(self):
if not self.vars.application or self.vars.force: if not self.vars.application or self.vars.force:
self.changed = True self.changed = True
with self.runner('state index_url install_deps force python system_site_packages editable pip_args name_source', check_mode_skip=True) as ctx: args = 'state index_url install_deps force python system_site_packages editable pip_args suffix name_source'
with self.runner(args, check_mode_skip=True) as ctx:
ctx.run(name_source=[self.vars.name, self.vars.source]) ctx.run(name_source=[self.vars.name, self.vars.source])
self._capture_results(ctx) self._capture_results(ctx)
state_present = state_install state_present = state_install
def state_upgrade(self): def state_upgrade(self):
name = _make_name(self.vars.name, self.vars.suffix)
if not self.vars.application: if not self.vars.application:
self.do_raise("Trying to upgrade a non-existent application: {0}".format(self.vars.name)) self.do_raise("Trying to upgrade a non-existent application: {0}".format(name))
if self.vars.force: if self.vars.force:
self.changed = True self.changed = True
with self.runner('state include_injected index_url force editable pip_args name', check_mode_skip=True) as ctx: with self.runner('state include_injected index_url force editable pip_args name', check_mode_skip=True) as ctx:
ctx.run() ctx.run(name=name)
self._capture_results(ctx) self._capture_results(ctx)
def state_uninstall(self): def state_uninstall(self):
if self.vars.application: if self.vars.application:
name = _make_name(self.vars.name, self.vars.suffix)
with self.runner('state name', check_mode_skip=True) as ctx: with self.runner('state name', check_mode_skip=True) as ctx:
ctx.run() ctx.run(name=name)
self._capture_results(ctx) self._capture_results(ctx)
state_absent = state_uninstall state_absent = state_uninstall
def state_reinstall(self): def state_reinstall(self):
name = _make_name(self.vars.name, self.vars.suffix)
if not self.vars.application: if not self.vars.application:
self.do_raise("Trying to reinstall a non-existent application: {0}".format(self.vars.name)) self.do_raise("Trying to reinstall a non-existent application: {0}".format(name))
self.changed = True self.changed = True
with self.runner('state name python', check_mode_skip=True) as ctx: with self.runner('state name python', check_mode_skip=True) as ctx:
ctx.run() ctx.run(name=name)
self._capture_results(ctx) self._capture_results(ctx)
def state_inject(self): def state_inject(self):
name = _make_name(self.vars.name, self.vars.suffix)
if not self.vars.application: if not self.vars.application:
self.do_raise("Trying to inject packages into a non-existent application: {0}".format(self.vars.name)) self.do_raise("Trying to inject packages into a non-existent application: {0}".format(name))
if self.vars.force: if self.vars.force:
self.changed = True self.changed = True
with self.runner('state index_url install_apps install_deps force editable pip_args name inject_packages', check_mode_skip=True) as ctx: with self.runner('state index_url install_apps install_deps force editable pip_args name inject_packages', check_mode_skip=True) as ctx:
ctx.run() ctx.run(name=name)
self._capture_results(ctx) self._capture_results(ctx)
def state_uninstall_all(self): def state_uninstall_all(self):
@ -314,7 +334,8 @@ class PipX(StateModuleHelper):
def state_latest(self): def state_latest(self):
if not self.vars.application or self.vars.force: if not self.vars.application or self.vars.force:
self.changed = True self.changed = True
with self.runner('state index_url install_deps force python system_site_packages editable pip_args name_source', check_mode_skip=True) as ctx: args = 'state index_url install_deps force python system_site_packages editable pip_args suffix name_source'
with self.runner(args, check_mode_skip=True) as ctx:
ctx.run(state='install', name_source=[self.vars.name, self.vars.source]) ctx.run(state='install', name_source=[self.vars.name, self.vars.source])
self._capture_results(ctx) self._capture_results(ctx)

View file

@ -171,7 +171,7 @@
state: latest state: latest
register: install_tox_latest_with_preinstall_again register: install_tox_latest_with_preinstall_again
- name: install application latest tox - name: install application latest tox (force)
community.general.pipx: community.general.pipx:
name: tox name: tox
state: latest state: latest
@ -339,3 +339,36 @@
assert: assert:
that: that:
- install_pyinstaller is changed - install_pyinstaller is changed
##############################################################################
# Test for issue 8656
- name: ensure application conan2 is uninstalled
community.general.pipx:
name: conan2
state: absent
- name: Install Python Package conan with suffix 2 (conan2)
community.general.pipx:
name: conan
state: install
suffix: "2"
register: install_conan2
- name: Install Python Package conan with suffix 2 (conan2) again
community.general.pipx:
name: conan
state: install
suffix: "2"
register: install_conan2_again
- name: cleanup conan2
community.general.pipx:
name: conan2
state: absent
- name: check assertions
assert:
that:
- install_conan2 is changed
- "' - conan2' in install_conan2.stdout"
- install_conan2_again is not changed