Merge "Add role: ensure-python-command, refactor similar roles"

This commit is contained in:
Zuul 2025-04-08 17:37:56 +00:00 committed by Gerrit Code Review
commit 35410214c5
16 changed files with 199 additions and 195 deletions

View File

@ -7,6 +7,7 @@ Python Roles
.. zuul:autorole:: ensure-if-python
.. zuul:autorole:: ensure-nox
.. zuul:autorole:: ensure-pip
.. zuul:autorole:: ensure-python-command
.. zuul:autorole:: ensure-poetry
.. zuul:autorole:: ensure-pyproject-build
.. zuul:autorole:: ensure-python

View File

@ -1,3 +1,2 @@
nox_executable: nox
ensure_nox_version: ''
nox_venv_path: '{{ ansible_user_dir }}/.local/nox'

View File

@ -1,34 +1,14 @@
- name: Install pip
- name: Check and install nox if necessary
include_role:
name: ensure-pip
name: ensure-python-command
vars:
ensure_python_command_name: nox
ensure_python_command_version: "{{ ensure_nox_version }}"
ensure_python_command_existing: "{{ nox_executable | default('') }}"
ensure_python_command_venv_path: "{{ nox_venv_path }}"
ensure_python_command_global_symlink: false # not supported
- name: Check if nox is installed
shell: |
command -v {{ nox_executable }} {{ nox_venv_path }}/bin/nox || exit 1
args:
executable: /bin/bash
register: nox_preinstalled
failed_when: false
- name: Export preinstalled nox_exectuable
- name: Export nox_executable path
set_fact:
nox_executable: '{{ nox_preinstalled.stdout_lines[0] }}'
nox_executable: "{{ ensure_python_command_executable }}"
cacheable: true
when: nox_preinstalled.rc == 0
- name: Install nox to local env
when: nox_preinstalled.rc != 0
block:
- name: Create local venv
command: '{{ ensure_pip_virtualenv_command }} {{ nox_venv_path }}'
- name: Install nox to local venv
command: '{{ nox_venv_path }}/bin/pip install nox{{ ensure_nox_version }}'
- name: Export installed nox_executable path
set_fact:
nox_executable: '{{ nox_venv_path }}/bin/nox'
cacheable: true
- name: Output nox version
command: "{{ nox_executable }} --version"

View File

@ -1,4 +1,3 @@
ensure_poetry_global_symlink: false
ensure_poetry_version: ""
ensure_poetry_executable: poetry
ensure_poetry_venv_path: "{{ ansible_user_dir }}/.local/poetry"

View File

@ -1,44 +1,14 @@
- name: Install pip
- name: Check and install poetry if necessary
include_role:
name: ensure-pip
name: ensure-python-command
vars:
ensure_python_command_name: poetry
ensure_python_command_version: "{{ ensure_poetry_version }}"
ensure_python_command_existing: "{{ ensure_poetry_executable | default('') }}"
ensure_python_command_venv_path: "{{ ensure_poetry_venv_path }}"
ensure_python_command_global_symlink: "{{ ensure_poetry_global_symlink }}"
- name: Check if poetry is installed
shell: |
command -v {{ ensure_poetry_executable }} {{ ensure_poetry_venv_path }}/bin/poetry || exit 1
args:
executable: /bin/bash
register: poetry_preinstalled
failed_when: false
- name: Export preinstalled ensure_poetry_executable
- name: Export ensure_poetry_executable path
set_fact:
ensure_poetry_executable: "{{ poetry_preinstalled.stdout_lines[0] }}"
ensure_poetry_executable: "{{ ensure_python_command_executable }}"
cacheable: true
when: poetry_preinstalled.rc == 0
- name: Install poetry to local env
when: poetry_preinstalled.rc != 0
block:
- name: Create local venv
command: "{{ ensure_pip_virtualenv_command }} {{ ensure_poetry_venv_path }}"
- name: Install poetry to local venv
command: "{{ ensure_poetry_venv_path }}/bin/pip install poetry{{ ensure_poetry_version }}"
- name: Export installed ensure_poetry_executable path
set_fact:
ensure_poetry_executable: "{{ ensure_poetry_venv_path }}/bin/poetry"
cacheable: true
- name: Output poetry version
command: "{{ ensure_poetry_executable }} --version"
- name: Make global symlink
when:
- ensure_poetry_global_symlink
- ensure_poetry_executable != '/usr/local/bin/poetry'
file:
state: link
src: "{{ ensure_poetry_executable }}"
dest: /usr/local/bin/poetry
become: yes

