ansible-core 2.19.0b4__py3-none-any.whl → 2.19.0b6__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/__init__.py +1 -1
- 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/_collection_proxy.py +1 -1
- ansible/_internal/_errors/_alarm_timeout.py +66 -0
- ansible/_internal/_errors/_captured.py +25 -30
- ansible/_internal/_errors/_error_factory.py +89 -0
- ansible/_internal/_errors/_error_utils.py +240 -0
- ansible/_internal/_errors/_task_timeout.py +28 -0
- ansible/_internal/_event_formatting.py +127 -0
- ansible/_internal/_json/__init__.py +5 -5
- ansible/_internal/_json/_profiles/_cache_persistence.py +2 -0
- ansible/_internal/_json/_profiles/_inventory_legacy.py +1 -1
- ansible/_internal/_json/_profiles/_legacy.py +3 -11
- ansible/_internal/_ssh/__init__.py +0 -0
- ansible/_internal/_ssh/_agent_launch.py +91 -0
- ansible/{utils → _internal/_ssh}/_ssh_agent.py +55 -93
- ansible/_internal/_templating/__init__.py +5 -3
- ansible/_internal/_templating/_datatag.py +2 -1
- ansible/_internal/_templating/_engine.py +3 -4
- ansible/_internal/_templating/_jinja_bits.py +28 -20
- ansible/_internal/_templating/_jinja_common.py +18 -27
- ansible/_internal/_templating/_jinja_plugins.py +36 -5
- ansible/_internal/_templating/_lazy_containers.py +5 -5
- ansible/_internal/_templating/_template_vars.py +72 -0
- ansible/_internal/_templating/_transform.py +26 -19
- ansible/_internal/_templating/_utils.py +1 -1
- ansible/_internal/_yaml/_constructor.py +4 -4
- ansible/_internal/_yaml/_dumper.py +26 -18
- ansible/_internal/_yaml/_errors.py +7 -7
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
- ansible/cli/__init__.py +11 -93
- ansible/cli/arguments/option_helpers.py +3 -4
- ansible/cli/console.py +1 -1
- ansible/cli/doc.py +86 -30
- ansible/cli/inventory.py +5 -7
- ansible/compat/importlib_resources.py +9 -12
- ansible/config/base.yml +46 -0
- ansible/errors/__init__.py +98 -50
- ansible/executor/module_common.py +75 -49
- ansible/executor/powershell/async_watchdog.ps1 +2 -2
- ansible/executor/powershell/async_wrapper.ps1 +3 -3
- ansible/executor/powershell/become_wrapper.ps1 +20 -2
- ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
- ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
- ansible/executor/powershell/exec_wrapper.ps1 +219 -6
- ansible/executor/powershell/module_manifest.py +52 -0
- ansible/executor/powershell/module_wrapper.ps1 +47 -21
- ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
- ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
- ansible/executor/process/worker.py +40 -115
- ansible/executor/task_executor.py +26 -61
- ansible/executor/task_result.py +2 -4
- ansible/galaxy/api.py +1 -4
- ansible/galaxy/collection/__init__.py +2 -10
- ansible/galaxy/collection/concrete_artifact_manager.py +2 -8
- ansible/galaxy/role.py +2 -2
- ansible/inventory/manager.py +1 -1
- ansible/module_utils/_internal/__init__.py +7 -7
- ansible/module_utils/_internal/_ambient_context.py +3 -3
- 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} +13 -39
- 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 +43 -15
- ansible/module_utils/_internal/_datatag/_tags.py +2 -2
- ansible/module_utils/_internal/_deprecator.py +67 -55
- ansible/module_utils/_internal/_errors.py +88 -17
- ansible/module_utils/_internal/_event_utils.py +61 -0
- ansible/module_utils/_internal/_json/_profiles/__init__.py +22 -4
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
- ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
- ansible/module_utils/{common/messages.py → _internal/_messages.py} +54 -49
- ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
- ansible/module_utils/_internal/_plugin_info.py +15 -2
- ansible/module_utils/_internal/_stack.py +22 -0
- ansible/module_utils/_internal/_text_utils.py +6 -0
- ansible/module_utils/_internal/_traceback.py +11 -8
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +95 -71
- ansible/module_utils/common/arg_spec.py +2 -2
- ansible/module_utils/common/collections.py +6 -0
- ansible/module_utils/common/json.py +2 -2
- ansible/module_utils/common/respawn.py +4 -41
- ansible/module_utils/common/text/converters.py +3 -3
- ansible/module_utils/common/validation.py +1 -1
- ansible/module_utils/common/warnings.py +80 -23
- ansible/module_utils/common/yaml.py +1 -1
- ansible/module_utils/connection.py +8 -11
- ansible/module_utils/datatag.py +5 -2
- ansible/module_utils/facts/hardware/linux.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 +16 -3
- ansible/module_utils/facts/system/local.py +1 -1
- ansible/module_utils/facts/virtual/linux.py +2 -2
- ansible/module_utils/service.py +3 -10
- ansible/module_utils/urls.py +4 -4
- ansible/modules/apt_repository.py +17 -39
- ansible/modules/assemble.py +2 -2
- ansible/modules/async_status.py +13 -11
- ansible/modules/async_wrapper.py +12 -22
- ansible/modules/command.py +3 -3
- ansible/modules/copy.py +4 -4
- ansible/modules/cron.py +1 -1
- ansible/modules/dnf5.py +14 -22
- 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 +0 -1
- ansible/modules/known_hosts.py +12 -14
- ansible/modules/package.py +6 -0
- ansible/modules/replace.py +2 -2
- ansible/modules/service.py +3 -9
- ansible/modules/slurp.py +10 -13
- ansible/modules/stat.py +5 -7
- ansible/modules/unarchive.py +6 -6
- ansible/modules/user.py +1 -1
- ansible/modules/wait_for.py +28 -30
- ansible/modules/yum_repository.py +4 -3
- ansible/parsing/ajson.py +3 -5
- ansible/parsing/dataloader.py +6 -6
- ansible/parsing/mod_args.py +1 -1
- ansible/parsing/plugin_docs.py +2 -2
- ansible/parsing/utils/yaml.py +3 -3
- ansible/parsing/vault/__init__.py +10 -14
- ansible/playbook/base.py +7 -2
- ansible/playbook/included_file.py +3 -1
- ansible/playbook/play_context.py +2 -0
- ansible/playbook/playbook_include.py +1 -1
- ansible/playbook/taggable.py +19 -8
- ansible/playbook/task.py +2 -0
- ansible/plugins/__init__.py +0 -25
- ansible/plugins/action/__init__.py +8 -31
- ansible/plugins/action/add_host.py +1 -1
- ansible/plugins/action/assemble.py +8 -16
- ansible/plugins/action/async_status.py +7 -2
- ansible/plugins/action/copy.py +8 -7
- ansible/plugins/action/fetch.py +3 -3
- ansible/plugins/action/gather_facts.py +8 -8
- ansible/plugins/action/package.py +5 -8
- ansible/plugins/action/script.py +8 -15
- ansible/plugins/action/service.py +3 -7
- ansible/plugins/action/template.py +11 -10
- ansible/plugins/action/unarchive.py +5 -15
- ansible/plugins/action/uri.py +9 -20
- ansible/plugins/cache/__init__.py +17 -19
- ansible/plugins/callback/__init__.py +4 -6
- ansible/plugins/callback/junit.py +4 -2
- ansible/plugins/callback/tree.py +5 -5
- ansible/plugins/connection/local.py +6 -6
- ansible/plugins/connection/paramiko_ssh.py +5 -5
- ansible/plugins/connection/ssh.py +25 -15
- ansible/plugins/connection/winrm.py +6 -3
- ansible/plugins/doc_fragments/constructed.py +2 -2
- ansible/plugins/filter/core.py +32 -27
- ansible/plugins/filter/encryption.py +14 -6
- ansible/plugins/inventory/__init__.py +11 -10
- ansible/plugins/inventory/script.py +1 -1
- ansible/plugins/list.py +73 -19
- ansible/plugins/loader.py +7 -7
- ansible/plugins/lookup/csvfile.py +16 -71
- ansible/plugins/lookup/first_found.py +2 -1
- ansible/plugins/lookup/template.py +9 -4
- ansible/plugins/shell/__init__.py +56 -2
- ansible/plugins/shell/powershell.py +67 -9
- ansible/plugins/shell/sh.py +10 -5
- ansible/plugins/strategy/__init__.py +3 -3
- ansible/plugins/test/core.py +22 -16
- ansible/plugins/test/finished.yml +1 -1
- ansible/plugins/test/uri.py +2 -5
- ansible/release.py +1 -1
- ansible/template/__init__.py +38 -54
- ansible/utils/collection_loader/_collection_finder.py +3 -3
- ansible/utils/display.py +124 -138
- ansible/utils/galaxy.py +2 -2
- ansible/utils/hashing.py +6 -8
- ansible/utils/listify.py +6 -4
- ansible/utils/path.py +5 -7
- ansible/utils/py3compat.py +2 -1
- ansible/utils/ssh_functions.py +3 -2
- ansible/utils/unsafe_proxy.py +1 -1
- ansible/vars/hostvars.py +1 -1
- ansible/vars/plugins.py +3 -3
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/RECORD +224 -204
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/WHEEL +1 -1
- ansible_test/_data/completion/docker.txt +3 -3
- ansible_test/_data/completion/remote.txt +1 -0
- ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
- ansible_test/_data/requirements/sanity.changelog.txt +2 -2
- ansible_test/_data/requirements/sanity.pep8.txt +1 -1
- ansible_test/_data/requirements/sanity.pylint.txt +4 -4
- ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
- ansible_test/_internal/commands/integration/coverage.py +7 -2
- ansible_test/_internal/host_profiles.py +62 -10
- ansible_test/_internal/provisioning.py +10 -4
- ansible_test/_internal/ssh.py +1 -5
- ansible_test/_internal/thread.py +2 -1
- ansible_test/_internal/timeout.py +1 -1
- ansible_test/_internal/util.py +40 -12
- ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +61 -7
- ansible_test/_util/target/setup/bootstrap.sh +31 -0
- ansible_test/_util/target/setup/requirements.py +3 -9
- ansible/_internal/_errors/_utils.py +0 -310
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/top_level.txt +0 -0
ansible/parsing/mod_args.py
CHANGED
@@ -160,7 +160,7 @@ class ModuleArgsParser:
|
|
160
160
|
final_args = dict()
|
161
161
|
if additional_args:
|
162
162
|
if isinstance(additional_args, (str, EncryptedString)):
|
163
|
-
#
|
163
|
+
# DTFIX5: should this be is_possibly_template?
|
164
164
|
if TemplateEngine().is_template(additional_args):
|
165
165
|
final_args['_variable_params'] = additional_args
|
166
166
|
else:
|
ansible/parsing/plugin_docs.py
CHANGED
@@ -41,7 +41,7 @@ def read_docstring_from_yaml_file(filename, verbose=True, ignore_errors=True):
|
|
41
41
|
file_data = yaml.load(yamlfile, Loader=AnsibleLoader)
|
42
42
|
except Exception as ex:
|
43
43
|
msg = f"Unable to parse yaml file {filename}"
|
44
|
-
# DTFIX-
|
44
|
+
# DTFIX-FUTURE: find a better pattern for this (can we use the new optional error behavior?)
|
45
45
|
if not ignore_errors:
|
46
46
|
raise AnsibleParserError(f'{msg}.') from ex
|
47
47
|
elif verbose:
|
@@ -93,7 +93,7 @@ def read_docstring_from_python_file(filename, verbose=True, ignore_errors=True):
|
|
93
93
|
|
94
94
|
except Exception as ex:
|
95
95
|
msg = f"Unable to parse documentation in python file {filename!r}"
|
96
|
-
# DTFIX-
|
96
|
+
# DTFIX-FUTURE: better pattern to conditionally raise/display
|
97
97
|
if not ignore_errors:
|
98
98
|
raise AnsibleParserError(f'{msg}.') from ex
|
99
99
|
elif verbose:
|
ansible/parsing/utils/yaml.py
CHANGED
@@ -11,7 +11,7 @@ import typing as t
|
|
11
11
|
import yaml
|
12
12
|
|
13
13
|
from ansible.errors import AnsibleJSONParserError
|
14
|
-
from ansible._internal._errors import
|
14
|
+
from ansible._internal._errors import _error_utils
|
15
15
|
from ansible.parsing.vault import VaultSecret
|
16
16
|
from ansible.parsing.yaml.loader import AnsibleLoader
|
17
17
|
from ansible._internal._yaml._errors import AnsibleYAMLParserError
|
@@ -34,7 +34,7 @@ def from_yaml(
|
|
34
34
|
|
35
35
|
data = origin.tag(data)
|
36
36
|
|
37
|
-
with
|
37
|
+
with _error_utils.RedactAnnotatedSourceContext.when(not show_content):
|
38
38
|
try:
|
39
39
|
# we first try to load this data as JSON.
|
40
40
|
# Fixes issues with extra vars json strings not being parsed correctly by the yaml parser
|
@@ -48,6 +48,6 @@ def from_yaml(
|
|
48
48
|
try:
|
49
49
|
return yaml.load(data, Loader=AnsibleLoader) # type: ignore[arg-type]
|
50
50
|
except Exception as yaml_ex:
|
51
|
-
# DTFIX-
|
51
|
+
# DTFIX-FUTURE: how can we indicate in Origin that the data is in-memory only, to support context information -- is that useful?
|
52
52
|
# we'd need to pass data to handle_exception so it could be used as the content instead of reading from disk
|
53
53
|
AnsibleYAMLParserError.handle_exception(yaml_ex, origin=origin)
|
@@ -17,7 +17,6 @@
|
|
17
17
|
|
18
18
|
from __future__ import annotations
|
19
19
|
|
20
|
-
import errno
|
21
20
|
import fcntl
|
22
21
|
import functools
|
23
22
|
import os
|
@@ -149,7 +148,7 @@ def _parse_vaulttext_envelope(b_vaulttext_envelope, default_vault_id=None):
|
|
149
148
|
vault_id = to_text(b_tmpheader[3].strip())
|
150
149
|
|
151
150
|
b_ciphertext = b''.join(b_tmpdata[1:])
|
152
|
-
#
|
151
|
+
# DTFIX7: possible candidate for propagate_origin
|
153
152
|
b_ciphertext = AnsibleTagHelper.tag_copy(b_vaulttext_envelope, b_ciphertext)
|
154
153
|
|
155
154
|
return b_ciphertext, b_version, cipher_name, vault_id
|
@@ -222,7 +221,7 @@ def format_vaulttext_envelope(b_ciphertext, cipher_name, version=None, vault_id=
|
|
222
221
|
|
223
222
|
def _unhexlify(b_data):
|
224
223
|
try:
|
225
|
-
#
|
224
|
+
# DTFIX7: possible candidate for propagate_origin
|
226
225
|
return AnsibleTagHelper.tag_copy(b_data, unhexlify(b_data))
|
227
226
|
except (BinasciiError, TypeError) as ex:
|
228
227
|
raise AnsibleVaultFormatError('Vault format unhexlify error.', obj=b_data) from ex
|
@@ -414,8 +413,8 @@ class FileVaultSecret(VaultSecret):
|
|
414
413
|
try:
|
415
414
|
with open(filename, "rb") as f:
|
416
415
|
vault_pass = f.read().strip()
|
417
|
-
except
|
418
|
-
raise AnsibleError("Could not read vault password file
|
416
|
+
except OSError as ex:
|
417
|
+
raise AnsibleError(f"Could not read vault password file {filename!r}.") from ex
|
419
418
|
|
420
419
|
b_vault_data, dummy = self.loader._decrypt_if_vault_data(vault_pass)
|
421
420
|
|
@@ -712,7 +711,7 @@ class VaultLib:
|
|
712
711
|
# secret = self.secrets[vault_secret_id]
|
713
712
|
display.vvvv(u'Trying secret %s for vault_id=%s' % (to_text(vault_secret), to_text(vault_secret_id)))
|
714
713
|
b_plaintext = this_cipher.decrypt(b_vaulttext, vault_secret)
|
715
|
-
#
|
714
|
+
# DTFIX7: possible candidate for propagate_origin
|
716
715
|
b_plaintext = AnsibleTagHelper.tag_copy(vaulttext, b_plaintext)
|
717
716
|
if b_plaintext is not None:
|
718
717
|
vault_id_used = vault_secret_id
|
@@ -1071,13 +1070,10 @@ class VaultEditor:
|
|
1071
1070
|
try:
|
1072
1071
|
# create file with secure permissions
|
1073
1072
|
fd = os.open(thefile, os.O_CREAT | os.O_EXCL | os.O_RDWR | os.O_TRUNC, mode)
|
1074
|
-
except
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
raise AnsibleError('Vault file got recreated while we were operating on it: %s' % to_native(ose))
|
1079
|
-
|
1080
|
-
raise AnsibleError('Problem creating temporary vault file: %s' % to_native(ose))
|
1073
|
+
except FileExistsError as ex:
|
1074
|
+
raise AnsibleError('Vault file got recreated while we were operating on it.') from ex
|
1075
|
+
except OSError as ex:
|
1076
|
+
raise AnsibleError('Problem creating temporary vault file.') from ex
|
1081
1077
|
|
1082
1078
|
try:
|
1083
1079
|
# now write to the file and ensure ours is only data in it
|
@@ -1520,7 +1516,7 @@ class VaultHelper:
|
|
1520
1516
|
tags = AnsibleTagHelper.tags(ciphertext) # ciphertext has tags but value does not
|
1521
1517
|
elif value_type is EncryptedString:
|
1522
1518
|
ciphertext = value._ciphertext
|
1523
|
-
elif value_type in _jinja_common.Marker.
|
1519
|
+
elif value_type in _jinja_common.Marker._concrete_subclasses: # avoid wasteful raise/except of Marker when calling get_tag below
|
1524
1520
|
ciphertext = None
|
1525
1521
|
elif vaulted_value := VaultedValue.get_tag(value):
|
1526
1522
|
ciphertext = vaulted_value.ciphertext
|
ansible/playbook/base.py
CHANGED
@@ -83,6 +83,11 @@ class _ClassProperty:
|
|
83
83
|
|
84
84
|
class FieldAttributeBase:
|
85
85
|
|
86
|
+
_post_validate_object = False
|
87
|
+
"""
|
88
|
+
`False` skips FieldAttribute post-validation on intermediate objects and mixins for attributes without `always_post_validate`.
|
89
|
+
Leaf objects (e.g., `Task`) should set this attribute `True` to opt-in to post-validation.
|
90
|
+
"""
|
86
91
|
fattributes = _ClassProperty()
|
87
92
|
|
88
93
|
@classmethod
|
@@ -566,8 +571,8 @@ class FieldAttributeBase:
|
|
566
571
|
# only import_role is checked here because import_tasks never reaches this point
|
567
572
|
return Sentinel
|
568
573
|
|
569
|
-
#
|
570
|
-
if not attribute.always_post_validate and self.
|
574
|
+
# Skip post validation unless always_post_validate is True, or the object requires post validation.
|
575
|
+
if not attribute.always_post_validate and not self._post_validate_object:
|
571
576
|
# Intermediate objects like Play() won't have their fields validated by
|
572
577
|
# default, as their values are often inherited by other objects and validated
|
573
578
|
# later, so we don't want them to fail out early
|
@@ -144,7 +144,9 @@ class IncludedFile:
|
|
144
144
|
parent_include_dir = parent_include._role_path
|
145
145
|
else:
|
146
146
|
try:
|
147
|
-
|
147
|
+
# FUTURE: Since the parent include path has already been resolved, it should be used here.
|
148
|
+
# Unfortunately it's not currently stored anywhere, so it must be calculated again.
|
149
|
+
parent_include_dir = os.path.dirname(templar.template(parent_include.args.get('_raw_params')))
|
148
150
|
except AnsibleError as e:
|
149
151
|
parent_include_dir = ''
|
150
152
|
display.warning(
|
ansible/playbook/play_context.py
CHANGED
@@ -164,5 +164,5 @@ class PlaybookInclude(Base, Conditional, Taggable):
|
|
164
164
|
if len(items) == 0:
|
165
165
|
raise AnsibleParserError("import_playbook statements must specify the file name to import", obj=ds)
|
166
166
|
|
167
|
-
#
|
167
|
+
# DTFIX3: investigate this as a possible "problematic strip"
|
168
168
|
new_ds['import_playbook'] = AnsibleTagHelper.tag_copy(v, items[0].strip())
|
ansible/playbook/taggable.py
CHANGED
@@ -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):
|
@@ -45,23 +47,32 @@ class Taggable:
|
|
45
47
|
return ds
|
46
48
|
|
47
49
|
if isinstance(ds, str):
|
48
|
-
# DTFIX-RELEASE: this allows each individual tag to be templated, but prevents the use of commas in templates, is that what we want?
|
49
|
-
# DTFIX-RELEASE: this can return empty tags (including a list of nothing but empty tags), is that correct?
|
50
|
-
# DTFIX-RELEASE: the original code seemed to attempt to preserve `ds` if there were no commas, but it never ran, what should it actually do?
|
51
50
|
return [AnsibleTagHelper.tag_copy(ds, item.strip()) for item in ds.split(',')]
|
52
51
|
|
53
52
|
raise AnsibleError('tags must be specified as a list', obj=ds)
|
54
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
|
+
|
55
66
|
def evaluate_tags(self, only_tags, skip_tags, all_vars):
|
56
|
-
"""
|
67
|
+
"""Check if the current item should be executed depending on the specified tags.
|
57
68
|
|
69
|
+
NOTE this method is assumed to be called only on Task objects.
|
70
|
+
"""
|
58
71
|
if self.tags:
|
59
72
|
templar = TemplateEngine(loader=self._loader, variables=all_vars)
|
60
|
-
obj
|
61
|
-
while obj is not None:
|
73
|
+
for obj in self._get_all_taggable_objects():
|
62
74
|
if (_tags := getattr(obj, "_tags", Sentinel)) is not Sentinel:
|
63
75
|
obj._tags = _flatten_tags(templar.template(_tags))
|
64
|
-
obj = obj._parent
|
65
76
|
tags = set(self.tags)
|
66
77
|
else:
|
67
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
|
ansible/plugins/__init__.py
CHANGED
@@ -189,28 +189,3 @@ class AnsibleJinja2Plugin(AnsiblePlugin, metaclass=abc.ABCMeta):
|
|
189
189
|
@property
|
190
190
|
def j2_function(self) -> t.Callable:
|
191
191
|
return self._function
|
192
|
-
|
193
|
-
|
194
|
-
_TCallable = t.TypeVar('_TCallable', bound=t.Callable)
|
195
|
-
|
196
|
-
|
197
|
-
def accept_args_markers(plugin: _TCallable) -> _TCallable:
|
198
|
-
"""
|
199
|
-
A decorator to mark a Jinja plugin as capable of handling `Marker` values for its top-level arguments.
|
200
|
-
Non-decorated plugin invocation is skipped when a top-level argument is a `Marker`, with the first such value substituted as the plugin result.
|
201
|
-
This ensures that only plugins which understand `Marker` instances for top-level arguments will encounter them.
|
202
|
-
"""
|
203
|
-
plugin.accept_args_markers = True
|
204
|
-
|
205
|
-
return plugin
|
206
|
-
|
207
|
-
|
208
|
-
def accept_lazy_markers(plugin: _TCallable) -> _TCallable:
|
209
|
-
"""
|
210
|
-
A decorator to mark a Jinja plugin as capable of handling `Marker` values retrieved from lazy containers.
|
211
|
-
Non-decorated plugins will trigger a `MarkerError` exception when attempting to retrieve a `Marker` from a lazy container.
|
212
|
-
This ensures that only plugins which understand lazy retrieval of `Marker` instances will encounter them.
|
213
|
-
"""
|
214
|
-
plugin.accept_lazy_markers = True
|
215
|
-
|
216
|
-
return plugin
|
@@ -20,9 +20,8 @@ from abc import ABC, abstractmethod
|
|
20
20
|
from collections.abc import Sequence
|
21
21
|
|
22
22
|
from ansible import constants as C
|
23
|
-
from ansible._internal._errors import _captured
|
23
|
+
from ansible._internal._errors import _captured, _error_utils
|
24
24
|
from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleActionSkip, AnsibleActionFail, AnsibleAuthenticationFailure
|
25
|
-
from ansible._internal._errors import _utils
|
26
25
|
from ansible.executor.module_common import modify_module, _BuiltModule
|
27
26
|
from ansible.executor.interpreter_discovery import discover_interpreter, InterpreterDiscoveryRequiredError
|
28
27
|
from ansible.module_utils._internal import _traceback
|
@@ -41,7 +40,6 @@ from ansible import _internal
|
|
41
40
|
from ansible._internal._templating import _engine
|
42
41
|
|
43
42
|
from .. import _AnsiblePluginInfoMixin
|
44
|
-
from ...module_utils.common.messages import PluginInfo
|
45
43
|
|
46
44
|
display = Display()
|
47
45
|
|
@@ -121,8 +119,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
121
119
|
|
122
120
|
* Module parameters. These are stored in self._task.args
|
123
121
|
"""
|
124
|
-
|
125
|
-
# does not default to {'changed': False, 'failed': False}, as it breaks async
|
122
|
+
# does not default to {'changed': False, 'failed': False}, as it used to break async
|
126
123
|
result = {}
|
127
124
|
|
128
125
|
if tmp is not None:
|
@@ -476,8 +473,8 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
476
473
|
|
477
474
|
become_unprivileged = self._is_become_unprivileged()
|
478
475
|
basefile = self._connection._shell._generate_temp_dir_name()
|
479
|
-
cmd = self._connection._shell.
|
480
|
-
result = self._low_level_execute_command(cmd, sudoable=False)
|
476
|
+
cmd = self._connection._shell._mkdtemp2(basefile=basefile, system=become_unprivileged, tmpdir=tmpdir)
|
477
|
+
result = self._low_level_execute_command(cmd.command, in_data=cmd.input_data, sudoable=False)
|
481
478
|
|
482
479
|
# error handling on this seems a little aggressive?
|
483
480
|
if result['rc'] != 0:
|
@@ -908,8 +905,8 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
908
905
|
expand_path = '~%s' % (self._get_remote_user() or '')
|
909
906
|
|
910
907
|
# use shell to construct appropriate command and execute
|
911
|
-
cmd = self._connection._shell.
|
912
|
-
data = self._low_level_execute_command(cmd, sudoable=False)
|
908
|
+
cmd = self._connection._shell._expand_user2(expand_path)
|
909
|
+
data = self._low_level_execute_command(cmd.command, in_data=cmd.input_data, sudoable=False)
|
913
910
|
|
914
911
|
try:
|
915
912
|
initial_fragment = data['stdout'].strip().splitlines()[-1]
|
@@ -1115,7 +1112,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
1115
1112
|
if wrap_async and not self._connection.always_pipeline_modules:
|
1116
1113
|
# configure, upload, and chmod the async_wrapper module
|
1117
1114
|
(async_module_bits, async_module_path) = self._configure_module(module_name='ansible.legacy.async_wrapper', module_args=dict(), task_vars=task_vars)
|
1118
|
-
(
|
1115
|
+
(shebang, async_module_data) = (async_module_bits.shebang, async_module_bits.b_module_data)
|
1119
1116
|
async_module_remote_filename = self._connection._shell.get_remote_filename(async_module_path)
|
1120
1117
|
remote_async_module_path = self._connection._shell.join_path(tmpdir, async_module_remote_filename)
|
1121
1118
|
self._transfer_data(remote_async_module_path, async_module_data)
|
@@ -1255,7 +1252,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
1255
1252
|
except AnsibleError as ansible_ex:
|
1256
1253
|
sentinel = object()
|
1257
1254
|
|
1258
|
-
data =
|
1255
|
+
data = _error_utils.result_dict_from_exception(ansible_ex)
|
1259
1256
|
data.update(
|
1260
1257
|
_ansible_parsed=False,
|
1261
1258
|
module_stdout=res.get('stdout', ''),
|
@@ -1436,23 +1433,3 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
1436
1433
|
|
1437
1434
|
# if missing it will return a file not found exception
|
1438
1435
|
return self._loader.path_dwim_relative_stack(path_stack, dirname, needle)
|
1439
|
-
|
1440
|
-
@staticmethod
|
1441
|
-
def result_dict_from_exception(exception: BaseException) -> dict[str, t.Any]:
|
1442
|
-
"""Return a failed task result dict from the given exception."""
|
1443
|
-
if ansible_remoted_error := _captured.AnsibleResultCapturedError.find_first_remoted_error(exception):
|
1444
|
-
result = ansible_remoted_error._result.copy()
|
1445
|
-
else:
|
1446
|
-
result = {}
|
1447
|
-
|
1448
|
-
error_summary = _utils._create_error_summary(exception, _traceback.TracebackEvent.ERROR)
|
1449
|
-
|
1450
|
-
result.update(
|
1451
|
-
failed=True,
|
1452
|
-
exception=error_summary,
|
1453
|
-
)
|
1454
|
-
|
1455
|
-
if 'msg' not in result:
|
1456
|
-
result.update(msg=_utils._dedupe_and_concat_message_chain([md.msg for md in error_summary.details]))
|
1457
|
-
|
1458
|
-
return result
|
@@ -77,7 +77,7 @@ class ActionModule(ActionBase):
|
|
77
77
|
elif isinstance(groups, string_types):
|
78
78
|
group_list = groups.split(",")
|
79
79
|
else:
|
80
|
-
raise AnsibleActionFail("Groups must be specified as a list.", obj=
|
80
|
+
raise AnsibleActionFail("Groups must be specified as a list.", obj=groups)
|
81
81
|
|
82
82
|
for group_name in group_list:
|
83
83
|
if group_name not in new_groups:
|
@@ -25,8 +25,8 @@ import re
|
|
25
25
|
import tempfile
|
26
26
|
|
27
27
|
from ansible import constants as C
|
28
|
-
from ansible.errors import
|
29
|
-
from ansible.module_utils.common.text.converters import
|
28
|
+
from ansible.errors import AnsibleActionFail
|
29
|
+
from ansible.module_utils.common.text.converters import to_text
|
30
30
|
from ansible.module_utils.parsing.convert_bool import boolean
|
31
31
|
from ansible.plugins.action import ActionBase
|
32
32
|
from ansible.utils.hashing import checksum_s
|
@@ -83,7 +83,7 @@ class ActionModule(ActionBase):
|
|
83
83
|
|
84
84
|
self._supports_check_mode = False
|
85
85
|
|
86
|
-
|
86
|
+
super(ActionModule, self).run(tmp, task_vars)
|
87
87
|
del tmp # tmp no longer has any effect
|
88
88
|
|
89
89
|
if task_vars is None:
|
@@ -104,13 +104,9 @@ class ActionModule(ActionBase):
|
|
104
104
|
|
105
105
|
if boolean(remote_src, strict=False):
|
106
106
|
# call assemble via ansible.legacy to allow library/ overrides of the module without collection search
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
try:
|
111
|
-
src = self._find_needle('files', src)
|
112
|
-
except AnsibleError as e:
|
113
|
-
raise AnsibleActionFail(to_native(e))
|
107
|
+
return self._execute_module(module_name='ansible.legacy.assemble', task_vars=task_vars)
|
108
|
+
|
109
|
+
src = self._find_needle('files', src)
|
114
110
|
|
115
111
|
if not os.path.isdir(src):
|
116
112
|
raise AnsibleActionFail(u"Source (%s) is not a directory" % src)
|
@@ -153,13 +149,9 @@ class ActionModule(ActionBase):
|
|
153
149
|
res = self._execute_module(module_name='ansible.legacy.copy', module_args=new_module_args, task_vars=task_vars)
|
154
150
|
if diff:
|
155
151
|
res['diff'] = diff
|
156
|
-
|
152
|
+
return res
|
157
153
|
else:
|
158
|
-
|
154
|
+
return self._execute_module(module_name='ansible.legacy.file', module_args=new_module_args, task_vars=task_vars)
|
159
155
|
|
160
|
-
except AnsibleAction as e:
|
161
|
-
result.update(e.result)
|
162
156
|
finally:
|
163
157
|
self._remove_tmp_path(self._connection._shell.tmpdir)
|
164
|
-
|
165
|
-
return result
|
@@ -28,7 +28,7 @@ class ActionModule(ActionBase):
|
|
28
28
|
)
|
29
29
|
|
30
30
|
# initialize response
|
31
|
-
results['started'] = results['finished'] =
|
31
|
+
results['started'] = results['finished'] = False
|
32
32
|
results['stdout'] = results['stderr'] = ''
|
33
33
|
results['stdout_lines'] = results['stderr_lines'] = []
|
34
34
|
|
@@ -43,9 +43,14 @@ class ActionModule(ActionBase):
|
|
43
43
|
results['erased'] = log_path
|
44
44
|
else:
|
45
45
|
results['results_file'] = log_path
|
46
|
-
results['started'] =
|
46
|
+
results['started'] = True
|
47
47
|
|
48
48
|
new_module_args['_async_dir'] = async_dir
|
49
49
|
results = merge_hash(results, self._execute_module(module_name='ansible.legacy.async_status', task_vars=task_vars, module_args=new_module_args))
|
50
50
|
|
51
|
+
# Backwards compat shim for when started/finished were ints,
|
52
|
+
# mostly to work with ansible.windows.async_status
|
53
|
+
for convert in ('started', 'finished'):
|
54
|
+
results[convert] = bool(results[convert])
|
55
|
+
|
51
56
|
return results
|
ansible/plugins/action/copy.py
CHANGED
@@ -27,7 +27,7 @@ import tempfile
|
|
27
27
|
from ansible import constants as C
|
28
28
|
from ansible.errors import AnsibleError, AnsibleActionFail, AnsibleFileNotFound
|
29
29
|
from ansible.module_utils.basic import FILE_COMMON_ARGUMENTS
|
30
|
-
from ansible.module_utils.common.text.converters import to_bytes,
|
30
|
+
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
31
31
|
from ansible.module_utils.parsing.convert_bool import boolean
|
32
32
|
from ansible.plugins.action import ActionBase
|
33
33
|
from ansible.utils.hashing import checksum
|
@@ -409,6 +409,7 @@ class ActionModule(ActionBase):
|
|
409
409
|
task_vars = dict()
|
410
410
|
|
411
411
|
result = super(ActionModule, self).run(tmp, task_vars)
|
412
|
+
|
412
413
|
del tmp # tmp no longer has any effect
|
413
414
|
|
414
415
|
# ensure user is not setting internal parameters
|
@@ -450,10 +451,10 @@ class ActionModule(ActionBase):
|
|
450
451
|
else:
|
451
452
|
content_tempfile = self._create_content_tempfile(content)
|
452
453
|
source = content_tempfile
|
453
|
-
except Exception as
|
454
|
-
result
|
455
|
-
|
456
|
-
|
454
|
+
except Exception as ex:
|
455
|
+
self._ensure_invocation(result)
|
456
|
+
|
457
|
+
raise AnsibleActionFail(message="could not write content temp file", result=result) from ex
|
457
458
|
|
458
459
|
# if we have first_available_file in our vars
|
459
460
|
# look up the files and use the first one we find as src
|
@@ -470,9 +471,9 @@ class ActionModule(ActionBase):
|
|
470
471
|
# find in expected paths
|
471
472
|
source = self._find_needle('files', source)
|
472
473
|
except AnsibleError as ex:
|
473
|
-
|
474
|
+
self._ensure_invocation(result)
|
474
475
|
|
475
|
-
|
476
|
+
raise AnsibleActionFail(result=result) from ex
|
476
477
|
|
477
478
|
if trailing_slash != source.endswith(os.path.sep):
|
478
479
|
if source[-1] == os.path.sep:
|
ansible/plugins/action/fetch.py
CHANGED
@@ -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
|
184
|
-
raise AnsibleActionFail("Failed to fetch the file
|
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:
|
@@ -13,6 +13,7 @@ from ansible.executor.module_common import _apply_action_arg_defaults
|
|
13
13
|
from ansible.module_utils.parsing.convert_bool import boolean
|
14
14
|
from ansible.plugins.action import ActionBase
|
15
15
|
from ansible.utils.vars import merge_hash
|
16
|
+
from ansible._internal._errors import _error_utils
|
16
17
|
|
17
18
|
|
18
19
|
class ActionModule(ActionBase):
|
@@ -127,8 +128,6 @@ class ActionModule(ActionBase):
|
|
127
128
|
# TODO: use gather_timeout to cut module execution if module itself does not support gather_timeout
|
128
129
|
res = self._execute_module(module_name=fact_module, module_args=mod_args, task_vars=task_vars, wrap_async=False)
|
129
130
|
if res.get('failed', False):
|
130
|
-
# DTFIX-RELEASE: this trashes the individual failure details and does not work with the new error handling; need to do something to
|
131
|
-
# invoke per-item error handling- perhaps returning this as a synthetic loop result?
|
132
131
|
failed[fact_module] = res
|
133
132
|
elif res.get('skipped', False):
|
134
133
|
skipped[fact_module] = res
|
@@ -159,10 +158,8 @@ class ActionModule(ActionBase):
|
|
159
158
|
for module in jobs:
|
160
159
|
poll_args = {'jid': jobs[module]['ansible_job_id'], '_async_dir': os.path.dirname(jobs[module]['results_file'])}
|
161
160
|
res = self._execute_module(module_name='ansible.legacy.async_status', module_args=poll_args, task_vars=task_vars, wrap_async=False)
|
162
|
-
if res.get('finished',
|
161
|
+
if res.get('finished', False):
|
163
162
|
if res.get('failed', False):
|
164
|
-
# DTFIX-RELEASE: this trashes the individual failure details and does not work with the new error handling; need to do something to
|
165
|
-
# invoke per-item error handling- perhaps returning this as a synthetic loop result?
|
166
163
|
failed[module] = res
|
167
164
|
elif res.get('skipped', False):
|
168
165
|
skipped[module] = res
|
@@ -180,16 +177,19 @@ class ActionModule(ActionBase):
|
|
180
177
|
self._task.async_val = async_val
|
181
178
|
|
182
179
|
if skipped:
|
183
|
-
result['msg'] = "The following modules were skipped:
|
180
|
+
result['msg'] = f"The following modules were skipped: {', '.join(skipped.keys())}."
|
184
181
|
result['skipped_modules'] = skipped
|
185
182
|
if len(skipped) == len(modules):
|
186
183
|
result['skipped'] = True
|
187
184
|
|
188
185
|
if failed:
|
189
|
-
result['failed'] = True
|
190
|
-
result['msg'] = "The following modules failed to execute: %s\n" % (', '.join(failed.keys()))
|
191
186
|
result['failed_modules'] = failed
|
192
187
|
|
188
|
+
result.update(_error_utils.result_dict_from_captured_errors(
|
189
|
+
msg=f"The following modules failed to execute: {', '.join(failed.keys())}.",
|
190
|
+
errors=[r['exception'] for r in failed.values()],
|
191
|
+
))
|
192
|
+
|
193
193
|
# tell executor facts were gathered
|
194
194
|
result['ansible_facts']['_ansible_facts_gathered'] = True
|
195
195
|
|
@@ -16,7 +16,7 @@
|
|
16
16
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
17
17
|
from __future__ import annotations
|
18
18
|
|
19
|
-
from ansible.errors import
|
19
|
+
from ansible.errors import AnsibleActionFail
|
20
20
|
from ansible.executor.module_common import _apply_action_arg_defaults
|
21
21
|
from ansible.module_utils.facts.system.pkg_mgr import PKG_MGRS
|
22
22
|
from ansible.plugins.action import ActionBase
|
@@ -38,7 +38,7 @@ class ActionModule(ActionBase):
|
|
38
38
|
self._supports_check_mode = True
|
39
39
|
self._supports_async = True
|
40
40
|
|
41
|
-
|
41
|
+
super(ActionModule, self).run(tmp, task_vars)
|
42
42
|
|
43
43
|
module = self._task.args.get('use', 'auto')
|
44
44
|
|
@@ -99,11 +99,8 @@ class ActionModule(ActionBase):
|
|
99
99
|
module = 'ansible.legacy.' + module
|
100
100
|
|
101
101
|
display.vvvv("Running %s" % module)
|
102
|
-
|
102
|
+
return self._execute_module(module_name=module, module_args=new_module_args, task_vars=task_vars, wrap_async=self._task.async_val)
|
103
103
|
else:
|
104
104
|
raise AnsibleActionFail('Could not detect which package manager to use. Try gathering facts or setting the "use" option.')
|
105
|
-
|
106
|
-
|
107
|
-
result.update(e.result)
|
108
|
-
|
109
|
-
return result
|
105
|
+
finally:
|
106
|
+
pass # avoid de-dent all on refactor
|