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
ansible/config/base.yml CHANGED
@@ -1,6 +1,26 @@
1
1
  # Copyright (c) 2017 Ansible Project
2
2
  # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
3
3
  ---
4
+ _ANSIBALLZ_COVERAGE_CONFIG:
5
+ name: Configure the AnsiballZ code coverage extension
6
+ description:
7
+ - Enables and configures the AnsiballZ code coverage extension.
8
+ - This is for internal use only.
9
+ env:
10
+ - {name: _ANSIBLE_ANSIBALLZ_COVERAGE_CONFIG}
11
+ vars:
12
+ - {name: _ansible_ansiballz_coverage_config}
13
+ version_added: '2.19'
14
+ _ANSIBALLZ_DEBUGGER_CONFIG:
15
+ name: Configure the AnsiballZ remote debugging extension
16
+ description:
17
+ - Enables and configures the AnsiballZ remote debugging extension.
18
+ - This is for internal use only.
19
+ env:
20
+ - {name: _ANSIBLE_ANSIBALLZ_DEBUGGER_CONFIG}
21
+ vars:
22
+ - {name: _ansible_ansiballz_debugger_config}
23
+ version_added: '2.19'
4
24
  _ANSIBLE_CONNECTION_PATH:
5
25
  env:
6
26
  - name: _ANSIBLE_CONNECTION_PATH
@@ -21,6 +41,15 @@ _CALLBACK_DISPATCH_ERROR_BEHAVIOR:
21
41
  ignore: just continue silently
22
42
  env: [ { name: _ANSIBLE_CALLBACK_DISPATCH_ERROR_BEHAVIOR } ]
23
43
  version_added: '2.19'
44
+ _MODULE_METADATA:
45
+ name: Enable experimental module metadata
46
+ description:
47
+ - Enables experimental module-level metadata controls for serialization profile selection.
48
+ - This is for internal use only.
49
+ type: boolean
50
+ default: false
51
+ env: [ { name: _ANSIBLE_MODULE_METADATA } ]
52
+ version_added: '2.19'
24
53
  ALLOW_BROKEN_CONDITIONALS:
25
54
  # This config option will be deprecated once it no longer has any effect (2.23).
26
55
  name: Allow broken conditionals
@@ -906,6 +935,10 @@ DEFAULT_MANAGED_STR:
906
935
  ini:
907
936
  - {key: ansible_managed, section: defaults}
908
937
  yaml: {key: defaults.ansible_managed}
938
+ deprecated:
939
+ why: The `ansible_managed` variable can be set just like any other variable, or a different variable can be used.
940
+ version: "2.23"
941
+ alternatives: Set the `ansible_managed` variable, or use any custom variable in templates.
909
942
  DEFAULT_MODULE_ARGS:
910
943
  name: Adhoc default arguments
911
944
  default: ~
@@ -2152,12 +2185,6 @@ WIN_ASYNC_STARTUP_TIMEOUT:
2152
2185
  vars:
2153
2186
  - {name: ansible_win_async_startup_timeout}
2154
2187
  version_added: '2.10'
2155
- WRAP_STDERR:
2156
- description: Control line-wrapping behavior on console warnings and errors from default output callbacks (eases pattern-based output testing)
2157
- env: [{name: ANSIBLE_WRAP_STDERR}]
2158
- default: false
2159
- type: bool
2160
- version_added: "2.19"
2161
2188
  YAML_FILENAME_EXTENSIONS:
2162
2189
  name: Valid YAML extensions
2163
2190
  default: [".yml", ".yaml", ".json"]
@@ -95,8 +95,9 @@ class AnsibleError(Exception):
95
95
  self._show_content = False
96
96
 
97
97
  Display().deprecated(
98
- msg=f"The `suppress_extended_error` argument to `{type(self).__name__}` is deprecated. Use `show_content=False` instead.",
98
+ msg=f"The `suppress_extended_error` argument to `{type(self).__name__}` is deprecated.",
99
99
  version="2.23",
100
+ help_text="Use `show_content=False` instead.",
100
101
  )
101
102
 
102
103
  @property
@@ -37,6 +37,8 @@ from ast import AST, Import, ImportFrom
37
37
  from io import BytesIO
38
38
 
39
39
  from ansible._internal import _locking
40
+ from ansible._internal._ansiballz import _builder
41
+ from ansible._internal import _ansiballz
40
42
  from ansible._internal._datatag import _utils
