Skip to content

Commit

Permalink
Enable access to available plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
ssbarnea committed May 24, 2023
1 parent 34c1459 commit 31cdff7
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,4 @@ dmypy.json
.pyre/
.test-results
*.lcov
ansible_collections
2 changes: 0 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
"[python]": {
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.fixAll.ruff": true,
"source.organizeImports": false,
"source.organizeImports.ruff": true
}
},
"editor.formatOnSave": true,
Expand Down
3 changes: 3 additions & 0 deletions ansible.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[defaults]
# isolate testing of ansible-compat from user local setup
collections_path = .
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ disable = [
"import-error",
# already covered by ruff which is faster
"too-many-arguments", # PLR0913
"raise-missing-from",
# Temporary disable duplicate detection we remove old code from prerun
"duplicate-code",
]
Expand Down
72 changes: 70 additions & 2 deletions src/ansible_compat/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@
import tempfile
import warnings
from collections import OrderedDict
from dataclasses import dataclass
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
from typing import TYPE_CHECKING, Any, Callable, Optional, Union, no_type_check

import subprocess_tee
from packaging.version import Version

from ansible_compat.config import (
AnsibleConfig,
ansible_collections_path,
ansible_version,
parse_ansible_version,
)
from ansible_compat.constants import (
Expand Down Expand Up @@ -73,6 +74,71 @@ def __init__(self, version: str) -> None:
super().__init__(version)


@dataclass
class Plugins: # pylint: disable=too-many-instance-attributes
"""Dataclass to access installed Ansible plugins, uses ansible-doc to retrieve them."""

runtime: "Runtime"
become: dict[str, str] = field(init=False)
cache: dict[str, str] = field(init=False)
callback: dict[str, str] = field(init=False)
cliconf: dict[str, str] = field(init=False)
connection: dict[str, str] = field(init=False)
httpapi: dict[str, str] = field(init=False)
inventory: dict[str, str] = field(init=False)
lookup: dict[str, str] = field(init=False)
netconf: dict[str, str] = field(init=False)
shell: dict[str, str] = field(init=False)
vars: dict[str, str] = field(init=False) # noqa: A003
module: dict[str, str] = field(init=False)
strategy: dict[str, str] = field(init=False)
test: dict[str, str] = field(init=False)
filter: dict[str, str] = field(init=False) # noqa: A003
role: dict[str, str] = field(init=False)
keyword: dict[str, str] = field(init=False)

@no_type_check
def __getattribute__(self, attr: str): # noqa: ANN204
"""Get attribute."""
if attr in {
"become",
"cache",
"callback",
"cliconf",
"connection",
"httpapi",
"inventory",
"lookup",
"netconf",
"shell",
"vars",
"module",
"strategy",
"test",
"filter",
"role",
"keyword",
}:
try:
result = super().__getattribute__(attr)
except AttributeError as exc:
if ansible_version() < Version("2.14") and attr in {"filter", "test"}:
msg = "Ansible version below 2.14 does not support retrieving filter and test plugins."
raise RuntimeError(msg) from exc
proc = self.runtime.run(
["ansible-doc", "--json", "-l", "-t", attr],
)
data = json.loads(proc.stdout)
if not isinstance(data, dict): # pragma: no cover
msg = "Unexpected output from ansible-doc"
raise AnsibleCompatError(msg) from exc
result = data
else:
result = super().__getattribute__(attr)

return result


# pylint: disable=too-many-instance-attributes
class Runtime:
"""Ansible Runtime manager."""
Expand All @@ -83,6 +149,7 @@ class Runtime:
# Used to track if we have already initialized the Ansible runtime as attempts
# to do it multiple tilmes will cause runtime warnings from within ansible-core
initialized: bool = False
plugins: Plugins

def __init__(
self,
Expand Down Expand Up @@ -119,6 +186,7 @@ def __init__(
self.isolated = isolated
self.max_retries = max_retries
self.environ = environ or os.environ.copy()
self.plugins = Plugins(runtime=self)
# Reduce noise from paramiko, unless user already defined PYTHONWARNINGS
# paramiko/transport.py:236: CryptographyDeprecationWarning: Blowfish has been deprecated
# https://github.com/paramiko/paramiko/issues/2038
Expand Down
42 changes: 42 additions & 0 deletions test/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from packaging.version import Version
from pytest_mock import MockerFixture

from ansible_compat.config import ansible_version
from ansible_compat.constants import INVALID_PREREQUISITES_RC
from ansible_compat.errors import (
AnsibleCommandError,
Expand Down Expand Up @@ -747,3 +748,44 @@ def test_runtime_exec_env(runtime: Runtime) -> None:
runtime.environ["FOO"] = "bar"
result = runtime.run(["printenv", "FOO"])
assert result.stdout.rstrip() == "bar"


def test_runtime_plugins(runtime: Runtime) -> None:
"""Tests ability to access detected plugins."""
assert len(runtime.plugins.cliconf) == 0
# ansible.netcommon.restconf might be in httpapi
assert isinstance(runtime.plugins.httpapi, dict)
# "ansible.netcommon.default" might be in runtime.plugins.netconf
assert isinstance(runtime.plugins.netconf, dict)
assert isinstance(runtime.plugins.role, dict)
assert "become" in runtime.plugins.keyword

if ansible_version() < Version("2.14.0"):
assert "sudo" in runtime.plugins.become
assert "memory" in runtime.plugins.cache
assert "default" in runtime.plugins.callback
assert "local" in runtime.plugins.connection
assert "ini" in runtime.plugins.inventory
assert "env" in runtime.plugins.lookup
assert "sh" in runtime.plugins.shell
assert "host_group_vars" in runtime.plugins.vars
assert "file" in runtime.plugins.module
assert "free" in runtime.plugins.strategy
# ansible-doc below 2.14 does not support listing 'test' and 'filter' types:
with pytest.raises(RuntimeError):
assert "is_abs" in runtime.plugins.test
with pytest.raises(RuntimeError):
assert "bool" in runtime.plugins.filter
else:
assert "ansible.builtin.sudo" in runtime.plugins.become
assert "ansible.builtin.memory" in runtime.plugins.cache
assert "ansible.builtin.default" in runtime.plugins.callback
assert "ansible.builtin.local" in runtime.plugins.connection
assert "ansible.builtin.ini" in runtime.plugins.inventory
assert "ansible.builtin.env" in runtime.plugins.lookup
assert "ansible.builtin.sh" in runtime.plugins.shell
assert "ansible.builtin.host_group_vars" in runtime.plugins.vars
assert "ansible.builtin.file" in runtime.plugins.module
assert "ansible.builtin.free" in runtime.plugins.strategy
assert "ansible.builtin.is_abs" in runtime.plugins.test
assert "ansible.builtin.bool" in runtime.plugins.filter
11 changes: 6 additions & 5 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ envlist =
py{39,310,311}{,-devel,-ansible212,-ansible213,-ansible214,-ansible215}
isolated_build = true
skip_missing_interpreters = True
skipsdist = true

[testenv]
description =
Expand All @@ -28,7 +27,8 @@ deps =
devel: ansible-core @ git+https://github.com/ansible/ansible.git # GPLv3+
# avoid installing ansible-core on -devel envs:
!devel: ansible-core
--editable .[test]
extras =
test

commands =
sh -c "ansible --version | head -n 1"
Expand Down Expand Up @@ -68,12 +68,14 @@ setenv =
PIP_DISABLE_PIP_VERSION_CHECK = 1
PIP_CONSTRAINT = {toxinidir}/requirements.txt
PRE_COMMIT_COLOR = always
PYTEST_REQPASS = 81
PYTEST_REQPASS = 82
FORCE_COLOR = 1
allowlist_externals =
ansible
git
sh
# https://tox.wiki/en/latest/upgrading.html#editable-mode
package = editable

[testenv:lint]
description = Run all linters
Expand Down Expand Up @@ -140,6 +142,5 @@ deps =
description = Build docs
commands =
mkdocs {posargs:build} --strict
deps =
--editable .[docs]
extras = docs
passenv = *

0 comments on commit 31cdff7

Please sign in to comment.