1010import tempfile
1111import warnings
1212from collections import OrderedDict
13- from dataclasses import dataclass
13+ from dataclasses import dataclass , field
1414from pathlib import Path
15- from typing import TYPE_CHECKING , Any , Callable , Optional , Union
15+ from typing import TYPE_CHECKING , Any , Callable , Optional , Union , no_type_check
1616
1717import subprocess_tee
1818from packaging .version import Version
1919
2020from ansible_compat .config import (
2121 AnsibleConfig ,
2222 ansible_collections_path ,
23+ ansible_version ,
2324 parse_ansible_version ,
2425)
2526from ansible_compat .constants import (
@@ -73,6 +74,71 @@ def __init__(self, version: str) -> None:
7374 super ().__init__ (version )
7475
7576
77+ @dataclass
78+ class Plugins : # pylint: disable=too-many-instance-attributes
79+ """Dataclass to access installed Ansible plugins, uses ansible-doc to retrieve them."""
80+
81+ runtime : "Runtime"
82+ become : dict [str , str ] = field (init = False )
83+ cache : dict [str , str ] = field (init = False )
84+ callback : dict [str , str ] = field (init = False )
85+ cliconf : dict [str , str ] = field (init = False )
86+ connection : dict [str , str ] = field (init = False )
87+ httpapi : dict [str , str ] = field (init = False )
88+ inventory : dict [str , str ] = field (init = False )
89+ lookup : dict [str , str ] = field (init = False )
90+ netconf : dict [str , str ] = field (init = False )
91+ shell : dict [str , str ] = field (init = False )
92+ vars : dict [str , str ] = field (init = False ) # noqa: A003
93+ module : dict [str , str ] = field (init = False )
94+ strategy : dict [str , str ] = field (init = False )
95+ test : dict [str , str ] = field (init = False )
96+ filter : dict [str , str ] = field (init = False ) # noqa: A003
97+ role : dict [str , str ] = field (init = False )
98+ keyword : dict [str , str ] = field (init = False )
99+
100+ @no_type_check
101+ def __getattribute__ (self , attr : str ): # noqa: ANN204
102+ """Get attribute."""
103+ if attr in {
104+ "become" ,
105+ "cache" ,
106+ "callback" ,
107+ "cliconf" ,
108+ "connection" ,
109+ "httpapi" ,
110+ "inventory" ,
111+ "lookup" ,
112+ "netconf" ,
113+ "shell" ,
114+ "vars" ,
115+ "module" ,
116+ "strategy" ,
117+ "test" ,
118+ "filter" ,
119+ "role" ,
120+ "keyword" ,
121+ }:
122+ try :
123+ result = super ().__getattribute__ (attr )
124+ except AttributeError as exc :
125+ if ansible_version () < Version ("2.14" ) and attr in {"filter" , "test" }:
126+ msg = "Ansible version below 2.14 does not support retrieving filter and test plugins."
127+ raise RuntimeError (msg ) from exc
128+ proc = self .runtime .run (
129+ ["ansible-doc" , "--json" , "-l" , "-t" , attr ],
130+ )
131+ data = json .loads (proc .stdout )
132+ if not isinstance (data , dict ): # pragma: no cover
133+ msg = "Unexpected output from ansible-doc"
134+ raise AnsibleCompatError (msg ) from exc
135+ result = data
136+ else :
137+ result = super ().__getattribute__ (attr )
138+
139+ return result
140+
141+
76142# pylint: disable=too-many-instance-attributes
77143class Runtime :
78144 """Ansible Runtime manager."""
@@ -83,6 +149,7 @@ class Runtime:
83149 # Used to track if we have already initialized the Ansible runtime as attempts
84150 # to do it multiple tilmes will cause runtime warnings from within ansible-core
85151 initialized : bool = False
152+ plugins : Plugins
86153
87154 def __init__ (
88155 self ,
@@ -119,6 +186,7 @@ def __init__(
119186 self .isolated = isolated
120187 self .max_retries = max_retries
121188 self .environ = environ or os .environ .copy ()
189+ self .plugins = Plugins (runtime = self )
122190 # Reduce noise from paramiko, unless user already defined PYTHONWARNINGS
123191 # paramiko/transport.py:236: CryptographyDeprecationWarning: Blowfish has been deprecated
124192 # https://github.com/paramiko/paramiko/issues/2038
0 commit comments