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.
- ansible/_internal/_ansiballz/__init__.py +0 -0
- ansible/_internal/_ansiballz/_builder.py +101 -0
- ansible/_internal/{_ansiballz.py → _ansiballz/_wrapper.py} +11 -11
- ansible/_internal/_templating/_jinja_bits.py +22 -4
- ansible/_internal/_templating/_jinja_common.py +1 -1
- ansible/_internal/_templating/_jinja_plugins.py +5 -2
- ansible/_internal/_templating/_template_vars.py +72 -0
- ansible/_internal/_templating/_transform.py +6 -0
- ansible/_internal/_yaml/_constructor.py +4 -4
- ansible/_internal/_yaml/_dumper.py +26 -18
- ansible/cli/__init__.py +9 -14
- ansible/cli/adhoc.py +6 -3
- ansible/cli/arguments/option_helpers.py +1 -1
- ansible/cli/console.py +2 -2
- ansible/cli/doc.py +4 -4
- ansible/cli/inventory.py +5 -7
- ansible/config/base.yml +33 -6
- ansible/errors/__init__.py +2 -1
- ansible/executor/module_common.py +75 -44
- ansible/executor/powershell/psrp_put_file.ps1 +1 -1
- ansible/executor/process/worker.py +2 -2
- ansible/executor/task_executor.py +2 -2
- ansible/executor/task_queue_manager.py +34 -70
- ansible/executor/task_result.py +1 -1
- ansible/galaxy/api.py +3 -6
- ansible/galaxy/collection/__init__.py +1 -6
- ansible/galaxy/collection/concrete_artifact_manager.py +4 -10
- ansible/galaxy/dependency_resolution/providers.py +3 -3
- ansible/galaxy/role.py +2 -2
- ansible/inventory/group.py +6 -1
- ansible/inventory/host.py +6 -1
- ansible/module_utils/_internal/__init__.py +7 -4
- ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
- ansible/module_utils/_internal/{_ansiballz.py → _ansiballz/_loader.py} +10 -38
- ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
- ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
- ansible/module_utils/_internal/_datatag/__init__.py +23 -1
- ansible/module_utils/_internal/_deprecator.py +39 -34
- ansible/module_utils/_internal/_json/_profiles/__init__.py +1 -0
- ansible/module_utils/_internal/_messages.py +26 -2
- ansible/module_utils/_internal/_plugin_info.py +14 -1
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +58 -70
- ansible/module_utils/common/respawn.py +4 -41
- ansible/module_utils/common/yaml.py +1 -1
- ansible/module_utils/connection.py +8 -11
- ansible/module_utils/csharp/Ansible.Basic.cs +1 -1
- ansible/module_utils/csharp/Ansible.Privilege.cs +2 -2
- ansible/module_utils/facts/hardware/base.py +1 -1
- ansible/module_utils/facts/hardware/linux.py +1 -1
- ansible/module_utils/facts/other/facter.py +1 -1
- ansible/module_utils/facts/sysctl.py +4 -6
- ansible/module_utils/facts/system/caps.py +2 -2
- ansible/module_utils/facts/system/distribution.py +2 -2
- ansible/module_utils/facts/system/local.py +1 -1
- ansible/module_utils/facts/virtual/linux.py +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1 +1 -1
- ansible/module_utils/service.py +1 -1
- ansible/module_utils/urls.py +5 -5
- ansible/modules/apt.py +9 -3
- ansible/modules/apt_repository.py +10 -10
- ansible/modules/assemble.py +7 -5
- ansible/modules/async_wrapper.py +7 -17
- ansible/modules/command.py +3 -3
- ansible/modules/copy.py +4 -4
- ansible/modules/cron.py +1 -1
- ansible/modules/expect.py +5 -5
- ansible/modules/file.py +16 -17
- ansible/modules/find.py +3 -3
- ansible/modules/get_url.py +17 -0
- ansible/modules/git.py +9 -7
- ansible/modules/hostname.py +2 -2
- ansible/modules/known_hosts.py +12 -14
- ansible/modules/package.py +6 -0
- ansible/modules/pip.py +9 -11
- ansible/modules/raw.py +2 -2
- ansible/modules/replace.py +2 -2
- ansible/modules/slurp.py +10 -13
- ansible/modules/stat.py +6 -8
- ansible/modules/unarchive.py +6 -6
- ansible/modules/user.py +1 -1
- ansible/modules/wait_for.py +38 -33
- ansible/modules/yum_repository.py +4 -3
- ansible/parsing/dataloader.py +2 -2
- ansible/parsing/mod_args.py +38 -20
- ansible/parsing/vault/__init__.py +9 -13
- ansible/playbook/base.py +7 -4
- ansible/playbook/helpers.py +1 -1
- ansible/playbook/included_file.py +3 -1
- ansible/playbook/play_context.py +2 -0
- ansible/playbook/playbook_include.py +23 -56
- ansible/playbook/role/__init__.py +38 -21
- ansible/playbook/taggable.py +19 -5
- ansible/playbook/task.py +2 -0
- ansible/plugins/action/__init__.py +2 -2
- ansible/plugins/action/assemble.py +2 -1
- ansible/plugins/action/assert.py +2 -2
- ansible/plugins/action/fetch.py +3 -3
- ansible/plugins/action/script.py +5 -4
- ansible/plugins/action/template.py +9 -3
- ansible/plugins/cache/__init__.py +17 -19
- ansible/plugins/callback/__init__.py +77 -87
- ansible/plugins/callback/default.py +0 -3
- ansible/plugins/callback/junit.py +0 -6
- ansible/plugins/callback/tree.py +5 -5
- ansible/plugins/connection/local.py +4 -4
- ansible/plugins/connection/paramiko_ssh.py +5 -5
- ansible/plugins/connection/ssh.py +9 -7
- ansible/plugins/connection/winrm.py +1 -1
- ansible/plugins/filter/core.py +19 -21
- ansible/plugins/filter/encryption.py +10 -2
- ansible/plugins/filter/pow.yml +1 -1
- ansible/plugins/filter/root.yml +1 -1
- ansible/plugins/filter/strftime.yml +3 -3
- ansible/plugins/filter/to_uuid.yml +1 -1
- ansible/plugins/inventory/script.py +1 -1
- ansible/plugins/list.py +5 -4
- ansible/plugins/loader.py +5 -0
- ansible/plugins/lookup/password.py +4 -6
- ansible/plugins/lookup/template.py +9 -4
- ansible/plugins/shell/powershell.py +3 -2
- ansible/plugins/shell/sh.py +3 -2
- ansible/plugins/strategy/__init__.py +3 -3
- ansible/plugins/test/core.py +2 -2
- ansible/release.py +1 -1
- ansible/template/__init__.py +9 -53
- ansible/utils/collection_loader/_collection_finder.py +3 -3
- ansible/utils/display.py +38 -37
- ansible/utils/galaxy.py +2 -2
- ansible/utils/hashing.py +6 -7
- ansible/utils/path.py +6 -8
- ansible/utils/py3compat.py +2 -1
- ansible/utils/ssh_functions.py +3 -2
- ansible/utils/vars.py +4 -1
- ansible/vars/manager.py +6 -3
- ansible/vars/plugins.py +3 -3
- ansible/vars/reserved.py +6 -4
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/RECORD +184 -173
- ansible_test/_internal/__init__.py +5 -0
- ansible_test/_internal/ansible_util.py +1 -1
- ansible_test/_internal/classification/python.py +6 -0
- ansible_test/_internal/cli/commands/__init__.py +0 -5
- ansible_test/_internal/cli/environments.py +51 -5
- ansible_test/_internal/commands/coverage/__init__.py +1 -1
- ansible_test/_internal/commands/integration/__init__.py +18 -5
- ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
- ansible_test/_internal/commands/integration/coverage.py +7 -2
- ansible_test/_internal/commands/sanity/__init__.py +3 -1
- ansible_test/_internal/commands/sanity/integration_aliases.py +11 -0
- ansible_test/_internal/commands/shell/__init__.py +43 -4
- ansible_test/_internal/commands/units/__init__.py +4 -1
- ansible_test/_internal/config.py +21 -13
- ansible_test/_internal/debugging.py +166 -0
- ansible_test/_internal/delegation.py +21 -13
- ansible_test/_internal/host_profiles.py +259 -16
- ansible_test/_internal/inventory.py +4 -0
- ansible_test/_internal/metadata.py +94 -4
- ansible_test/_internal/processes.py +80 -0
- ansible_test/_internal/provisioning.py +10 -4
- ansible_test/_internal/python_requirements.py +27 -0
- ansible_test/_internal/ssh.py +1 -5
- ansible_test/_internal/target.py +8 -0
- ansible_test/_internal/thread.py +2 -1
- ansible_test/_internal/timeout.py +1 -1
- ansible_test/_internal/util.py +20 -12
- ansible_test/_internal/util_common.py +13 -3
- ansible_test/_util/target/injector/python.py +8 -0
- ansible_test/_util/target/setup/requirements.py +3 -9
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {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"]
|
ansible/errors/__init__.py
CHANGED
@@ -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.
|
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
|
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
|
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
|
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
|
-
#
|
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
|
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(
|
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(
|
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]
|
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(
|
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
|
-
|
910
|
+
zip_module_path = '/'.join(module_path_parts) + '.py'
|
888
911
|
zf.writestr(
|
889
|
-
_make_zinfo(
|
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,
|
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
|
-
|
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(
|
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
|
-
|
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=
|
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
|
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
|
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
|
-
|
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(
|
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
|
-
|
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
|
-
#
|
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
|
142
|
-
# attempting to log a debug message may trigger another
|
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/
|
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
|
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
|
-
|
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.
|
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.
|
200
|
+
if self._callback_plugins:
|
203
201
|
return
|
204
202
|
|
205
|
-
|
206
|
-
|
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
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
256
|
-
|
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
|
-
|
274
|
-
|
275
|
-
|
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
|
283
|
-
display.
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
455
|
-
|
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
|
-
|
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
|
ansible/executor/task_result.py
CHANGED
@@ -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
|
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
|
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
|
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:
|