ansible-core 2.19.0b5__py3-none-any.whl → 2.19.0b7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. ansible/_internal/_ansiballz/__init__.py +0 -0
  2. ansible/_internal/_ansiballz/_builder.py +101 -0
  3. ansible/_internal/{_ansiballz.py → _ansiballz/_wrapper.py} +11 -11
  4. ansible/_internal/_templating/_jinja_bits.py +22 -4
  5. ansible/_internal/_templating/_jinja_common.py +1 -1
  6. ansible/_internal/_templating/_jinja_plugins.py +5 -2
  7. ansible/_internal/_templating/_template_vars.py +72 -0
  8. ansible/_internal/_templating/_transform.py +6 -0
  9. ansible/_internal/_yaml/_constructor.py +4 -4
  10. ansible/_internal/_yaml/_dumper.py +26 -18
  11. ansible/cli/__init__.py +9 -14
  12. ansible/cli/adhoc.py +6 -3
  13. ansible/cli/arguments/option_helpers.py +1 -1
  14. ansible/cli/console.py +2 -2
  15. ansible/cli/doc.py +4 -4
  16. ansible/cli/inventory.py +5 -7
  17. ansible/config/base.yml +33 -6
  18. ansible/errors/__init__.py +2 -1
  19. ansible/executor/module_common.py +75 -44
  20. ansible/executor/powershell/psrp_put_file.ps1 +1 -1
  21. ansible/executor/process/worker.py +2 -2
  22. ansible/executor/task_executor.py +2 -2
  23. ansible/executor/task_queue_manager.py +34 -70
  24. ansible/executor/task_result.py +1 -1
  25. ansible/galaxy/api.py +3 -6
  26. ansible/galaxy/collection/__init__.py +1 -6
  27. ansible/galaxy/collection/concrete_artifact_manager.py +4 -10
  28. ansible/galaxy/dependency_resolution/providers.py +3 -3
  29. ansible/galaxy/role.py +2 -2
  30. ansible/inventory/group.py +6 -1
  31. ansible/inventory/host.py +6 -1
  32. ansible/module_utils/_internal/__init__.py +7 -4
  33. ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
  34. ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
  35. ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
  36. ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
  37. ansible/module_utils/_internal/{_ansiballz.py → _ansiballz/_loader.py} +10 -38
  38. ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
  39. ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
  40. ansible/module_utils/_internal/_datatag/__init__.py +23 -1
  41. ansible/module_utils/_internal/_deprecator.py +39 -34
  42. ansible/module_utils/_internal/_json/_profiles/__init__.py +1 -0
  43. ansible/module_utils/_internal/_messages.py +26 -2
  44. ansible/module_utils/_internal/_plugin_info.py +14 -1
  45. ansible/module_utils/ansible_release.py +1 -1
  46. ansible/module_utils/basic.py +58 -70
  47. ansible/module_utils/common/respawn.py +4 -41
  48. ansible/module_utils/common/yaml.py +1 -1
  49. ansible/module_utils/connection.py +8 -11
  50. ansible/module_utils/csharp/Ansible.Basic.cs +1 -1
  51. ansible/module_utils/csharp/Ansible.Privilege.cs +2 -2
  52. ansible/module_utils/facts/hardware/base.py +1 -1
  53. ansible/module_utils/facts/hardware/linux.py +1 -1
  54. ansible/module_utils/facts/other/facter.py +1 -1
  55. ansible/module_utils/facts/sysctl.py +4 -6
  56. ansible/module_utils/facts/system/caps.py +2 -2
  57. ansible/module_utils/facts/system/distribution.py +2 -2
  58. ansible/module_utils/facts/system/local.py +1 -1
  59. ansible/module_utils/facts/virtual/linux.py +1 -1
  60. ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 +1 -1
  61. ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 +1 -1
  62. ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 +1 -1
  63. ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1 +1 -1
  64. ansible/module_utils/service.py +1 -1
  65. ansible/module_utils/urls.py +5 -5
  66. ansible/modules/apt.py +9 -3
  67. ansible/modules/apt_repository.py +10 -10
  68. ansible/modules/assemble.py +7 -5
  69. ansible/modules/async_wrapper.py +7 -17
  70. ansible/modules/command.py +3 -3
  71. ansible/modules/copy.py +4 -4
  72. ansible/modules/cron.py +1 -1
  73. ansible/modules/expect.py +5 -5
  74. ansible/modules/file.py +16 -17
  75. ansible/modules/find.py +3 -3
  76. ansible/modules/get_url.py +17 -0
  77. ansible/modules/git.py +9 -7
  78. ansible/modules/hostname.py +2 -2
  79. ansible/modules/known_hosts.py +12 -14
  80. ansible/modules/package.py +6 -0
  81. ansible/modules/pip.py +9 -11
  82. ansible/modules/raw.py +2 -2
  83. ansible/modules/replace.py +2 -2
  84. ansible/modules/slurp.py +10 -13
  85. ansible/modules/stat.py +6 -8
  86. ansible/modules/unarchive.py +6 -6
  87. ansible/modules/user.py +1 -1
  88. ansible/modules/wait_for.py +38 -33
  89. ansible/modules/yum_repository.py +4 -3
  90. ansible/parsing/dataloader.py +2 -2
  91. ansible/parsing/mod_args.py +38 -20
  92. ansible/parsing/vault/__init__.py +9 -13
  93. ansible/playbook/base.py +7 -4
  94. ansible/playbook/helpers.py +1 -1
  95. ansible/playbook/included_file.py +3 -1
  96. ansible/playbook/play_context.py +2 -0
  97. ansible/playbook/playbook_include.py +23 -56
  98. ansible/playbook/role/__init__.py +38 -21
  99. ansible/playbook/taggable.py +19 -5
  100. ansible/playbook/task.py +2 -0
  101. ansible/plugins/action/__init__.py +2 -2
  102. ansible/plugins/action/assemble.py +2 -1
  103. ansible/plugins/action/assert.py +2 -2
  104. ansible/plugins/action/fetch.py +3 -3
  105. ansible/plugins/action/script.py +5 -4
  106. ansible/plugins/action/template.py +9 -3
  107. ansible/plugins/cache/__init__.py +17 -19
  108. ansible/plugins/callback/__init__.py +77 -87
  109. ansible/plugins/callback/default.py +0 -3
  110. ansible/plugins/callback/junit.py +0 -6
  111. ansible/plugins/callback/tree.py +5 -5
  112. ansible/plugins/connection/local.py +4 -4
  113. ansible/plugins/connection/paramiko_ssh.py +5 -5
  114. ansible/plugins/connection/ssh.py +9 -7
  115. ansible/plugins/connection/winrm.py +1 -1
  116. ansible/plugins/filter/core.py +19 -21
  117. ansible/plugins/filter/encryption.py +10 -2
  118. ansible/plugins/filter/pow.yml +1 -1
  119. ansible/plugins/filter/root.yml +1 -1
  120. ansible/plugins/filter/strftime.yml +3 -3
  121. ansible/plugins/filter/to_uuid.yml +1 -1
  122. ansible/plugins/inventory/script.py +1 -1
  123. ansible/plugins/list.py +5 -4
  124. ansible/plugins/loader.py +5 -0
  125. ansible/plugins/lookup/password.py +4 -6
  126. ansible/plugins/lookup/template.py +9 -4
  127. ansible/plugins/shell/powershell.py +3 -2
  128. ansible/plugins/shell/sh.py +3 -2
  129. ansible/plugins/strategy/__init__.py +3 -3
  130. ansible/plugins/test/core.py +2 -2
  131. ansible/release.py +1 -1
  132. ansible/template/__init__.py +9 -53
  133. ansible/utils/collection_loader/_collection_finder.py +3 -3
  134. ansible/utils/display.py +38 -37
  135. ansible/utils/galaxy.py +2 -2
  136. ansible/utils/hashing.py +6 -7
  137. ansible/utils/path.py +6 -8
  138. ansible/utils/py3compat.py +2 -1
  139. ansible/utils/ssh_functions.py +3 -2
  140. ansible/utils/vars.py +4 -1
  141. ansible/vars/manager.py +6 -3
  142. ansible/vars/plugins.py +3 -3
  143. ansible/vars/reserved.py +6 -4
  144. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/METADATA +1 -1
  145. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/RECORD +184 -173
  146. ansible_test/_internal/__init__.py +5 -0
  147. ansible_test/_internal/ansible_util.py +1 -1
  148. ansible_test/_internal/classification/python.py +6 -0
  149. ansible_test/_internal/cli/commands/__init__.py +0 -5
  150. ansible_test/_internal/cli/environments.py +51 -5
  151. ansible_test/_internal/commands/coverage/__init__.py +1 -1
  152. ansible_test/_internal/commands/integration/__init__.py +18 -5
  153. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  154. ansible_test/_internal/commands/integration/coverage.py +7 -2
  155. ansible_test/_internal/commands/sanity/__init__.py +3 -1
  156. ansible_test/_internal/commands/sanity/integration_aliases.py +11 -0
  157. ansible_test/_internal/commands/shell/__init__.py +43 -4
  158. ansible_test/_internal/commands/units/__init__.py +4 -1
  159. ansible_test/_internal/config.py +21 -13
  160. ansible_test/_internal/debugging.py +166 -0
  161. ansible_test/_internal/delegation.py +21 -13
  162. ansible_test/_internal/host_profiles.py +259 -16
  163. ansible_test/_internal/inventory.py +4 -0
  164. ansible_test/_internal/metadata.py +94 -4
  165. ansible_test/_internal/processes.py +80 -0
  166. ansible_test/_internal/provisioning.py +10 -4
  167. ansible_test/_internal/python_requirements.py +27 -0
  168. ansible_test/_internal/ssh.py +1 -5
  169. ansible_test/_internal/target.py +8 -0
  170. ansible_test/_internal/thread.py +2 -1
  171. ansible_test/_internal/timeout.py +1 -1
  172. ansible_test/_internal/util.py +20 -12
  173. ansible_test/_internal/util_common.py +13 -3
  174. ansible_test/_util/target/injector/python.py +8 -0
  175. ansible_test/_util/target/setup/requirements.py +3 -9
  176. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/WHEEL +0 -0
  177. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/entry_points.txt +0 -0
  178. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/COPYING +0 -0
  179. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  180. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  181. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  182. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  183. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  184. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/top_level.txt +0 -0
