Skip to content

Commit

Permalink
Properly supports ansible-core >= 2.15 and Python 3.12
Browse files Browse the repository at this point in the history
  • Loading branch information
Daverball committed Aug 28, 2024
1 parent 36f7337 commit 4637986
Show file tree
Hide file tree
Showing 9 changed files with 60 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, '3.10', '3.11']
python-version: [3.8, 3.9, '3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/python-tox.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, '3.10', '3.11']
python-version: [3.8, 3.9, '3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Compatibility
-------------

* Python 3.8+
* Ansible 2.8+
* Ansible 6+
* Mitogen 0.3.7+

Support for older releases is kept only if possible. New Ansible releases
Expand Down
27 changes: 25 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ build-backend = "setuptools.build_meta"
[tool.pytest.ini_options]
log_level = "INFO"
testpaths = ["tests"]
filterwarnings = [
"ignore:The _yaml extension module is now located at yaml._yaml",
]

[tool.coverage.run]
branch = true
Expand All @@ -29,21 +32,41 @@ warn_return_any = false
[tool.tox]
legacy_tox_ini = """
[tox]
envlist = py38,py39,py310,py311,flake8,bandit,mypy,report
envlist =
py{38,39,310}-ansible6
py{39,310,311}-ansible7
py{39,310,311}-ansible8
py{39,310,311,312}-ansible9
py{310,311,312}-ansible10
flake8
bandit
mypy
report
[gh-actions]
python =
3.8: py38
3.9: py39
3.10: py310,flake8,bandit,mypy
3.11: py311
3.12: py312
[testenv]
usedevelop = true
setenv =
py{38,39,310,311}: COVERAGE_FILE = .coverage.{envname}
py{38,39,310,311,312}: COVERAGE_FILE = .coverage.{envname}
deps =
-e{toxinidir}[tests]
ansible6: ansible==6.*
ansible6: ansible-core==2.13.*
ansible7: ansible==7.*
ansible7: ansible-core==2.14.*
ansible8: ansible==8.*
ansible8: ansible-core==2.15.*
ansible9: ansible==9.*
ansible9: ansible-core==2.16.*
ansible10: ansible==10.*
ansible10: ansible-core==2.17.*
commands = pytest --cov --cov-report= {posargs}
Expand Down
6 changes: 3 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ classifiers =
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Programming Language :: Python :: Implementation :: CPython
Topic :: Software Development :: Libraries :: Python Modules

Expand All @@ -36,9 +37,8 @@ packages =
python_requires = >= 3.8
platforms = any
install_requires =
ansible>=2.8
ansible-core
ansible-base
ansible>=6
ansible-core>=2.13

[options.extras_require]
dev =
Expand Down
9 changes: 2 additions & 7 deletions src/suitable/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import __main__

from ansible.utils.display import Display # type:ignore[import-untyped]
__main__.display = Display()

from suitable.api import Api # noqa: E402
from suitable.api import install_strategy_plugins # noqa: E402
from suitable.api import Api
from suitable.api import install_strategy_plugins

__all__ = ('Api', 'install_strategy_plugins')
24 changes: 21 additions & 3 deletions src/suitable/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import logging
import os

from ansible import constants as C # type:ignore[import-untyped]
from ansible import __version__, constants # type:ignore[import-untyped]
from ansible.plugins.loader import module_loader # type:ignore[import-untyped]
from ansible.plugins.loader import strategy_loader
from contextlib import contextmanager
from packaging.version import Version
from suitable._modules import AnsibleModules
from suitable.errors import UnreachableError, ModuleError
from suitable.module_runner import ModuleRunner
Expand All @@ -16,7 +17,7 @@

if TYPE_CHECKING:
from _typeshed import StrPath
from collections.abc import Callable, Generator, Iterable
from collections.abc import Callable, Generator, Iterable, Sequence
from suitable.runner_results import RunnerResults
from suitable.types import Incomplete, ResultData, Verbosity

Expand Down Expand Up @@ -50,6 +51,7 @@ def __init__(
verbosity: Verbosity = 'info',
environment: dict[str, str] | None = None,
strategy: Incomplete | None = None,
collections_path: str | Sequence[str] | None = None,
**options: Incomplete
):
"""
Expand Down Expand Up @@ -130,6 +132,12 @@ def __init__(
:meth:`install_strategy_plugins` before using strategies provided
by plugins.
:param collections_path:
Provide a custom path or sequence of path to look for ansible
collections when loading/hooking the modules.
Requires ansible-core >= 2.15
:param host_key_checking:
Set to false to disable host key checking.
Expand Down Expand Up @@ -187,7 +195,8 @@ def __init__(
for default in required_defaults:
if default not in options:
options[default] = getattr(
C, 'DEFAULT_{}'.format(default.upper())
constants,
f'DEFAULT_{default.upper()}'
)

# unfortunately, not all options seem to have accessible defaults
Expand Down Expand Up @@ -222,6 +231,15 @@ def __init__(
self.environment = environment or {}
self.strategy = strategy

if collections_path is None:
collections_path = []
elif isinstance(collections_path, str):
collections_path = [collections_path]

if Version(__version__) >= Version('2.15'):
from ansible.plugins.loader import init_plugin_loader
init_plugin_loader(collections_path)

for module in list_ansible_modules():
ModuleRunner(module).hookup(self)

Expand Down
12 changes: 4 additions & 8 deletions src/suitable/module_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import signal
import sys

from __main__ import display
from ansible.executor.task_queue_manager import TaskQueueManager # type:ignore
from ansible.parsing.dataloader import DataLoader # type:ignore
from ansible.inventory.manager import InventoryManager # type:ignore
from ansible.playbook.play import Play # type:ignore[import-untyped]
from ansible.utils.display import Display # type:ignore[import-untyped]
from ansible.vars.manager import VariableManager # type:ignore[import-untyped]
from contextlib import contextmanager
from datetime import datetime
Expand Down Expand Up @@ -46,6 +46,7 @@ def ansible_verbosity(verbosity: int) -> Generator[None, None, None]:
To be sure, import suitable before importing ansible. ansible.
"""
display = Display()
previous = display.verbosity
display.verbosity = verbosity
yield
Expand Down Expand Up @@ -194,7 +195,7 @@ def execute(self, *args: Any, **kwargs: Any) -> RunnerResults:
loader=loader, inventory=inventory_manager)

play_source = {
'name': "Suitable Play",
'name': 'Suitable Play',
'hosts': 'all',
'gather_facts': 'no',
'tasks': [{
Expand All @@ -220,12 +221,7 @@ def execute(self, *args: Any, **kwargs: Any) -> RunnerResults:
if self.api.strategy:
play.strategy = self.api.strategy

log.info(
u'running {}'.format(u'- {module_name}: {module_args}'.format(
module_name=self.module_name,
module_args=module_args
))
)
log.info(f'running - {self.module_name}: {module_args}')

# ansible uses various levels of verbosity (from -v to -vvvvvv)
# offering various amounts of debug information
Expand Down
4 changes: 2 additions & 2 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import gc
import os
import os.path
from crypt import crypt
from secrets import token_hex

import pytest
from ansible.utils.display import Display
Expand Down Expand Up @@ -292,7 +292,7 @@ def test_enable_hostkey_checking_vanilla(container):
@pytest.mark.skip()
def test_interleaving(container):
# make sure we can interleave calls of different API objects
password = crypt("foobar", "salt")
password = token_hex(16)

root = container.vanilla_api(connection='paramiko')
root.host_key_checking = False
Expand Down

0 comments on commit 4637986

Please sign in to comment.