ansible-core 2.19.0b2__py3-none-any.whl → 2.19.0b3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ansible/_internal/_ansiballz.py +1 -4
- ansible/_internal/_templating/_datatag.py +3 -4
- ansible/_internal/_templating/_engine.py +6 -1
- ansible/_internal/_templating/_jinja_plugins.py +2 -6
- ansible/cli/__init__.py +3 -2
- ansible/cli/arguments/option_helpers.py +4 -1
- ansible/cli/doc.py +0 -1
- ansible/config/manager.py +2 -2
- ansible/constants.py +0 -62
- ansible/errors/__init__.py +6 -2
- ansible/executor/module_common.py +11 -7
- ansible/executor/task_executor.py +6 -8
- ansible/galaxy/api.py +1 -1
- ansible/galaxy/collection/__init__.py +3 -3
- ansible/module_utils/_internal/_ansiballz.py +4 -30
- ansible/module_utils/_internal/_datatag/_tags.py +3 -25
- ansible/module_utils/_internal/_deprecator.py +134 -0
- ansible/module_utils/_internal/_plugin_info.py +25 -0
- ansible/module_utils/_internal/_validation.py +14 -0
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +64 -17
- ansible/module_utils/common/arg_spec.py +8 -3
- ansible/module_utils/common/messages.py +40 -23
- ansible/module_utils/common/process.py +0 -1
- ansible/module_utils/common/respawn.py +0 -7
- ansible/module_utils/common/warnings.py +13 -13
- ansible/module_utils/datatag.py +13 -13
- ansible/modules/async_status.py +1 -1
- ansible/modules/dnf5.py +1 -1
- ansible/modules/get_url.py +1 -1
- ansible/playbook/task.py +0 -2
- ansible/plugins/__init__.py +18 -8
- ansible/plugins/action/__init__.py +6 -14
- ansible/plugins/action/gather_facts.py +2 -4
- ansible/plugins/callback/oneline.py +7 -1
- ansible/plugins/callback/tree.py +7 -1
- ansible/plugins/connection/local.py +1 -1
- ansible/plugins/connection/paramiko_ssh.py +9 -2
- ansible/plugins/doc_fragments/action_core.py +1 -1
- ansible/plugins/filter/core.py +4 -1
- ansible/plugins/inventory/__init__.py +2 -2
- ansible/plugins/loader.py +194 -130
- ansible/plugins/lookup/url.py +2 -2
- ansible/plugins/strategy/__init__.py +6 -6
- ansible/release.py +1 -1
- ansible/template/__init__.py +1 -1
- ansible/utils/collection_loader/_collection_meta.py +5 -3
- ansible/utils/display.py +133 -71
- ansible/utils/py3compat.py +1 -7
- ansible/utils/ssh_functions.py +4 -1
- ansible/vars/manager.py +18 -10
- ansible/vars/plugins.py +4 -4
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/RECORD +67 -65
- ansible_test/_internal/commands/sanity/pylint.py +1 -0
- ansible_test/_internal/docker_util.py +4 -3
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +475 -0
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_comment.py +137 -0
- ansible/module_utils/_internal/_dataclass_annotation_patch.py +0 -64
- ansible/module_utils/_internal/_plugin_exec_context.py +0 -49
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +0 -399
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/Apache-License.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/COPYING +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/MIT-license.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/PSF-license.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import typing as t
|
4
|
+
|
5
|
+
from ..common import messages as _messages
|
6
|
+
|
7
|
+
|
8
|
+
class HasPluginInfo(t.Protocol):
|
9
|
+
"""Protocol to type-annotate and expose PluginLoader-set values."""
|
10
|
+
|
11
|
+
@property
|
12
|
+
def ansible_name(self) -> str | None:
|
13
|
+
"""Fully resolved plugin name."""
|
14
|
+
|
15
|
+
@property
|
16
|
+
def plugin_type(self) -> str:
|
17
|
+
"""Plugin type name."""
|
18
|
+
|
19
|
+
|
20
|
+
def get_plugin_info(value: HasPluginInfo) -> _messages.PluginInfo:
|
21
|
+
"""Utility method that returns a `PluginInfo` from an object implementing the `HasPluginInfo` protocol."""
|
22
|
+
return _messages.PluginInfo(
|
23
|
+
resolved_name=value.ansible_name,
|
24
|
+
type=value.plugin_type,
|
25
|
+
)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import keyword
|
4
|
+
|
5
|
+
|
6
|
+
def validate_collection_name(collection_name: object, name: str = 'collection_name') -> None:
|
7
|
+
"""Validate a collection name."""
|
8
|
+
if not isinstance(collection_name, str):
|
9
|
+
raise TypeError(f"{name} must be {str} instead of {type(collection_name)}")
|
10
|
+
|
11
|
+
parts = collection_name.split('.')
|
12
|
+
|
13
|
+
if len(parts) != 2 or not all(part.isidentifier() and not keyword.iskeyword(part) for part in parts):
|
14
|
+
raise ValueError(f"{name} must consist of two non-keyword identifiers separated by '.'")
|
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
|
78
|
+
from ._internal import _traceback, _errors, _debugging, _deprecator
|
79
79
|
|
80
80
|
from .common.text.converters import (
|
81
81
|
to_native,
|
@@ -509,16 +509,31 @@ class AnsibleModule(object):
|
|
509
509
|
warn(warning)
|
510
510
|
self.log('[WARNING] %s' % warning)
|
511
511
|
|
512
|
-
def deprecate(
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
512
|
+
def deprecate(
|
513
|
+
self,
|
514
|
+
msg: str,
|
515
|
+
version: str | None = None,
|
516
|
+
date: str | None = None,
|
517
|
+
collection_name: str | None = None,
|
518
|
+
*,
|
519
|
+
deprecator: _messages.PluginInfo | None = None,
|
520
|
+
help_text: str | None = None,
|
521
|
+
) -> None:
|
522
|
+
"""
|
523
|
+
Record a deprecation warning to be returned with the module result.
|
524
|
+
Most callers do not need to provide `collection_name` or `deprecator` -- but provide only one if needed.
|
525
|
+
Specify `version` or `date`, but not both.
|
526
|
+
If `date` is a string, it must be in the form `YYYY-MM-DD`.
|
527
|
+
"""
|
528
|
+
_skip_stackwalk = True
|
529
|
+
|
530
|
+
deprecate( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
|
531
|
+
msg=msg,
|
532
|
+
version=version,
|
533
|
+
date=date,
|
534
|
+
deprecator=_deprecator.get_best_deprecator(deprecator=deprecator, collection_name=collection_name),
|
535
|
+
help_text=help_text,
|
536
|
+
)
|
522
537
|
|
523
538
|
def load_file_common_arguments(self, params, path=None):
|
524
539
|
"""
|
@@ -1404,6 +1419,7 @@ class AnsibleModule(object):
|
|
1404
1419
|
self.cleanup(path)
|
1405
1420
|
|
1406
1421
|
def _return_formatted(self, kwargs):
|
1422
|
+
_skip_stackwalk = True
|
1407
1423
|
|
1408
1424
|
self.add_path_info(kwargs)
|
1409
1425
|
|
@@ -1411,6 +1427,13 @@ class AnsibleModule(object):
|
|
1411
1427
|
kwargs['invocation'] = {'module_args': self.params}
|
1412
1428
|
|
1413
1429
|
if 'warnings' in kwargs:
|
1430
|
+
self.deprecate( # pylint: disable=ansible-deprecated-unnecessary-collection-name
|
1431
|
+
msg='Passing `warnings` to `exit_json` or `fail_json` is deprecated.',
|
1432
|
+
version='2.23',
|
1433
|
+
help_text='Use `AnsibleModule.warn` instead.',
|
1434
|
+
deprecator=_deprecator.ANSIBLE_CORE_DEPRECATOR,
|
1435
|
+
)
|
1436
|
+
|
1414
1437
|
if isinstance(kwargs['warnings'], list):
|
1415
1438
|
for w in kwargs['warnings']:
|
1416
1439
|
self.warn(w)
|
@@ -1422,17 +1445,38 @@ class AnsibleModule(object):
|
|
1422
1445
|
kwargs['warnings'] = warnings
|
1423
1446
|
|
1424
1447
|
if 'deprecations' in kwargs:
|
1448
|
+
self.deprecate( # pylint: disable=ansible-deprecated-unnecessary-collection-name
|
1449
|
+
msg='Passing `deprecations` to `exit_json` or `fail_json` is deprecated.',
|
1450
|
+
version='2.23',
|
1451
|
+
help_text='Use `AnsibleModule.deprecate` instead.',
|
1452
|
+
deprecator=_deprecator.ANSIBLE_CORE_DEPRECATOR,
|
1453
|
+
)
|
1454
|
+
|
1425
1455
|
if isinstance(kwargs['deprecations'], list):
|
1426
1456
|
for d in kwargs['deprecations']:
|
1427
|
-
if isinstance(d,
|
1428
|
-
self.deprecate(
|
1457
|
+
if isinstance(d, (KeysView, Sequence)) and len(d) == 2:
|
1458
|
+
self.deprecate( # pylint: disable=ansible-deprecated-unnecessary-collection-name,ansible-invalid-deprecated-version
|
1459
|
+
msg=d[0],
|
1460
|
+
version=d[1],
|
1461
|
+
deprecator=_deprecator.get_best_deprecator(),
|
1462
|
+
)
|
1429
1463
|
elif isinstance(d, Mapping):
|
1430
|
-
self.deprecate(
|
1431
|
-
|
1464
|
+
self.deprecate( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
|
1465
|
+
msg=d['msg'],
|
1466
|
+
version=d.get('version'),
|
1467
|
+
date=d.get('date'),
|
1468
|
+
deprecator=_deprecator.get_best_deprecator(collection_name=d.get('collection_name')),
|
1469
|
+
)
|
1432
1470
|
else:
|
1433
|
-
self.deprecate(
|
1471
|
+
self.deprecate( # pylint: disable=ansible-deprecated-unnecessary-collection-name,ansible-deprecated-no-version
|
1472
|
+
msg=d,
|
1473
|
+
deprecator=_deprecator.get_best_deprecator(),
|
1474
|
+
)
|
1434
1475
|
else:
|
1435
|
-
self.deprecate(
|
1476
|
+
self.deprecate( # pylint: disable=ansible-deprecated-unnecessary-collection-name,ansible-deprecated-no-version
|
1477
|
+
msg=kwargs['deprecations'],
|
1478
|
+
deprecator=_deprecator.get_best_deprecator(),
|
1479
|
+
)
|
1436
1480
|
|
1437
1481
|
deprecations = get_deprecations()
|
1438
1482
|
if deprecations:
|
@@ -1452,6 +1496,7 @@ class AnsibleModule(object):
|
|
1452
1496
|
|
1453
1497
|
def exit_json(self, **kwargs) -> t.NoReturn:
|
1454
1498
|
""" return from the module, without error """
|
1499
|
+
_skip_stackwalk = True
|
1455
1500
|
|
1456
1501
|
self.do_cleanup_files()
|
1457
1502
|
self._return_formatted(kwargs)
|
@@ -1473,6 +1518,8 @@ class AnsibleModule(object):
|
|
1473
1518
|
When `exception` is not specified, a formatted traceback will be retrieved from the current exception.
|
1474
1519
|
If no exception is pending, the current call stack will be used instead.
|
1475
1520
|
"""
|
1521
|
+
_skip_stackwalk = True
|
1522
|
+
|
1476
1523
|
msg = str(msg) # coerce to str instead of raising an error due to an invalid type
|
1477
1524
|
|
1478
1525
|
kwargs.update(
|
@@ -22,6 +22,7 @@ from ansible.module_utils.common.parameters import (
|
|
22
22
|
|
23
23
|
from ansible.module_utils.common.text.converters import to_native
|
24
24
|
from ansible.module_utils.common.warnings import deprecate, warn
|
25
|
+
from ansible.module_utils.common import messages as _messages
|
25
26
|
|
26
27
|
from ansible.module_utils.common.validation import (
|
27
28
|
check_mutually_exclusive,
|
@@ -300,9 +301,13 @@ class ModuleArgumentSpecValidator(ArgumentSpecValidator):
|
|
300
301
|
result = super(ModuleArgumentSpecValidator, self).validate(parameters)
|
301
302
|
|
302
303
|
for d in result._deprecations:
|
303
|
-
|
304
|
-
|
305
|
-
|
304
|
+
# DTFIX-FUTURE: pass an actual deprecator instead of one derived from collection_name
|
305
|
+
deprecate( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
|
306
|
+
msg=d['msg'],
|
307
|
+
version=d.get('version'),
|
308
|
+
date=d.get('date'),
|
309
|
+
deprecator=_messages.PluginInfo._from_collection_name(d.get('collection_name')),
|
310
|
+
)
|
306
311
|
|
307
312
|
for w in result._warnings:
|
308
313
|
warn('Both option {option} and its alias {alias} are set.'.format(option=w['option'], alias=w['alias']))
|
@@ -13,7 +13,7 @@ import dataclasses as _dataclasses
|
|
13
13
|
# deprecated: description='typing.Self exists in Python 3.11+' python_version='3.10'
|
14
14
|
from ..compat import typing as _t
|
15
15
|
|
16
|
-
from ansible.module_utils._internal import _datatag
|
16
|
+
from ansible.module_utils._internal import _datatag, _validation
|
17
17
|
|
18
18
|
if _sys.version_info >= (3, 10):
|
19
19
|
# Using slots for reduced memory usage and improved performance.
|
@@ -27,13 +27,27 @@ else:
|
|
27
27
|
class PluginInfo(_datatag.AnsibleSerializableDataclass):
|
28
28
|
"""Information about a loaded plugin."""
|
29
29
|
|
30
|
-
requested_name: str
|
31
|
-
"""The plugin name as requested, before resolving, which may be partially or fully qualified."""
|
32
30
|
resolved_name: str
|
33
31
|
"""The resolved canonical plugin name; always fully-qualified for collection plugins."""
|
34
32
|
type: str
|
35
33
|
"""The plugin type."""
|
36
34
|
|
35
|
+
_COLLECTION_ONLY_TYPE: _t.ClassVar[str] = 'collection'
|
36
|
+
"""This is not a real plugin type. It's a placeholder for use by a `PluginInfo` instance which references a collection without a plugin."""
|
37
|
+
|
38
|
+
@classmethod
|
39
|
+
def _from_collection_name(cls, collection_name: str | None) -> _t.Self | None:
|
40
|
+
"""Returns an instance with the special `collection` type to refer to a non-plugin or ambiguous caller within a collection."""
|
41
|
+
if not collection_name:
|
42
|
+
return None
|
43
|
+
|
44
|
+
_validation.validate_collection_name(collection_name)
|
45
|
+
|
46
|
+
return cls(
|
47
|
+
resolved_name=collection_name,
|
48
|
+
type=cls._COLLECTION_ONLY_TYPE,
|
49
|
+
)
|
50
|
+
|
37
51
|
|
38
52
|
@_dataclasses.dataclass(**_dataclass_kwargs)
|
39
53
|
class Detail(_datatag.AnsibleSerializableDataclass):
|
@@ -75,34 +89,37 @@ class WarningSummary(SummaryBase):
|
|
75
89
|
class DeprecationSummary(WarningSummary):
|
76
90
|
"""Deprecation summary with details (possibly derived from an exception __cause__ chain) and an optional traceback."""
|
77
91
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
@property
|
83
|
-
def collection_name(self) -> _t.Optional[str]:
|
84
|
-
if not self.plugin:
|
85
|
-
return None
|
86
|
-
|
87
|
-
parts = self.plugin.resolved_name.split('.')
|
88
|
-
|
89
|
-
if len(parts) < 2:
|
90
|
-
return None
|
91
|
-
|
92
|
-
collection_name = '.'.join(parts[:2])
|
92
|
+
deprecator: _t.Optional[PluginInfo] = None
|
93
|
+
"""
|
94
|
+
The identifier for the content which is being deprecated.
|
95
|
+
"""
|
93
96
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
+
date: _t.Optional[str] = None
|
98
|
+
"""
|
99
|
+
The date after which a new release of `deprecator` will remove the feature described by `msg`.
|
100
|
+
Ignored if `deprecator` is not provided.
|
101
|
+
"""
|
97
102
|
|
98
|
-
|
103
|
+
version: _t.Optional[str] = None
|
104
|
+
"""
|
105
|
+
The version of `deprecator` which will remove the feature described by `msg`.
|
106
|
+
Ignored if `deprecator` is not provided.
|
107
|
+
Ignored if `date` is provided.
|
108
|
+
"""
|
99
109
|
|
100
110
|
def _as_simple_dict(self) -> _t.Dict[str, _t.Any]:
|
101
111
|
"""Returns a dictionary representation of the deprecation object in the format exposed to playbooks."""
|
112
|
+
from ansible.module_utils._internal._deprecator import INDETERMINATE_DEPRECATOR # circular import from messages
|
113
|
+
|
114
|
+
if self.deprecator and self.deprecator != INDETERMINATE_DEPRECATOR:
|
115
|
+
collection_name = '.'.join(self.deprecator.resolved_name.split('.')[:2])
|
116
|
+
else:
|
117
|
+
collection_name = None
|
118
|
+
|
102
119
|
result = self._as_dict()
|
103
120
|
result.update(
|
104
121
|
msg=self._format(),
|
105
|
-
collection_name=
|
122
|
+
collection_name=collection_name,
|
106
123
|
)
|
107
124
|
|
108
125
|
return result
|
@@ -3,14 +3,12 @@
|
|
3
3
|
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
|
-
import dataclasses
|
7
6
|
import os
|
8
7
|
import pathlib
|
9
8
|
import subprocess
|
10
9
|
import sys
|
11
10
|
import typing as t
|
12
11
|
|
13
|
-
from ansible.module_utils._internal import _plugin_exec_context
|
14
12
|
from ansible.module_utils.common.text.converters import to_bytes
|
15
13
|
|
16
14
|
_ANSIBLE_PARENT_PATH = pathlib.Path(__file__).parents[3]
|
@@ -99,7 +97,6 @@ if __name__ == '__main__':
|
|
99
97
|
|
100
98
|
json_params = {json_params!r}
|
101
99
|
profile = {profile!r}
|
102
|
-
plugin_info_dict = {plugin_info_dict!r}
|
103
100
|
module_fqn = {module_fqn!r}
|
104
101
|
modlib_path = {modlib_path!r}
|
105
102
|
|
@@ -110,19 +107,15 @@ if __name__ == '__main__':
|
|
110
107
|
_ansiballz.run_module(
|
111
108
|
json_params=json_params,
|
112
109
|
profile=profile,
|
113
|
-
plugin_info_dict=plugin_info_dict,
|
114
110
|
module_fqn=module_fqn,
|
115
111
|
modlib_path=modlib_path,
|
116
112
|
init_globals=dict(_respawned=True),
|
117
113
|
)
|
118
114
|
"""
|
119
115
|
|
120
|
-
plugin_info = _plugin_exec_context.PluginExecContext.get_current_plugin_info()
|
121
|
-
|
122
116
|
respawn_code = respawn_code_template.format(
|
123
117
|
json_params=basic._ANSIBLE_ARGS,
|
124
118
|
profile=basic._ANSIBLE_PROFILE,
|
125
|
-
plugin_info_dict=dataclasses.asdict(plugin_info),
|
126
119
|
module_fqn=module_fqn,
|
127
120
|
modlib_path=modlib_path,
|
128
121
|
)
|
@@ -4,15 +4,12 @@
|
|
4
4
|
|
5
5
|
from __future__ import annotations as _annotations
|
6
6
|
|
7
|
-
import datetime as _datetime
|
8
7
|
import typing as _t
|
9
8
|
|
10
|
-
from ansible.module_utils._internal import _traceback,
|
9
|
+
from ansible.module_utils._internal import _traceback, _deprecator
|
11
10
|
from ansible.module_utils.common import messages as _messages
|
12
11
|
from ansible.module_utils import _internal
|
13
12
|
|
14
|
-
_UNSET = _t.cast(_t.Any, object())
|
15
|
-
|
16
13
|
|
17
14
|
def warn(warning: str) -> None:
|
18
15
|
"""Record a warning to be returned with the module result."""
|
@@ -28,22 +25,23 @@ def warn(warning: str) -> None:
|
|
28
25
|
def deprecate(
|
29
26
|
msg: str,
|
30
27
|
version: str | None = None,
|
31
|
-
date: str |
|
32
|
-
collection_name: str | None =
|
28
|
+
date: str | None = None,
|
29
|
+
collection_name: str | None = None,
|
33
30
|
*,
|
31
|
+
deprecator: _messages.PluginInfo | None = None,
|
34
32
|
help_text: str | None = None,
|
35
33
|
obj: object | None = None,
|
36
34
|
) -> None:
|
37
35
|
"""
|
38
|
-
Record a deprecation warning
|
36
|
+
Record a deprecation warning.
|
39
37
|
The `obj` argument is only useful in a controller context; it is ignored for target-side callers.
|
38
|
+
Most callers do not need to provide `collection_name` or `deprecator` -- but provide only one if needed.
|
39
|
+
Specify `version` or `date`, but not both.
|
40
|
+
If `date` is a string, it must be in the form `YYYY-MM-DD`.
|
40
41
|
"""
|
41
|
-
|
42
|
-
date = str(date)
|
42
|
+
_skip_stackwalk = True
|
43
43
|
|
44
|
-
|
45
|
-
# if collection_name is not _UNSET:
|
46
|
-
# deprecate('The `collection_name` argument to `deprecate` is deprecated.', version='2.27')
|
44
|
+
deprecator = _deprecator.get_best_deprecator(deprecator=deprecator, collection_name=collection_name)
|
47
45
|
|
48
46
|
if _internal.is_controller:
|
49
47
|
_display = _internal.import_controller_module('ansible.utils.display').Display()
|
@@ -53,6 +51,8 @@ def deprecate(
|
|
53
51
|
date=date,
|
54
52
|
help_text=help_text,
|
55
53
|
obj=obj,
|
54
|
+
# skip passing collection_name; get_best_deprecator already accounted for it when present
|
55
|
+
deprecator=deprecator,
|
56
56
|
)
|
57
57
|
|
58
58
|
return
|
@@ -64,7 +64,7 @@ def deprecate(
|
|
64
64
|
formatted_traceback=_traceback.maybe_capture_traceback(_traceback.TracebackEvent.DEPRECATED),
|
65
65
|
version=version,
|
66
66
|
date=date,
|
67
|
-
|
67
|
+
deprecator=deprecator,
|
68
68
|
)] = None
|
69
69
|
|
70
70
|
|
ansible/module_utils/datatag.py
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
"""Public API for data tagging."""
|
2
2
|
from __future__ import annotations as _annotations
|
3
3
|
|
4
|
-
import datetime as _datetime
|
5
4
|
import typing as _t
|
6
5
|
|
7
|
-
from ._internal import
|
6
|
+
from ._internal import _datatag, _deprecator
|
8
7
|
from ._internal._datatag import _tags
|
8
|
+
from .common import messages as _messages
|
9
9
|
|
10
10
|
_T = _t.TypeVar('_T')
|
11
11
|
|
@@ -14,28 +14,28 @@ def deprecate_value(
|
|
14
14
|
value: _T,
|
15
15
|
msg: str,
|
16
16
|
*,
|
17
|
+
version: str | None = None,
|
18
|
+
date: str | None = None,
|
19
|
+
collection_name: str | None = None,
|
20
|
+
deprecator: _messages.PluginInfo | None = None,
|
17
21
|
help_text: str | None = None,
|
18
|
-
removal_date: str | _datetime.date | None = None,
|
19
|
-
removal_version: str | None = None,
|
20
22
|
) -> _T:
|
21
23
|
"""
|
22
24
|
Return `value` tagged with the given deprecation details.
|
23
25
|
The types `None` and `bool` cannot be deprecated and are returned unmodified.
|
24
26
|
Raises a `TypeError` if `value` is not a supported type.
|
25
|
-
|
26
|
-
|
27
|
+
Most callers do not need to provide `collection_name` or `deprecator` -- but provide only one if needed.
|
28
|
+
Specify `version` or `date`, but not both.
|
29
|
+
If `date` is provided, it should be in the form `YYYY-MM-DD`.
|
27
30
|
"""
|
28
|
-
|
29
|
-
# The `fromisoformat` method accepts other ISO 8601 formats than `YYYY-MM-DD` starting with Python 3.11.
|
30
|
-
# That should be considered undocumented behavior of `deprecate_value` rather than an intentional feature.
|
31
|
-
removal_date = _datetime.date.fromisoformat(removal_date)
|
31
|
+
_skip_stackwalk = True
|
32
32
|
|
33
33
|
deprecated = _tags.Deprecated(
|
34
34
|
msg=msg,
|
35
35
|
help_text=help_text,
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
date=date,
|
37
|
+
version=version,
|
38
|
+
deprecator=_deprecator.get_best_deprecator(deprecator=deprecator, collection_name=collection_name),
|
39
39
|
)
|
40
40
|
|
41
41
|
return deprecated.tag(value)
|
ansible/modules/async_status.py
CHANGED
ansible/modules/dnf5.py
CHANGED
@@ -797,7 +797,7 @@ class Dnf5Module(YumDnf):
|
|
797
797
|
if self.module.check_mode:
|
798
798
|
if results:
|
799
799
|
msg = "Check mode: No changes made, but would have if not in check mode"
|
800
|
-
|
800
|
+
elif changed:
|
801
801
|
transaction.download()
|
802
802
|
if not self.download_only:
|
803
803
|
transaction.set_description("ansible dnf5 module")
|
ansible/modules/get_url.py
CHANGED
@@ -87,7 +87,7 @@ options:
|
|
87
87
|
- 'If a checksum is passed to this parameter, the digest of the
|
88
88
|
destination file will be calculated after it is downloaded to ensure
|
89
89
|
its integrity and verify that the transfer completed successfully.
|
90
|
-
Format: <algorithm>:<checksum|url>, for example C(checksum="sha256:D98291AC[...]B6DC7B97",
|
90
|
+
Format: <algorithm>:<checksum|url>, for example C(checksum="sha256:D98291AC[...]B6DC7B97"),
|
91
91
|
C(checksum="sha256:http://example.com/path/sha256sum.txt").'
|
92
92
|
- If you worry about portability, only the sha1 algorithm is available
|
93
93
|
on all platforms and python versions.
|
ansible/playbook/task.py
CHANGED
@@ -227,8 +227,6 @@ class Task(Base, Conditional, Taggable, CollectionSearch, Notifiable, Delegatabl
|
|
227
227
|
raise AnsibleError("you must specify a value when using %s" % k, obj=ds)
|
228
228
|
new_ds['loop_with'] = loop_name
|
229
229
|
new_ds['loop'] = v
|
230
|
-
# display.deprecated("with_ type loops are being phased out, use the 'loop' keyword instead",
|
231
|
-
# version="2.10", collection_name='ansible.builtin')
|
232
230
|
|
233
231
|
def preprocess_data(self, ds):
|
234
232
|
"""
|
ansible/plugins/__init__.py
CHANGED
@@ -20,24 +20,26 @@
|
|
20
20
|
from __future__ import annotations
|
21
21
|
|
22
22
|
import abc
|
23
|
+
import functools
|
23
24
|
import types
|
24
25
|
import typing as t
|
25
26
|
|
26
27
|
from ansible import constants as C
|
27
28
|
from ansible.errors import AnsibleError
|
28
29
|
from ansible.utils.display import Display
|
30
|
+
from ansible.utils import display as _display
|
29
31
|
|
30
|
-
from ansible.module_utils._internal import
|
32
|
+
from ansible.module_utils._internal import _plugin_info
|
31
33
|
|
32
34
|
display = Display()
|
33
35
|
|
34
36
|
if t.TYPE_CHECKING:
|
35
|
-
from .
|
37
|
+
from . import loader as _t_loader
|
36
38
|
|
37
39
|
# Global so that all instances of a PluginLoader will share the caches
|
38
40
|
MODULE_CACHE = {} # type: dict[str, dict[str, types.ModuleType]]
|
39
|
-
PATH_CACHE = {} # type: dict[str, list[PluginPathContext] | None]
|
40
|
-
PLUGIN_PATH_CACHE = {} # type: dict[str, dict[str, dict[str, PluginPathContext]]]
|
41
|
+
PATH_CACHE = {} # type: dict[str, list[_t_loader.PluginPathContext] | None]
|
42
|
+
PLUGIN_PATH_CACHE = {} # type: dict[str, dict[str, dict[str, _t_loader.PluginPathContext]]]
|
41
43
|
|
42
44
|
|
43
45
|
def get_plugin_class(obj):
|
@@ -50,10 +52,10 @@ def get_plugin_class(obj):
|
|
50
52
|
class _ConfigurablePlugin(t.Protocol):
|
51
53
|
"""Protocol to provide type-safe access to config for plugin-related mixins."""
|
52
54
|
|
53
|
-
def get_option(self, option: str, hostvars: dict[str, object] | None = None) ->
|
55
|
+
def get_option(self, option: str, hostvars: dict[str, object] | None = None) -> t.Any: ...
|
54
56
|
|
55
57
|
|
56
|
-
class _AnsiblePluginInfoMixin(
|
58
|
+
class _AnsiblePluginInfoMixin(_plugin_info.HasPluginInfo):
|
57
59
|
"""Mixin to provide type annotations and default values for existing PluginLoader-set load-time attrs."""
|
58
60
|
_original_path: str | None = None
|
59
61
|
_load_name: str | None = None
|
@@ -102,6 +104,14 @@ class AnsiblePlugin(_AnsiblePluginInfoMixin, _ConfigurablePlugin, metaclass=abc.
|
|
102
104
|
raise KeyError(str(e))
|
103
105
|
return option_value, origin
|
104
106
|
|
107
|
+
@functools.cached_property
|
108
|
+
def __plugin_info(self):
|
109
|
+
"""
|
110
|
+
Internal cached property to retrieve `PluginInfo` for this plugin instance.
|
111
|
+
Only for use by the `AnsiblePlugin` base class.
|
112
|
+
"""
|
113
|
+
return _plugin_info.get_plugin_info(self)
|
114
|
+
|
105
115
|
def get_option(self, option, hostvars=None):
|
106
116
|
|
107
117
|
if option not in self._options:
|
@@ -117,7 +127,7 @@ class AnsiblePlugin(_AnsiblePluginInfoMixin, _ConfigurablePlugin, metaclass=abc.
|
|
117
127
|
|
118
128
|
def set_option(self, option, value):
|
119
129
|
self._options[option] = C.config.get_config_value(option, plugin_type=self.plugin_type, plugin_name=self._load_name, direct={option: value})
|
120
|
-
|
130
|
+
_display._report_config_warnings(self.__plugin_info)
|
121
131
|
|
122
132
|
def set_options(self, task_keys=None, var_options=None, direct=None):
|
123
133
|
"""
|
@@ -134,7 +144,7 @@ class AnsiblePlugin(_AnsiblePluginInfoMixin, _ConfigurablePlugin, metaclass=abc.
|
|
134
144
|
if self.allow_extras and var_options and '_extras' in var_options:
|
135
145
|
# these are largely unvalidated passthroughs, either plugin or underlying API will validate
|
136
146
|
self._options['_extras'] = var_options['_extras']
|
137
|
-
|
147
|
+
_display._report_config_warnings(self.__plugin_info)
|
138
148
|
|
139
149
|
def has_option(self, option):
|
140
150
|
if not self._options:
|
@@ -318,13 +318,6 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
318
318
|
final_environment: dict[str, t.Any] = {}
|
319
319
|
self._compute_environment_string(final_environment)
|
320
320
|
|
321
|
-
# `modify_module` adapts PluginInfo to allow target-side use of `PluginExecContext` since modules aren't plugins
|
322
|
-
plugin = PluginInfo(
|
323
|
-
requested_name=module_name,
|
324
|
-
resolved_name=result.resolved_fqcn,
|
325
|
-
type='module',
|
326
|
-
)
|
327
|
-
|
328
321
|
# modify_module will exit early if interpreter discovery is required; re-run after if necessary
|
329
322
|
for _dummy in (1, 2):
|
330
323
|
try:
|
@@ -338,7 +331,6 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
338
331
|
async_timeout=self._task.async_val,
|
339
332
|
environment=final_environment,
|
340
333
|
remote_is_local=bool(getattr(self._connection, '_remote_is_local', False)),
|
341
|
-
plugin=plugin,
|
342
334
|
become_plugin=self._connection.become,
|
343
335
|
)
|
344
336
|
|
@@ -649,12 +641,12 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
649
641
|
# done. Make the files +x if we're asked to, and return.
|
650
642
|
if not self._is_become_unprivileged():
|
651
643
|
if execute:
|
652
|
-
# Can't depend on the file being transferred with
|
644
|
+
# Can't depend on the file being transferred with required permissions.
|
653
645
|
# Only need user perms because no become was used here
|
654
|
-
res = self._remote_chmod(remote_paths, 'u+
|
646
|
+
res = self._remote_chmod(remote_paths, 'u+rwx')
|
655
647
|
if res['rc'] != 0:
|
656
648
|
raise AnsibleError(
|
657
|
-
'Failed to set
|
649
|
+
'Failed to set permissions on remote files '
|
658
650
|
'(rc: {0}, err: {1})'.format(
|
659
651
|
res['rc'],
|
660
652
|
to_native(res['stderr'])))
|
@@ -695,10 +687,10 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
695
687
|
return remote_paths
|
696
688
|
|
697
689
|
# Step 3b: Set execute if we need to. We do this before anything else
|
698
|
-
# because some of the methods below might work but not let us set
|
699
|
-
# as part of them.
|
690
|
+
# because some of the methods below might work but not let us set
|
691
|
+
# permissions as part of them.
|
700
692
|
if execute:
|
701
|
-
res = self._remote_chmod(remote_paths, 'u+
|
693
|
+
res = self._remote_chmod(remote_paths, 'u+rwx')
|
702
694
|
if res['rc'] != 0:
|
703
695
|
raise AnsibleError(
|
704
696
|
'Failed to set file mode or acl on remote temporary files '
|