41
43
  from ansible.module_utils._internal import _dataclass_validation
42
44
  from ansible.module_utils.common.yaml import yaml_load
@@ -54,7 +56,8 @@ from ansible.plugins.loader import module_utils_loader
54
56
  from ansible._internal._templating._engine import TemplateOptions, TemplateEngine
55
57
  from ansible.template import Templar
56
58
  from ansible.utils.collection_loader._collection_finder import _get_collection_metadata, _nested_dict_get
57
- from ansible.module_utils._internal import _json, _ansiballz
59
+ from ansible.module_utils._internal import _json
60
+ from ansible.module_utils._internal._ansiballz import _loader
58
61
  from ansible.module_utils import basic as _basic
59
62
 
60
63
  if t.TYPE_CHECKING:
@@ -117,7 +120,7 @@ def _strip_comments(source: str) -> str:
117
120
 
118
121
 
119
122
  def _read_ansiballz_code() -> str:
120
- code = (pathlib.Path(__file__).parent.parent / '_internal/_ansiballz.py').read_text()
123
+ code = (pathlib.Path(_ansiballz.__file__).parent / '_wrapper.py').read_text()
121
124
 
122
125
  if not C.DEFAULT_KEEP_REMOTE_FILES:
123
126
  # Keep comments when KEEP_REMOTE_FILES is set. That way users will see