@@ -19,12 +19,7 @@ from __future__ import annotations
19
19
 
20
20
  import os
21
21
 
22
- import ansible.constants as C
23
- from ansible.errors import AnsibleParserError, AnsibleAssertionError
24
22
  from ansible.module_utils.common.text.converters import to_bytes
25
- from ansible.module_utils._internal._datatag import AnsibleTagHelper
26
- from ansible.module_utils.six import string_types
27
- from ansible.parsing.splitter import split_args
28
23
  from ansible.playbook.attribute import NonInheritableFieldAttribute
29
24
  from ansible.playbook.base import Base
30
25
  from ansible.playbook.conditional import Conditional
@@ -32,16 +27,29 @@ from ansible.playbook.taggable import Taggable
32
27
  from ansible.utils.collection_loader import AnsibleCollectionConfig
33
28
  from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path, _get_collection_playbook_path
34
29
  from ansible._internal._templating._engine import TemplateEngine
35
- from ansible.utils.display import Display
36
-
37
- display = Display()
30
+ from ansible.errors import AnsibleError
31
+ from ansible import constants as C
38
32
 
39
33
 
40
34
  class PlaybookInclude(Base, Conditional, Taggable):
41
35
 
42
- import_playbook = NonInheritableFieldAttribute(isa='string')
36
+ import_playbook = NonInheritableFieldAttribute(isa='string', required=True)
43
37
  vars_val = NonInheritableFieldAttribute(isa='dict', default=dict, alias='vars')