View File

@ -1,4 +1,3 @@
ensure_pyproject_build_global_symlink: false
ensure_pyproject_build_version: ""
ensure_pyproject_build_executable: pyproject-build
ensure_pyproject_build_venv_path: "{{ ansible_user_dir }}/.local/pyproject-build"

View File

@ -1,44 +1,15 @@
- name: Install pip
- name: Check and install pyproject-build if necessary
include_role:
name: ensure-pip
name: ensure-python-command
vars:
ensure_python_command_name: pyproject-build
ensure_python_command_package: build
ensure_python_command_version: "{{ ensure_pyproject_build_version }}"
ensure_python_command_existing: "{{ ensure_pyproject_build_executable | default('') }}"
ensure_python_command_venv_path: "{{ ensure_pyproject_build_venv_path }}"
ensure_python_command_global_symlink: "{{ ensure_pyproject_build_global_symlink }}"
- name: Check if pyproject-build is installed
shell: |
command -v {{ ensure_pyproject_build_executable }} {{ ensure_pyproject_build_venv_path }}/bin/pyproject-build || exit 1
args:
executable: /bin/bash
register: pyproject_build_preinstalled
failed_when: false
- name: Export preinstalled ensure_pyproject_build_executable
- name: Export ensure_pyproject_build_executable path
set_fact:
ensure_pyproject_build_executable: "{{ pyproject_build_preinstalled.stdout_lines[0] }}"
ensure_pyproject_build_executable: "{{ ensure_python_command_executable }}"
cacheable: true
when: pyproject_build_preinstalled.rc == 0
- name: Install pyproject-build to local env
when: pyproject_build_preinstalled.rc != 0
block:
- name: Create local venv
command: "{{ ensure_pip_virtualenv_command }} {{ ensure_pyproject_build_venv_path }}"
- name: Install pyproject-build to local venv
command: "{{ ensure_pyproject_build_venv_path }}/bin/pip install build{{ ensure_pyproject_build_version }}"
- name: Export installed ensure_pyproject_build_executable path
set_fact:
ensure_pyproject_build_executable: "{{ ensure_pyproject_build_venv_path }}/bin/pyproject-build"
cacheable: true
- name: Output pyproject-build version
command: "{{ ensure_pyproject_build_executable }} --version"
- name: Make global symlink
when:
- ensure_pyproject_build_global_symlink
- ensure_pyproject_build_executable != '/usr/local/bin/pyproject-build'
file:
state: link
src: "{{ ensure_pyproject_build_executable }}"
dest: /usr/local/bin/pyproject-build
become: yes

View File

@ -0,0 +1,71 @@
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.

View File

@ -0,0 +1,5 @@
ensure_python_command_global_symlink: false
ensure_python_command_version: ""
ensure_python_command_existing: ""
ensure_python_command_package: "{{ ensure_python_command_name }}"
ensure_python_command_venv_path: "{{ ansible_user_dir }}/.local/{{ ensure_python_command_package }}"

View File

@ -0,0 +1,64 @@
- name: Install pip
include_role:
name: ensure-pip
- name: Check if the command name is set and non-empty
fail:
msg: Required variable ensure_python_command_name is not set or empty
when: ensure_python_command_name is not defined or not ensure_python_command_name.strip()
# Part 1
# Check if the command is already available. If not, pip-install it to a local venv.
# Either way, save the command's full path to the output variable.
- name: Check if {{ ensure_python_command_name }} is available
# 1. Full path optionally informed by the role caller
# 2. Ansible user $PATH (bare command name)
# 3. Local venv (installed from a previous run for this role)
shell: |
command -v \
{{ ensure_python_command_existing }} \
{{ ensure_python_command_name | quote }} \
{{ ensure_python_command_venv_executable | quote }} \
|| exit 1
args:
executable: /bin/bash
register: ensure_python_command_preinstalled
failed_when: false
- name: Export preinstalled ensure_python_command_executable
set_fact:
ensure_python_command_executable: "{{ ensure_python_command_preinstalled.stdout_lines[0] }}"
cacheable: true
when: ensure_python_command_preinstalled.rc == 0
- name: Install {{ ensure_python_command_package }} to local env
when: ensure_python_command_preinstalled.rc != 0
block:
- name: Create local venv
command: "{{ ensure_pip_virtualenv_command }} {{ ensure_python_command_venv_path }}"
- name: Install {{ ensure_python_command_package }} to local venv
command: "{{ ensure_python_command_venv_path }}/bin/pip install {{ ensure_python_command_package }}{{ ensure_python_command_version }}"
- name: Export installed ensure_python_command_executable path
set_fact:
ensure_python_command_executable: "{{ ensure_python_command_venv_executable }}"
cacheable: true
# Part 2
# Try to show the command's version and maybe also create a symlink to it in /usr/local/bin
- name: Output {{ ensure_python_command_name }} version
command: "{{ ensure_python_command_executable }} --version"
failed_when: false
- name: Make global symlink
when:
- ensure_python_command_global_symlink
- ensure_python_command_executable != ensure_python_command_global_symlink_path
file:
state: link
src: "{{ ensure_python_command_executable }}"
dest: "{{ ensure_python_command_global_symlink_path }}"
become: yes

View File

@ -0,0 +1,2 @@
ensure_python_command_global_symlink_path: /usr/local/bin/{{ ensure_python_command_name }}
ensure_python_command_venv_executable: "{{ ensure_python_command_venv_path }}/bin/{{ ensure_python_command_name }}"

View File

@ -1,5 +1,4 @@
ensure_twine_global_symlink: false
# version 6.1.0 is breaking test-release-openstack CI job
ensure_twine_version: ">1.12.0,!=6.1.0"
pypi_twine_executable: twine
ensure_twine_venv_path: "{{ ansible_user_dir }}/.local/twine"

View File

@ -1,44 +1,14 @@
- name: Install pip
- name: Check and install twine if necessary
include_role:
name: ensure-pip
name: ensure-python-command
vars:
ensure_python_command_name: twine
ensure_python_command_version: "{{ ensure_twine_version }}"
ensure_python_command_existing: "{{ pypi_twine_executable | default('') }}"
ensure_python_command_venv_path: "{{ ensure_twine_venv_path }}"
ensure_python_command_global_symlink: "{{ ensure_twine_global_symlink }}"
- name: Check if twine is installed
shell: |
command -v {{ pypi_twine_executable }} {{ ensure_twine_venv_path }}/bin/twine || exit 1
args:
executable: /bin/bash
register: twine_preinstalled
failed_when: false
- name: Export preinstalled pypi_twine_executable
- name: Export pypi_twine_executable path
set_fact:
pypi_twine_executable: "{{ twine_preinstalled.stdout_lines[0] }}"
pypi_twine_executable: "{{ ensure_python_command_executable }}"
cacheable: true
when: twine_preinstalled.rc == 0
- name: Install twine to local env
when: twine_preinstalled.rc != 0
block:
- name: Create local venv
command: "{{ ensure_pip_virtualenv_command }} {{ ensure_twine_venv_path }}"
- name: Install twine to local venv
command: "{{ ensure_twine_venv_path }}/bin/pip install twine{{ ensure_twine_version }}"
- name: Export installed pypi_twine_executable path
set_fact:
pypi_twine_executable: "{{ ensure_twine_venv_path }}/bin/twine"
cacheable: true
- name: Output twine version
command: "{{ pypi_twine_executable }} --version"
- name: Make global symlink
when:
- ensure_twine_global_symlink
- pypi_twine_executable != '/usr/local/bin/twine'
file:
state: link
src: "{{ pypi_twine_executable }}"
dest: /usr/local/bin/twine
become: yes