@@ -361,7 +364,7 @@ def _get_shebang(interpreter, task_vars, templar: _template.Templar, args=tuple(
361
364
  options=TemplateOptions(value_for_omit=None))
362
365
 
363
366
  if not interpreter_out:
364
- # nothing matched(None) or in case someone configures empty string or empty intepreter
367
+ # nothing matched(None) or in case someone configures empty string or empty interpreter
365
368
  interpreter_out = interpreter
366
369
 
367
370
  # set shebang
@@ -656,9 +659,14 @@ metadata_versions: dict[t.Any, type[ModuleMetadata]] = {
656
659
  1: ModuleMetadataV1,
657
660
  }
658
661
 
662
+ _DEFAULT_LEGACY_METADATA = ModuleMetadataV1(serialization_profile='legacy')
663
+
659
664
 
660
665
  def _get_module_metadata(module: ast.Module) -> ModuleMetadata:
661
- # DTFIX2: while module metadata works, this feature isn't fully baked and should be turned off before release
666
+ # experimental module metadata; off by default
667
+ if not C.config.get_config_value('_MODULE_METADATA'):
668
+ return _DEFAULT_LEGACY_METADATA
669
+
662
670
  metadata_nodes: list[ast.Assign] = []
663
671
 
664
672
  for node in module.body:
@@ -671,9 +679,7 @@ def _get_module_metadata(module: ast.Module) -> ModuleMetadata:
671
679
  metadata_nodes.append(node)
672
680
 
673
681
  if not metadata_nodes:
674
- return ModuleMetadataV1(
675
- serialization_profile='legacy',
676
- )
682
+ return _DEFAULT_LEGACY_METADATA
677
683
 
678
684
  if len(metadata_nodes) > 1:
679
685
  raise ValueError('Module METADATA must defined only once.')
@@ -709,7 +715,14 @@ def _get_module_metadata(module: ast.Module) -> ModuleMetadata:
709
715
  return metadata
710
716
 
711
717
 
712
- def recursive_finder(name: str, module_fqn: str, module_data: str | bytes, zf: zipfile.ZipFile, date_time: datetime.datetime) -> ModuleMetadata:
718
+ def recursive_finder(
719
+ name: str,
720
+ module_fqn: str,
721
+ module_data: str | bytes,
722
+ zf: zipfile.ZipFile,
723
+ date_time: datetime.datetime,
724
+ extension_manager: _builder.ExtensionManager,
725
+ ) -> ModuleMetadata:
713
726
  """
714
727
  Using ModuleDepFinder, make sure we have all of the module_utils files that
715
728
  the module and its module_utils files needs. (no longer actually recursive)
@@ -755,12 +768,14 @@ def recursive_finder(name: str, module_fqn: str, module_data: str | bytes, zf: z
755
768
 
756
769
  # include module_utils that are always required
757
770
  modules_to_process.extend((
758
- _ModuleUtilsProcessEntry.from_module(_ansiballz),
771
+ _ModuleUtilsProcessEntry.from_module(_loader),
759
772
  _ModuleUtilsProcessEntry.from_module(_basic),
760
773
  _ModuleUtilsProcessEntry.from_module_name(_json.get_module_serialization_profile_module_name(profile, True)),
761
774
  _ModuleUtilsProcessEntry.from_module_name(_json.get_module_serialization_profile_module_name(profile, False)),
762
775
  ))
763
776
 
777
+ modules_to_process.extend(_ModuleUtilsProcessEntry.from_module_name(name) for name in extension_manager.module_names)
778
+
764
779
  module_info: ModuleUtilLocatorBase
765
780
 
766
781
  # we'll be adding new modules inline as we discover them, so just keep going til we've processed them all
@@ -815,12 +830,13 @@ def recursive_finder(name: str, module_fqn: str, module_data: str | bytes, zf: z
815
830
  modules_to_process.append(_ModuleUtilsProcessEntry(normalized_name, False, module_info.redirected, is_optional=entry.is_optional))
816
831
 
817
832
  for py_module_name in py_module_cache:
818
- py_module_file_name = py_module_cache[py_module_name][1]
833
+ source_code, py_module_file_name = py_module_cache[py_module_name]
834
+
835
+ zf.writestr(_make_zinfo(py_module_file_name, date_time, zf=zf), source_code)
836
+
837
+ if extension_manager.debugger_enabled and (origin := Origin.get_tag(source_code)) and origin.path:
838
+ extension_manager.source_mapping[origin.path] = py_module_file_name
819
839
 
820
- zf.writestr(
821
- _make_zinfo(py_module_file_name, date_time, zf=zf),
822
- py_module_cache[py_module_name][0]
823
- )
824
840
  mu_file = to_text(py_module_file_name, errors='surrogate_or_strict')
825
841
  display.vvvvv("Including module_utils file %s" % mu_file)
826
842
 
@@ -879,17 +895,27 @@ def _get_ansible_module_fqn(module_path):
879
895
  return remote_module_fqn
880
896
 
881
897
 
882
- def _add_module_to_zip(zf: zipfile.ZipFile, date_time: datetime.datetime, remote_module_fqn: str, b_module_data: bytes) -> None:
898
+ def _add_module_to_zip(
899
+ zf: zipfile.ZipFile,
900
+ date_time: datetime.datetime,
901
+ remote_module_fqn: str,
902
+ b_module_data: bytes,
903
+ module_path: str,
904
+ extension_manager: _builder.ExtensionManager,
905
+ ) -> None:
883
906
  """Add a module from ansible or from an ansible collection into the module zip"""
884
907
  module_path_parts = remote_module_fqn.split('.')
885
908
 
886
909
  # Write the module
887
- module_path = '/'.join(module_path_parts) + '.py'
910
+ zip_module_path = '/'.join(module_path_parts) + '.py'
888
911
  zf.writestr(
889
- _make_zinfo(module_path, date_time, zf=zf),
912
+ _make_zinfo(zip_module_path, date_time, zf=zf),
890
913
  b_module_data
891
914
  )
892
915
 
916
+ if extension_manager.debugger_enabled:
917
+ extension_manager.source_mapping[module_path] = zip_module_path
918
+
893
919
  existing_paths: frozenset[str]
894
920
 
895
921
  # Write the __init__.py's necessary to get there
@@ -932,6 +958,8 @@ class _CachedModule:
932
958
 
933
959
  zip_data: bytes
934
960
  metadata: ModuleMetadata
961
+ source_mapping: dict[str, str]
962
+ """A mapping of controller absolute source locations to target relative source locations within the AnsiballZ payload."""
935
963
 
936
964
  def dump(self, path: str) -> None:
937
965
  temp_path = pathlib.Path(path + '-part')
@@ -1029,6 +1057,7 @@ def _find_module_utils(
1029
1057
 
1030
1058
  if module_substyle == 'python':
1031
1059
  date_time = datetime.datetime.now(datetime.timezone.utc)
1060
+
1032
1061
  if date_time.year < 1980:
1033
1062
  raise AnsibleError(f'Cannot create zipfile due to pre-1980 configured date: {date_time}')
1034
1063
 
@@ -1038,19 +1067,19 @@ def _find_module_utils(
1038
1067
  display.warning(u'Bad module compression string specified: %s. Using ZIP_STORED (no compression)' % module_compression)
1039
1068
  compression_method = zipfile.ZIP_STORED
1040
1069
 
1070
+ extension_manager = _builder.ExtensionManager.create(task_vars=task_vars)
1071
+ extension_key = '~'.join(extension_manager.extension_names) if extension_manager.extension_names else 'none'
1041
1072
  lookup_path = os.path.join(C.DEFAULT_LOCAL_TMP, 'ansiballz_cache') # type: ignore[attr-defined]
1042
- cached_module_filename = os.path.join(lookup_path, "%s-%s" % (remote_module_fqn, module_compression))
1073
+ cached_module_filename = os.path.join(lookup_path, '-'.join((remote_module_fqn, module_compression, extension_key)))
1043
1074
 
1044
1075
  os.makedirs(os.path.dirname(cached_module_filename), exist_ok=True)
1045
1076
 
1046
- zipdata: bytes | None = None
1047
- module_metadata: ModuleMetadata | None = None
1077
+ cached_module: _CachedModule | None = None
1048
1078
 
1049
1079
  # Optimization -- don't lock if the module has already been cached
1050
1080
  if os.path.exists(cached_module_filename):
1051
1081
  display.debug('ANSIBALLZ: using cached module: %s' % cached_module_filename)
1052
1082
  cached_module = _CachedModule.load(cached_module_filename)
1053
- zipdata, module_metadata = cached_module.zip_data, cached_module.metadata
1054
1083
  else:
1055
1084
  display.debug('ANSIBALLZ: Acquiring lock')
1056
1085
  lock_path = f'{cached_module_filename}.lock'
@@ -1065,35 +1094,40 @@ def _find_module_utils(
1065
1094
  zf = zipfile.ZipFile(zipoutput, mode='w', compression=compression_method)
1066
1095
 
1067
1096
  # walk the module imports, looking for module_utils to send- they'll be added to the zipfile
1068
- module_metadata = recursive_finder(module_name, remote_module_fqn, Origin(path=module_path).tag(b_module_data), zf, date_time)
1097
+ module_metadata = recursive_finder(
1098
+ module_name,
1099
+ remote_module_fqn,
1100
+ Origin(path=module_path).tag(b_module_data),
1101
+ zf,
1102
+ date_time,
1103
+ extension_manager,
1104
+ )
1069
1105
 
1070
1106
  display.debug('ANSIBALLZ: Writing module into payload')
1071
- _add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data)
1107
+ _add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data, module_path, extension_manager)
1072
1108
 
1073
1109
  zf.close()
1074
- zipdata = base64.b64encode(zipoutput.getvalue())
1110
+ zip_data = base64.b64encode(zipoutput.getvalue())
1075
1111
 
1076
1112
  # Write the assembled module to a temp file (write to temp
1077
1113
  # so that no one looking for the file reads a partially
1078
1114
  # written file)
1079
1115
  os.makedirs(lookup_path, exist_ok=True)
1080
1116
  display.debug('ANSIBALLZ: Writing module')
1081
- cached_module = _CachedModule(zip_data=zipdata, metadata=module_metadata)
1117
+ cached_module = _CachedModule(zip_data=zip_data, metadata=module_metadata, source_mapping=extension_manager.source_mapping)
1082
1118
  cached_module.dump(cached_module_filename)
1083
1119
  display.debug('ANSIBALLZ: Done creating module')
1084
1120
 
1085
- if not zipdata:
1121
+ if not cached_module:
1086
1122
  display.debug('ANSIBALLZ: Reading module after lock')
1087
1123
  # Another process wrote the file while we were waiting for
1088
1124
  # the write lock. Go ahead and read the data from disk
1089
1125
  # instead of re-creating it.
1090
1126
  try:
1091
1127
  cached_module = _CachedModule.load(cached_module_filename)
1092
- except IOError:
1128
+ except OSError as ex:
1093
1129
  raise AnsibleError('A different worker process failed to create module file. '
1094
- 'Look at traceback for that process for debugging information.')
1095
-
1096
- zipdata, module_metadata = cached_module.zip_data, cached_module.metadata
1130
+ 'Look at traceback for that process for debugging information.') from ex
1097
1131
 
1098
1132
  o_interpreter, o_args = _extract_interpreter(b_module_data)
1099
1133
  if o_interpreter is None:
@@ -1107,40 +1141,36 @@ def _find_module_utils(
1107
1141
  if not isinstance(rlimit_nofile, int):
1108
1142
  rlimit_nofile = int(templar._engine.template(rlimit_nofile, options=TemplateOptions(value_for_omit=0)))
1109
1143
 
1110
- coverage_config = os.environ.get('_ANSIBLE_COVERAGE_CONFIG')
1111
-
1112
- if coverage_config:
1113
- coverage_output = os.environ['_ANSIBLE_COVERAGE_OUTPUT']
1114
- else:
1115
- coverage_output = None
1116
-
1117
- if not isinstance(module_metadata, ModuleMetadataV1):
1144
+ if not isinstance(cached_module.metadata, ModuleMetadataV1):
1118
1145
  raise NotImplementedError()
1119
1146
 
1120
1147
  params = dict(ANSIBLE_MODULE_ARGS=module_args,)
1121
- encoder = get_module_encoder(module_metadata.serialization_profile, Direction.CONTROLLER_TO_MODULE)
1148
+ encoder = get_module_encoder(cached_module.metadata.serialization_profile, Direction.CONTROLLER_TO_MODULE)
1149
+
1122
1150
  try:
1123
1151
  encoded_params = json.dumps(params, cls=encoder)
1124
1152
  except TypeError as ex:
1125
1153
  raise AnsibleError(f'Failed to serialize arguments for the {module_name!r} module.') from ex
1126
1154
 
1155
+ extension_manager.source_mapping = cached_module.source_mapping
1156
+
1127
1157
  code = _get_ansiballz_code(shebang)
1128
1158
  args = dict(
1129
- zipdata=to_text(zipdata),
1130
1159
  ansible_module=module_name,
1131
1160
  module_fqn=remote_module_fqn,
1132
- params=encoded_params,
1133
- profile=module_metadata.serialization_profile,
1161
+ profile=cached_module.metadata.serialization_profile,
1134
1162
  date_time=date_time,
1135
- coverage_config=coverage_config,
1136
- coverage_output=coverage_output,
1137
1163
  rlimit_nofile=rlimit_nofile,
1164
+ params=encoded_params,
1165
+ extensions=extension_manager.get_extensions(),
1166
+ zip_data=to_text(cached_module.zip_data),
1138
1167
  )
1139
1168
 
1140
1169
  args_string = '\n'.join(f'{key}={value!r},' for key, value in args.items())
1141
1170
 
1142
1171
  wrapper = f"""{code}
1143
1172
 
1173
+
1144
1174
  if __name__ == "__main__":
1145
1175
  _ansiballz_main(
1146
1176
  {args_string}
@@ -1149,6 +1179,7 @@ if __name__ == "__main__":
1149
1179
 
1150
1180
  output.write(to_bytes(wrapper))
1151
1181
 
1182
+ module_metadata = cached_module.metadata
1152
1183
  b_module_data = output.getvalue()
1153
1184
 
1154
1185
  elif module_substyle == 'powershell':
@@ -102,7 +102,7 @@ begin {
102
102
  Set-Property 'MaximumAllowedMemory' $null
103
103
  }
104
104
  catch {
105
- # Satify pslint, we purposefully ignore this error as it is not critical it works.
105
+ # Satisfy pslint, we purposefully ignore this error as it is not critical it works.
106
106
  $null = $null
107
107
  }
108
108
  }
@@ -138,8 +138,8 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
138
138
  try:
139
139
  display.debug(u"WORKER HARD EXIT: %s" % to_text(e))
140
140
  except BaseException:
141
- # If the cause of the fault is IOError being generated by stdio,
142
- # attempting to log a debug message may trigger another IOError.
141
+ # If the cause of the fault is OSError being generated by stdio,
142
+ # attempting to log a debug message may trigger another OSError.
143
143
  # Try printing once then give up.
144
144
  pass
145
145
 
@@ -872,7 +872,7 @@ class TaskExecutor:
872
872
  async_result = async_handler.run(task_vars=task_vars)
873
873
  # We do not bail out of the loop in cases where the failure
874
874
  # is associated with a parsing error. The async_runner can
875
- # have issues which result in a half-written/unparseable result
875
+ # have issues which result in a half-written/unparsable result
876
876
  # file on disk, which manifests to the user as a timeout happening
877
877
  # before it's time to timeout.
878
878
  if (async_result.get('finished', False) or
@@ -910,7 +910,7 @@ class TaskExecutor:
910
910
  if async_result.get('_ansible_parsed'):
911
911
  return dict(failed=True, msg="async task did not complete within the requested time - %ss" % self._task.async_val, async_result=async_result)
912
912
  else:
913
- return dict(failed=True, msg="async task produced unparseable results", async_result=async_result)
913
+ return dict(failed=True, msg="async task produced unparsable results", async_result=async_result)
914
914
  else:
915
915
  # If the async task finished, automatically cleanup the temporary
916
916
  # status file left behind.
@@ -34,7 +34,6 @@ from ansible.executor.play_iterator import PlayIterator
34
34
  from ansible.executor.stats import AggregateStats
35
35
  from ansible.executor.task_result import _RawTaskResult, _WireTaskResult
36
36
  from ansible.inventory.data import InventoryData
37
- from ansible.module_utils.six import string_types
38
37
  from ansible.module_utils.common.text.converters import to_native
39
38
  from ansible.parsing.dataloader import DataLoader
40
39
  from ansible.playbook.play_context import PlayContext
@@ -139,7 +138,7 @@ class TaskQueueManager:
139
138
  variable_manager: VariableManager,
140
139
  loader: DataLoader,
141
140
  passwords: dict[str, str | None],
142
- stdout_callback: str | None = None,
141
+ stdout_callback_name: str | None = None,
143
142
  run_additional_callbacks: bool = True,
144
143
  run_tree: bool = False,
145
144
  forks: int | None = None,
@@ -149,12 +148,11 @@ class TaskQueueManager:
149
148
  self._loader = loader
150
149
  self._stats = AggregateStats()
151
150
  self.passwords = passwords
152
- self._stdout_callback: str | None | CallbackBase = stdout_callback
151
+ self._stdout_callback_name: str | None = stdout_callback_name or C.DEFAULT_STDOUT_CALLBACK
153
152
  self._run_additional_callbacks = run_additional_callbacks
154
153
  self._run_tree = run_tree
155
154
  self._forks = forks or 5
156
155
 
157
- self._callbacks_loaded = False
158
156
  self._callback_plugins: list[CallbackBase] = []
159
157
  self._start_at_done = False
160
158
 
@@ -199,44 +197,40 @@ class TaskQueueManager:
199
197
  only one such callback plugin will be loaded.
200
198
  """
201
199
 
202
- if self._callbacks_loaded:
200
+ if self._callback_plugins:
203
201
  return
204
202
 
205
- stdout_callback_loaded = False
206
- if self._stdout_callback is None:
207
- self._stdout_callback = C.DEFAULT_STDOUT_CALLBACK
203
+ if not self._stdout_callback_name:
204
+ raise AnsibleError("No stdout callback name provided.")
208
205
 
209
- if isinstance(self._stdout_callback, CallbackBase):
210
- stdout_callback_loaded = True
211
- elif isinstance(self._stdout_callback, string_types):
212
- if self._stdout_callback not in callback_loader:
213
- raise AnsibleError("Invalid callback for stdout specified: %s" % self._stdout_callback)
214
- else:
215
- self._stdout_callback = callback_loader.get(self._stdout_callback)
216
- self._stdout_callback.set_options()
217
- stdout_callback_loaded = True
218
- else:
219
- raise AnsibleError("callback must be an instance of CallbackBase or the name of a callback plugin")
206
+ stdout_callback = callback_loader.get(self._stdout_callback_name)
207
+
208
+ if not stdout_callback:
209
+ raise AnsibleError(f"Could not load {self._stdout_callback_name!r} callback plugin.")
210
+
211
+ stdout_callback._init_callback_methods()
212
+ stdout_callback.set_options()
213
+
214
+ self._callback_plugins.append(stdout_callback)
220
215
 
221
216
  # get all configured loadable callbacks (adjacent, builtin)
222
- callback_list = list(callback_loader.all(class_only=True))
217
+ plugin_types = {plugin_type.ansible_name: plugin_type for plugin_type in callback_loader.all(class_only=True)}
223
218
 
224
219
  # add enabled callbacks that refer to collections, which might not appear in normal listing
225
220
  for c in C.CALLBACKS_ENABLED:
226
221
  # load all, as collection ones might be using short/redirected names and not a fqcn
227
222
  plugin = callback_loader.get(c, class_only=True)
228
223
 
229
- # TODO: check if this skip is redundant, loader should handle bad file/plugin cases already
230
224
  if plugin:
231
225
  # avoids incorrect and dupes possible due to collections
232
- if plugin not in callback_list:
233
- callback_list.append(plugin)
226
+ plugin_types.setdefault(plugin.ansible_name, plugin)
234
227
  else:
235
228
  display.warning("Skipping callback plugin '%s', unable to load" % c)
236
229
 
237
- # for each callback in the list see if we should add it to 'active callbacks' used in the play
238
- for callback_plugin in callback_list:
230
+ plugin_types.pop(stdout_callback.ansible_name, None)
239
231
 
232
+ # for each callback in the list see if we should add it to 'active callbacks' used in the play
233
+ for callback_plugin in plugin_types.values():
240
234
  callback_type = getattr(callback_plugin, 'CALLBACK_TYPE', '')
241
235
  callback_needs_enabled = getattr(callback_plugin, 'CALLBACK_NEEDS_ENABLED', getattr(callback_plugin, 'CALLBACK_NEEDS_WHITELIST', False))
242
236
 
@@ -252,10 +246,8 @@ class TaskQueueManager:
252
246
  display.vvvvv("Attempting to use '%s' callback." % (callback_name))
253
247
  if callback_type == 'stdout':
254
248
  # we only allow one callback of type 'stdout' to be loaded,
255
- if callback_name != self._stdout_callback or stdout_callback_loaded:
256
- display.vv("Skipping callback '%s', as we already have a stdout callback." % (callback_name))
257
- continue
258
- stdout_callback_loaded = True
249
+ display.vv("Skipping callback '%s', as we already have a stdout callback." % (callback_name))
250
+ continue
259
251
  elif callback_name == 'tree' and self._run_tree:
260
252
  # TODO: remove special case for tree, which is an adhoc cli option --tree
261
253
  pass
@@ -270,21 +262,16 @@ class TaskQueueManager:
270
262
  # avoid bad plugin not returning an object, only needed cause we do class_only load and bypass loader checks,
271
263
  # really a bug in the plugin itself which we ignore as callback errors are not supposed to be fatal.
272
264
  if callback_obj:
273
- # skip initializing if we already did the work for the same plugin (even with diff names)
274
- if callback_obj not in self._callback_plugins:
275
- callback_obj.set_options()
276
- self._callback_plugins.append(callback_obj)
277
- else:
278
- display.vv("Skipping callback '%s', already loaded as '%s'." % (callback_plugin, callback_name))
265
+ callback_obj._init_callback_methods()
266
+ callback_obj.set_options()
267
+ self._callback_plugins.append(callback_obj)
279
268
  else:
280
269
  display.warning("Skipping callback '%s', as it does not create a valid plugin instance." % callback_name)
281
270
  continue
282
- except Exception as e:
283
- display.warning("Skipping callback '%s', unable to load due to: %s" % (callback_name, to_native(e)))
271
+ except Exception as ex:
272
+ display.warning_as_error(f"Failed to load callback plugin {callback_name!r}.", exception=ex)
284
273
  continue
285
274
 
286
- self._callbacks_loaded = True
287
-
288
275
  def run(self, play):
289
276
  """
290
277
  Iterates over the roles/tasks in a play, using the given (or default)
@@ -294,8 +281,7 @@ class TaskQueueManager:
294
281
  are done with the current task).
295
282
  """
296
283
 
297
- if not self._callbacks_loaded:
298
- self.load_callbacks()
284
+ self.load_callbacks()
299
285
 
300
286
  all_vars = self._variable_manager.get_vars(play=play)
301
287
  templar = TemplateEngine(loader=self._loader, variables=all_vars)
@@ -311,13 +297,9 @@ class TaskQueueManager:
311
297
  )
312
298
 
313
299
  play_context = PlayContext(new_play, self.passwords, self._connection_lockfile.fileno())
314
- if (self._stdout_callback and
315
- hasattr(self._stdout_callback, 'set_play_context')):
316
- self._stdout_callback.set_play_context(play_context)
317
300
 
318
301
  for callback_plugin in self._callback_plugins:
319
- if hasattr(callback_plugin, 'set_play_context'):
320
- callback_plugin.set_play_context(play_context)
302
+ callback_plugin.set_play_context(play_context)
321
303
 
322
304
  self.send_callback('v2_playbook_on_play_start', new_play)
323
305
 
@@ -437,7 +419,7 @@ class TaskQueueManager:
437
419
  @lock_decorator(attr='_callback_lock')
438
420
  def send_callback(self, method_name, *args, **kwargs):
439
421
  # We always send events to stdout callback first, rest should follow config order
440
- for callback_plugin in [self._stdout_callback] + self._callback_plugins:
422
+ for callback_plugin in self._callback_plugins:
441
423
  # a plugin that set self.disabled to True will not be called
442
424
  # see osx_say.py example for such a plugin
443
425
  if callback_plugin.disabled:
@@ -448,31 +430,13 @@ class TaskQueueManager:
448
430
  if not callback_plugin.wants_implicit_tasks and (task_arg := self._first_arg_of_type(Task, args)) and task_arg.implicit:
449
431
  continue
450
432
 
451
- # try to find v2 method, fallback to v1 method, ignore callback if no method found
452
433
  methods = []
453
434
 
454
- for possible in [method_name, 'v2_on_any']:
455
- method = getattr(callback_plugin, possible, None)
456
-
457
- if method is None:
458
- method = getattr(callback_plugin, possible.removeprefix('v2_'), None)
459
-
460
- if method is not None:
461
- display.deprecated(
462
- msg='The v1 callback API is deprecated.',
463
- version='2.23',
464
- help_text='Use `v2_` prefixed callback methods instead.',
465
- )
466
-
467
- if method is not None and not getattr(method, '_base_impl', False): # don't bother dispatching to the base impls
468
- if possible == 'v2_on_any':
469
- display.deprecated(
470
- msg='The `v2_on_any` callback method is deprecated.',
471
- version='2.23',
472
- help_text='Use event-specific callback methods instead.',
473
- )
435
+ if method_name in callback_plugin._implemented_callback_methods:
436
+ methods.append(getattr(callback_plugin, method_name))
474
437
 
475
- methods.append(method)
438
+ if 'v2_on_any' in callback_plugin._implemented_callback_methods:
439
+ methods.append(getattr(callback_plugin, 'v2_on_any'))
476
440
 
477
441
  for method in methods:
478
442
  # send clean copies
@@ -498,4 +462,4 @@ class TaskQueueManager:
498
462
  except Exception as ex:
499
463
  raise AnsibleCallbackError(f"Callback dispatch {method_name!r} failed for plugin {callback_plugin._load_name!r}.") from ex
500
464
 
501
- callback_plugin._current_task_result = None
465
+ callback_plugin._current_task_result = None # clear temporary instance storage hack
@@ -27,7 +27,7 @@ _SUB_PRESERVE = {'_ansible_delegated_vars': {'ansible_host', 'ansible_port', 'an
27
27
  CLEAN_EXCEPTIONS = (
28
28
  '_ansible_verbose_always', # for debug and other actions, to always expand data (pretty jsonification)
29
29
  '_ansible_item_label', # to know actual 'item' variable
30
- '_ansible_no_log', # jic we didnt clean up well enough, DON'T LOG
30
+ '_ansible_no_log', # jic we didn't clean up well enough, DON'T LOG
31
31
  '_ansible_verbose_override', # controls display of ansible_facts, gathering would be very noise with -v otherwise
32
32
  )
33
33
 
ansible/galaxy/api.py CHANGED
@@ -92,7 +92,7 @@ def g_connect(versions):
92
92
  try:
93
93
  data = self._call_galaxy(n_url, method='GET', error_context_msg=error_context_msg, cache=True)
94
94
  except (AnsibleError, GalaxyError, ValueError, KeyError) as err:
95
- # Either the URL doesnt exist, or other error. Or the URL exists, but isn't a galaxy API
95
+ # Either the URL doesn't exist, or other error. Or the URL exists, but isn't a galaxy API
96
96
  # root (not JSON, no 'available_versions') so try appending '/api/'
97
97
  if n_url.endswith('/api') or n_url.endswith('/api/'):
98
98
  raise
@@ -337,10 +337,7 @@ class GalaxyAPI:
337
337
  if not isinstance(other_galaxy_api, self.__class__):
338
338
  return NotImplemented
339
339
 
340
- return (
341
- self._priority > other_galaxy_api._priority or
342
- self.name < self.name
343
- )
340
+ return self._priority > other_galaxy_api._priority
344
341
 
345
342
  @property # type: ignore[misc] # https://github.com/python/mypy/issues/1362
346
343
  @g_connect(['v1', 'v2', 'v3'])
@@ -880,7 +877,7 @@ class GalaxyAPI:
880
877
  except GalaxyError as err:
881
878
  if err.http_code != 404:
882
879
  raise
883
- # v3 doesn't raise a 404 so we need to mimick the empty response from APIs that do.
880
+ # v3 doesn't raise a 404 so we need to mimic the empty response from APIs that do.
884
881
  return []
885
882
 
886
883
  if 'data' in data: