ansible-core 2.19.0b4__py3-none-any.whl → 2.19.0b5__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/_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 +21 -16
- ansible/_internal/_templating/_jinja_common.py +18 -27
- ansible/_internal/_templating/_jinja_plugins.py +31 -3
- ansible/_internal/_templating/_lazy_containers.py +5 -5
- ansible/_internal/_templating/_transform.py +20 -19
- ansible/_internal/_templating/_utils.py +1 -1
- ansible/_internal/_yaml/_dumper.py +1 -1
- 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 +5 -82
- ansible/cli/arguments/option_helpers.py +2 -3
- ansible/cli/doc.py +84 -28
- ansible/cli/inventory.py +1 -1
- ansible/compat/importlib_resources.py +9 -12
- ansible/config/base.yml +22 -0
- ansible/errors/__init__.py +96 -49
- ansible/executor/module_common.py +8 -10
- 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 +38 -113
- ansible/executor/task_executor.py +26 -61
- ansible/executor/task_result.py +2 -4
- ansible/galaxy/collection/__init__.py +1 -4
- ansible/inventory/manager.py +1 -1
- ansible/module_utils/_internal/__init__.py +0 -3
- ansible/module_utils/_internal/_ambient_context.py +3 -3
- ansible/module_utils/_internal/_ansiballz.py +4 -2
- ansible/module_utils/_internal/_datatag/__init__.py +20 -14
- ansible/module_utils/_internal/_datatag/_tags.py +2 -2
- ansible/module_utils/_internal/_deprecator.py +66 -48
- 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 +21 -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} +28 -47
- ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
- ansible/module_utils/_internal/_plugin_info.py +1 -1
- 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 +49 -15
- 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/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/datatag.py +5 -2
- ansible/module_utils/facts/system/distribution.py +16 -3
- ansible/module_utils/facts/virtual/linux.py +1 -1
- ansible/module_utils/service.py +2 -9
- ansible/modules/apt_repository.py +7 -29
- ansible/modules/async_status.py +13 -11
- ansible/modules/async_wrapper.py +5 -5
- ansible/modules/dnf5.py +14 -22
- ansible/modules/hostname.py +0 -1
- ansible/modules/service.py +3 -9
- ansible/parsing/ajson.py +3 -5
- ansible/parsing/dataloader.py +4 -4
- 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 +4 -4
- ansible/playbook/playbook_include.py +1 -1
- ansible/playbook/taggable.py +0 -3
- 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/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 +3 -8
- ansible/plugins/action/unarchive.py +5 -15
- ansible/plugins/action/uri.py +9 -20
- ansible/plugins/callback/__init__.py +4 -6
- ansible/plugins/callback/junit.py +4 -2
- ansible/plugins/connection/local.py +2 -2
- ansible/plugins/connection/ssh.py +17 -9
- ansible/plugins/connection/winrm.py +5 -2
- ansible/plugins/doc_fragments/constructed.py +2 -2
- ansible/plugins/filter/core.py +13 -6
- ansible/plugins/filter/encryption.py +4 -4
- ansible/plugins/inventory/__init__.py +11 -10
- ansible/plugins/inventory/script.py +1 -1
- ansible/plugins/list.py +69 -16
- ansible/plugins/loader.py +7 -7
- ansible/plugins/lookup/csvfile.py +16 -71
- ansible/plugins/lookup/first_found.py +2 -1
- ansible/plugins/shell/__init__.py +56 -2
- ansible/plugins/shell/powershell.py +66 -9
- ansible/plugins/shell/sh.py +9 -5
- ansible/plugins/test/core.py +21 -15
- ansible/plugins/test/finished.yml +1 -1
- ansible/plugins/test/uri.py +2 -5
- ansible/release.py +1 -1
- ansible/template/__init__.py +30 -2
- ansible/utils/display.py +103 -128
- ansible/utils/hashing.py +0 -1
- ansible/utils/listify.py +6 -4
- ansible/utils/unsafe_proxy.py +1 -1
- ansible/vars/hostvars.py +1 -1
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/RECORD +162 -151
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.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/util.py +20 -0
- 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/_internal/_errors/_utils.py +0 -310
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/top_level.txt +0 -0
ansible/modules/async_wrapper.py
CHANGED
@@ -149,7 +149,7 @@ def _run_module(wrapped_cmd, jid):
|
|
149
149
|
|
150
150
|
# DTFIX-FUTURE: needs rework for serialization profiles
|
151
151
|
|
152
|
-
jwrite({"started":
|
152
|
+
jwrite({"started": True, "finished": False, "ansible_job_id": jid})
|
153
153
|
|
154
154
|
result = {}
|
155
155
|
|
@@ -203,7 +203,7 @@ def _run_module(wrapped_cmd, jid):
|
|
203
203
|
except (OSError, IOError):
|
204
204
|
e = sys.exc_info()[1]
|
205
205
|
result = {
|
206
|
-
"failed":
|
206
|
+
"failed": True,
|
207
207
|
"cmd": wrapped_cmd,
|
208
208
|
"msg": to_text(e),
|
209
209
|
"outdata": outdata, # temporary notice only
|
@@ -214,7 +214,7 @@ def _run_module(wrapped_cmd, jid):
|
|
214
214
|
|
215
215
|
except (ValueError, Exception):
|
216
216
|
result = {
|
217
|
-
"failed":
|
217
|
+
"failed": True,
|
218
218
|
"cmd": wrapped_cmd,
|
219
219
|
"data": outdata, # temporary notice only
|
220
220
|
"stderr": stderr,
|
@@ -260,7 +260,7 @@ def main():
|
|
260
260
|
_make_temp_dir(jobdir)
|
261
261
|
except Exception as e:
|
262
262
|
end({
|
263
|
-
"failed":
|
263
|
+
"failed": True,
|
264
264
|
"msg": "could not create directory: %s - %s" % (jobdir, to_text(e)),
|
265
265
|
"exception": to_text(traceback.format_exc()), # NB: task executor compat will coerce to the correct dataclass type
|
266
266
|
}, 1)
|
@@ -293,7 +293,7 @@ def main():
|
|
293
293
|
continue
|
294
294
|
|
295
295
|
notice("Return async_wrapper task started.")
|
296
|
-
end({"failed":
|
296
|
+
end({"failed": False, "started": True, "finished": False, "ansible_job_id": jid, "results_file": job_path,
|
297
297
|
"_ansible_suppress_tmpdir_delete": (not preserve_tmp)}, 0)
|
298
298
|
else:
|
299
299
|
# The actual wrapper process
|
ansible/modules/dnf5.py
CHANGED
@@ -365,7 +365,7 @@ from ansible.module_utils.yumdnf import YumDnf, yumdnf_argument_spec
|
|
365
365
|
|
366
366
|
libdnf5 = None
|
367
367
|
# Through dnf5-5.2.12 all exceptions raised through swig became RuntimeError
|
368
|
-
|
368
|
+
LIBDNF5_ERRORS = RuntimeError
|
369
369
|
|
370
370
|
|
371
371
|
def is_installed(base, spec):
|
@@ -423,7 +423,9 @@ def is_newer_version_installed(base, spec):
|
|
423
423
|
|
424
424
|
try:
|
425
425
|
spec_nevra = next(iter(libdnf5.rpm.Nevra.parse(spec)))
|
426
|
-
except
|
426
|
+
except LIBDNF5_ERRORS:
|
427
|
+
return False
|
428
|
+
except StopIteration:
|
427
429
|
return False
|
428
430
|
|
429
431
|
spec_version = spec_nevra.get_version()
|
@@ -517,7 +519,7 @@ class Dnf5Module(YumDnf):
|
|
517
519
|
os.environ["LANGUAGE"] = os.environ["LANG"] = locale
|
518
520
|
|
519
521
|
global libdnf5
|
520
|
-
global
|
522
|
+
global LIBDNF5_ERRORS
|
521
523
|
has_dnf = True
|
522
524
|
try:
|
523
525
|
import libdnf5 # type: ignore[import]
|
@@ -526,7 +528,7 @@ class Dnf5Module(YumDnf):
|
|
526
528
|
|
527
529
|
try:
|
528
530
|
import libdnf5.exception # type: ignore[import-not-found]
|
529
|
-
|
531
|
+
LIBDNF5_ERRORS = (libdnf5.exception.Error, libdnf5.exception.NonLibdnf5Exception)
|
530
532
|
except (ImportError, AttributeError):
|
531
533
|
pass
|
532
534
|
|
@@ -581,15 +583,7 @@ class Dnf5Module(YumDnf):
|
|
581
583
|
if self.conf_file:
|
582
584
|
conf.config_file_path = self.conf_file
|
583
585
|
|
584
|
-
|
585
|
-
base.load_config()
|
586
|
-
except LIBDNF5_ERROR as e:
|
587
|
-
self.module.fail_json(
|
588
|
-
msg=str(e),
|
589
|
-
conf_file=self.conf_file,
|
590
|
-
failures=[],
|
591
|
-
rc=1,
|
592
|
-
)
|
586
|
+
base.load_config()
|
593
587
|
|
594
588
|
if self.releasever is not None:
|
595
589
|
variables = base.get_vars()
|
@@ -745,19 +739,13 @@ class Dnf5Module(YumDnf):
|
|
745
739
|
goal.add_install(spec, settings)
|
746
740
|
elif self.state in {"absent", "removed"}:
|
747
741
|
for spec in self.names:
|
748
|
-
|
749
|
-
goal.add_remove(spec, settings)
|
750
|
-
except LIBDNF5_ERROR as e:
|
751
|
-
self.module.fail_json(msg=str(e), failures=[], rc=1)
|
742
|
+
goal.add_remove(spec, settings)
|
752
743
|
if self.autoremove:
|
753
744
|
for pkg in get_unneeded_pkgs(base):
|
754
745
|
goal.add_rpm_remove(pkg, settings)
|
755
746
|
|
756
747
|
goal.set_allow_erasing(self.allowerasing)
|
757
|
-
|
758
|
-
transaction = goal.resolve()
|
759
|
-
except LIBDNF5_ERROR as e:
|
760
|
-
self.module.fail_json(msg=str(e), failures=[], rc=1)
|
748
|
+
transaction = goal.resolve()
|
761
749
|
|
762
750
|
if transaction.get_problems():
|
763
751
|
failures = []
|
@@ -833,7 +821,11 @@ def main():
|
|
833
821
|
auto_install_module_deps=dict(type="bool", default=True),
|
834
822
|
)
|
835
823
|
)
|
836
|
-
|
824
|
+
module = AnsibleModule(**yumdnf_argument_spec)
|
825
|
+
try:
|
826
|
+
Dnf5Module(module).run()
|
827
|
+
except LIBDNF5_ERRORS as e:
|
828
|
+
module.fail_json(msg=str(e), failures=[], rc=1)
|
837
829
|
|
838
830
|
|
839
831
|
if __name__ == "__main__":
|
ansible/modules/hostname.py
CHANGED
ansible/modules/service.py
CHANGED
@@ -180,7 +180,7 @@ from ansible.module_utils.basic import AnsibleModule
|
|
180
180
|
from ansible.module_utils.common.locale import get_best_parsable_locale
|
181
181
|
from ansible.module_utils.common.sys_info import get_platform_subclass
|
182
182
|
from ansible.module_utils.service import fail_if_missing, is_systemd_managed
|
183
|
-
from ansible.module_utils.six import
|
183
|
+
from ansible.module_utils.six import b
|
184
184
|
|
185
185
|
|
186
186
|
class Service(object):
|
@@ -285,14 +285,8 @@ class Service(object):
|
|
285
285
|
os._exit(0)
|
286
286
|
|
287
287
|
# Start the command
|
288
|
-
|
289
|
-
|
290
|
-
cmd = to_bytes(cmd, errors='surrogate_or_strict')
|
291
|
-
cmd = shlex.split(cmd)
|
292
|
-
else:
|
293
|
-
# Python3.x shex.split text strings.
|
294
|
-
cmd = to_text(cmd, errors='surrogate_or_strict')
|
295
|
-
cmd = [to_bytes(c, errors='surrogate_or_strict') for c in shlex.split(cmd)]
|
288
|
+
cmd = to_text(cmd, errors='surrogate_or_strict')
|
289
|
+
cmd = [to_bytes(c, errors='surrogate_or_strict') for c in shlex.split(cmd)]
|
296
290
|
# In either of the above cases, pass a list of byte strings to Popen
|
297
291
|
|
298
292
|
# chkconfig localizes messages and we're screen scraping so make
|
ansible/parsing/ajson.py
CHANGED
@@ -4,15 +4,13 @@
|
|
4
4
|
from __future__ import annotations as _annotations
|
5
5
|
|
6
6
|
# from ansible.utils.display import Display as _Display
|
7
|
-
|
8
|
-
|
9
|
-
# DTFIX-RELEASE: The pylint deprecated checker does not detect `Display().deprecated` calls, of which we have many.
|
10
|
-
|
7
|
+
#
|
8
|
+
#
|
11
9
|
# deprecated: description='deprecate ajson' core_version='2.23'
|
12
10
|
# _Display().deprecated(
|
13
11
|
# msg='The `ansible.parsing.ajson` module is deprecated.',
|
14
12
|
# version='2.27',
|
15
|
-
# help_text="", # DTFIX-
|
13
|
+
# help_text="", # DTFIX-FUTURE: complete this help text
|
16
14
|
# )
|
17
15
|
|
18
16
|
# Imported for backward compat
|
ansible/parsing/dataloader.py
CHANGED
@@ -15,7 +15,7 @@ import typing as t
|
|
15
15
|
|
16
16
|
from ansible import constants as C
|
17
17
|
from ansible.errors import AnsibleFileNotFound, AnsibleParserError
|
18
|
-
from ansible._internal._errors import
|
18
|
+
from ansible._internal._errors import _error_utils
|
19
19
|
from ansible.module_utils.basic import is_executable
|
20
20
|
from ansible._internal._datatag._tags import Origin, TrustedAsTemplate, SourceWasEncrypted
|
21
21
|
from ansible.module_utils._internal._datatag import AnsibleTagHelper
|
@@ -81,12 +81,12 @@ class DataLoader:
|
|
81
81
|
def load(
|
82
82
|
self,
|
83
83
|
data: str,
|
84
|
-
file_name: str | None = None, # DTFIX-
|
85
|
-
show_content: bool = True, # DTFIX-
|
84
|
+
file_name: str | None = None, # DTFIX-FUTURE: consider deprecating this in favor of tagging Origin on data
|
85
|
+
show_content: bool = True, # DTFIX-FUTURE: consider future deprecation, but would need RedactAnnotatedSourceContext public
|
86
86
|
json_only: bool = False,
|
87
87
|
) -> t.Any:
|
88
88
|
"""Backwards compat for now"""
|
89
|
-
with
|
89
|
+
with _error_utils.RedactAnnotatedSourceContext.when(not show_content):
|
90
90
|
return from_yaml(data=data, file_name=file_name, json_only=json_only)
|
91
91
|
|
92
92
|
def load_from_file(self, file_name: str, cache: str = 'all', unsafe: bool = False, json_only: bool = False, trusted_as_template: bool = False) -> t.Any:
|
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)
|
@@ -149,7 +149,7 @@ def _parse_vaulttext_envelope(b_vaulttext_envelope, default_vault_id=None):
|
|
149
149
|
vault_id = to_text(b_tmpheader[3].strip())
|
150
150
|
|
151
151
|
b_ciphertext = b''.join(b_tmpdata[1:])
|
152
|
-
#
|
152
|
+
# DTFIX7: possible candidate for propagate_origin
|
153
153
|
b_ciphertext = AnsibleTagHelper.tag_copy(b_vaulttext_envelope, b_ciphertext)
|
154
154
|
|
155
155
|
return b_ciphertext, b_version, cipher_name, vault_id
|
@@ -222,7 +222,7 @@ def format_vaulttext_envelope(b_ciphertext, cipher_name, version=None, vault_id=
|
|
222
222
|
|
223
223
|
def _unhexlify(b_data):
|
224
224
|
try:
|
225
|
-
#
|
225
|
+
# DTFIX7: possible candidate for propagate_origin
|
226
226
|
return AnsibleTagHelper.tag_copy(b_data, unhexlify(b_data))
|
227
227
|
except (BinasciiError, TypeError) as ex:
|
228
228
|
raise AnsibleVaultFormatError('Vault format unhexlify error.', obj=b_data) from ex
|
@@ -712,7 +712,7 @@ class VaultLib:
|
|
712
712
|
# secret = self.secrets[vault_secret_id]
|
713
713
|
display.vvvv(u'Trying secret %s for vault_id=%s' % (to_text(vault_secret), to_text(vault_secret_id)))
|
714
714
|
b_plaintext = this_cipher.decrypt(b_vaulttext, vault_secret)
|
715
|
-
#
|
715
|
+
# DTFIX7: possible candidate for propagate_origin
|
716
716
|
b_plaintext = AnsibleTagHelper.tag_copy(vaulttext, b_plaintext)
|
717
717
|
if b_plaintext is not None:
|
718
718
|
vault_id_used = vault_secret_id
|
@@ -1520,7 +1520,7 @@ class VaultHelper:
|
|
1520
1520
|
tags = AnsibleTagHelper.tags(ciphertext) # ciphertext has tags but value does not
|
1521
1521
|
elif value_type is EncryptedString:
|
1522
1522
|
ciphertext = value._ciphertext
|
1523
|
-
elif value_type in _jinja_common.Marker.
|
1523
|
+
elif value_type in _jinja_common.Marker._concrete_subclasses: # avoid wasteful raise/except of Marker when calling get_tag below
|
1524
1524
|
ciphertext = None
|
1525
1525
|
elif vaulted_value := VaultedValue.get_tag(value):
|
1526
1526
|
ciphertext = vaulted_value.ciphertext
|
@@ -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
@@ -45,9 +45,6 @@ class Taggable:
|
|
45
45
|
return ds
|
46
46
|
|
47
47
|
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
48
|
return [AnsibleTagHelper.tag_copy(ds, item.strip()) for item in ds.split(',')]
|
52
49
|
|
53
50
|
raise AnsibleError('tags must be specified as a list', obj=ds)
|
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:
|
@@ -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
|
|