44
38
 
39
+ _post_validate_object = True # manually post_validate to get free arg validation/coercion
40
+
41
+ def preprocess_data(self, ds):
42
+ keys = {action for action in C._ACTION_IMPORT_PLAYBOOK if action in ds}
43
+
44
+ if len(keys) != 1:
45
+ raise AnsibleError(f'Found conflicting import_playbook actions: {", ".join(sorted(keys))}')
46
+
47
+ key = next(iter(keys))
48
+
49
+ ds['import_playbook'] = ds.pop(key)
50
+
51
+ return ds
52
+
45
53
  @staticmethod
46
54
  def load(data, basedir, variable_manager=None, loader=None):
47
55
  return PlaybookInclude().load_data(ds=data, basedir=basedir, variable_manager=variable_manager, loader=loader)
@@ -62,18 +70,22 @@ class PlaybookInclude(Base, Conditional, Taggable):
62
70
  new_obj = super(PlaybookInclude, self).load_data(ds, variable_manager, loader)
63
71
 
64
72
  all_vars = self.vars.copy()
73
+
65
74
  if variable_manager:
66
75
  all_vars |= variable_manager.get_vars()
67
76
 
68
77
  templar = TemplateEngine(loader=loader, variables=all_vars)
69
78
 
79
+ new_obj.post_validate(templar)
80
+
70
81
  # then we use the object to load a Playbook
71
82
  pb = Playbook(loader=loader)
72
83
 
73
- file_name = templar.template(new_obj.import_playbook)
84
+ file_name = new_obj.import_playbook
74
85
 
75
86
  # check for FQCN
76
87
  resource = _get_collection_playbook_path(file_name)
88
+
77
89
  if resource is not None:
78
90
  playbook = resource[1]
79
91
  playbook_collection = resource[2]
@@ -92,6 +104,7 @@ class PlaybookInclude(Base, Conditional, Taggable):
92
104
  else:
93
105
  # it is NOT a collection playbook, setup adjacent paths
94
106
  AnsibleCollectionConfig.playbook_paths.append(os.path.dirname(os.path.abspath(to_bytes(playbook, errors='surrogate_or_strict'))))
107
+ # broken, see: https://github.com/ansible/ansible/issues/85357
95
108
 
96
109
  pb._load_playbook_data(file_name=playbook, variable_manager=variable_manager, vars=self.vars.copy())
97
110
 
@@ -120,49 +133,3 @@ class PlaybookInclude(Base, Conditional, Taggable):
120
133
  task_block._when = new_obj.when[:] + task_block.when[:]
121
134
 
122
135
  return pb
123
-
124
- def preprocess_data(self, ds):
125
- """
126
- Reorganizes the data for a PlaybookInclude datastructure to line
127
- up with what we expect the proper attributes to be
128
- """
129
-
130
- if not isinstance(ds, dict):
131
- raise AnsibleAssertionError('ds (%s) should be a dict but was a %s' % (ds, type(ds)))
132
-
133
- # the new, cleaned datastructure, which will have legacy items reduced to a standard structure suitable for the
134
- # attributes of the task class; copy any tagged data to preserve things like origin
135
- new_ds = AnsibleTagHelper.tag_copy(ds, {})
136
-
137
- for (k, v) in ds.items():
138
- if k in C._ACTION_IMPORT_PLAYBOOK:
139
- self._preprocess_import(ds, new_ds, k, v)
140
- else:
141
- # some basic error checking, to make sure vars are properly
142
- # formatted and do not conflict with k=v parameters
143
- if k == 'vars':
144
- if 'vars' in new_ds:
145
- raise AnsibleParserError("import_playbook parameters cannot be mixed with 'vars' entries for import statements", obj=ds)
146
- elif not isinstance(v, dict):
147
- raise AnsibleParserError("vars for import_playbook statements must be specified as a dictionary", obj=ds)
148
- new_ds[k] = v
149
-
150
- return super(PlaybookInclude, self).preprocess_data(new_ds)
151
-
152
- def _preprocess_import(self, ds, new_ds, k, v):
153
- """
154
- Splits the playbook import line up into filename and parameters
155
- """
156
- if v is None:
157
- raise AnsibleParserError("playbook import parameter is missing", obj=ds)
158
- elif not isinstance(v, string_types):
159
- raise AnsibleParserError("playbook import parameter must be a string indicating a file path, got %s instead" % type(v), obj=ds)
160
-
161
- # The import_playbook line must include at least one item, which is the filename
162
- # to import. Anything after that should be regarded as a parameter to the import
163
- items = split_args(v)
164
- if len(items) == 0:
165
- raise AnsibleParserError("import_playbook statements must specify the file name to import", obj=ds)
166
-
167
- # DTFIX3: investigate this as a possible "problematic strip"
168
- new_ds['import_playbook'] = AnsibleTagHelper.tag_copy(v, items[0].strip())
@@ -18,6 +18,7 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  import os
21
+ import typing as _t
21
22
 
22
23
  from collections.abc import Container, Mapping, Set, Sequence
23
24
  from types import MappingProxyType
@@ -39,6 +40,16 @@ from ansible.utils.collection_loader import AnsibleCollectionConfig
39
40
  from ansible.utils.path import is_subpath
