ansible-core 2.19.0b3__py3-none-any.whl → 2.19.0b5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ansible/_internal/__init__.py +2 -2
- ansible/_internal/_collection_proxy.py +1 -1
- ansible/_internal/_errors/_alarm_timeout.py +66 -0
- ansible/_internal/_errors/_captured.py +25 -30
- ansible/_internal/_errors/_error_factory.py +89 -0
- ansible/_internal/_errors/_error_utils.py +240 -0
- ansible/_internal/_errors/_task_timeout.py +28 -0
- ansible/_internal/_event_formatting.py +127 -0
- ansible/_internal/_json/__init__.py +6 -6
- ansible/_internal/_json/_profiles/_cache_persistence.py +2 -0
- ansible/_internal/_json/_profiles/_inventory_legacy.py +1 -1
- ansible/_internal/_json/_profiles/_legacy.py +3 -11
- ansible/_internal/_ssh/__init__.py +0 -0
- ansible/_internal/_ssh/_agent_launch.py +91 -0
- ansible/{utils → _internal/_ssh}/_ssh_agent.py +55 -93
- ansible/_internal/_templating/__init__.py +5 -3
- ansible/_internal/_templating/_datatag.py +2 -1
- ansible/_internal/_templating/_engine.py +3 -4
- ansible/_internal/_templating/_jinja_bits.py +21 -16
- ansible/_internal/_templating/_jinja_common.py +18 -27
- ansible/_internal/_templating/_jinja_plugins.py +31 -3
- ansible/_internal/_templating/_lazy_containers.py +5 -5
- ansible/_internal/_templating/_transform.py +20 -19
- ansible/_internal/_templating/_utils.py +1 -1
- ansible/_internal/_testing.py +26 -0
- ansible/_internal/_yaml/_dumper.py +1 -1
- ansible/_internal/_yaml/_errors.py +7 -7
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
- ansible/cli/__init__.py +5 -82
- ansible/cli/arguments/option_helpers.py +8 -5
- ansible/cli/doc.py +84 -28
- ansible/cli/inventory.py +1 -1
- ansible/compat/importlib_resources.py +9 -12
- ansible/config/base.yml +27 -23
- ansible/config/manager.py +142 -101
- ansible/constants.py +1 -1
- ansible/errors/__init__.py +96 -49
- ansible/executor/module_common.py +8 -10
- ansible/executor/powershell/async_watchdog.ps1 +2 -2
- ansible/executor/powershell/async_wrapper.ps1 +3 -3
- ansible/executor/powershell/become_wrapper.ps1 +20 -2
- ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
- ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
- ansible/executor/powershell/exec_wrapper.ps1 +219 -6
- ansible/executor/powershell/module_manifest.py +52 -0
- ansible/executor/powershell/module_wrapper.ps1 +47 -21
- ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
- ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
- ansible/executor/process/worker.py +38 -113
- ansible/executor/task_executor.py +26 -61
- ansible/executor/task_result.py +2 -4
- ansible/galaxy/collection/__init__.py +1 -4
- ansible/inventory/manager.py +1 -0
- ansible/module_utils/_internal/__init__.py +0 -3
- ansible/module_utils/_internal/_ambient_context.py +3 -3
- ansible/module_utils/_internal/_ansiballz.py +4 -2
- ansible/module_utils/_internal/_datatag/__init__.py +20 -14
- ansible/module_utils/_internal/_datatag/_tags.py +2 -2
- ansible/module_utils/_internal/_deprecator.py +66 -48
- ansible/module_utils/_internal/_errors.py +88 -17
- ansible/module_utils/_internal/_event_utils.py +61 -0
- ansible/module_utils/_internal/_json/_profiles/__init__.py +21 -4
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
- ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
- ansible/module_utils/{common/messages.py → _internal/_messages.py} +28 -47
- ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
- ansible/module_utils/_internal/_plugin_info.py +1 -1
- ansible/module_utils/_internal/_stack.py +22 -0
- ansible/module_utils/_internal/_text_utils.py +6 -0
- ansible/module_utils/_internal/_traceback.py +11 -8
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +49 -15
- ansible/module_utils/common/arg_spec.py +2 -2
- ansible/module_utils/common/collections.py +6 -0
- ansible/module_utils/common/json.py +2 -2
- ansible/module_utils/common/text/converters.py +3 -3
- ansible/module_utils/common/validation.py +1 -1
- ansible/module_utils/common/warnings.py +80 -23
- ansible/module_utils/common/yaml.py +1 -1
- ansible/module_utils/datatag.py +5 -2
- ansible/module_utils/facts/system/distribution.py +16 -3
- ansible/module_utils/facts/virtual/linux.py +2 -2
- ansible/module_utils/parsing/convert_bool.py +6 -0
- ansible/module_utils/service.py +2 -9
- ansible/modules/apt_repository.py +7 -29
- ansible/modules/assemble.py +4 -4
- ansible/modules/async_status.py +13 -11
- ansible/modules/async_wrapper.py +5 -5
- ansible/modules/cron.py +3 -5
- ansible/modules/dnf5.py +15 -22
- ansible/modules/git.py +1 -6
- ansible/modules/hostname.py +0 -1
- ansible/modules/pip.py +2 -4
- ansible/modules/service.py +3 -9
- ansible/modules/sysvinit.py +3 -3
- ansible/parsing/ajson.py +3 -5
- ansible/parsing/dataloader.py +4 -4
- ansible/parsing/mod_args.py +1 -1
- ansible/parsing/plugin_docs.py +2 -2
- ansible/parsing/utils/yaml.py +3 -3
- ansible/parsing/vault/__init__.py +4 -4
- ansible/playbook/playbook_include.py +1 -1
- ansible/playbook/taggable.py +0 -3
- ansible/plugins/__init__.py +0 -25
- ansible/plugins/action/__init__.py +9 -32
- ansible/plugins/action/add_host.py +1 -1
- ansible/plugins/action/assemble.py +8 -16
- ansible/plugins/action/async_status.py +7 -2
- ansible/plugins/action/copy.py +8 -7
- ansible/plugins/action/gather_facts.py +8 -8
- ansible/plugins/action/package.py +5 -8
- ansible/plugins/action/script.py +8 -15
- ansible/plugins/action/service.py +3 -7
- ansible/plugins/action/template.py +6 -8
- ansible/plugins/action/unarchive.py +5 -15
- ansible/plugins/action/uri.py +9 -20
- ansible/plugins/callback/__init__.py +4 -6
- ansible/plugins/callback/junit.py +4 -2
- ansible/plugins/connection/local.py +2 -2
- ansible/plugins/connection/ssh.py +17 -9
- ansible/plugins/connection/winrm.py +5 -2
- ansible/plugins/doc_fragments/constructed.py +2 -2
- ansible/plugins/filter/core.py +13 -6
- ansible/plugins/filter/encryption.py +4 -4
- ansible/plugins/inventory/__init__.py +11 -10
- ansible/plugins/inventory/script.py +1 -1
- ansible/plugins/list.py +69 -16
- ansible/plugins/loader.py +10 -9
- ansible/plugins/lookup/csvfile.py +16 -71
- ansible/plugins/lookup/first_found.py +2 -1
- ansible/plugins/shell/__init__.py +56 -2
- ansible/plugins/shell/powershell.py +66 -9
- ansible/plugins/shell/sh.py +9 -5
- ansible/plugins/test/core.py +21 -15
- ansible/plugins/test/finished.yml +1 -1
- ansible/plugins/test/uri.py +2 -5
- ansible/release.py +1 -1
- ansible/template/__init__.py +30 -2
- ansible/utils/collection_loader/__init__.py +2 -0
- ansible/utils/display.py +107 -128
- ansible/utils/hashing.py +0 -1
- ansible/utils/listify.py +6 -4
- ansible/utils/plugin_docs.py +2 -1
- ansible/utils/unsafe_proxy.py +1 -1
- ansible/vars/hostvars.py +1 -1
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/METADATA +3 -2
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/RECORD +173 -161
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/WHEEL +1 -1
- ansible_test/_data/completion/docker.txt +3 -3
- ansible_test/_data/completion/remote.txt +1 -0
- ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
- ansible_test/_data/requirements/sanity.changelog.txt +2 -2
- ansible_test/_data/requirements/sanity.pep8.txt +1 -1
- ansible_test/_data/requirements/sanity.pylint.txt +4 -4
- ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
- ansible_test/_internal/util.py +20 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +73 -8
- ansible_test/_util/target/setup/bootstrap.sh +31 -0
- ansible/_internal/_errors/_utils.py +0 -310
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses}/COPYING +0 -0
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/Apache-License.txt +0 -0
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/MIT-license.txt +0 -0
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/PSF-license.txt +0 -0
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
|
-
base image=quay.io/ansible/base-test-container:8.
|
2
|
-
default image=quay.io/ansible/default-test-container:11.
|
3
|
-
default image=quay.io/ansible/ansible-core-test-container:11.
|
1
|
+
base image=quay.io/ansible/base-test-container:8.2.0 python=3.13,3.8,3.9,3.10,3.11,3.12
|
2
|
+
default image=quay.io/ansible/default-test-container:11.6.0 python=3.13,3.8,3.9,3.10,3.11,3.12 context=collection
|
3
|
+
default image=quay.io/ansible/ansible-core-test-container:11.6.0 python=3.13,3.8,3.9,3.10,3.11,3.12 context=ansible-core
|
4
4
|
alpine321 image=quay.io/ansible/alpine321-test-container:9.1.0 python=3.12 cgroup=none audit=none
|
5
5
|
fedora41 image=quay.io/ansible/fedora41-test-container:9.0.0 python=3.13 cgroup=v2-only
|
6
6
|
ubuntu2204 image=quay.io/ansible/ubuntu2204-test-container:9.0.0 python=3.10
|
@@ -8,6 +8,7 @@ freebsd python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64
|
|
8
8
|
macos/15.3 python=3.13 python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64
|
9
9
|
macos python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64
|
10
10
|
rhel/9.5 python=3.9,3.12 become=sudo provider=aws arch=x86_64
|
11
|
+
rhel/10.0 python=3.12 become=sudo provider=aws arch=x86_64
|
11
12
|
rhel become=sudo provider=aws arch=x86_64
|
12
13
|
ubuntu/22.04 python=3.10 become=sudo provider=aws arch=x86_64
|
13
14
|
ubuntu/24.04 python=3.12 become=sudo provider=aws arch=x86_64
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# edit "sanity.changelog.in" and generate with: hacking/update-sanity-requirements.py --test changelog
|
2
2
|
antsibull-changelog==0.29.0
|
3
3
|
docutils==0.18.1
|
4
|
-
packaging==
|
4
|
+
packaging==25.0
|
5
5
|
PyYAML==6.0.2
|
6
6
|
rstcheck==5.0.0
|
7
7
|
semantic-version==2.10.0
|
8
8
|
types-docutils==0.18.3
|
9
|
-
typing_extensions==4.
|
9
|
+
typing_extensions==4.13.2
|
@@ -1,2 +1,2 @@
|
|
1
1
|
# edit "sanity.pep8.in" and generate with: hacking/update-sanity-requirements.py --test pep8
|
2
|
-
pycodestyle==2.
|
2
|
+
pycodestyle==2.13.0
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# edit "sanity.pylint.in" and generate with: hacking/update-sanity-requirements.py --test pylint
|
2
|
-
astroid==3.3.
|
3
|
-
dill==0.
|
2
|
+
astroid==3.3.10
|
3
|
+
dill==0.4.0
|
4
4
|
isort==6.0.1
|
5
5
|
mccabe==0.7.0
|
6
|
-
platformdirs==4.3.
|
7
|
-
pylint==3.3.
|
6
|
+
platformdirs==4.3.8
|
7
|
+
pylint==3.3.7
|
8
8
|
PyYAML==6.0.2
|
9
9
|
tomlkit==0.13.2
|
ansible_test/_internal/util.py
CHANGED
@@ -254,12 +254,29 @@ def filter_args(args: list[str], filters: dict[str, int]) -> list[str]:
|
|
254
254
|
"""Return a filtered version of the given command line arguments."""
|
255
255
|
remaining = 0
|
256
256
|
result = []
|
257
|
+
pass_through_args: list[str] = []
|
258
|
+
pass_through_explicit = False
|
259
|
+
pass_through_implicit = False
|
257
260
|
|
258
261
|
for arg in args:
|
262
|
+
if pass_through_explicit:
|
263
|
+
pass_through_args.append(arg)
|
264
|
+
continue
|
265
|
+
|
266
|
+
if arg == '--':
|
267
|
+
pass_through_explicit = True
|
268
|
+
continue
|
269
|
+
|
259
270
|
if not arg.startswith('-') and remaining:
|
260
271
|
remaining -= 1
|
272
|
+
pass_through_implicit = not remaining
|
261
273
|
continue
|
262
274
|
|
275
|
+
if not arg.startswith('-') and pass_through_implicit:
|
276
|
+
pass_through_args.append(arg)
|
277
|
+
continue
|
278
|
+
|
279
|
+
pass_through_implicit = False
|
263
280
|
remaining = 0
|
264
281
|
|
265
282
|
parts = arg.split('=', 1)
|
@@ -271,6 +288,9 @@ def filter_args(args: list[str], filters: dict[str, int]) -> list[str]:
|
|
271
288
|
|
272
289
|
result.append(arg)
|
273
290
|
|
291
|
+
if pass_through_args:
|
292
|
+
result += ['--'] + pass_through_args
|
293
|
+
|
274
294
|
return result
|
275
295
|
|
276
296
|
|
@@ -6,6 +6,7 @@ load-plugins=
|
|
6
6
|
pylint.extensions.docstyle,
|
7
7
|
|
8
8
|
disable=
|
9
|
+
bad-super-call, # flakey test, can report false positives due to inference issue when using deprecate_calls plugin
|
9
10
|
docstring-first-line-empty,
|
10
11
|
consider-using-f-string, # Python 2.x support still required
|
11
12
|
cyclic-import, # consistent results require running with --jobs 1 and testing all files
|
@@ -6,6 +6,7 @@ load-plugins=
|
|
6
6
|
pylint.extensions.docstyle,
|
7
7
|
|
8
8
|
disable=
|
9
|
+
bad-super-call, # flakey test, can report false positives due to inference issue when using deprecate_calls plugin
|
9
10
|
docstring-first-line-empty,
|
10
11
|
consider-using-f-string, # many occurrences
|
11
12
|
cyclic-import, # consistent results require running with --jobs 1 and testing all files
|
@@ -6,6 +6,7 @@ load-plugins=
|
|
6
6
|
pylint.extensions.docstyle,
|
7
7
|
|
8
8
|
disable=
|
9
|
+
bad-super-call, # flakey test, can report false positives due to inference issue when using deprecate_calls plugin
|
9
10
|
docstring-first-line-empty,
|
10
11
|
consider-using-f-string, # many occurrences
|
11
12
|
cyclic-import, # consistent results require running with --jobs 1 and testing all files
|
@@ -11,6 +11,7 @@ disable=
|
|
11
11
|
attribute-defined-outside-init,
|
12
12
|
bad-indentation,
|
13
13
|
bad-mcs-classmethod-argument,
|
14
|
+
bad-super-call, # flakey test, can report false positives due to inference issue when using deprecate_calls plugin
|
14
15
|
broad-exception-caught,
|
15
16
|
broad-exception-raised,
|
16
17
|
c-extension-no-member,
|
@@ -16,6 +16,7 @@ disable=
|
|
16
16
|
attribute-defined-outside-init,
|
17
17
|
bad-indentation,
|
18
18
|
bad-mcs-classmethod-argument,
|
19
|
+
bad-super-call, # flakey test, can report false positives due to inference issue when using deprecate_calls plugin
|
19
20
|
broad-exception-caught,
|
20
21
|
broad-exception-raised,
|
21
22
|
c-extension-no-member,
|
@@ -39,6 +39,10 @@ class DeprecationCallArgs:
|
|
39
39
|
removed: object = None # only on Display.deprecated
|
40
40
|
value: object = None # only on deprecate_value
|
41
41
|
|
42
|
+
def all_args_dynamic(self) -> bool:
|
43
|
+
"""True if all args are dynamic or None, otherwise False."""
|
44
|
+
return all(arg is None or isinstance(arg, astroid.NodeNG) for arg in dataclasses.asdict(self).values())
|
45
|
+
|
42
46
|
|
43
47
|
class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker):
|
44
48
|
"""Checks for deprecated calls to ensure proper usage."""
|
@@ -172,7 +176,6 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker):
|
|
172
176
|
def __init__(self, *args, **kwargs) -> None:
|
173
177
|
super().__init__(*args, **kwargs)
|
174
178
|
|
175
|
-
self.inference_context = astroid.context.InferenceContext()
|
176
179
|
self.module_cache: dict[str, astroid.Module] = {}
|
177
180
|
|
178
181
|
@functools.cached_property
|
@@ -237,7 +240,7 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker):
|
|
237
240
|
inferred: astroid.typing.InferenceResult | None = None
|
238
241
|
|
239
242
|
while target:
|
240
|
-
if inferred := astroid.util.safe_infer(target
|
243
|
+
if inferred := astroid.util.safe_infer(target):
|
241
244
|
break
|
242
245
|
|
243
246
|
if isinstance(target, astroid.Call):
|
@@ -259,6 +262,20 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker):
|
|
259
262
|
break
|
260
263
|
|
261
264
|
for name in reversed(names):
|
265
|
+
if isinstance(inferred, astroid.Instance):
|
266
|
+
try:
|
267
|
+
attr = next(iter(inferred.getattr(name)), None)
|
268
|
+
except astroid.AttributeInferenceError:
|
269
|
+
break
|
270
|
+
|
271
|
+
if isinstance(attr, astroid.AssignAttr):
|
272
|
+
inferred = self.get_ansible_module(attr)
|
273
|
+
continue
|
274
|
+
|
275
|
+
if isinstance(attr, astroid.FunctionDef):
|
276
|
+
inferred = attr
|
277
|
+
continue
|
278
|
+
|
262
279
|
if not isinstance(inferred, (astroid.Module, astroid.ClassDef)):
|
263
280
|
inferred = None
|
264
281
|
break
|
@@ -278,25 +295,46 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker):
|
|
278
295
|
def infer_name(self, node: astroid.Name) -> astroid.NodeNG | None:
|
279
296
|
"""Infer the node referenced by the given name, or `None` if it cannot be unambiguously inferred."""
|
280
297
|
scope = node.scope()
|
281
|
-
|
298
|
+
inferred: astroid.NodeNG | None = None
|
299
|
+
name = node.name
|
282
300
|
|
283
301
|
while scope:
|
284
302
|
try:
|
285
|
-
assignment = scope[
|
303
|
+
assignment = scope[name]
|
286
304
|
except KeyError:
|
287
305
|
scope = scope.parent.scope() if scope.parent else None
|
288
306
|
continue
|
289
307
|
|
290
308
|
if isinstance(assignment, astroid.AssignName) and isinstance(assignment.parent, astroid.Assign):
|
291
|
-
|
309
|
+
inferred = assignment.parent.value
|
310
|
+
elif (
|
311
|
+
isinstance(scope, astroid.FunctionDef)
|
312
|
+
and isinstance(assignment, astroid.AssignName)
|
313
|
+
and isinstance(assignment.parent, astroid.Arguments)
|
314
|
+
and assignment.parent.annotations
|
315
|
+
):
|
316
|
+
idx, _node = assignment.parent.find_argname(name)
|
317
|
+
|
318
|
+
if idx is not None:
|
319
|
+
try:
|
320
|
+
annotation = assignment.parent.annotations[idx]
|
321
|
+
except IndexError:
|
322
|
+
pass
|
323
|
+
else:
|
324
|
+
if isinstance(annotation, astroid.Name):
|
325
|
+
name = annotation.name
|
326
|
+
continue
|
327
|
+
elif isinstance(assignment, astroid.ClassDef):
|
328
|
+
inferred = assignment
|
292
329
|
elif isinstance(assignment, astroid.ImportFrom):
|
293
330
|
if module := self.get_module(assignment):
|
331
|
+
name = assignment.real_name(name)
|
294
332
|
scope = module.scope()
|
295
333
|
continue
|
296
334
|
|
297
335
|
break
|
298
336
|
|
299
|
-
return
|
337
|
+
return inferred
|
300
338
|
|
301
339
|
def get_module(self, node: astroid.ImportFrom) -> astroid.Module | None:
|
302
340
|
"""Import the requested module if possible and cache the result."""
|
@@ -346,6 +384,10 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker):
|
|
346
384
|
self.add_message('ansible-deprecated-date-not-permitted', node=node, args=(name,))
|
347
385
|
return
|
348
386
|
|
387
|
+
if call_args.all_args_dynamic():
|
388
|
+
# assume collection maintainers know what they're doing if all args are dynamic
|
389
|
+
return
|
390
|
+
|
349
391
|
if call_args.version and call_args.date:
|
350
392
|
self.add_message('ansible-deprecated-both-version-and-date', node=node, args=(name,))
|
351
393
|
return
|
@@ -407,10 +449,13 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker):
|
|
407
449
|
self.add_message('ansible-deprecated-unnecessary-collection-name', node=node, args=('deprecator', name,))
|
408
450
|
return
|
409
451
|
|
452
|
+
if args.all_args_dynamic():
|
453
|
+
# assume collection maintainers know what they're doing if all args are dynamic
|
454
|
+
return
|
455
|
+
|
410
456
|
expected_collection_name = 'ansible.builtin' if self.is_ansible_core else self.collection_name
|
411
457
|
|
412
458
|
if args.collection_name and args.collection_name != expected_collection_name:
|
413
|
-
# if collection_name is provided and a constant, report when it does not match the expected name
|
414
459
|
self.add_message('wrong-collection-deprecated', node=node, args=(args.collection_name, name))
|
415
460
|
|
416
461
|
def check_version(self, node: astroid.Call, name: str, args: DeprecationCallArgs) -> None:
|
@@ -469,7 +514,27 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker):
|
|
469
514
|
|
470
515
|
raise TypeError(type(value))
|
471
516
|
|
517
|
+
def get_ansible_module(self, node: astroid.AssignAttr) -> astroid.Instance | None:
|
518
|
+
"""Infer an AnsibleModule instance node from the given assignment."""
|
519
|
+
if isinstance(node.parent, astroid.Assign) and isinstance(node.parent.type_annotation, astroid.Name):
|
520
|
+
inferred = self.infer_name(node.parent.type_annotation)
|
521
|
+
elif isinstance(node.parent, astroid.Assign) and isinstance(node.parent.parent, astroid.FunctionDef) and isinstance(node.parent.value, astroid.Name):
|
522
|
+
inferred = self.infer_name(node.parent.value)
|
523
|
+
elif isinstance(node.parent, astroid.AnnAssign) and isinstance(node.parent.annotation, astroid.Name):
|
524
|
+
inferred = self.infer_name(node.parent.annotation)
|
525
|
+
else:
|
526
|
+
inferred = None
|
527
|
+
|
528
|
+
if isinstance(inferred, astroid.ClassDef) and inferred.name == 'AnsibleModule':
|
529
|
+
return inferred.instantiate_class()
|
530
|
+
|
531
|
+
return None
|
532
|
+
|
533
|
+
def register(self) -> None:
|
534
|
+
"""Register this plugin."""
|
535
|
+
self.linter.register_checker(self)
|
536
|
+
|
472
537
|
|
473
538
|
def register(linter: pylint.lint.PyLinter) -> None:
|
474
539
|
"""Required method to auto-register this checker."""
|
475
|
-
|
540
|
+
AnsibleDeprecatedChecker(linter).register()
|
@@ -281,10 +281,41 @@ bootstrap_remote_rhel_9()
|
|
281
281
|
done
|
282
282
|
}
|
283
283
|
|
284
|
+
bootstrap_remote_rhel_10()
|
285
|
+
{
|
286
|
+
py_pkg_prefix="python3"
|
287
|
+
|
288
|
+
packages="
|
289
|
+
gcc
|
290
|
+
${py_pkg_prefix}-devel
|
291
|
+
${py_pkg_prefix}-pip
|
292
|
+
"
|
293
|
+
|
294
|
+
if [ "${controller}" ]; then
|
295
|
+
packages="
|
296
|
+
${packages}
|
297
|
+
${py_pkg_prefix}-cryptography
|
298
|
+
${py_pkg_prefix}-jinja2
|
299
|
+
${py_pkg_prefix}-packaging
|
300
|
+
${py_pkg_prefix}-pyyaml
|
301
|
+
${py_pkg_prefix}-resolvelib
|
302
|
+
"
|
303
|
+
fi
|
304
|
+
|
305
|
+
while true; do
|
306
|
+
# shellcheck disable=SC2086
|
307
|
+
dnf install -q -y ${packages} \
|
308
|
+
&& break
|
309
|
+
echo "Failed to install packages. Sleeping before trying again..."
|
310
|
+
sleep 10
|
311
|
+
done
|
312
|
+
}
|
313
|
+
|
284
314
|
bootstrap_remote_rhel()
|
285
315
|
{
|
286
316
|
case "${platform_version}" in
|
287
317
|
9.*) bootstrap_remote_rhel_9 ;;
|
318
|
+
10.*) bootstrap_remote_rhel_10 ;;
|
288
319
|
esac
|
289
320
|
}
|
290
321
|
|
@@ -1,310 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import dataclasses
|
4
|
-
import itertools
|
5
|
-
import pathlib
|
6
|
-
import sys
|
7
|
-
import textwrap
|
8
|
-
import typing as t
|
9
|
-
|
10
|
-
from ansible.module_utils.common.messages import Detail, ErrorSummary
|
11
|
-
from ansible._internal._datatag._tags import Origin
|
12
|
-
from ansible.module_utils._internal import _ambient_context, _traceback
|
13
|
-
from ansible import errors
|
14
|
-
|
15
|
-
if t.TYPE_CHECKING:
|
16
|
-
from ansible.utils.display import Display
|
17
|
-
|
18
|
-
|
19
|
-
class RedactAnnotatedSourceContext(_ambient_context.AmbientContextBase):
|
20
|
-
"""
|
21
|
-
When active, this context will redact annotated source lines, showing only the origin.
|
22
|
-
"""
|
23
|
-
|
24
|
-
|
25
|
-
def _dedupe_and_concat_message_chain(message_parts: list[str]) -> str:
|
26
|
-
message_parts = list(reversed(message_parts))
|
27
|
-
|
28
|
-
message = message_parts.pop(0)
|
29
|
-
|
30
|
-
for message_part in message_parts:
|
31
|
-
# avoid duplicate messages where the cause was already concatenated to the exception message
|
32
|
-
if message_part.endswith(message):
|
33
|
-
message = message_part
|
34
|
-
else:
|
35
|
-
message = concat_message(message_part, message)
|
36
|
-
|
37
|
-
return message
|
38
|
-
|
39
|
-
|
40
|
-
def _collapse_error_details(error_details: t.Sequence[Detail]) -> list[Detail]:
|
41
|
-
"""
|
42
|
-
Return a potentially modified error chain, with redundant errors collapsed into previous error(s) in the chain.
|
43
|
-
This reduces the verbosity of messages by eliminating repetition when multiple errors in the chain share the same contextual information.
|
44
|
-
"""
|
45
|
-
previous_error = error_details[0]
|
46
|
-
previous_warnings: list[str] = []
|
47
|
-
collapsed_error_details: list[tuple[Detail, list[str]]] = [(previous_error, previous_warnings)]
|
48
|
-
|
49
|
-
for error in error_details[1:]:
|
50
|
-
details_present = error.formatted_source_context or error.help_text
|
51
|
-
details_changed = error.formatted_source_context != previous_error.formatted_source_context or error.help_text != previous_error.help_text
|
52
|
-
|
53
|
-
if details_present and details_changed:
|
54
|
-
previous_error = error
|
55
|
-
previous_warnings = []
|
56
|
-
collapsed_error_details.append((previous_error, previous_warnings))
|
57
|
-
else:
|
58
|
-
previous_warnings.append(error.msg)
|
59
|
-
|
60
|
-
final_error_details: list[Detail] = []
|
61
|
-
|
62
|
-
for error, messages in collapsed_error_details:
|
63
|
-
final_error_details.append(dataclasses.replace(error, msg=_dedupe_and_concat_message_chain([error.msg] + messages)))
|
64
|
-
|
65
|
-
return final_error_details
|
66
|
-
|
67
|
-
|
68
|
-
def _get_cause(exception: BaseException) -> BaseException | None:
|
69
|
-
# deprecated: description='remove support for orig_exc (deprecated in 2.23)' core_version='2.27'
|
70
|
-
|
71
|
-
if not isinstance(exception, errors.AnsibleError):
|
72
|
-
return exception.__cause__
|
73
|
-
|
74
|
-
if exception.__cause__:
|
75
|
-
if exception.orig_exc and exception.orig_exc is not exception.__cause__:
|
76
|
-
_get_display().warning(
|
77
|
-
msg=f"The `orig_exc` argument to `{type(exception).__name__}` was given, but differed from the cause given by `raise ... from`.",
|
78
|
-
)
|
79
|
-
|
80
|
-
return exception.__cause__
|
81
|
-
|
82
|
-
if exception.orig_exc:
|
83
|
-
# encourage the use of `raise ... from` before deprecating `orig_exc`
|
84
|
-
_get_display().warning(msg=f"The `orig_exc` argument to `{type(exception).__name__}` was given without using `raise ... from orig_exc`.")
|
85
|
-
|
86
|
-
return exception.orig_exc
|
87
|
-
|
88
|
-
return None
|
89
|
-
|
90
|
-
|
91
|
-
class _TemporaryDisplay:
|
92
|
-
# DTFIX-FUTURE: generalize this and hide it in the display module so all users of Display can benefit
|
93
|
-
|
94
|
-
@staticmethod
|
95
|
-
def warning(*args, **kwargs):
|
96
|
-
print(f'FALLBACK WARNING: {args} {kwargs}', file=sys.stderr)
|
97
|
-
|
98
|
-
@staticmethod
|
99
|
-
def deprecated(*args, **kwargs):
|
100
|
-
print(f'FALLBACK DEPRECATION: {args} {kwargs}', file=sys.stderr)
|
101
|
-
|
102
|
-
|
103
|
-
def _get_display() -> Display | _TemporaryDisplay:
|
104
|
-
try:
|
105
|
-
from ansible.utils.display import Display
|
106
|
-
except ImportError:
|
107
|
-
return _TemporaryDisplay()
|
108
|
-
|
109
|
-
return Display()
|
110
|
-
|
111
|
-
|
112
|
-
def _create_error_summary(exception: BaseException, event: _traceback.TracebackEvent | None = None) -> ErrorSummary:
|
113
|
-
from . import _captured # avoid circular import due to AnsibleError import
|
114
|
-
|
115
|
-
current_exception: BaseException | None = exception
|
116
|
-
error_details: list[Detail] = []
|
117
|
-
|
118
|
-
if event:
|
119
|
-
formatted_traceback = _traceback.maybe_extract_traceback(exception, event)
|
120
|
-
else:
|
121
|
-
formatted_traceback = None
|
122
|
-
|
123
|
-
while current_exception:
|
124
|
-
if isinstance(current_exception, errors.AnsibleError):
|
125
|
-
include_cause_message = current_exception._include_cause_message
|
126
|
-
edc = Detail(
|
127
|
-
msg=current_exception._original_message.strip(),
|
128
|
-
formatted_source_context=current_exception._formatted_source_context,
|
129
|
-
help_text=current_exception._help_text,
|
130
|
-
)
|
131
|
-
else:
|
132
|
-
include_cause_message = True
|
133
|
-
edc = Detail(
|
134
|
-
msg=str(current_exception).strip(),
|
135
|
-
)
|
136
|
-
|
137
|
-
error_details.append(edc)
|
138
|
-
|
139
|
-
if isinstance(current_exception, _captured.AnsibleCapturedError):
|
140
|
-
detail = current_exception.error_summary
|
141
|
-
error_details.extend(detail.details)
|
142
|
-
|
143
|
-
if formatted_traceback and detail.formatted_traceback:
|
144
|
-
formatted_traceback = (
|
145
|
-
f'{detail.formatted_traceback}\n'
|
146
|
-
f'The {current_exception.context} exception above was the direct cause of the following controller exception:\n\n'
|
147
|
-
f'{formatted_traceback}'
|
148
|
-
)
|
149
|
-
|
150
|
-
if not include_cause_message:
|
151
|
-
break
|
152
|
-
|
153
|
-
current_exception = _get_cause(current_exception)
|
154
|
-
|
155
|
-
return ErrorSummary(details=tuple(error_details), formatted_traceback=formatted_traceback)
|
156
|
-
|
157
|
-
|
158
|
-
def concat_message(left: str, right: str) -> str:
|
159
|
-
"""Normalize `left` by removing trailing punctuation and spaces before appending new punctuation and `right`."""
|
160
|
-
return f'{left.rstrip(". ")}: {right}'
|
161
|
-
|
162
|
-
|
163
|
-
def get_chained_message(exception: BaseException) -> str:
|
164
|
-
"""
|
165
|
-
Return the full chain of exception messages by concatenating the cause(s) until all are exhausted.
|
166
|
-
"""
|
167
|
-
error_summary = _create_error_summary(exception)
|
168
|
-
message_parts = [edc.msg for edc in error_summary.details]
|
169
|
-
|
170
|
-
return _dedupe_and_concat_message_chain(message_parts)
|
171
|
-
|
172
|
-
|
173
|
-
@dataclasses.dataclass(kw_only=True, frozen=True)
|
174
|
-
class SourceContext:
|
175
|
-
origin: Origin
|
176
|
-
annotated_source_lines: list[str]
|
177
|
-
target_line: str | None
|
178
|
-
|
179
|
-
def __str__(self) -> str:
|
180
|
-
msg_lines = [f'Origin: {self.origin}']
|
181
|
-
|
182
|
-
if self.annotated_source_lines:
|
183
|
-
msg_lines.append('')
|
184
|
-
msg_lines.extend(self.annotated_source_lines)
|
185
|
-
|
186
|
-
return '\n'.join(msg_lines)
|
187
|
-
|
188
|
-
@classmethod
|
189
|
-
def from_value(cls, value: t.Any) -> SourceContext | None:
|
190
|
-
"""Attempt to retrieve source and render a contextual indicator from the value's origin (if any)."""
|
191
|
-
if value is None:
|
192
|
-
return None
|
193
|
-
|
194
|
-
if isinstance(value, Origin):
|
195
|
-
origin = value
|
196
|
-
value = None
|
197
|
-
else:
|
198
|
-
origin = Origin.get_tag(value)
|
199
|
-
|
200
|
-
if RedactAnnotatedSourceContext.current(optional=True):
|
201
|
-
return cls.error('content redacted')
|
202
|
-
|
203
|
-
if origin and origin.path:
|
204
|
-
return cls.from_origin(origin)
|
205
|
-
|
206
|
-
# DTFIX-RELEASE: redaction context may not be sufficient to avoid secret disclosure without SensitiveData and other enhancements
|
207
|
-
if value is None:
|
208
|
-
truncated_value = None
|
209
|
-
annotated_source_lines = []
|
210
|
-
else:
|
211
|
-
# DTFIX-FUTURE: cleanup/share width
|
212
|
-
try:
|
213
|
-
value = str(value)
|
214
|
-
except Exception as ex:
|
215
|
-
value = f'<< context unavailable: {ex} >>'
|
216
|
-
|
217
|
-
truncated_value = textwrap.shorten(value, width=120)
|
218
|
-
annotated_source_lines = [truncated_value]
|
219
|
-
|
220
|
-
return SourceContext(
|
221
|
-
origin=origin or Origin.UNKNOWN,
|
222
|
-
annotated_source_lines=annotated_source_lines,
|
223
|
-
target_line=truncated_value,
|
224
|
-
)
|
225
|
-
|
226
|
-
@staticmethod
|
227
|
-
def error(message: str | None, origin: Origin | None = None) -> SourceContext:
|
228
|
-
return SourceContext(
|
229
|
-
origin=origin,
|
230
|
-
annotated_source_lines=[f'(source not shown: {message})'] if message else [],
|
231
|
-
target_line=None,
|
232
|
-
)
|
233
|
-
|
234
|
-
@classmethod
|
235
|
-
def from_origin(cls, origin: Origin) -> SourceContext:
|
236
|
-
"""Attempt to retrieve source and render a contextual indicator of an error location."""
|
237
|
-
from ansible.parsing.vault import is_encrypted # avoid circular import
|
238
|
-
|
239
|
-
# DTFIX-FUTURE: support referencing the column after the end of the target line, so we can indicate where a missing character (quote) needs to be added
|
240
|
-
# this is also useful for cases like end-of-stream reported by the YAML parser
|
241
|
-
|
242
|
-
# DTFIX-FUTURE: Implement line wrapping and match annotated line width to the terminal display width.
|
243
|
-
|
244
|
-
context_line_count: t.Final = 2
|
245
|
-
max_annotated_line_width: t.Final = 120
|
246
|
-
truncation_marker: t.Final = '...'
|
247
|
-
|
248
|
-
target_line_num = origin.line_num
|
249
|
-
|
250
|
-
if RedactAnnotatedSourceContext.current(optional=True):
|
251
|
-
return cls.error('content redacted', origin)
|
252
|
-
|
253
|
-
if not target_line_num or target_line_num < 1:
|
254
|
-
return cls.error(None, origin) # message omitted since lack of line number is obvious from pos
|
255
|
-
|
256
|
-
start_line_idx = max(0, (target_line_num - 1) - context_line_count) # if near start of file
|
257
|
-
target_col_num = origin.col_num
|
258
|
-
|
259
|
-
try:
|
260
|
-
with pathlib.Path(origin.path).open() as src:
|
261
|
-
first_line = src.readline()
|
262
|
-
lines = list(itertools.islice(itertools.chain((first_line,), src), start_line_idx, target_line_num))
|
263
|
-
except Exception as ex:
|
264
|
-
return cls.error(type(ex).__name__, origin)
|
265
|
-
|
266
|
-
if is_encrypted(first_line):
|
267
|
-
return cls.error('content encrypted', origin)
|
268
|
-
|
269
|
-
if len(lines) != target_line_num - start_line_idx:
|
270
|
-
return cls.error('file truncated', origin)
|
271
|
-
|
272
|
-
annotated_source_lines = []
|
273
|
-
|
274
|
-
line_label_width = len(str(target_line_num))
|
275
|
-
max_src_line_len = max_annotated_line_width - line_label_width - 1
|
276
|
-
|
277
|
-
usable_line_len = max_src_line_len
|
278
|
-
|
279
|
-
for line_num, line in enumerate(lines, start_line_idx + 1):
|
280
|
-
line = line.rstrip('\n') # universal newline default mode on `open` ensures we'll never see anything but \n
|
281
|
-
line = line.replace('\t', ' ') # mixed tab/space handling is intentionally disabled since we're both format and display config agnostic
|
282
|
-
|
283
|
-
if len(line) > max_src_line_len:
|
284
|
-
line = line[: max_src_line_len - len(truncation_marker)] + truncation_marker
|
285
|
-
usable_line_len = max_src_line_len - len(truncation_marker)
|
286
|
-
|
287
|
-
annotated_source_lines.append(f'{str(line_num).rjust(line_label_width)}{" " if line else ""}{line}')
|
288
|
-
|
289
|
-
if target_col_num and usable_line_len >= target_col_num >= 1:
|
290
|
-
column_marker = f'column {target_col_num}'
|
291
|
-
|
292
|
-
target_col_idx = target_col_num - 1
|
293
|
-
|
294
|
-
if target_col_idx + 2 + len(column_marker) > max_src_line_len:
|
295
|
-
column_marker = f'{" " * (target_col_idx - len(column_marker) - 1)}{column_marker} ^'
|
296
|
-
else:
|
297
|
-
column_marker = f'{" " * target_col_idx}^ {column_marker}'
|
298
|
-
|
299
|
-
column_marker = f'{" " * line_label_width} {column_marker}'
|
300
|
-
|
301
|
-
annotated_source_lines.append(column_marker)
|
302
|
-
elif target_col_num is None:
|
303
|
-
underline_length = len(annotated_source_lines[-1]) - line_label_width - 1
|
304
|
-
annotated_source_lines.append(f'{" " * line_label_width} {"^" * underline_length}')
|
305
|
-
|
306
|
-
return SourceContext(
|
307
|
-
origin=origin,
|
308
|
-
annotated_source_lines=annotated_source_lines,
|
309
|
-
target_line=lines[-1].rstrip('\n'), # universal newline default mode on `open` ensures we'll never see anything but \n
|
310
|
-
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|