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.
Files changed (85) hide show
  1. ansible/_internal/__init__.py +1 -1
  2. ansible/_internal/_ansiballz.py +1 -4
  3. ansible/_internal/_json/__init__.py +1 -1
  4. ansible/_internal/_templating/_datatag.py +3 -4
  5. ansible/_internal/_templating/_engine.py +6 -1
  6. ansible/_internal/_templating/_jinja_plugins.py +2 -6
  7. ansible/_internal/_testing.py +26 -0
  8. ansible/cli/__init__.py +3 -2
  9. ansible/cli/arguments/option_helpers.py +10 -3
  10. ansible/cli/doc.py +0 -1
  11. ansible/config/base.yml +5 -23
  12. ansible/config/manager.py +144 -103
  13. ansible/constants.py +1 -63
  14. ansible/errors/__init__.py +6 -2
  15. ansible/executor/module_common.py +11 -7
  16. ansible/executor/task_executor.py +6 -8
  17. ansible/galaxy/api.py +1 -1
  18. ansible/galaxy/collection/__init__.py +3 -3
  19. ansible/inventory/manager.py +1 -0
  20. ansible/module_utils/_internal/_ansiballz.py +4 -30
  21. ansible/module_utils/_internal/_datatag/_tags.py +3 -25
  22. ansible/module_utils/_internal/_deprecator.py +134 -0
  23. ansible/module_utils/_internal/_plugin_info.py +25 -0
  24. ansible/module_utils/_internal/_validation.py +14 -0
  25. ansible/module_utils/ansible_release.py +1 -1
  26. ansible/module_utils/basic.py +64 -17
  27. ansible/module_utils/common/arg_spec.py +8 -3
  28. ansible/module_utils/common/messages.py +40 -23
  29. ansible/module_utils/common/process.py +0 -1
  30. ansible/module_utils/common/respawn.py +0 -7
  31. ansible/module_utils/common/warnings.py +13 -13
  32. ansible/module_utils/datatag.py +13 -13
  33. ansible/module_utils/facts/virtual/linux.py +1 -1
  34. ansible/module_utils/parsing/convert_bool.py +6 -0
  35. ansible/modules/assemble.py +4 -4
  36. ansible/modules/async_status.py +1 -1
  37. ansible/modules/cron.py +3 -5
  38. ansible/modules/dnf5.py +2 -1
  39. ansible/modules/get_url.py +1 -1
  40. ansible/modules/git.py +1 -6
  41. ansible/modules/pip.py +2 -4
  42. ansible/modules/sysvinit.py +3 -3
  43. ansible/playbook/task.py +0 -2
  44. ansible/plugins/__init__.py +18 -8
  45. ansible/plugins/action/__init__.py +7 -15
  46. ansible/plugins/action/gather_facts.py +2 -4
  47. ansible/plugins/action/template.py +3 -0
  48. ansible/plugins/callback/oneline.py +7 -1
  49. ansible/plugins/callback/tree.py +7 -1
  50. ansible/plugins/connection/local.py +1 -1
  51. ansible/plugins/connection/paramiko_ssh.py +9 -2
  52. ansible/plugins/doc_fragments/action_core.py +1 -1
  53. ansible/plugins/filter/core.py +4 -1
  54. ansible/plugins/inventory/__init__.py +2 -2
  55. ansible/plugins/loader.py +197 -132
  56. ansible/plugins/lookup/url.py +2 -2
  57. ansible/plugins/strategy/__init__.py +6 -6
  58. ansible/release.py +1 -1
  59. ansible/template/__init__.py +1 -1
  60. ansible/utils/collection_loader/__init__.py +2 -0
  61. ansible/utils/collection_loader/_collection_meta.py +5 -3
  62. ansible/utils/display.py +137 -71
  63. ansible/utils/plugin_docs.py +2 -1
  64. ansible/utils/py3compat.py +1 -7
  65. ansible/utils/ssh_functions.py +4 -1
  66. ansible/vars/manager.py +18 -10
  67. ansible/vars/plugins.py +4 -4
  68. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info}/METADATA +3 -2
  69. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info}/RECORD +82 -79
  70. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info}/WHEEL +1 -1
  71. ansible_test/_internal/commands/sanity/pylint.py +1 -0
  72. ansible_test/_internal/docker_util.py +4 -3
  73. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +486 -0
  74. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_comment.py +137 -0
  75. ansible/module_utils/_internal/_dataclass_annotation_patch.py +0 -64
  76. ansible/module_utils/_internal/_plugin_exec_context.py +0 -49
  77. ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +0 -399
  78. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info}/entry_points.txt +0 -0
  79. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses}/COPYING +0 -0
  80. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/Apache-License.txt +0 -0
  81. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/BSD-3-Clause.txt +0 -0
  82. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/MIT-license.txt +0 -0
  83. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/PSF-license.txt +0 -0
  84. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/simplified_bsd.txt +0 -0
  85. {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
- # FIXME: see if we can unify in module_utils with similar function used by argspec
69
- def ensure_type(value, value_type, origin=None, origin_ftype=None):
70
- """ return a configuration variable with casting
71
- :arg value: The value to ensure correct typing of
72
- :kwarg value_type: The type of the value. This can be any of the following strings:
73
- :boolean: sets the value to a True or False value
74
- :bool: Same as 'boolean'
75
- :integer: Sets the value to an integer or raises a ValueType error
76
- :int: Same as 'integer'
77
- :float: Sets the value to a float or raises a ValueType error
78
- :list: Treats the value as a comma separated list. Split the value
79
- and return it as a python list.
80
- :none: Sets the value to None
81
- :path: Expands any environment variables and tilde's in the value.
82
- :tmppath: Create a unique temporary directory inside of the directory
83
- specified by value and return its path.
84
- :temppath: Same as 'tmppath'
85
- :tmp: Same as 'tmppath'
86
- :pathlist: Treat the value as a typical PATH string. (On POSIX, this
87
- means comma separated strings.) Split the value and then expand
88
- each part for environment variables and tildes.
89
- :pathspec: Treat the value as a PATH string. Expands any environment variables
90
- tildes's in the value.
91
- :str: Sets the value to string types.
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
- errmsg = ''
96
- basedir = None
97
- if origin and os.path.isabs(origin) and os.path.exists(to_bytes(origin)):
98
- basedir = origin
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
- if value is not None:
104
- if value_type in ('boolean', 'bool'):
105
- value = boolean(value, strict=False)
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
- elif value_type in ('integer', 'int'):
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
- value = int_part
112
- else:
113
- errmsg = 'int'
114
- except decimal.DecimalException:
115
- errmsg = 'int'
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
- elif value_type == 'float':
118
- if not isinstance(value, float):
119
- value = float(value)
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
- elif value_type == 'list':
122
- if isinstance(value, string_types):
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
- elif value_type == 'none':
164
+ if isinstance(value, Sequence) and not isinstance(value, bytes):
165
+ return list(value)
166
+
167
+ case 'none':
128
168
  if value == "None":
129
- value = None
169
+ return None
130
170
 
131
- if value is not None:
132
- errmsg = 'None'
171
+ case 'path':
172
+ if isinstance(value, str):
173
+ return resolve_path(value, basedir=basedir)
133
174
 
134
- elif value_type == 'path':
135
- if isinstance(value, string_types):
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
- elif value_type == 'pathspec':
152
- if isinstance(value, string_types):
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
- value = [resolve_path(x, basedir=basedir) for x in value]
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
- elif value_type == 'pathlist':
161
- if isinstance(value, string_types):
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
- value = [resolve_path(x, basedir=basedir) for x in value]
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
- elif value_type in ('dict', 'dictionary'):
170
- if not isinstance(value, Mapping):
171
- errmsg = 'dictionary'
202
+ case 'dictionary' | 'dict':
203
+ if isinstance(value, dict):
204
+ return value
172
205
 
173
- elif value_type in ('str', 'string'):
174
- if isinstance(value, (string_types, bool, int, float, complex)):
175
- value = to_text(value, errors='surrogate_or_strict')
176
- if origin_ftype and origin_ftype == 'ini':
177
- value = unquote(value)
178
- else:
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
- # defaults to string type
182
- elif isinstance(value, (string_types)):
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
- if errmsg:
188
- raise ValueError(f'Invalid type provided for {errmsg!r}: {value!r}')
216
+ if isinstance(value, _EncryptedStringProtocol):
217
+ return value._decrypt()
189
218
 
190
- return to_text(value, errors='surrogate_or_strict', nonstring='passthru')
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(object):
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, string_types) and (value.startswith('{{') and value.endswith('}}')) and variables is not None:
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
- t = NativeEnvironment().from_string(value)
378
- value = t.render(variables)
379
- except Exception:
380
- pass # not templatable
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'], string_types):
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 = ('.pyc', '.pyo', '.swp', '.bak', '~', '.rpm', '.md', '.txt', '.rst')
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()
@@ -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(msg, removal_version, removed, removal_date, self._collection_name)
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
- remote_module_fqn = 'ansible.modules.%s' % module_name
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._internal._plugin_exec_context import PluginExecContext
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
- with PluginExecContext(self._handler):
644
- result = self._handler.run(task_vars=vars_copy)
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
- pass
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): # type: (str) -> None
205
- self.collection_name = collection_name # type: str
206
- self.success = True # type: bool
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):
@@ -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