40
41
  from ansible.utils.vars import combine_vars
41
42
 
43
+ # NOTE: This import is only needed for the type-checking in __init__. While there's an alternative
44
+ # available by using forward references this seems not to work well with commonly used IDEs.
45
+ # Therefore the TYPE_CHECKING hack seems to be a more universal approach, even if not being very elegant.
46
+ # References:
47
+ # * https://stackoverflow.com/q/39740632/199513
48
+ # * https://peps.python.org/pep-0484/#forward-references
49
+ if _t.TYPE_CHECKING:
50
+ from ansible.playbook.block import Block
51
+ from ansible.playbook.play import Play
52
+
42
53
  __all__ = ['Role', 'hash_params']
43
54
 
44
55
  # TODO: this should be a utility function, but can't be a member of
@@ -97,13 +108,19 @@ def hash_params(params):
97
108
 
98
109
  class Role(Base, Conditional, Taggable, CollectionSearch, Delegatable):
99
110
 
100
- def __init__(self, play=None, from_files=None, from_include=False, validate=True, public=None, static=True):
101
- self._role_name = None
102
- self._role_path = None
103
- self._role_collection = None
104
- self._role_params = dict()
111
+ def __init__(self,
112
+ play: Play = None,
113
+ from_files: dict[str, list[str]] = None,
114
+ from_include: bool = False,
115
+ validate: bool = True,
116
+ public: bool = None,
117
+ static: bool = True) -> None:
118
+ self._role_name: str = None
119
+ self._role_path: str = None
120
+ self._role_collection: str = None
121
+ self._role_params: dict[str, dict[str, str]] = dict()
105
122
  self._loader = None
106
- self.static = static
123
+ self.static: bool = static
107
124
 
108
125
  # includes (static=false) default to private, while imports (static=true) default to public
109
126
  # but both can be overridden by global config if set
@@ -116,26 +133,26 @@ class Role(Base, Conditional, Taggable, CollectionSearch, Delegatable):
116
133
  else:
117
134
  self.public = public
118
135
 
119
- self._metadata = RoleMetadata()
120
- self._play = play
121
- self._parents = []
122
- self._dependencies = []
123
- self._all_dependencies = None
124
- self._task_blocks = []
125
- self._handler_blocks = []
126
- self._compiled_handler_blocks = None
127
- self._default_vars = dict()
128
- self._role_vars = dict()
129
- self._had_task_run = dict()
130
- self._completed = dict()
131
- self._should_validate = validate
136
+ self._metadata: RoleMetadata = RoleMetadata()
137
+ self._play: Play = play
138
+ self._parents: list[Role] = []
139
+ self._dependencies: list[Role] = []
140
+ self._all_dependencies: list[Role] | None = None
141
+ self._task_blocks: list[Block] = []
142
+ self._handler_blocks: list[Block] = []
143
+ self._compiled_handler_blocks: list[Block] | None = None
144
+ self._default_vars: dict[str, str] | None = dict()
145
+ self._role_vars: dict[str, str] | None = dict()
146
+ self._had_task_run: dict[str, bool] = dict()
147
+ self._completed: dict[str, bool] = dict()
148
+ self._should_validate: bool = validate
132
149
 
133
150
  if from_files is None:
134
151
  from_files = {}
135
- self._from_files = from_files
152
+ self._from_files: dict[str, list[str]] = from_files
136
153
 
137
154
  # Indicates whether this role was included via include/import_role
138
- self.from_include = from_include
155
+ self.from_include: bool = from_include
139
156
 
140
157
  self._hash = None
141
158
 
@@ -17,6 +17,8 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
+ import typing as t
21
+
20
22
  from ansible.errors import AnsibleError
21
23
  from ansible.module_utils.six import string_types
22
24
  from ansible.module_utils.common.sentinel import Sentinel
@@ -25,7 +27,7 @@ from ansible.playbook.attribute import FieldAttribute
25
27
  from ansible._internal._templating._engine import TemplateEngine
26
28
 
27
29
 
28
- def _flatten_tags(tags: list) -> list:
30
+ def _flatten_tags(tags: list[str | int]) -> list[str | int]:
29
31
  rv = set()
30
32
  for tag in tags:
31
33
  if isinstance(tag, list):
