ansible-core 2.19.0b2__py3-none-any.whl → 2.19.0b4__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.py +1 -4
- ansible/_internal/_json/__init__.py +1 -1
- ansible/_internal/_templating/_datatag.py +3 -4
- ansible/_internal/_templating/_engine.py +6 -1
- ansible/_internal/_templating/_jinja_plugins.py +2 -6
- ansible/_internal/_testing.py +26 -0
- ansible/cli/__init__.py +3 -2
- ansible/cli/arguments/option_helpers.py +10 -3
- ansible/cli/doc.py +0 -1
- ansible/config/base.yml +5 -23
- ansible/config/manager.py +144 -103
- ansible/constants.py +1 -63
- 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/inventory/manager.py +1 -0
- 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/module_utils/facts/virtual/linux.py +1 -1
- ansible/module_utils/parsing/convert_bool.py +6 -0
- ansible/modules/assemble.py +4 -4
- ansible/modules/async_status.py +1 -1
- ansible/modules/cron.py +3 -5
- ansible/modules/dnf5.py +2 -1
- ansible/modules/get_url.py +1 -1
- ansible/modules/git.py +1 -6
- ansible/modules/pip.py +2 -4
- ansible/modules/sysvinit.py +3 -3
- ansible/playbook/task.py +0 -2
- ansible/plugins/__init__.py +18 -8
- ansible/plugins/action/__init__.py +7 -15
- ansible/plugins/action/gather_facts.py +2 -4
- ansible/plugins/action/template.py +3 -0
- 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 +197 -132
- 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/__init__.py +2 -0
- ansible/utils/collection_loader/_collection_meta.py +5 -3
- ansible/utils/display.py +137 -71
- ansible/utils/plugin_docs.py +2 -1
- 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.0b4.dist-info}/METADATA +3 -2
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info}/RECORD +82 -79
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info}/WHEEL +1 -1
- 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 +486 -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.0b4.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses}/COPYING +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/Apache-License.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/MIT-license.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/PSF-license.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info}/top_level.txt +0 -0
ansible/config/manager.py
CHANGED
@@ -16,11 +16,12 @@ import typing as t
|
|
16
16
|
from collections.abc import Mapping, Sequence
|
17
17
|
from jinja2.nativetypes import NativeEnvironment
|
18
18
|
|
19
|
+
from ansible._internal._datatag import _tags
|
19
20
|
from ansible.errors import AnsibleOptionsError, AnsibleError, AnsibleUndefinedConfigEntry, AnsibleRequiredOptionError
|
21
|
+
from ansible.module_utils._internal._datatag import AnsibleTagHelper
|
20
22
|
from ansible.module_utils.common.sentinel import Sentinel
|
21
23
|
from ansible.module_utils.common.text.converters import to_text, to_bytes, to_native
|
22
24
|
from ansible.module_utils.common.yaml import yaml_load
|
23
|
-
from ansible.module_utils.six import string_types
|
24
25
|
from ansible.module_utils.parsing.convert_bool import boolean
|
25
26
|
from ansible.parsing.quoting import unquote
|
26
27
|
from ansible.utils.path import cleanup_tmp_file, makedirs_safe, unfrackpath
|
@@ -50,6 +51,14 @@ GALAXY_SERVER_ADDITIONAL = {
|
|
50
51
|
}
|
51
52
|
|
52
53
|
|
54
|
+
@t.runtime_checkable
|
55
|
+
class _EncryptedStringProtocol(t.Protocol):
|
56
|
+
"""Protocol representing an `EncryptedString`, since it cannot be imported here."""
|
57
|
+
# DTFIX-FUTURE: collapse this with the one in collection loader, once we can
|
58
|
+
|
59
|
+
def _decrypt(self) -> str: ...
|
60
|
+
|
61
|
+
|
53
62
|
def _get_config_label(plugin_type: str, plugin_name: str, config: str) -> str:
|
54
63
|
"""Return a label for the given config."""
|
55
64
|
entry = f'{config!r}'
|
@@ -65,133 +74,157 @@ def _get_config_label(plugin_type: str, plugin_name: str, config: str) -> str:
|
|
65
74
|
return entry
|
66
75
|
|
67
76
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
:string: Same as 'str'
|
77
|
+
def ensure_type(value: object, value_type: str | None, origin: str | None = None, origin_ftype: str | None = None) -> t.Any:
|
78
|
+
"""
|
79
|
+
Converts `value` to the requested `value_type`; raises `ValueError` for failed conversions.
|
80
|
+
|
81
|
+
Values for `value_type` are:
|
82
|
+
|
83
|
+
* boolean/bool: Return a `bool` by applying non-strict `bool` filter rules:
|
84
|
+
'y', 'yes', 'on', '1', 'true', 't', 1, 1.0, True return True, any other value is False.
|
85
|
+
* integer/int: Return an `int`. Accepts any `str` parseable by `int` or numeric value with a zero mantissa (including `bool`).
|
86
|
+
* float: Return a `float`. Accepts any `str` parseable by `float` or numeric value (including `bool`).
|
87
|
+
* list: Return a `list`. Accepts `list` or `Sequence`. Also accepts, `str`, splitting on ',' while stripping whitespace and unquoting items.
|
88
|
+
* none: Return `None`. Accepts only the string "None".
|
89
|
+
* path: Return a resolved path. Accepts `str`.
|
90
|
+
* temppath/tmppath/tmp: Return a unique temporary directory inside the resolved path specified by the value.
|
91
|
+
* pathspec: Return a `list` of resolved paths. Accepts a `list` or `Sequence`. Also accepts `str`, splitting on ':'.
|
92
|
+
* pathlist: Return a `list` of resolved paths. Accepts a `list` or `Sequence`. Also accepts `str`, splitting on `,` while stripping whitespace from paths.
|
93
|
+
* dictionary/dict: Return a `dict`. Accepts `dict` or `Mapping`.
|
94
|
+
* string/str: Return a `str`. Accepts `bool`, `int`, `float`, `complex` or `str`.
|
95
|
+
|
96
|
+
Path resolution ensures paths are `str` with expansion of '{{CWD}}', environment variables and '~'.
|
97
|
+
Non-absolute paths are expanded relative to the basedir from `origin`, if specified.
|
98
|
+
|
99
|
+
No conversion is performed if `value_type` is unknown or `value` is `None`.
|
100
|
+
When `origin_ftype` is "ini", a `str` result will be unquoted.
|
93
101
|
"""
|
94
102
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
103
|
+
if value is None:
|
104
|
+
return None
|
105
|
+
|
106
|
+
original_value = value
|
107
|
+
copy_tags = value_type not in ('temppath', 'tmppath', 'tmp')
|
108
|
+
|
109
|
+
value = _ensure_type(value, value_type, origin)
|
110
|
+
|
111
|
+
if copy_tags and value is not original_value:
|
112
|
+
if isinstance(value, list):
|
113
|
+
value = [AnsibleTagHelper.tag_copy(original_value, item) for item in value]
|
114
|
+
|
115
|
+
value = AnsibleTagHelper.tag_copy(original_value, value)
|
116
|
+
|
117
|
+
if isinstance(value, str) and origin_ftype and origin_ftype == 'ini':
|
118
|
+
value = unquote(value)
|
119
|
+
|
120
|
+
return value
|
121
|
+
|
122
|
+
|
123
|
+
def _ensure_type(value: object, value_type: str | None, origin: str | None = None) -> t.Any:
|
124
|
+
"""Internal implementation for `ensure_type`, call that function instead."""
|
125
|
+
original_value = value
|
126
|
+
basedir = origin if origin and os.path.isabs(origin) and os.path.exists(to_bytes(origin)) else None
|
99
127
|
|
100
128
|
if value_type:
|
101
129
|
value_type = value_type.lower()
|
102
130
|
|
103
|
-
|
104
|
-
|
105
|
-
|
131
|
+
match value_type:
|
132
|
+
case 'boolean' | 'bool':
|
133
|
+
return boolean(value, strict=False)
|
134
|
+
|
135
|
+
case 'integer' | 'int':
|
136
|
+
if isinstance(value, int): # handle both int and bool (which is an int)
|
137
|
+
return int(value)
|
106
138
|
|
107
|
-
|
108
|
-
if not isinstance(value, int):
|
139
|
+
if isinstance(value, (float, str)):
|
109
140
|
try:
|
141
|
+
# use Decimal for all other source type conversions; non-zero mantissa is a failure
|
110
142
|
if (decimal_value := decimal.Decimal(value)) == (int_part := int(decimal_value)):
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
143
|
+
return int_part
|
144
|
+
except (decimal.DecimalException, ValueError):
|
145
|
+
pass
|
146
|
+
|
147
|
+
case 'float':
|
148
|
+
if isinstance(value, float):
|
149
|
+
return value
|
116
150
|
|
117
|
-
|
118
|
-
|
119
|
-
|
151
|
+
if isinstance(value, (int, str)):
|
152
|
+
try:
|
153
|
+
return float(value)
|
154
|
+
except ValueError:
|
155
|
+
pass
|
156
|
+
|
157
|
+
case 'list':
|
158
|
+
if isinstance(value, list):
|
159
|
+
return value
|
120
160
|
|
121
|
-
|
122
|
-
|
123
|
-
value = [unquote(x.strip()) for x in value.split(',')]
|
124
|
-
elif not isinstance(value, Sequence):
|
125
|
-
errmsg = 'list'
|
161
|
+
if isinstance(value, str):
|
162
|
+
return [unquote(x.strip()) for x in value.split(',')]
|
126
163
|
|
127
|
-
|
164
|
+
if isinstance(value, Sequence) and not isinstance(value, bytes):
|
165
|
+
return list(value)
|
166
|
+
|
167
|
+
case 'none':
|
128
168
|
if value == "None":
|
129
|
-
|
169
|
+
return None
|
130
170
|
|
131
|
-
|
132
|
-
|
171
|
+
case 'path':
|
172
|
+
if isinstance(value, str):
|
173
|
+
return resolve_path(value, basedir=basedir)
|
133
174
|
|
134
|
-
|
135
|
-
if isinstance(value,
|
175
|
+
case 'temppath' | 'tmppath' | 'tmp':
|
176
|
+
if isinstance(value, str):
|
136
177
|
value = resolve_path(value, basedir=basedir)
|
137
|
-
else:
|
138
|
-
errmsg = 'path'
|
139
178
|
|
140
|
-
elif value_type in ('tmp', 'temppath', 'tmppath'):
|
141
|
-
if isinstance(value, string_types):
|
142
|
-
value = resolve_path(value, basedir=basedir)
|
143
179
|
if not os.path.exists(value):
|
144
180
|
makedirs_safe(value, 0o700)
|
181
|
+
|
145
182
|
prefix = 'ansible-local-%s' % os.getpid()
|
146
183
|
value = tempfile.mkdtemp(prefix=prefix, dir=value)
|
147
184
|
atexit.register(cleanup_tmp_file, value, warn=True)
|
148
|
-
else:
|
149
|
-
errmsg = 'temppath'
|
150
185
|
|
151
|
-
|
152
|
-
|
186
|
+
return value
|
187
|
+
|
188
|
+
case 'pathspec':
|
189
|
+
if isinstance(value, str):
|
153
190
|
value = value.split(os.pathsep)
|
154
191
|
|
155
|
-
if isinstance(value, Sequence):
|
156
|
-
|
157
|
-
else:
|
158
|
-
errmsg = 'pathspec'
|
192
|
+
if isinstance(value, Sequence) and not isinstance(value, bytes) and all(isinstance(x, str) for x in value):
|
193
|
+
return [resolve_path(x, basedir=basedir) for x in value]
|
159
194
|
|
160
|
-
|
161
|
-
if isinstance(value,
|
195
|
+
case 'pathlist':
|
196
|
+
if isinstance(value, str):
|
162
197
|
value = [x.strip() for x in value.split(',')]
|
163
198
|
|
164
|
-
if isinstance(value, Sequence):
|
165
|
-
|
166
|
-
else:
|
167
|
-
errmsg = 'pathlist'
|
199
|
+
if isinstance(value, Sequence) and not isinstance(value, bytes) and all(isinstance(x, str) for x in value):
|
200
|
+
return [resolve_path(x, basedir=basedir) for x in value]
|
168
201
|
|
169
|
-
|
170
|
-
if
|
171
|
-
|
202
|
+
case 'dictionary' | 'dict':
|
203
|
+
if isinstance(value, dict):
|
204
|
+
return value
|
172
205
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
errmsg = 'string'
|
206
|
+
if isinstance(value, Mapping):
|
207
|
+
return dict(value)
|
208
|
+
|
209
|
+
case 'string' | 'str':
|
210
|
+
if isinstance(value, str):
|
211
|
+
return value
|
180
212
|
|
181
|
-
|
182
|
-
|
183
|
-
value = to_text(value, errors='surrogate_or_strict')
|
184
|
-
if origin_ftype and origin_ftype == 'ini':
|
185
|
-
value = unquote(value)
|
213
|
+
if isinstance(value, (bool, int, float, complex)):
|
214
|
+
return str(value)
|
186
215
|
|
187
|
-
|
188
|
-
|
216
|
+
if isinstance(value, _EncryptedStringProtocol):
|
217
|
+
return value._decrypt()
|
189
218
|
|
190
|
-
|
219
|
+
case _:
|
220
|
+
# FIXME: define and document a pass-through value_type (None, 'raw', 'object', '', ...) and then deprecate acceptance of unknown types
|
221
|
+
return value # return non-str values of unknown value_type as-is
|
222
|
+
|
223
|
+
raise ValueError(f'Invalid value provided for {value_type!r}: {original_value!r}')
|
191
224
|
|
192
225
|
|
193
226
|
# FIXME: see if this can live in utils/path
|
194
|
-
def resolve_path(path, basedir=None):
|
227
|
+
def resolve_path(path: str, basedir: str | None = None) -> str:
|
195
228
|
""" resolve relative or 'variable' paths """
|
196
229
|
if '{{CWD}}' in path: # allow users to force CWD using 'magic' {{CWD}}
|
197
230
|
path = path.replace('{{CWD}}', os.getcwd())
|
@@ -304,11 +337,13 @@ def _add_base_defs_deprecations(base_defs):
|
|
304
337
|
process(entry)
|
305
338
|
|
306
339
|
|
307
|
-
class ConfigManager
|
340
|
+
class ConfigManager:
|
308
341
|
|
309
342
|
DEPRECATED = [] # type: list[tuple[str, dict[str, str]]]
|
310
343
|
WARNINGS = set() # type: set[str]
|
311
344
|
|
345
|
+
_errors: list[tuple[str, Exception]]
|
346
|
+
|
312
347
|
def __init__(self, conf_file=None, defs_file=None):
|
313
348
|
|
314
349
|
self._base_defs = {}
|
@@ -329,6 +364,9 @@ class ConfigManager(object):
|
|
329
364
|
# initialize parser and read config
|
330
365
|
self._parse_config_file()
|
331
366
|
|
367
|
+
self._errors = []
|
368
|
+
"""Deferred errors that will be turned into warnings."""
|
369
|
+
|
332
370
|
# ensure we always have config def entry
|
333
371
|
self._base_defs['CONFIG_FILE'] = {'default': None, 'type': 'path'}
|
334
372
|
|
@@ -368,16 +406,16 @@ class ConfigManager(object):
|
|
368
406
|
defs = dict((k, server_config_def(server_key, k, req, value_type)) for k, req, value_type in GALAXY_SERVER_DEF)
|
369
407
|
self.initialize_plugin_configuration_definitions('galaxy_server', server_key, defs)
|
370
408
|
|
371
|
-
def template_default(self, value, variables):
|
372
|
-
if isinstance(value,
|
409
|
+
def template_default(self, value, variables, key_name: str = '<unknown>'):
|
410
|
+
if isinstance(value, str) and (value.startswith('{{') and value.endswith('}}')) and variables is not None:
|
373
411
|
# template default values if possible
|
374
412
|
# NOTE: cannot use is_template due to circular dep
|
375
413
|
try:
|
376
414
|
# FIXME: This really should be using an immutable sandboxed native environment, not just native environment
|
377
|
-
|
378
|
-
value =
|
379
|
-
except Exception:
|
380
|
-
|
415
|
+
template = NativeEnvironment().from_string(value)
|
416
|
+
value = template.render(variables)
|
417
|
+
except Exception as ex:
|
418
|
+
self._errors.append((f'Failed to template default for config {key_name}.', ex))
|
381
419
|
return value
|
382
420
|
|
383
421
|
def _read_config_yaml_file(self, yml_file):
|
@@ -480,9 +518,9 @@ class ConfigManager(object):
|
|
480
518
|
else:
|
481
519
|
ret = self._plugins.get(plugin_type, {}).get(name, {})
|
482
520
|
|
483
|
-
if ignore_private:
|
521
|
+
if ignore_private: # ignore 'test' config entries, they should not change runtime behaviors
|
484
522
|
for cdef in list(ret.keys()):
|
485
|
-
if cdef.startswith('
|
523
|
+
if cdef.startswith('_Z_'):
|
486
524
|
del ret[cdef]
|
487
525
|
return ret
|
488
526
|
|
@@ -631,7 +669,7 @@ class ConfigManager(object):
|
|
631
669
|
raise AnsibleRequiredOptionError(f"Required config {_get_config_label(plugin_type, plugin_name, config)} not provided.")
|
632
670
|
else:
|
633
671
|
origin = 'default'
|
634
|
-
value = self.template_default(defs[config].get('default'), variables)
|
672
|
+
value = self.template_default(defs[config].get('default'), variables, key_name=_get_config_label(plugin_type, plugin_name, config))
|
635
673
|
|
636
674
|
try:
|
637
675
|
# ensure correct type, can raise exceptions on mismatched types
|
@@ -658,7 +696,7 @@ class ConfigManager(object):
|
|
658
696
|
|
659
697
|
if isinstance(defs[config]['choices'], Mapping):
|
660
698
|
valid = ', '.join([to_text(k) for k in defs[config]['choices'].keys()])
|
661
|
-
elif isinstance(defs[config]['choices'],
|
699
|
+
elif isinstance(defs[config]['choices'], str):
|
662
700
|
valid = defs[config]['choices']
|
663
701
|
elif isinstance(defs[config]['choices'], Sequence):
|
664
702
|
valid = ', '.join([to_text(c) for c in defs[config]['choices']])
|
@@ -674,6 +712,9 @@ class ConfigManager(object):
|
|
674
712
|
else:
|
675
713
|
raise AnsibleUndefinedConfigEntry(f'No config definition exists for {_get_config_label(plugin_type, plugin_name, config)}.')
|
676
714
|
|
715
|
+
if not _tags.Origin.is_tagged_on(value):
|
716
|
+
value = _tags.Origin(description=f'<Config {origin}>').tag(value)
|
717
|
+
|
677
718
|
return value, origin
|
678
719
|
|
679
720
|
def initialize_plugin_configuration_definitions(self, plugin_type, name, defs):
|
ansible/constants.py
CHANGED
@@ -10,9 +10,7 @@ from string import ascii_letters, digits
|
|
10
10
|
|
11
11
|
from ansible.config.manager import ConfigManager
|
12
12
|
from ansible.module_utils.common.text.converters import to_text
|
13
|
-
from ansible.module_utils.common.collections import Sequence
|
14
13
|
from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE
|
15
|
-
from ansible.release import __version__
|
16
14
|
from ansible.utils.fqcn import add_internal_fqcns
|
17
15
|
|
18
16
|
# initialize config manager/config data to read/store global settings
|
@@ -20,68 +18,11 @@ from ansible.utils.fqcn import add_internal_fqcns
|
|
20
18
|
config = ConfigManager()
|
21
19
|
|
22
20
|
|
23
|
-
def _warning(msg):
|
24
|
-
""" display is not guaranteed here, nor it being the full class, but try anyways, fallback to sys.stderr.write """
|
25
|
-
try:
|
26
|
-
from ansible.utils.display import Display
|
27
|
-
Display().warning(msg)
|
28
|
-
except Exception:
|
29
|
-
import sys
|
30
|
-
sys.stderr.write(' [WARNING] %s\n' % (msg))
|
31
|
-
|
32
|
-
|
33
|
-
def _deprecated(msg, version):
|
34
|
-
""" display is not guaranteed here, nor it being the full class, but try anyways, fallback to sys.stderr.write """
|
35
|
-
try:
|
36
|
-
from ansible.utils.display import Display
|
37
|
-
Display().deprecated(msg, version=version)
|
38
|
-
except Exception:
|
39
|
-
import sys
|
40
|
-
sys.stderr.write(' [DEPRECATED] %s, to be removed in %s\n' % (msg, version))
|
41
|
-
|
42
|
-
|
43
|
-
def handle_config_noise(display=None):
|
44
|
-
|
45
|
-
if display is not None:
|
46
|
-
w = display.warning
|
47
|
-
d = display.deprecated
|
48
|
-
else:
|
49
|
-
w = _warning
|
50
|
-
d = _deprecated
|
51
|
-
|
52
|
-
while config.WARNINGS:
|
53
|
-
warn = config.WARNINGS.pop()
|
54
|
-
w(warn)
|
55
|
-
|
56
|
-
while config.DEPRECATED:
|
57
|
-
# tuple with name and options
|
58
|
-
dep = config.DEPRECATED.pop(0)
|
59
|
-
msg = config.get_deprecated_msg_from_config(dep[1])
|
60
|
-
# use tabs only for ansible-doc?
|
61
|
-
msg = msg.replace("\t", "")
|
62
|
-
d(f"{dep[0]} option. {msg}", version=dep[1]['version'])
|
63
|
-
|
64
|
-
|
65
21
|
def set_constant(name, value, export=vars()):
|
66
22
|
""" sets constants and returns resolved options dict """
|
67
23
|
export[name] = value
|
68
24
|
|
69
25
|
|
70
|
-
class _DeprecatedSequenceConstant(Sequence):
|
71
|
-
def __init__(self, value, msg, version):
|
72
|
-
self._value = value
|
73
|
-
self._msg = msg
|
74
|
-
self._version = version
|
75
|
-
|
76
|
-
def __len__(self):
|
77
|
-
_deprecated(self._msg, self._version)
|
78
|
-
return len(self._value)
|
79
|
-
|
80
|
-
def __getitem__(self, y):
|
81
|
-
_deprecated(self._msg, self._version)
|
82
|
-
return self._value[y]
|
83
|
-
|
84
|
-
|
85
26
|
# CONSTANTS ### yes, actual ones
|
86
27
|
|
87
28
|
# The following are hard-coded action names
|
@@ -119,7 +60,7 @@ COLOR_CODES = {
|
|
119
60
|
'magenta': u'0;35', 'bright magenta': u'1;35',
|
120
61
|
'normal': u'0',
|
121
62
|
}
|
122
|
-
REJECT_EXTS =
|
63
|
+
REJECT_EXTS = ['.pyc', '.pyo', '.swp', '.bak', '~', '.rpm', '.md', '.txt', '.rst'] # this is concatenated with other config settings as lists; cannot be tuple
|
123
64
|
BOOL_TRUE = BOOLEANS_TRUE
|
124
65
|
COLLECTION_PTYPE_COMPAT = {'module': 'modules'}
|
125
66
|
|
@@ -245,6 +186,3 @@ MAGIC_VARIABLE_MAPPING = dict(
|
|
245
186
|
# POPULATE SETTINGS FROM CONFIG ###
|
246
187
|
for setting in config.get_configuration_definitions():
|
247
188
|
set_constant(setting, config.get_config_value(setting, variables=vars()))
|
248
|
-
|
249
|
-
# emit any warnings or deprecations
|
250
|
-
handle_config_noise()
|
ansible/errors/__init__.py
CHANGED
@@ -18,6 +18,9 @@ from ..module_utils.datatag import native_type_name
|
|
18
18
|
from ansible._internal._datatag import _tags
|
19
19
|
from .._internal._errors import _utils
|
20
20
|
|
21
|
+
if t.TYPE_CHECKING:
|
22
|
+
from ansible.plugins import loader as _t_loader
|
23
|
+
|
21
24
|
|
22
25
|
class ExitCode(enum.IntEnum):
|
23
26
|
SUCCESS = 0 # used by TQM, must be bit-flag safe
|
@@ -374,8 +377,9 @@ class _AnsibleActionDone(AnsibleAction):
|
|
374
377
|
class AnsiblePluginError(AnsibleError):
|
375
378
|
"""Base class for Ansible plugin-related errors that do not need AnsibleError contextual data."""
|
376
379
|
|
377
|
-
def __init__(self, message=None, plugin_load_context=None):
|
378
|
-
super(AnsiblePluginError, self).__init__(message)
|
380
|
+
def __init__(self, message: str | None = None, plugin_load_context: _t_loader.PluginLoadContext | None = None, help_text: str | None = None) -> None:
|
381
|
+
super(AnsiblePluginError, self).__init__(message, help_text=help_text)
|
382
|
+
|
379
383
|
self.plugin_load_context = plugin_load_context
|
380
384
|
|
381
385
|
|
@@ -39,7 +39,6 @@ from io import BytesIO
|
|
39
39
|
from ansible._internal import _locking
|
40
40
|
from ansible._internal._datatag import _utils
|
41
41
|
from ansible.module_utils._internal import _dataclass_validation
|
42
|
-
from ansible.module_utils.common.messages import PluginInfo
|
43
42
|
from ansible.module_utils.common.yaml import yaml_load
|
44
43
|
from ansible._internal._datatag._tags import Origin
|
45
44
|
from ansible.module_utils.common.json import Direction, get_module_encoder
|
@@ -56,6 +55,7 @@ from ansible.template import Templar
|
|
56
55
|
from ansible.utils.collection_loader._collection_finder import _get_collection_metadata, _nested_dict_get
|
57
56
|
from ansible.module_utils._internal import _json, _ansiballz
|
58
57
|
from ansible.module_utils import basic as _basic
|
58
|
+
from ansible.module_utils.common import messages as _messages
|
59
59
|
|
60
60
|
if t.TYPE_CHECKING:
|
61
61
|
from ansible import template as _template
|
@@ -434,7 +434,13 @@ class ModuleUtilLocatorBase:
|
|
434
434
|
else:
|
435
435
|
msg += '.'
|
436
436
|
|
437
|
-
display.deprecated(
|
437
|
+
display.deprecated( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
|
438
|
+
msg=msg,
|
439
|
+
version=removal_version,
|
440
|
+
removed=removed,
|
441
|
+
date=removal_date,
|
442
|
+
deprecator=_messages.PluginInfo._from_collection_name(self._collection_name),
|
443
|
+
)
|
438
444
|
if 'redirect' in routing_entry:
|
439
445
|
self.redirected = True
|
440
446
|
source_pkg = '.'.join(name_parts)
|
@@ -944,7 +950,6 @@ class _CachedModule:
|
|
944
950
|
def _find_module_utils(
|
945
951
|
*,
|
946
952
|
module_name: str,
|
947
|
-
plugin: PluginInfo,
|
948
953
|
b_module_data: bytes,
|
949
954
|
module_path: str,
|
950
955
|
module_args: dict[object, object],
|
@@ -1020,7 +1025,9 @@ def _find_module_utils(
|
|
1020
1025
|
# People should start writing collections instead of modules in roles so we
|
1021
1026
|
# may never fix this
|
1022
1027
|
display.debug('ANSIBALLZ: Could not determine module FQN')
|
1023
|
-
|
1028
|
+
# FIXME: add integration test to validate that builtins and legacy modules with the same name are tracked separately by the caching mechanism
|
1029
|
+
# FIXME: surrogate FQN should be unique per source path- role-packaged modules with name collisions can still be aliased
|
1030
|
+
remote_module_fqn = 'ansible.legacy.%s' % module_name
|
1024
1031
|
|
1025
1032
|
if module_substyle == 'python':
|
1026
1033
|
date_time = datetime.datetime.now(datetime.timezone.utc)
|
@@ -1126,7 +1133,6 @@ def _find_module_utils(
|
|
1126
1133
|
module_fqn=remote_module_fqn,
|
1127
1134
|
params=encoded_params,
|
1128
1135
|
profile=module_metadata.serialization_profile,
|
1129
|
-
plugin_info_dict=dataclasses.asdict(plugin),
|
1130
1136
|
date_time=date_time,
|
1131
1137
|
coverage_config=coverage_config,
|
1132
1138
|
coverage_output=coverage_output,
|
@@ -1236,7 +1242,6 @@ def _extract_interpreter(b_module_data):
|
|
1236
1242
|
def modify_module(
|
1237
1243
|
*,
|
1238
1244
|
module_name: str,
|
1239
|
-
plugin: PluginInfo,
|
1240
1245
|
module_path,
|
1241
1246
|
module_args,
|
1242
1247
|
templar,
|
@@ -1277,7 +1282,6 @@ def modify_module(
|
|
1277
1282
|
|
1278
1283
|
module_bits = _find_module_utils(
|
1279
1284
|
module_name=module_name,
|
1280
|
-
plugin=plugin,
|
1281
1285
|
b_module_data=b_module_data,
|
1282
1286
|
module_path=module_path,
|
1283
1287
|
module_args=module_args,
|
@@ -22,8 +22,7 @@ from ansible.errors import (
|
|
22
22
|
)
|
23
23
|
from ansible.executor.task_result import _RawTaskResult
|
24
24
|
from ansible._internal._datatag import _utils
|
25
|
-
from ansible.module_utils.
|
26
|
-
from ansible.module_utils.common.messages import Detail, WarningSummary, DeprecationSummary
|
25
|
+
from ansible.module_utils.common.messages import Detail, WarningSummary, DeprecationSummary, PluginInfo
|
27
26
|
from ansible.module_utils.datatag import native_type_name
|
28
27
|
from ansible._internal._datatag._tags import TrustedAsTemplate
|
29
28
|
from ansible.module_utils.parsing.convert_bool import boolean
|
@@ -640,8 +639,8 @@ class TaskExecutor:
|
|
640
639
|
if self._task.timeout:
|
641
640
|
old_sig = signal.signal(signal.SIGALRM, task_timeout)
|
642
641
|
signal.alarm(self._task.timeout)
|
643
|
-
|
644
|
-
|
642
|
+
|
643
|
+
result = self._handler.run(task_vars=vars_copy)
|
645
644
|
|
646
645
|
# DTFIX-RELEASE: nuke this, it hides a lot of error detail- remove the active exception propagation hack from AnsibleActionFail at the same time
|
647
646
|
except (AnsibleActionFail, AnsibleActionSkip) as e:
|
@@ -844,13 +843,12 @@ class TaskExecutor:
|
|
844
843
|
if not isinstance(deprecation, DeprecationSummary):
|
845
844
|
# translate non-DeprecationMessageDetail message dicts
|
846
845
|
try:
|
847
|
-
if deprecation.pop('collection_name', ...) is not ...:
|
846
|
+
if (collection_name := deprecation.pop('collection_name', ...)) is not ...:
|
848
847
|
# deprecated: description='enable the deprecation message for collection_name' core_version='2.23'
|
848
|
+
# CAUTION: This deprecation cannot be enabled until the replacement (deprecator) has been documented, and the schema finalized.
|
849
849
|
# self.deprecated('The `collection_name` key in the `deprecations` dictionary is deprecated.', version='2.27')
|
850
|
-
|
850
|
+
deprecation.update(deprecator=PluginInfo._from_collection_name(collection_name))
|
851
851
|
|
852
|
-
# DTFIX-RELEASE: when plugin isn't set, do it at the boundary where we receive the module/action results
|
853
|
-
# that may even allow us to never set it in modules/actions directly and to populate it at the boundary
|
854
852
|
deprecation = DeprecationSummary(
|
855
853
|
details=(
|
856
854
|
Detail(msg=deprecation.pop('msg')),
|
ansible/galaxy/api.py
CHANGED
@@ -138,7 +138,7 @@ def g_connect(versions):
|
|
138
138
|
'The v2 Ansible Galaxy API is deprecated and no longer supported. '
|
139
139
|
'Ensure that you have configured the ansible-galaxy CLI to utilize an '
|
140
140
|
'updated and supported version of Ansible Galaxy.',
|
141
|
-
version='2.20'
|
141
|
+
version='2.20',
|
142
142
|
)
|
143
143
|
|
144
144
|
return method(self, *args, **kwargs)
|
@@ -201,9 +201,9 @@ class CollectionSignatureError(Exception):
|
|
201
201
|
|
202
202
|
# FUTURE: expose actual verify result details for a collection on this object, maybe reimplement as dataclass on py3.8+
|
203
203
|
class CollectionVerifyResult:
|
204
|
-
def __init__(self, collection_name
|
205
|
-
self.collection_name = collection_name
|
206
|
-
self.success = True
|
204
|
+
def __init__(self, collection_name: str) -> None:
|
205
|
+
self.collection_name = collection_name
|
206
|
+
self.success = True
|
207
207
|
|
208
208
|
|
209
209
|
def verify_local_collection(local_collection, remote_collection, artifacts_manager):
|
ansible/inventory/manager.py
CHANGED
@@ -313,6 +313,7 @@ class InventoryManager(object):
|
|
313
313
|
ex.obj = origin
|
314
314
|
failures.append({'src': source, 'plugin': plugin_name, 'exc': ex})
|
315
315
|
except Exception as ex:
|
316
|
+
# DTFIX-RELEASE: fix this error handling to correctly deal with messaging
|
316
317
|
try:
|
317
318
|
# omit line number to prevent contextual display of script or possibly sensitive info
|
318
319
|
raise AnsibleError(str(ex), obj=origin) from ex
|