View File

@ -1,4 +1,3 @@
ensure_uv_global_symlink: false
ensure_uv_version: ""
ensure_uv_executable: uv
ensure_uv_venv_path: "{{ ansible_user_dir }}/.local/uv"

View File

@ -1,44 +1,14 @@
- name: Install pip
- name: Check and install uv if necessary
include_role:
name: ensure-pip
name: ensure-python-command
vars:
ensure_python_command_name: uv
ensure_python_command_version: "{{ ensure_uv_version }}"
ensure_python_command_existing: "{{ ensure_uv_executable | default('') }}"
ensure_python_command_venv_path: "{{ ensure_uv_venv_path }}"
ensure_python_command_global_symlink: "{{ ensure_uv_global_symlink }}"
- name: Check if uv is installed
shell: |
command -v {{ ensure_uv_executable }} {{ ensure_uv_venv_path }}/bin/uv || exit 1
args:
executable: /bin/bash
register: uv_preinstalled
failed_when: false
- name: Export preinstalled ensure_uv_executable
- name: Export ensure_uv_executable path
set_fact:
ensure_uv_executable: "{{ uv_preinstalled.stdout_lines[0] }}"
ensure_uv_executable: "{{ ensure_python_command_executable }}"
cacheable: true
when: uv_preinstalled.rc == 0
- name: Install uv to local env
when: uv_preinstalled.rc != 0
block:
- name: Create local venv
command: "{{ ensure_pip_virtualenv_command }} {{ ensure_uv_venv_path }}"
- name: Install uv to local venv
command: "{{ ensure_uv_venv_path }}/bin/pip install uv{{ ensure_uv_version }}"
- name: Export installed ensure_uv_executable path
set_fact:
ensure_uv_executable: "{{ ensure_uv_venv_path }}/bin/uv"
cacheable: true
- name: Output uv version
command: "{{ ensure_uv_executable }} --version"
- name: Make global symlink
when:
- ensure_uv_global_symlink
- ensure_uv_executable != '/usr/local/bin/uv'
file:
state: link
src: "{{ ensure_uv_executable }}"
dest: /usr/local/bin/uv
become: yes

View File

@ -3,6 +3,7 @@
description: Test the ensure-nox role
files:
- roles/ensure-nox/.*
- roles/ensure-python-command/.*
- test-playbooks/ensure-nox.yaml
run: test-playbooks/ensure-nox.yaml
tags: all-platforms
@ -149,6 +150,7 @@
description: Test the ensure-poetry role
files:
- roles/ensure-poetry/.*
- roles/ensure-python-command/.*
- test-playbooks/ensure-poetry.yaml
run: test-playbooks/ensure-poetry.yaml
tags: all-platforms
@ -218,6 +220,7 @@
description: Test the ensure-pyproject-build role
files:
- roles/ensure-pyproject-build/.*
- roles/ensure-python-command/.*
- test-playbooks/ensure-pyproject-build.yaml
run: test-playbooks/ensure-pyproject-build.yaml
tags: all-platforms
@ -287,6 +290,7 @@
description: Test the ensure-twine role
files:
- roles/ensure-twine/.*
- roles/ensure-python-command/.*
- test-playbooks/ensure-twine.yaml
run: test-playbooks/ensure-twine.yaml
tags: all-platforms
@ -434,6 +438,7 @@
description: Test the ensure-uv role
files:
- roles/ensure-uv/.*
- roles/ensure-python-command/.*
- test-playbooks/ensure-uv.yaml
run: test-playbooks/ensure-uv.yaml
tags: all-platforms