@@ -49,16 +51,28 @@ class Taggable:
49
51
 
50
52
  raise AnsibleError('tags must be specified as a list', obj=ds)
51
53
 
54
+ def _get_all_taggable_objects(self) -> t.Iterable[Taggable]:
55
+ obj = self
56
+ while obj is not None:
57
+ yield obj
58
+
59
+ if (role := getattr(obj, "_role", Sentinel)) is not Sentinel:
60
+ yield role # type: ignore[misc]
61
+
62
+ obj = obj._parent
63
+
64
+ yield self.get_play()
65
+
52
66
  def evaluate_tags(self, only_tags, skip_tags, all_vars):
53
- """ this checks if the current item should be executed depending on tag options """
67
+ """Check if the current item should be executed depending on the specified tags.
54
68
 
69
+ NOTE this method is assumed to be called only on Task objects.
70
+ """
55
71
  if self.tags:
56
72
  templar = TemplateEngine(loader=self._loader, variables=all_vars)
57
- obj = self
58
- while obj is not None:
73
+ for obj in self._get_all_taggable_objects():
59
74
  if (_tags := getattr(obj, "_tags", Sentinel)) is not Sentinel:
60
75
  obj._tags = _flatten_tags(templar.template(_tags))
61
- obj = obj._parent
62
76
  tags = set(self.tags)
63
77
  else:
64
78
  # this makes isdisjoint work for untagged
