Skip to content

Commit d1434fa

Browse files
Refactor(pyavd): Add AVDTemplar to avoid accessing private Ansible Templar attributes and triggering deprecation (#6658)
Co-authored-by: Claus Holbech <[email protected]>
1 parent 83e7eff commit d1434fa

17 files changed

Lines changed: 133 additions & 63 deletions

File tree

ansible_collections/arista/avd/plugins/action/eos_designs_facts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
from ansible.parsing.yaml.dumper import AnsibleDumper
1515
from ansible.plugins.action import ActionBase
1616

17+
from ansible_collections.arista.avd.plugins.plugin_utils.constants import ANSIBLE_ABOVE_2_19
1718
from ansible_collections.arista.avd.plugins.plugin_utils.utils import (
18-
ANSIBLE_ABOVE_2_19,
1919
AVDFileHandler,
2020
AVDVaultHandler,
2121
get_eos_designs_facts_path,

ansible_collections/arista/avd/plugins/action/eos_designs_structured_config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
from ansible.parsing.yaml.dumper import AnsibleDumper
1616
from ansible.plugins.action import ActionBase
1717

18+
from ansible_collections.arista.avd.plugins.plugin_utils.constants import ANSIBLE_ABOVE_2_19
1819
from ansible_collections.arista.avd.plugins.plugin_utils.utils import (
19-
ANSIBLE_ABOVE_2_19,
2020
AVDFileHandler,
2121
AvdSwitchFactsDefaultDict,
2222
AVDVaultHandler,
@@ -123,6 +123,7 @@ def run(self, tmp: Any = None, task_vars: dict | None = None) -> dict:
123123
template = template_item["template"]
124124

125125
# Here we parse the template, expecting the result to be a YAML formatted string
126+
# self.templar is an AVDTemplar instance which already contains loader and searchpath
126127
template_result = templater(template, template_vars, self.templar)
127128

128129
# Skip if template result is None or empty string

ansible_collections/arista/avd/plugins/plugin_utils/utils/constants.py renamed to ansible_collections/arista/avd/plugins/plugin_utils/constants.py

File renamed without changes.

ansible_collections/arista/avd/plugins/plugin_utils/pyavd_wrappers.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,17 @@
88
from typing import TYPE_CHECKING, Any, Literal
99

1010
from ansible.errors import AnsibleFilterError, AnsibleInternalError, AnsibleUndefinedVariable
11-
from ansible.module_utils.basic import to_native
1211
from ansible.utils.display import Display
1312
from jinja2.exceptions import UndefinedError
1413

14+
from .constants import ANSIBLE_ABOVE_2_19
15+
16+
if ANSIBLE_ABOVE_2_19:
17+
from ansible.module_utils.common.text.converters import to_native
18+
else:
19+
# Fallback for ansible-core < 2.19
20+
from ansible.module_utils.basic import to_native
21+
1522
if TYPE_CHECKING:
1623
from collections.abc import Callable
1724
from typing import NoReturn

ansible_collections/arista/avd/plugins/plugin_utils/utils/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from .avd_switch_facts_default_dict import AvdSwitchFactsDefaultDict
88
from .avd_vault_handler import AVDVaultHandler
99
from .compile_searchpath import compile_searchpath
10-
from .constants import ANSIBLE_ABOVE_2_19
1110
from .cprofile_decorator import cprofile
1211
from .get_templar import get_templar
1312
from .get_workers import get_workers
@@ -20,7 +19,6 @@
2019
from .yaml_loader import YamlLoader
2120

2221
__all__ = [
23-
"ANSIBLE_ABOVE_2_19",
2422
"AVDFileHandler",
2523
"AVDVaultHandler",
2624
"ActionPluginVars",

ansible_collections/arista/avd/plugins/plugin_utils/utils/action_plugin_vars.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from ansible.vars.hostvars import HostVarsVars
99

10-
from .constants import ANSIBLE_ABOVE_2_19
10+
from ansible_collections.arista.avd.plugins.plugin_utils.constants import ANSIBLE_ABOVE_2_19
1111

1212
if TYPE_CHECKING: # pragma: no cover
1313
from ansible.inventory.manager import InventoryManager

ansible_collections/arista/avd/plugins/plugin_utils/utils/get_templar.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,39 @@
33
# that can be found in the LICENSE file.
44
from __future__ import annotations
55

6-
from typing import TYPE_CHECKING
6+
from typing import TYPE_CHECKING, Any
77

88
if TYPE_CHECKING:
99
from ansible.plugins.action import ActionBase
10-
from ansible.template import Templar
10+
11+
from ansible_collections.arista.avd.plugins.plugin_utils.pyavd_wrappers import RaiseOnUse
1112

1213
from .compile_searchpath import compile_searchpath
1314

15+
if TYPE_CHECKING:
16+
from pyavd._utils import AVDTemplar
17+
else:
18+
try:
19+
from pyavd._utils import AVDTemplar
20+
except ImportError as e:
21+
AVDTemplar = RaiseOnUse(ImportError(f"The 'arista.avd' collection requires the 'pyavd' Python library. Got import error {e}"))
22+
1423

15-
def get_templar(action_plugin_instance: ActionBase, task_vars: dict) -> Templar:
24+
def get_templar(action_plugin_instance: ActionBase, task_vars: dict[str, Any]) -> AVDTemplar:
1625
"""
17-
Return a new instance of Ansible Templar Class based on the "._templar" from the given action_plugin_instance.
26+
Return a new AVDTemplar instance based on the action plugin instance.
1827
19-
The new instance is loaded with new searchpath based on
28+
The templar is loaded with new searchpath based on
2029
".ansible_search_path" from the given task_vars.
30+
31+
Args:
32+
action_plugin_instance: The Ansible ActionBase plugin instance
33+
task_vars: Task variables containing ansible_search_path
34+
35+
Returns:
36+
AVDTemplar instance with configured templar, loader, and searchpath
2137
"""
22-
return action_plugin_instance._templar.copy_with_new_env(
23-
searchpath=compile_searchpath(task_vars.get("ansible_search_path", [])),
24-
)
38+
searchpath = compile_searchpath(task_vars.get("ansible_search_path", []))
39+
templar = action_plugin_instance._templar.copy_with_new_env(searchpath=searchpath)
40+
loader = action_plugin_instance._task.get_loader()
41+
return AVDTemplar(templar, loader, searchpath)

ansible_collections/arista/avd/plugins/vars/global_vars.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,15 @@
8989
from ansible.errors import AnsibleParserError
9090
from ansible.inventory.group import Group
9191
from ansible.inventory.host import Host
92-
from ansible.module_utils.basic import to_native
9392
from ansible.plugins.vars import BaseVarsPlugin
9493
from ansible.utils.vars import combine_vars
9594

96-
from ansible_collections.arista.avd.plugins.plugin_utils.utils import ANSIBLE_ABOVE_2_19
95+
from ansible_collections.arista.avd.plugins.plugin_utils.constants import ANSIBLE_ABOVE_2_19
96+
97+
if ANSIBLE_ABOVE_2_19:
98+
from ansible.module_utils.common.text.converters import to_native
99+
else:
100+
from ansible.module_utils.basic import to_native
97101

98102
if ANSIBLE_ABOVE_2_19:
99103
from ansible.template import trust_as_template

python-avd/pyavd/_eos_designs/eos_designs_facts/get_facts.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
if TYPE_CHECKING:
1313
from collections.abc import Mapping, MutableMapping
1414

15-
from ansible.template import Templar
16-
15+
from pyavd._utils import AVDTemplar
1716
from pyavd.api.pool_manager import PoolManager
1817
from pyavd.api.schemas import AVDDesign
1918

@@ -23,7 +22,7 @@
2322
def get_facts(
2423
all_inputs: Mapping[str, AVDDesign | Mapping],
2524
all_hostvars: Mapping[str, MutableMapping[str, Any]] | None = None,
26-
templar: Templar | None = None,
25+
templar: AVDTemplar | None = None,
2726
pool_manager: PoolManager | None = None,
2827
digital_twin: bool = False,
2928
) -> dict[str, EosDesignsFacts]:
@@ -35,7 +34,7 @@ def get_facts(
3534
Supporting dicts as well for backwards compatibility.
3635
all_hostvars: Raw hostvars exposed to custom jinja templates or custom python logic for each device.
3736
This is optional and only needed if custom templates or python modules are used for descriptions or IP addressing.
38-
templar: Templater used to render custom jinja templates.
37+
templar: AVDTemplar wrapper used to render custom jinja templates.
3938
This is optional and only needed if custom templates are used for descriptions or IP addressing.
4039
pool_manager: instance of pool-manager used for dynamic assignments like node ids.
4140
digital_twin: Optional flag to enable avd_digital_twin_mode.
@@ -91,7 +90,7 @@ def _create_generator_instance(
9190
hostname: str,
9291
inputs: AVDDesign,
9392
hostvars: MutableMapping,
94-
templar: Templar | None,
93+
templar: AVDTemplar | None,
9594
pool_manager: PoolManager | None,
9695
digital_twin: bool,
9796
peer_facts_generators: dict[str, EosDesignsFactsGenerator],

python-avd/pyavd/_eos_designs/shared_utils/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,9 @@
3535
if TYPE_CHECKING:
3636
from collections.abc import Mapping, MutableMapping
3737

38-
from ansible.template import Templar
39-
4038
from pyavd._eos_designs.eos_designs_facts.schema import EosDesignsFactsProtocol
4139
from pyavd._eos_designs.schema import EosDesigns
40+
from pyavd._utils import AVDTemplar
4241
from pyavd.api.pool_manager import PoolManager
4342

4443

@@ -73,7 +72,7 @@ class SharedUtilsProtocol(
7372
hostname: str
7473
hostvars: MutableMapping
7574
inputs: EosDesigns
76-
templar: Templar | None
75+
templar: AVDTemplar | None
7776
peer_facts: Mapping[str, EosDesignsFactsProtocol]
7877
pool_manager: PoolManager | None
7978
digital_twin: bool
@@ -97,7 +96,7 @@ def __init__(
9796
hostname: str,
9897
hostvars: MutableMapping,
9998
inputs: EosDesigns,
100-
templar: Templar | None,
99+
templar: AVDTemplar | None,
101100
peer_facts: Mapping[str, EosDesignsFactsProtocol],
102101
pool_manager: PoolManager | None = None,
103102
digital_twin: bool = False,

0 commit comments

Comments
 (0)