Aurelio Jargas 34c30b9fa5 Add role: ensure-python-command, refactor similar roles
This role ensures that a specific pip-installable command is
available.

Example usage:

    - role: ensure-python-command
      vars:
        ensure_python_command_name: poetry
        ensure_python_command_version: ==1.8.5  # omit to install latest

In this case, if the `poetry` command is not already available, pip will
install it in a new venv. Either way, after running this role, the
`ensure_python_command_executable` variable will hold the full path to
the command.

We already have similar roles for specific commands:

- ensure-nox
- ensure-poetry
- ensure-pyproject-build
- ensure-tox
- ensure-twine
- ensure-uv

These roles are essentially copies of each other with different command
names. This new role consolidates that code. The existing roles now act
as wrappers that just set variables and call the new role.

> Note: The `ensure-tox` role has not been refactored due to exclusive
> legacy code related to Python 2, which must be removed first.

The new role introduces three variables to replace the overloaded
`ensure_<command>_executable` variable from the other roles:

- `ensure_python_command_name` (input, command name)
- `ensure_python_command_existing` (input, existing path for the command)
- `ensure_python_command_executable` (output, detected/installed path)

This separation avoids using the same variable as both input and output,
which can cause issues due to Ansible's variable precedence rules:

    https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html

    Understanding variable precedence

    ...
    19. set_facts / registered vars
    20. role (and include_role) params
    ...

Since we use `set_fact` inside the role, it is ineffective when the same
variable is also passed as a role parameter :/

I'm not adding tests for the new role because its functionality is
already covered by the existing tests for all the refactored roles:

- test-playbooks/ensure-nox.yaml
- test-playbooks/ensure-poetry.yaml
- test-playbooks/ensure-pyproject-build.yaml
- test-playbooks/ensure-twine.yaml
- test-playbooks/ensure-uv.yaml

Change-Id: Idd970cb31bd928576bca3602ce96fbc491ecdb60
2025-03-25 22:06:34 +01:00

72 lines
2.4 KiB
ReStructuredText

Ensure a pip-installed command is available
This role checks for the specified command, and if not found, installs
it via ``pip`` into a virtual environment for the current user.
The minimal required input is the command name. Additionally, you can
specify a version or a path to a local venv, among other things.
Example:
.. code-block:: yaml
- role: ensure-python-command
vars:
ensure_python_command_name: poetry
ensure_python_command_version: ==1.8.5 # omit to install latest
In this case, if the ``poetry`` command is not already available, pip
will install it in a new venv. Either way, after running this role, the
``ensure_python_command_executable`` variable will hold the full path to
the command.
**Role Variables**
.. zuul:rolevar:: ensure_python_command_name
Required. The name of the command to ensure is available.
.. zuul:rolevar:: ensure_python_command_package
:default: {{ ensure_python_command_name }}
The name of the Python package that provides the desired command.
Defaults to the command name, since this is usually the case.
Set this variable when they differ.
.. zuul:rolevar:: ensure_python_command_version
:default: ''
The version specifier to select the version of the package to install.
If omitted, the latest version will be installed.
.. zuul:rolevar:: ensure_python_command_existing
:default: ''
Look for an existing command at this specific path. For example, if your base
image pre-installs the command in an out-of-path environment, set this so the
role does not attempt to install the command again.
.. zuul:rolevar:: ensure_python_command_venv_path
:default: {{ ansible_user_dir }}/.local/{{ ensure_python_command_package }}
Directory for the Python venv where the package should be installed.
.. zuul:rolevar:: ensure_python_command_global_symlink
:default: False
Install a symlink to the command executable into ``/usr/local/bin/``.
This can be useful when scripts need to be run that expect to find the
command in a more standard location and plumbing through the value
of ``ensure_python_command_executable`` would be onerous.
Setting this requires root access, so should only be done in
circumstances where root access is available.
**Output Variables**
.. zuul:rolevar:: ensure_python_command_executable
The full path to the command executable, whether it was detected or
installed by the role.