ansible/playbook/task.py CHANGED
@@ -65,6 +65,8 @@ class Task(Base, Conditional, Taggable, CollectionSearch, Notifiable, Delegatabl
65
65
  Task.something(...)
66
66
  """
67
67
 
68
+ _post_validate_object = True
69
+
68
70
  # =================================================================================
69
71
  # ATTRIBUTES
70
72
  # load_<attribute_name> and
@@ -103,7 +103,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
103
103
  self._display = display
104
104
 
105
105
  @abstractmethod
106
- def run(self, tmp=None, task_vars=None):
106
+ def run(self, tmp: str | None = None, task_vars: dict[str, t.Any] | None = None) -> dict[str, t.Any]:
107
107
  """ Action Plugins should implement this method to perform their
108
108
  tasks. Everything else in this base class is a helper method for the
109
109
  action plugin to do that.
@@ -120,7 +120,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
120
120
  * Module parameters. These are stored in self._task.args
121
121
  """
122
122
  # does not default to {'changed': False, 'failed': False}, as it used to break async
123
- result = {}
123
+ result: dict[str, t.Any] = {}
124
124
 
125
125
  if tmp is not None:
126
126
  display.warning('ActionModule.run() no longer honors the tmp parameter. Action'
@@ -81,9 +81,10 @@ class ActionModule(ActionBase):
81
81
 
82
82
  def run(self, tmp=None, task_vars=None):
83
83
 
84
- self._supports_check_mode = False
84
+ self._supports_check_mode = True
85
85
 
86
86
  super(ActionModule, self).run(tmp, task_vars)
87
+
87
88
  del tmp # tmp no longer has any effect
88
89
 
89
90
  if task_vars is None:
@@ -72,12 +72,12 @@ class ActionModule(ActionBase):
72
72
  fail_msg = new_module_args['fail_msg']
73
73
  success_msg = new_module_args['success_msg']
74
74
  quiet = new_module_args['quiet']
75
- thats = new_module_args['that']
75
+ that_list = new_module_args['that']
76
76
 
77
77
  if not quiet:
78
78
  result['_ansible_verbose_always'] = True
79
79
 
80
- for that in thats:
80
+ for that in that_list:
81
81
  test_result = self._templar.evaluate_conditional(conditional=that)
82
82
  if not test_result:
83
83
  result['failed'] = True
@@ -119,7 +119,7 @@ class ActionModule(ActionBase):
119
119
 
120
120
  if 'not found' in slurpres.get('msg', ''):
121
121
  result['msg'] = "the remote file does not exist, not transferring, ignored"
122
- elif slurpres.get('msg', '').startswith('source is a directory'):
122
+ elif slurpres.get('msg', '').lower().startswith('source is a directory'):
123
123
  result['msg'] = "remote file is a directory, fetch cannot work on directories"
124
124
 
125
125
  return result
@@ -180,8 +180,8 @@ class ActionModule(ActionBase):
180
180
  try:
181
181
  with open(to_bytes(dest, errors='surrogate_or_strict'), 'wb') as f:
182
182
  f.write(remote_data)
183
- except (IOError, OSError) as e:
184
- raise AnsibleActionFail("Failed to fetch the file: %s" % e)
183
+ except OSError as ex:
184
+ raise AnsibleActionFail("Failed to fetch the file.") from ex
185
185
  new_checksum = secure_hash(dest)
186
186
  # For backwards compatibility. We'll return None on FIPS enabled systems
187
187
  try:
@@ -20,6 +20,7 @@ import os
20
20
  import pathlib
21
21
  import re
22
22
  import shlex
23
+ import typing as _t
23
24
 
24
25
  from ansible.errors import AnsibleError, AnsibleActionFail, AnsibleActionSkip
25
26
  from ansible.executor.powershell import module_manifest as ps_manifest
@@ -35,7 +36,7 @@ class ActionModule(ActionBase):
35
36
  # after chopping off a potential drive letter.
36
37
  windows_absolute_path_detection = re.compile(r'^(?:[a-zA-Z]\:)?(\\|\/)')
37
38
 
38
- def run(self, tmp=None, task_vars=None):
39
+ def run(self, tmp: str | None = None, task_vars: dict[str, _t.Any] | None = None) -> dict[str, _t.Any]:
39
40
  """ handler for file transfer operations """
40
41
  if task_vars is None:
41
42
  task_vars = dict()
@@ -130,7 +131,7 @@ class ActionModule(ActionBase):
130
131
  self._fixup_perms2((self._connection._shell.tmpdir, tmp_src), execute=True)
131
132
 
132
133
  # add preparation steps to one ssh roundtrip executing the script
133
- env_dict = dict()
134
+ env_dict: dict[str, _t.Any] = {}
134
135
  env_string = self._compute_environment_string(env_dict)
135
136
 
136
137
  if executable:
@@ -164,10 +165,10 @@ class ActionModule(ActionBase):
164
165
  script_cmd = self._connection._shell.build_module_command(env_string='', shebang='#!powershell', cmd='')
165
166
 
166
167
  # now we execute script, always assume changed.
167
- result = dict(self._low_level_execute_command(cmd=script_cmd, in_data=exec_data, sudoable=True, chdir=chdir), changed=True)
168
+ result: dict[str, object] = dict(self._low_level_execute_command(cmd=script_cmd, in_data=exec_data, sudoable=True, chdir=chdir), changed=True)
168
169
 
169
170
  if 'rc' in result and result['rc'] != 0:
170
- raise AnsibleActionFail('non-zero return code', result=result)
171
+ result.update(msg='non-zero return code', failed=True)
171
172
 
172
173
  return result
173
174
  finally:
@@ -25,7 +25,8 @@ from ansible.module_utils.common.text.converters import to_bytes, to_text, to_na
25
25
  from ansible.module_utils.parsing.convert_bool import boolean
26
26
  from ansible.module_utils.six import string_types
27
27
  from ansible.plugins.action import ActionBase
28
- from ansible.template import generate_ansible_template_vars, trust_as_template
28
+ from ansible.template import trust_as_template
29
+ from ansible._internal._templating import _template_vars
29
30
 
30
31
 
31
32
  class ActionModule(ActionBase):
@@ -43,7 +44,7 @@ class ActionModule(ActionBase):
43
44
  del tmp # tmp no longer has any effect
44
45
 
45
46
  # Options type validation
46
- # stings
47
+ # strings
47
48
  for s_type in ('src', 'dest', 'state', 'newline_sequence', 'variable_start_string', 'variable_end_string', 'block_start_string',
48
49
  'block_end_string', 'comment_start_string', 'comment_end_string'):
49
50
  if s_type in self._task.args:
@@ -115,7 +116,12 @@ class ActionModule(ActionBase):
115
116
 
116
117
  # add ansible 'template' vars
117
118
  temp_vars = task_vars.copy()
118
- temp_vars.update(generate_ansible_template_vars(self._task.args.get('src', None), fullpath=source, dest_path=dest))
119
+ temp_vars.update(_template_vars.generate_ansible_template_vars(
120
+ path=self._task.args.get('src', None),
121
+ fullpath=source,
122
+ dest_path=dest,
123
+ include_ansible_managed='ansible_managed' not in temp_vars, # do not clobber ansible_managed when set by the user
124
+ ))
119
125
 
120
126
  overrides = dict(
121
127
  block_start_string=block_start_string,
@@ -18,7 +18,6 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  import copy
21
- import errno
22
21
  import os
23
22
  import tempfile
24
23
  import time
@@ -108,8 +107,8 @@ class BaseFileCacheModule(BaseCacheModule):
108
107
  if not os.path.exists(self._cache_dir):
109
108
  try:
110
109
  os.makedirs(self._cache_dir)
111
- except (OSError, IOError) as e:
112
- raise AnsibleError("error in '%s' cache plugin while trying to create cache dir %s : %s" % (self.plugin_name, self._cache_dir, to_bytes(e)))
110
+ except OSError as ex:
111
+ raise AnsibleError(f"Error in {self.plugin_name!r} cache plugin while trying to create cache dir {self._cache_dir!r}.") from ex
113
112
  else:
114
113
  for x in (os.R_OK, os.W_OK, os.X_OK):
115
114
  if not os.access(self._cache_dir, x):
@@ -160,13 +159,13 @@ class BaseFileCacheModule(BaseCacheModule):
160
159
  try:
161
160
  try:
162
161
  self._dump(value, tmpfile_path)
163
- except (OSError, IOError) as e:
164
- display.warning("error in '%s' cache plugin while trying to write to '%s' : %s" % (self.plugin_name, tmpfile_path, to_bytes(e)))
162
+ except OSError as ex:
163
+ display.error_as_warning(f"Error in {self.plugin_name!r} cache plugin while trying to write to {tmpfile_path!r}.", exception=ex)
165
164
  try:
166
165
  os.rename(tmpfile_path, cachefile)
167
166
  os.chmod(cachefile, mode=S_IRWU_RG_RO)
168
- except (OSError, IOError) as e:
169
- display.warning("error in '%s' cache plugin while trying to move '%s' to '%s' : %s" % (self.plugin_name, tmpfile_path, cachefile, to_bytes(e)))
167
+ except OSError as ex:
168
+ display.error_as_warning(f"Error in {self.plugin_name!r} cache plugin while trying to move {tmpfile_path!r} to {cachefile!r}.", exception=ex)
170
169
  finally:
171
170
  try:
172
171
  os.unlink(tmpfile_path)
@@ -181,12 +180,12 @@ class BaseFileCacheModule(BaseCacheModule):
181
180
  cachefile = self._get_cache_file_name(key)
182
181
  try:
183
182
  st = os.stat(cachefile)
184
- except (OSError, IOError) as e:
185
- if e.errno == errno.ENOENT:
186
- return False
187
- else:
188
- display.warning("error in '%s' cache plugin while trying to stat %s : %s" % (self.plugin_name, cachefile, to_bytes(e)))
189
- return False
183
+ except FileNotFoundError:
184
+ return False
185
+ except OSError as ex:
186
+ display.error_as_warning(f"Error in {self.plugin_name!r} cache plugin while trying to stat {cachefile!r}.", exception=ex)
187
+
188
+ return False
190
189
 
191
190
  if time.time() - st.st_mtime <= self._timeout:
192
191
  return False
@@ -223,11 +222,10 @@ class BaseFileCacheModule(BaseCacheModule):
223
222
  try:
224
223
  os.stat(cachefile)
225
224
  return True
226
- except (OSError, IOError) as e:
227
- if e.errno == errno.ENOENT:
228
- return False
229
- else:
230
- display.warning("error in '%s' cache plugin while trying to stat %s : %s" % (self.plugin_name, cachefile, to_bytes(e)))
225
+ except FileNotFoundError:
226
+ return False
227
+ except OSError as ex:
228
+ display.error_as_warning(f"Error in {self.plugin_name!r} cache plugin while trying to stat {cachefile!r}.", exception=ex)
231
229
 
232
230
  def delete(self, key):
233
231
  try:
@@ -236,7 +234,7 @@ class BaseFileCacheModule(BaseCacheModule):
236
234
  pass
237
235
  try:
238
236
  os.remove(self._get_cache_file_name(key))
239
- except (OSError, IOError):
237
+ except OSError:
240
238
  pass # TODO: only pass on non existing?
241
239
 
242
240
  def flush(self):