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/module_utils/basic.py
CHANGED
@@ -75,7 +75,7 @@ except ImportError:
|
|
75
75
|
# Python2 & 3 way to get NoneType
|
76
76
|
NoneType = type(None)
|
77
77
|
|
78
|
-
from ._internal import _traceback, _errors, _debugging, _deprecator
|
78
|
+
from ._internal import _traceback, _errors, _debugging, _deprecator, _messages
|
79
79
|
|
80
80
|
from .common.text.converters import (
|
81
81
|
to_native,
|
@@ -161,10 +161,10 @@ from ansible.module_utils.common.validation import (
|
|
161
161
|
safe_eval,
|
162
162
|
)
|
163
163
|
from ansible.module_utils.common._utils import get_all_subclasses as _get_all_subclasses
|
164
|
-
from ansible.module_utils.common import messages as _messages
|
165
164
|
from ansible.module_utils.parsing.convert_bool import BOOLEANS, BOOLEANS_FALSE, BOOLEANS_TRUE, boolean
|
166
165
|
from ansible.module_utils.common.warnings import (
|
167
166
|
deprecate,
|
167
|
+
error_as_warning,
|
168
168
|
get_deprecations,
|
169
169
|
get_warnings,
|
170
170
|
warn,
|
@@ -480,9 +480,11 @@ class AnsibleModule(object):
|
|
480
480
|
if basedir is not None and not os.path.exists(basedir):
|
481
481
|
try:
|
482
482
|
os.makedirs(basedir, mode=0o700)
|
483
|
-
except
|
484
|
-
self.
|
485
|
-
|
483
|
+
except OSError as ex:
|
484
|
+
self.error_as_warning(
|
485
|
+
msg=f"Unable to use {basedir!r} as temporary directory, falling back to system default.",
|
486
|
+
exception=ex,
|
487
|
+
)
|
486
488
|
basedir = None
|
487
489
|
else:
|
488
490
|
self.warn("Module remote_tmp %s did not exist and was "
|
@@ -494,20 +496,45 @@ class AnsibleModule(object):
|
|
494
496
|
basefile = "ansible-moduletmp-%s-" % time.time()
|
495
497
|
try:
|
496
498
|
tmpdir = tempfile.mkdtemp(prefix=basefile, dir=basedir)
|
497
|
-
except
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
)
|
499
|
+
except OSError as ex:
|
500
|
+
raise Exception(
|
501
|
+
f"Failed to create remote module tmp path at dir {basedir!r} "
|
502
|
+
f"with prefix {basefile!r}.",
|
503
|
+
) from ex
|
502
504
|
if not self._keep_remote_files:
|
503
505
|
atexit.register(shutil.rmtree, tmpdir)
|
504
506
|
self._tmpdir = tmpdir
|
505
507
|
|
506
508
|
return self._tmpdir
|
507
509
|
|
508
|
-
def warn(
|
509
|
-
|
510
|
-
|
510
|
+
def warn(
|
511
|
+
self,
|
512
|
+
warning: str,
|
513
|
+
*,
|
514
|
+
help_text: str | None = None,
|
515
|
+
) -> None:
|
516
|
+
_skip_stackwalk = True
|
517
|
+
|
518
|
+
warn(
|
519
|
+
warning=warning,
|
520
|
+
help_text=help_text,
|
521
|
+
)
|
522
|
+
|
523
|
+
def error_as_warning(
|
524
|
+
self,
|
525
|
+
msg: str | None,
|
526
|
+
exception: BaseException,
|
527
|
+
*,
|
528
|
+
help_text: str | None = None,
|
529
|
+
) -> None:
|
530
|
+
"""Display an exception as a warning."""
|
531
|
+
_skip_stackwalk = True
|
532
|
+
|
533
|
+
error_as_warning(
|
534
|
+
msg=msg,
|
535
|
+
exception=exception,
|
536
|
+
help_text=help_text,
|
537
|
+
)
|
511
538
|
|
512
539
|
def deprecate(
|
513
540
|
self,
|
@@ -633,11 +660,8 @@ class AnsibleModule(object):
|
|
633
660
|
return context
|
634
661
|
try:
|
635
662
|
ret = selinux.lgetfilecon_raw(to_native(path, errors='surrogate_or_strict'))
|
636
|
-
except OSError as
|
637
|
-
|
638
|
-
self.fail_json(path=path, msg='path %s does not exist' % path)
|
639
|
-
else:
|
640
|
-
self.fail_json(path=path, msg='failed to retrieve selinux context')
|
663
|
+
except OSError as ex:
|
664
|
+
self.fail_json(path=path, msg='Failed to retrieve selinux context.', exception=ex)
|
641
665
|
if ret[0] == -1:
|
642
666
|
return context
|
643
667
|
# Limit split to 4 because the selevel, the last in the list,
|
@@ -777,9 +801,9 @@ class AnsibleModule(object):
|
|
777
801
|
return True
|
778
802
|
try:
|
779
803
|
os.lchown(b_path, uid, -1)
|
780
|
-
except
|
804
|
+
except OSError as ex:
|
781
805
|
path = to_text(b_path)
|
782
|
-
self.fail_json(path=path, msg='chown failed
|
806
|
+
self.fail_json(path=path, msg='chown failed', exception=ex)
|
783
807
|
changed = True
|
784
808
|
return changed
|
785
809
|
|
@@ -1305,7 +1329,7 @@ class AnsibleModule(object):
|
|
1305
1329
|
else:
|
1306
1330
|
journal.send(MESSAGE=u"%s %s" % (module, journal_msg),
|
1307
1331
|
**dict(journal_args))
|
1308
|
-
except
|
1332
|
+
except OSError:
|
1309
1333
|
# fall back to syslog since logging to journal failed
|
1310
1334
|
self._log_to_syslog(journal_msg)
|
1311
1335
|
else:
|
@@ -1399,7 +1423,7 @@ class AnsibleModule(object):
|
|
1399
1423
|
# deprecate(
|
1400
1424
|
# msg="The `AnsibleModule.jsonify' method is deprecated.",
|
1401
1425
|
# version="2.27",
|
1402
|
-
# # help_text="", # DTFIX-
|
1426
|
+
# # help_text="", # DTFIX-FUTURE: fill in this help text
|
1403
1427
|
# )
|
1404
1428
|
|
1405
1429
|
try:
|
@@ -1528,16 +1552,25 @@ class AnsibleModule(object):
|
|
1528
1552
|
)
|
1529
1553
|
|
1530
1554
|
if isinstance(exception, BaseException):
|
1531
|
-
# Include a `_messages.
|
1532
|
-
# The `msg` is included in the
|
1533
|
-
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
|
1555
|
+
# Include a `_messages.Event` in the result.
|
1556
|
+
# The `msg` is included in the chain to ensure it is not lost when looking only at `exception` from the result.
|
1557
|
+
|
1558
|
+
kwargs.update(
|
1559
|
+
exception=_messages.ErrorSummary(
|
1560
|
+
event=_messages.Event(
|
1561
|
+
msg=msg,
|
1562
|
+
formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.ERROR),
|
1563
|
+
chain=_messages.EventChain(
|
1564
|
+
msg_reason=_errors.MSG_REASON_DIRECT_CAUSE,
|
1565
|
+
traceback_reason="The above exception was the direct cause of the following error:",
|
1566
|
+
event=_errors.EventFactory.from_exception(exception, _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR)),
|
1567
|
+
),
|
1568
|
+
),
|
1569
|
+
),
|
1570
|
+
)
|
1538
1571
|
elif _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR):
|
1539
1572
|
# Include only a formatted traceback string in the result.
|
1540
|
-
# The controller will combine this with `msg` to create an `_messages.
|
1573
|
+
# The controller will combine this with `msg` to create an `_messages.ErrorSummary`.
|
1541
1574
|
|
1542
1575
|
formatted_traceback: str | None
|
1543
1576
|
|
@@ -1546,7 +1579,7 @@ class AnsibleModule(object):
|
|
1546
1579
|
elif exception is _UNSET and (current_exception := t.cast(t.Optional[BaseException], sys.exc_info()[1])):
|
1547
1580
|
formatted_traceback = _traceback.maybe_extract_traceback(current_exception, _traceback.TracebackEvent.ERROR)
|
1548
1581
|
else:
|
1549
|
-
formatted_traceback = _traceback.maybe_capture_traceback(_traceback.TracebackEvent.ERROR)
|
1582
|
+
formatted_traceback = _traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.ERROR)
|
1550
1583
|
|
1551
1584
|
if formatted_traceback:
|
1552
1585
|
kwargs.update(exception=formatted_traceback)
|
@@ -1626,8 +1659,8 @@ class AnsibleModule(object):
|
|
1626
1659
|
|
1627
1660
|
try:
|
1628
1661
|
self.preserved_copy(fn, backupdest)
|
1629
|
-
except (shutil.Error,
|
1630
|
-
|
1662
|
+
except (shutil.Error, OSError) as ex:
|
1663
|
+
raise Exception(f'Could not make backup of {fn!r} to {backupdest!r}.') from ex
|
1631
1664
|
|
1632
1665
|
return backupdest
|
1633
1666
|
|
@@ -1701,28 +1734,25 @@ class AnsibleModule(object):
|
|
1701
1734
|
try:
|
1702
1735
|
# Optimistically try a rename, solves some corner cases and can avoid useless work, throws exception if not atomic.
|
1703
1736
|
os.rename(b_src, b_dest)
|
1704
|
-
except
|
1705
|
-
if
|
1737
|
+
except OSError as ex:
|
1738
|
+
if ex.errno in (errno.EPERM, errno.EXDEV, errno.EACCES, errno.ETXTBSY, errno.EBUSY):
|
1706
1739
|
# only try workarounds for errno 18 (cross device), 1 (not permitted), 13 (permission denied)
|
1707
1740
|
# and 26 (text file busy) which happens on vagrant synced folders and other 'exotic' non posix file systems
|
1708
|
-
self.fail_json(msg='Could not replace file: %s to %s: %s' % (src, dest, to_native(e)))
|
1709
|
-
else:
|
1710
1741
|
# Use bytes here. In the shippable CI, this fails with
|
1711
1742
|
# a UnicodeError with surrogateescape'd strings for an unknown
|
1712
1743
|
# reason (doesn't happen in a local Ubuntu16.04 VM)
|
1713
1744
|
b_dest_dir = os.path.dirname(b_dest)
|
1714
1745
|
b_suffix = os.path.basename(b_dest)
|
1715
|
-
error_msg = None
|
1716
1746
|
tmp_dest_name = None
|
1717
1747
|
try:
|
1718
1748
|
tmp_dest_fd, tmp_dest_name = tempfile.mkstemp(prefix=b'.ansible_tmp', dir=b_dest_dir, suffix=b_suffix)
|
1719
|
-
except
|
1720
|
-
error_msg = 'The destination directory (%s) is not writable by the current user. Error was: %s' % (os.path.dirname(dest), to_native(e))
|
1721
|
-
|
1749
|
+
except OSError as ex:
|
1722
1750
|
if unsafe_writes:
|
1723
1751
|
self._unsafe_writes(b_src, b_dest)
|
1724
1752
|
else:
|
1725
|
-
|
1753
|
+
raise Exception(
|
1754
|
+
f'The destination directory {os.path.dirname(dest)!r} is not writable by the current user.'
|
1755
|
+
) from ex
|
1726
1756
|
|
1727
1757
|
if tmp_dest_name:
|
1728
1758
|
b_tmp_dest_name = to_bytes(tmp_dest_name, errors='surrogate_or_strict')
|
@@ -1751,24 +1781,27 @@ class AnsibleModule(object):
|
|
1751
1781
|
if dest_stat and (tmp_stat.st_uid != dest_stat.st_uid or tmp_stat.st_gid != dest_stat.st_gid):
|
1752
1782
|
os.chown(b_tmp_dest_name, dest_stat.st_uid, dest_stat.st_gid)
|
1753
1783
|
os.utime(b_tmp_dest_name, times=(time.time(), time.time()))
|
1754
|
-
except OSError as
|
1755
|
-
if
|
1784
|
+
except OSError as ex:
|
1785
|
+
if ex.errno != errno.EPERM:
|
1756
1786
|
raise
|
1757
1787
|
try:
|
1758
1788
|
os.rename(b_tmp_dest_name, b_dest)
|
1759
|
-
except (shutil.Error, OSError
|
1760
|
-
if unsafe_writes and
|
1789
|
+
except (shutil.Error, OSError) as ex:
|
1790
|
+
if unsafe_writes and ex.errno == errno.EBUSY:
|
1761
1791
|
self._unsafe_writes(b_tmp_dest_name, b_dest)
|
1762
1792
|
else:
|
1763
|
-
|
1764
|
-
|
1765
|
-
|
1793
|
+
raise Exception(
|
1794
|
+
f'Unable to make {src!r} into to {dest!r}, failed final rename from {to_text(b_tmp_dest_name)!r}.'
|
1795
|
+
) from ex
|
1796
|
+
except (shutil.Error, OSError) as ex:
|
1766
1797
|
if unsafe_writes:
|
1767
1798
|
self._unsafe_writes(b_src, b_dest)
|
1768
1799
|
else:
|
1769
|
-
|
1800
|
+
raise Exception(f'Failed to replace {dest!r} with {src!r}.') from ex
|
1770
1801
|
finally:
|
1771
1802
|
self.cleanup(b_tmp_dest_name)
|
1803
|
+
else:
|
1804
|
+
raise Exception(f'Could not replace {dest!r} with {src!r}.') from ex
|
1772
1805
|
|
1773
1806
|
if creating:
|
1774
1807
|
# make sure the file has the correct permissions
|
@@ -1795,18 +1828,11 @@ class AnsibleModule(object):
|
|
1795
1828
|
# sadly there are some situations where we cannot ensure atomicity, but only if
|
1796
1829
|
# the user insists and we get the appropriate error we update the file unsafely
|
1797
1830
|
try:
|
1798
|
-
|
1799
|
-
|
1800
|
-
|
1801
|
-
|
1802
|
-
|
1803
|
-
finally: # assuring closed files in 2.4 compatible way
|
1804
|
-
if out_dest:
|
1805
|
-
out_dest.close()
|
1806
|
-
if in_src:
|
1807
|
-
in_src.close()
|
1808
|
-
except (shutil.Error, OSError, IOError) as e:
|
1809
|
-
self.fail_json(msg='Could not write data to file (%s) from (%s): %s' % (dest, src, to_native(e)))
|
1831
|
+
with open(dest, 'wb') as out_dest:
|
1832
|
+
with open(src, 'rb') as in_src:
|
1833
|
+
shutil.copyfileobj(in_src, out_dest)
|
1834
|
+
except (shutil.Error, OSError) as ex:
|
1835
|
+
raise Exception(f'Could not write data to file {dest!r} from {src!r}.') from ex
|
1810
1836
|
|
1811
1837
|
def _clean_args(self, args):
|
1812
1838
|
|
@@ -2092,18 +2118,16 @@ class AnsibleModule(object):
|
|
2092
2118
|
selector.close()
|
2093
2119
|
|
2094
2120
|
rc = cmd.returncode
|
2095
|
-
except
|
2096
|
-
self.log("Error Executing CMD:%s Exception:%s" % (self._clean_args(args), to_native(e)))
|
2121
|
+
except OSError as ex:
|
2097
2122
|
if handle_exceptions:
|
2098
|
-
self.fail_json(rc=
|
2123
|
+
self.fail_json(rc=ex.errno, stdout='', stderr='', msg="Error executing command.", cmd=self._clean_args(args), exception=ex)
|
2099
2124
|
else:
|
2100
|
-
raise
|
2101
|
-
except Exception as
|
2102
|
-
self.log("Error Executing CMD:%s Exception:%s" % (self._clean_args(args), to_native(traceback.format_exc())))
|
2125
|
+
raise
|
2126
|
+
except Exception as ex:
|
2103
2127
|
if handle_exceptions:
|
2104
|
-
self.fail_json(rc=257, stdout=
|
2128
|
+
self.fail_json(rc=257, stdout='', stderr='', msg="Error executing command.", cmd=self._clean_args(args), exception=ex)
|
2105
2129
|
else:
|
2106
|
-
raise
|
2130
|
+
raise
|
2107
2131
|
|
2108
2132
|
if rc != 0 and check_rc:
|
2109
2133
|
msg = heuristic_log_sanitize(stderr.rstrip(), self.no_log_values)
|
@@ -2174,7 +2198,7 @@ def __getattr__(importable_name):
|
|
2174
2198
|
importable = repeat
|
2175
2199
|
elif importable_name in {
|
2176
2200
|
'PY2', 'PY3', 'b', 'binary_type', 'integer_types',
|
2177
|
-
'iteritems', 'string_types', '
|
2201
|
+
'iteritems', 'string_types', 'text_type',
|
2178
2202
|
}:
|
2179
2203
|
import importlib
|
2180
2204
|
importable = getattr(
|
@@ -6,6 +6,7 @@ from __future__ import annotations
|
|
6
6
|
|
7
7
|
from copy import deepcopy
|
8
8
|
|
9
|
+
from ansible.module_utils.datatag import deprecator_from_collection_name
|
9
10
|
from ansible.module_utils.common.parameters import (
|
10
11
|
_ADDITIONAL_CHECKS,
|
11
12
|
_get_legal_inputs,
|
@@ -22,7 +23,6 @@ from ansible.module_utils.common.parameters import (
|
|
22
23
|
|
23
24
|
from ansible.module_utils.common.text.converters import to_native
|
24
25
|
from ansible.module_utils.common.warnings import deprecate, warn
|
25
|
-
from ansible.module_utils.common import messages as _messages
|
26
26
|
|
27
27
|
from ansible.module_utils.common.validation import (
|
28
28
|
check_mutually_exclusive,
|
@@ -306,7 +306,7 @@ class ModuleArgumentSpecValidator(ArgumentSpecValidator):
|
|
306
306
|
msg=d['msg'],
|
307
307
|
version=d.get('version'),
|
308
308
|
date=d.get('date'),
|
309
|
-
deprecator=
|
309
|
+
deprecator=deprecator_from_collection_name(d.get('collection_name')),
|
310
310
|
)
|
311
311
|
|
312
312
|
for w in result._warnings:
|
@@ -6,6 +6,7 @@
|
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
8
|
|
9
|
+
from ansible.module_utils.common import warnings as _warnings
|
9
10
|
from ansible.module_utils.six import binary_type, text_type
|
10
11
|
from ansible.module_utils.six.moves.collections_abc import Hashable, Mapping, MutableMapping, Sequence # pylint: disable=unused-import
|
11
12
|
|
@@ -102,6 +103,11 @@ def count(seq):
|
|
102
103
|
code is run on Python 2.6.* where collections.Counter is not available. It should be
|
103
104
|
deprecated and replaced when support for Python < 2.7 is dropped.
|
104
105
|
"""
|
106
|
+
_warnings.deprecate(
|
107
|
+
msg="The `ansible.module_utils.common.collections.count` function is deprecated.",
|
108
|
+
version="2.23",
|
109
|
+
help_text="Use `collections.Counter` from the Python standard library instead.",
|
110
|
+
)
|
105
111
|
if not is_iterable(seq):
|
106
112
|
raise Exception('Argument provided is not an iterable')
|
107
113
|
counters = dict()
|
@@ -20,7 +20,7 @@ def __getattr__(name: str) -> object:
|
|
20
20
|
# _warnings.deprecate(
|
21
21
|
# msg="The `AnsibleJSONEncoder` type is deprecated.",
|
22
22
|
# version="2.27",
|
23
|
-
# help_text="Use a profile-based encoder instead.", # DTFIX-
|
23
|
+
# help_text="Use a profile-based encoder instead.", # DTFIX-FUTURE: improve this help text
|
24
24
|
# )
|
25
25
|
|
26
26
|
return _get_legacy_encoder()
|
@@ -31,7 +31,7 @@ def __getattr__(name: str) -> object:
|
|
31
31
|
# _warnings.deprecate(
|
32
32
|
# msg="The `AnsibleJSONDecoder` type is deprecated.",
|
33
33
|
# version="2.27",
|
34
|
-
# help_text="Use a profile-based decoder instead.", # DTFIX-
|
34
|
+
# help_text="Use a profile-based decoder instead.", # DTFIX-FUTURE: improve this help text
|
35
35
|
# )
|
36
36
|
|
37
37
|
return _tagless.Decoder
|
@@ -10,6 +10,7 @@ import sys
|
|
10
10
|
import typing as t
|
11
11
|
|
12
12
|
from ansible.module_utils.common.text.converters import to_bytes
|
13
|
+
from ansible.module_utils._internal._ansiballz import _respawn
|
13
14
|
|
14
15
|
_ANSIBLE_PARENT_PATH = pathlib.Path(__file__).parents[3]
|
15
16
|
|
@@ -39,7 +40,7 @@ def respawn_module(interpreter_path) -> t.NoReturn:
|
|
39
40
|
raise Exception('module has already been respawned')
|
40
41
|
|
41
42
|
# FUTURE: we need a safe way to log that a respawn has occurred for forensic/debug purposes
|
42
|
-
payload =
|
43
|
+
payload = _respawn.create_payload()
|
43
44
|
stdin_read, stdin_write = os.pipe()
|
44
45
|
os.write(stdin_write, to_bytes(payload))
|
45
46
|
os.close(stdin_write)
|
@@ -59,10 +60,12 @@ def probe_interpreters_for_module(interpreter_paths, module_name):
|
|
59
60
|
:arg module_name: fully-qualified Python module name to probe for (for example, ``selinux``)
|
60
61
|
"""
|
61
62
|
PYTHONPATH = os.getenv('PYTHONPATH', '')
|
63
|
+
|
62
64
|
env = os.environ.copy()
|
63
65
|
env.update({
|
64
66
|
'PYTHONPATH': f'{_ANSIBLE_PARENT_PATH}:{PYTHONPATH}'.rstrip(': ')
|
65
67
|
})
|
68
|
+
|
66
69
|
for interpreter_path in interpreter_paths:
|
67
70
|
if not os.path.exists(interpreter_path):
|
68
71
|
continue
|
@@ -81,43 +84,3 @@ def probe_interpreters_for_module(interpreter_paths, module_name):
|
|
81
84
|
continue
|
82
85
|
|
83
86
|
return None
|
84
|
-
|
85
|
-
|
86
|
-
def _create_payload():
|
87
|
-
# FIXME: move this into _ansiballz and skip the template
|
88
|
-
from ansible.module_utils import basic
|
89
|
-
|
90
|
-
module_fqn = sys.modules['__main__']._module_fqn
|
91
|
-
modlib_path = sys.modules['__main__']._modlib_path
|
92
|
-
|
93
|
-
respawn_code_template = """
|
94
|
-
if __name__ == '__main__':
|
95
|
-
import runpy
|
96
|
-
import sys
|
97
|
-
|
98
|
-
json_params = {json_params!r}
|
99
|
-
profile = {profile!r}
|
100
|
-
module_fqn = {module_fqn!r}
|
101
|
-
modlib_path = {modlib_path!r}
|
102
|
-
|
103
|
-
sys.path.insert(0, modlib_path)
|
104
|
-
|
105
|
-
from ansible.module_utils._internal import _ansiballz
|
106
|
-
|
107
|
-
_ansiballz.run_module(
|
108
|
-
json_params=json_params,
|
109
|
-
profile=profile,
|
110
|
-
module_fqn=module_fqn,
|
111
|
-
modlib_path=modlib_path,
|
112
|
-
init_globals=dict(_respawned=True),
|
113
|
-
)
|
114
|
-
"""
|
115
|
-
|
116
|
-
respawn_code = respawn_code_template.format(
|
117
|
-
json_params=basic._ANSIBLE_ARGS,
|
118
|
-
profile=basic._ANSIBLE_PROFILE,
|
119
|
-
module_fqn=module_fqn,
|
120
|
-
modlib_path=modlib_path,
|
121
|
-
)
|
122
|
-
|
123
|
-
return respawn_code
|
@@ -245,7 +245,7 @@ def jsonify(data, **kwargs):
|
|
245
245
|
# deprecate(
|
246
246
|
# msg="The `jsonify` function is deprecated.",
|
247
247
|
# version="2.27",
|
248
|
-
# # help_text="", # DTFIX-
|
248
|
+
# # help_text="", # DTFIX-FUTURE: fill in this help text
|
249
249
|
# )
|
250
250
|
|
251
251
|
return json.dumps(data, cls=_common_json._get_legacy_encoder(), _decode_bytes=True, **kwargs)
|
@@ -257,7 +257,7 @@ def container_to_bytes(d, encoding='utf-8', errors='surrogate_or_strict'):
|
|
257
257
|
Specialized for json return because this only handles, lists, tuples,
|
258
258
|
and dict container types (the containers that the json module returns)
|
259
259
|
"""
|
260
|
-
# DTFIX-
|
260
|
+
# DTFIX-FUTURE: deprecate
|
261
261
|
|
262
262
|
if isinstance(d, text_type):
|
263
263
|
return to_bytes(d, encoding=encoding, errors=errors)
|
@@ -277,7 +277,7 @@ def container_to_text(d, encoding='utf-8', errors='surrogate_or_strict'):
|
|
277
277
|
Specialized for json return because this only handles, lists, tuples,
|
278
278
|
and dict container types (the containers that the json module returns)
|
279
279
|
"""
|
280
|
-
# DTFIX-
|
280
|
+
# DTFIX-FUTURE: deprecate
|
281
281
|
|
282
282
|
if isinstance(d, binary_type):
|
283
283
|
# Warning, can traceback
|
@@ -402,7 +402,7 @@ def check_type_list(value):
|
|
402
402
|
if isinstance(value, list):
|
403
403
|
return value
|
404
404
|
|
405
|
-
# DTFIX-
|
405
|
+
# DTFIX-FUTURE: deprecate legacy comma split functionality, eventually replace with `_check_type_list_strict`
|
406
406
|
if isinstance(value, string_types):
|
407
407
|
return value.split(",")
|
408
408
|
elif isinstance(value, int) or isinstance(value, float):
|
@@ -6,20 +6,77 @@ from __future__ import annotations as _annotations
|
|
6
6
|
|
7
7
|
import typing as _t
|
8
8
|
|
9
|
-
from ansible.module_utils._internal import _traceback, _deprecator
|
10
|
-
from ansible.module_utils.common import messages as _messages
|
9
|
+
from ansible.module_utils._internal import _traceback, _deprecator, _event_utils, _messages, _errors
|
11
10
|
from ansible.module_utils import _internal
|
12
11
|
|
13
12
|
|
14
|
-
def warn(
|
13
|
+
def warn(
|
14
|
+
warning: str,
|
15
|
+
*,
|
16
|
+
help_text: str | None = None,
|
17
|
+
obj: object | None = None,
|
18
|
+
) -> None:
|
15
19
|
"""Record a warning to be returned with the module result."""
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
_skip_stackwalk = True
|
21
|
+
|
22
|
+
if _internal.is_controller:
|
23
|
+
_display = _internal.import_controller_module('ansible.utils.display').Display()
|
24
|
+
_display.warning(
|
25
|
+
msg=warning,
|
26
|
+
help_text=help_text,
|
27
|
+
obj=obj,
|
28
|
+
)
|
29
|
+
|
30
|
+
return
|
31
|
+
|
32
|
+
warning = _messages.WarningSummary(
|
33
|
+
event=_messages.Event(
|
34
|
+
msg=warning,
|
35
|
+
help_text=help_text,
|
36
|
+
formatted_traceback=_traceback.maybe_capture_traceback(warning, _traceback.TracebackEvent.WARNING),
|
37
|
+
),
|
38
|
+
)
|
39
|
+
|
40
|
+
_global_warnings[warning] = None
|
41
|
+
|
42
|
+
|
43
|
+
def error_as_warning(
|
44
|
+
msg: str | None,
|
45
|
+
exception: BaseException,
|
46
|
+
*,
|
47
|
+
help_text: str | None = None,
|
48
|
+
obj: object = None,
|
49
|
+
) -> None:
|
50
|
+
"""Display an exception as a warning."""
|
51
|
+
_skip_stackwalk = True
|
52
|
+
|
53
|
+
if _internal.is_controller:
|
54
|
+
_display = _internal.import_controller_module('ansible.utils.display').Display()
|
55
|
+
_display.error_as_warning(
|
56
|
+
msg=msg,
|
57
|
+
exception=exception,
|
58
|
+
help_text=help_text,
|
59
|
+
obj=obj,
|
60
|
+
)
|
61
|
+
|
62
|
+
return
|
63
|
+
|
64
|
+
event = _errors.EventFactory.from_exception(exception, _traceback.is_traceback_enabled(_traceback.TracebackEvent.WARNING))
|
65
|
+
|
66
|
+
warning = _messages.WarningSummary(
|
67
|
+
event=_messages.Event(
|
68
|
+
msg=msg,
|
69
|
+
help_text=help_text,
|
70
|
+
formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.WARNING),
|
71
|
+
chain=_messages.EventChain(
|
72
|
+
msg_reason=_errors.MSG_REASON_DIRECT_CAUSE,
|
73
|
+
traceback_reason=_errors.TRACEBACK_REASON_EXCEPTION_DIRECT_WARNING,
|
74
|
+
event=event,
|
75
|
+
),
|
20
76
|
),
|
21
|
-
|
22
|
-
|
77
|
+
)
|
78
|
+
|
79
|
+
_global_warnings[warning] = None
|
23
80
|
|
24
81
|
|
25
82
|
def deprecate(
|
@@ -57,30 +114,30 @@ def deprecate(
|
|
57
114
|
|
58
115
|
return
|
59
116
|
|
60
|
-
|
61
|
-
|
62
|
-
|
117
|
+
warning = _messages.DeprecationSummary(
|
118
|
+
event=_messages.Event(
|
119
|
+
msg=msg,
|
120
|
+
help_text=help_text,
|
121
|
+
formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.DEPRECATED),
|
63
122
|
),
|
64
|
-
formatted_traceback=_traceback.maybe_capture_traceback(_traceback.TracebackEvent.DEPRECATED),
|
65
123
|
version=version,
|
66
124
|
date=date,
|
67
125
|
deprecator=deprecator,
|
68
|
-
)
|
126
|
+
)
|
127
|
+
|
128
|
+
_global_deprecations[warning] = None
|
69
129
|
|
70
130
|
|
71
131
|
def get_warning_messages() -> tuple[str, ...]:
|
72
132
|
"""Return a tuple of warning messages accumulated over this run."""
|
73
|
-
#
|
74
|
-
return tuple(item.
|
75
|
-
|
76
|
-
|
77
|
-
_DEPRECATION_MESSAGE_KEYS = frozenset({'msg', 'date', 'version', 'collection_name'})
|
133
|
+
# DTFIX7: add future deprecation comment
|
134
|
+
return tuple(_event_utils.format_event_brief_message(item.event) for item in _global_warnings)
|
78
135
|
|
79
136
|
|
80
137
|
def get_deprecation_messages() -> tuple[dict[str, _t.Any], ...]:
|
81
138
|
"""Return a tuple of deprecation warning messages accumulated over this run."""
|
82
|
-
#
|
83
|
-
return tuple(
|
139
|
+
# DTFIX7: add future deprecation comment
|
140
|
+
return tuple(_event_utils.deprecation_as_dict(item) for item in _global_deprecations)
|
84
141
|
|
85
142
|
|
86
143
|
def get_warnings() -> list[_messages.WarningSummary]:
|
@@ -94,7 +151,7 @@ def get_deprecations() -> list[_messages.DeprecationSummary]:
|
|
94
151
|
|
95
152
|
|
96
153
|
_global_warnings: dict[_messages.WarningSummary, object] = {}
|
97
|
-
"""Global, ordered, de-
|
154
|
+
"""Global, ordered, de-duplicated storage of accumulated warnings for the current module run."""
|
98
155
|
|
99
156
|
_global_deprecations: dict[_messages.DeprecationSummary, object] = {}
|
100
|
-
"""Global, ordered, de-
|
157
|
+
"""Global, ordered, de-duplicated storage of accumulated deprecations for the current module run."""
|
@@ -24,7 +24,7 @@ except ImportError:
|
|
24
24
|
else:
|
25
25
|
HAS_YAML = True
|
26
26
|
|
27
|
-
# DTFIX-
|
27
|
+
# DTFIX-FUTURE: refactor this to share the implementation with the controller version
|
28
28
|
# use an abstract base class, with __init_subclass__ for representer registration, and instance methods for overridable representers
|
29
29
|
# then tests can be consolidated intead of having two nearly identical copies
|
30
30
|
|