ansible-core 2.19.0b4__py3-none-any.whl → 2.19.0b6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ansible/_internal/__init__.py +1 -1
- ansible/_internal/_ansiballz/__init__.py +0 -0
- ansible/_internal/_ansiballz/_builder.py +101 -0
- ansible/_internal/{_ansiballz.py → _ansiballz/_wrapper.py} +11 -11
- ansible/_internal/_collection_proxy.py +1 -1
- ansible/_internal/_errors/_alarm_timeout.py +66 -0
- ansible/_internal/_errors/_captured.py +25 -30
- ansible/_internal/_errors/_error_factory.py +89 -0
- ansible/_internal/_errors/_error_utils.py +240 -0
- ansible/_internal/_errors/_task_timeout.py +28 -0
- ansible/_internal/_event_formatting.py +127 -0
- ansible/_internal/_json/__init__.py +5 -5
- ansible/_internal/_json/_profiles/_cache_persistence.py +2 -0
- ansible/_internal/_json/_profiles/_inventory_legacy.py +1 -1
- ansible/_internal/_json/_profiles/_legacy.py +3 -11
- ansible/_internal/_ssh/__init__.py +0 -0
- ansible/_internal/_ssh/_agent_launch.py +91 -0
- ansible/{utils → _internal/_ssh}/_ssh_agent.py +55 -93
- ansible/_internal/_templating/__init__.py +5 -3
- ansible/_internal/_templating/_datatag.py +2 -1
- ansible/_internal/_templating/_engine.py +3 -4
- ansible/_internal/_templating/_jinja_bits.py +28 -20
- ansible/_internal/_templating/_jinja_common.py +18 -27
- ansible/_internal/_templating/_jinja_plugins.py +36 -5
- ansible/_internal/_templating/_lazy_containers.py +5 -5
- ansible/_internal/_templating/_template_vars.py +72 -0
- ansible/_internal/_templating/_transform.py +26 -19
- ansible/_internal/_templating/_utils.py +1 -1
- ansible/_internal/_yaml/_constructor.py +4 -4
- ansible/_internal/_yaml/_dumper.py +26 -18
- ansible/_internal/_yaml/_errors.py +7 -7
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
- ansible/cli/__init__.py +11 -93
- ansible/cli/arguments/option_helpers.py +3 -4
- ansible/cli/console.py +1 -1
- ansible/cli/doc.py +86 -30
- ansible/cli/inventory.py +5 -7
- ansible/compat/importlib_resources.py +9 -12
- ansible/config/base.yml +46 -0
- ansible/errors/__init__.py +98 -50
- ansible/executor/module_common.py +75 -49
- ansible/executor/powershell/async_watchdog.ps1 +2 -2
- ansible/executor/powershell/async_wrapper.ps1 +3 -3
- ansible/executor/powershell/become_wrapper.ps1 +20 -2
- ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
- ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
- ansible/executor/powershell/exec_wrapper.ps1 +219 -6
- ansible/executor/powershell/module_manifest.py +52 -0
- ansible/executor/powershell/module_wrapper.ps1 +47 -21
- ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
- ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
- ansible/executor/process/worker.py +40 -115
- ansible/executor/task_executor.py +26 -61
- ansible/executor/task_result.py +2 -4
- ansible/galaxy/api.py +1 -4
- ansible/galaxy/collection/__init__.py +2 -10
- ansible/galaxy/collection/concrete_artifact_manager.py +2 -8
- ansible/galaxy/role.py +2 -2
- ansible/inventory/manager.py +1 -1
- ansible/module_utils/_internal/__init__.py +7 -7
- ansible/module_utils/_internal/_ambient_context.py +3 -3
- ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
- ansible/module_utils/_internal/{_ansiballz.py → _ansiballz/_loader.py} +13 -39
- ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
- ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
- ansible/module_utils/_internal/_datatag/__init__.py +43 -15
- ansible/module_utils/_internal/_datatag/_tags.py +2 -2
- ansible/module_utils/_internal/_deprecator.py +67 -55
- ansible/module_utils/_internal/_errors.py +88 -17
- ansible/module_utils/_internal/_event_utils.py +61 -0
- ansible/module_utils/_internal/_json/_profiles/__init__.py +22 -4
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
- ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
- ansible/module_utils/{common/messages.py → _internal/_messages.py} +54 -49
- ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
- ansible/module_utils/_internal/_plugin_info.py +15 -2
- ansible/module_utils/_internal/_stack.py +22 -0
- ansible/module_utils/_internal/_text_utils.py +6 -0
- ansible/module_utils/_internal/_traceback.py +11 -8
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +95 -71
- ansible/module_utils/common/arg_spec.py +2 -2
- ansible/module_utils/common/collections.py +6 -0
- ansible/module_utils/common/json.py +2 -2
- ansible/module_utils/common/respawn.py +4 -41
- ansible/module_utils/common/text/converters.py +3 -3
- ansible/module_utils/common/validation.py +1 -1
- ansible/module_utils/common/warnings.py +80 -23
- ansible/module_utils/common/yaml.py +1 -1
- ansible/module_utils/connection.py +8 -11
- ansible/module_utils/datatag.py +5 -2
- ansible/module_utils/facts/hardware/linux.py +1 -1
- ansible/module_utils/facts/sysctl.py +4 -6
- ansible/module_utils/facts/system/caps.py +2 -2
- ansible/module_utils/facts/system/distribution.py +16 -3
- ansible/module_utils/facts/system/local.py +1 -1
- ansible/module_utils/facts/virtual/linux.py +2 -2
- ansible/module_utils/service.py +3 -10
- ansible/module_utils/urls.py +4 -4
- ansible/modules/apt_repository.py +17 -39
- ansible/modules/assemble.py +2 -2
- ansible/modules/async_status.py +13 -11
- ansible/modules/async_wrapper.py +12 -22
- ansible/modules/command.py +3 -3
- ansible/modules/copy.py +4 -4
- ansible/modules/cron.py +1 -1
- ansible/modules/dnf5.py +14 -22
- ansible/modules/file.py +16 -17
- ansible/modules/find.py +3 -3
- ansible/modules/get_url.py +17 -0
- ansible/modules/git.py +9 -7
- ansible/modules/hostname.py +0 -1
- ansible/modules/known_hosts.py +12 -14
- ansible/modules/package.py +6 -0
- ansible/modules/replace.py +2 -2
- ansible/modules/service.py +3 -9
- ansible/modules/slurp.py +10 -13
- ansible/modules/stat.py +5 -7
- ansible/modules/unarchive.py +6 -6
- ansible/modules/user.py +1 -1
- ansible/modules/wait_for.py +28 -30
- ansible/modules/yum_repository.py +4 -3
- ansible/parsing/ajson.py +3 -5
- ansible/parsing/dataloader.py +6 -6
- ansible/parsing/mod_args.py +1 -1
- ansible/parsing/plugin_docs.py +2 -2
- ansible/parsing/utils/yaml.py +3 -3
- ansible/parsing/vault/__init__.py +10 -14
- ansible/playbook/base.py +7 -2
- ansible/playbook/included_file.py +3 -1
- ansible/playbook/play_context.py +2 -0
- ansible/playbook/playbook_include.py +1 -1
- ansible/playbook/taggable.py +19 -8
- ansible/playbook/task.py +2 -0
- ansible/plugins/__init__.py +0 -25
- ansible/plugins/action/__init__.py +8 -31
- ansible/plugins/action/add_host.py +1 -1
- ansible/plugins/action/assemble.py +8 -16
- ansible/plugins/action/async_status.py +7 -2
- ansible/plugins/action/copy.py +8 -7
- ansible/plugins/action/fetch.py +3 -3
- ansible/plugins/action/gather_facts.py +8 -8
- ansible/plugins/action/package.py +5 -8
- ansible/plugins/action/script.py +8 -15
- ansible/plugins/action/service.py +3 -7
- ansible/plugins/action/template.py +11 -10
- ansible/plugins/action/unarchive.py +5 -15
- ansible/plugins/action/uri.py +9 -20
- ansible/plugins/cache/__init__.py +17 -19
- ansible/plugins/callback/__init__.py +4 -6
- ansible/plugins/callback/junit.py +4 -2
- ansible/plugins/callback/tree.py +5 -5
- ansible/plugins/connection/local.py +6 -6
- ansible/plugins/connection/paramiko_ssh.py +5 -5
- ansible/plugins/connection/ssh.py +25 -15
- ansible/plugins/connection/winrm.py +6 -3
- ansible/plugins/doc_fragments/constructed.py +2 -2
- ansible/plugins/filter/core.py +32 -27
- ansible/plugins/filter/encryption.py +14 -6
- ansible/plugins/inventory/__init__.py +11 -10
- ansible/plugins/inventory/script.py +1 -1
- ansible/plugins/list.py +73 -19
- ansible/plugins/loader.py +7 -7
- ansible/plugins/lookup/csvfile.py +16 -71
- ansible/plugins/lookup/first_found.py +2 -1
- ansible/plugins/lookup/template.py +9 -4
- ansible/plugins/shell/__init__.py +56 -2
- ansible/plugins/shell/powershell.py +67 -9
- ansible/plugins/shell/sh.py +10 -5
- ansible/plugins/strategy/__init__.py +3 -3
- ansible/plugins/test/core.py +22 -16
- ansible/plugins/test/finished.yml +1 -1
- ansible/plugins/test/uri.py +2 -5
- ansible/release.py +1 -1
- ansible/template/__init__.py +38 -54
- ansible/utils/collection_loader/_collection_finder.py +3 -3
- ansible/utils/display.py +124 -138
- ansible/utils/galaxy.py +2 -2
- ansible/utils/hashing.py +6 -8
- ansible/utils/listify.py +6 -4
- ansible/utils/path.py +5 -7
- ansible/utils/py3compat.py +2 -1
- ansible/utils/ssh_functions.py +3 -2
- ansible/utils/unsafe_proxy.py +1 -1
- ansible/vars/hostvars.py +1 -1
- ansible/vars/plugins.py +3 -3
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/RECORD +224 -204
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/WHEEL +1 -1
- ansible_test/_data/completion/docker.txt +3 -3
- ansible_test/_data/completion/remote.txt +1 -0
- ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
- ansible_test/_data/requirements/sanity.changelog.txt +2 -2
- ansible_test/_data/requirements/sanity.pep8.txt +1 -1
- ansible_test/_data/requirements/sanity.pylint.txt +4 -4
- ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
- ansible_test/_internal/commands/integration/coverage.py +7 -2
- ansible_test/_internal/host_profiles.py +62 -10
- ansible_test/_internal/provisioning.py +10 -4
- ansible_test/_internal/ssh.py +1 -5
- ansible_test/_internal/thread.py +2 -1
- ansible_test/_internal/timeout.py +1 -1
- ansible_test/_internal/util.py +40 -12
- ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +61 -7
- ansible_test/_util/target/setup/bootstrap.sh +31 -0
- ansible_test/_util/target/setup/requirements.py +3 -9
- ansible/_internal/_errors/_utils.py +0 -310
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/top_level.txt +0 -0
@@ -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
|
@@ -3,6 +3,7 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import abc
|
6
|
+
import json
|
6
7
|
import os
|
7
8
|
import shutil
|
8
9
|
import tempfile
|
@@ -240,9 +241,13 @@ class PosixCoverageHandler(CoverageHandler[PosixConfig]):
|
|
240
241
|
# cause the 'coverage' module to be found, but not imported or enabled
|
241
242
|
coverage_file = ''
|
242
243
|
|
244
|
+
coverage_options = dict(
|
245
|
+
config=config_file,
|
246
|
+
output=coverage_file,
|
247
|
+
)
|
248
|
+
|
243
249
|
variables = dict(
|
244
|
-
|
245
|
-
_ANSIBLE_COVERAGE_OUTPUT=coverage_file,
|
250
|
+
_ANSIBLE_ANSIBALLZ_COVERAGE_CONFIG=json.dumps(coverage_options),
|
246
251
|
)
|
247
252
|
|
248
253
|
return variables
|
@@ -206,7 +206,7 @@ class Inventory:
|
|
206
206
|
inventory_text += f'[{group}]\n'
|
207
207
|
|
208
208
|
for host, variables in hosts.items():
|
209
|
-
kvp = ' '.join(f
|
209
|
+
kvp = ' '.join(f"{key}={value!r}" for key, value in variables.items())
|
210
210
|
inventory_text += f'{host} {kvp}\n'
|
211
211
|
|
212
212
|
inventory_text += '\n'
|
@@ -235,18 +235,24 @@ class HostProfile(t.Generic[THostConfig], metaclass=abc.ABCMeta):
|
|
235
235
|
*,
|
236
236
|
args: EnvironmentConfig,
|
237
237
|
config: THostConfig,
|
238
|
-
|
238
|
+
controller: ControllerHostProfile,
|
239
239
|
) -> None:
|
240
240
|
self.args = args
|
241
241
|
self.config = config
|
242
|
-
self.controller =
|
243
|
-
self.targets = targets
|
242
|
+
self.controller = not controller # this profile is a controller whenever the `controller` arg was not provided
|
243
|
+
self.targets = args.targets if self.controller else [] # only keep targets if this profile is a controller
|
244
|
+
self.controller_profile = controller if isinstance(self, ControllerProfile) else None
|
244
245
|
|
245
246
|
self.state: dict[str, t.Any] = {}
|
246
247
|
"""State that must be persisted across delegation."""
|
247
248
|
self.cache: dict[str, t.Any] = {}
|
248
249
|
"""Cache that must not be persisted across delegation."""
|
249
250
|
|
251
|
+
@property
|
252
|
+
@abc.abstractmethod
|
253
|
+
def name(self) -> str:
|
254
|
+
"""The name of the host profile."""
|
255
|
+
|
250
256
|
def provision(self) -> None:
|
251
257
|
"""Provision the host before delegation."""
|
252
258
|
|
@@ -274,6 +280,9 @@ class HostProfile(t.Generic[THostConfig], metaclass=abc.ABCMeta):
|
|
274
280
|
# args will be populated after the instances are restored
|
275
281
|
self.cache = {}
|
276
282
|
|
283
|
+
def __str__(self) -> str:
|
284
|
+
return f'{self.__class__.__name__}: {self.name}'
|
285
|
+
|
277
286
|
|
278
287
|
class PosixProfile(HostProfile[TPosixConfig], metaclass=abc.ABCMeta):
|
279
288
|
"""Base class for POSIX host profiles."""
|
@@ -320,6 +329,11 @@ class SshTargetHostProfile(HostProfile[THostConfig], metaclass=abc.ABCMeta):
|
|
320
329
|
class RemoteProfile(SshTargetHostProfile[TRemoteConfig], metaclass=abc.ABCMeta):
|
321
330
|
"""Base class for remote instance profiles."""
|
322
331
|
|
332
|
+
@property
|
333
|
+
def name(self) -> str:
|
334
|
+
"""The name of the host profile."""
|
335
|
+
return self.config.name
|
336
|
+
|
323
337
|
@property
|
324
338
|
def core_ci_state(self) -> t.Optional[dict[str, str]]:
|
325
339
|
"""The saved Ansible Core CI state."""
|
@@ -339,6 +353,8 @@ class RemoteProfile(SshTargetHostProfile[TRemoteConfig], metaclass=abc.ABCMeta):
|
|
339
353
|
|
340
354
|
def deprovision(self) -> None:
|
341
355
|
"""Deprovision the host after delegation has completed."""
|
356
|
+
super().deprovision()
|
357
|
+
|
342
358
|
if self.args.remote_terminate == TerminateMode.ALWAYS or (self.args.remote_terminate == TerminateMode.SUCCESS and self.args.success):
|
343
359
|
self.delete_instance()
|
344
360
|
|
@@ -397,6 +413,11 @@ class RemoteProfile(SshTargetHostProfile[TRemoteConfig], metaclass=abc.ABCMeta):
|
|
397
413
|
class ControllerProfile(SshTargetHostProfile[ControllerConfig], PosixProfile[ControllerConfig]):
|
398
414
|
"""Host profile for the controller as a target."""
|
399
415
|
|
416
|
+
@property
|
417
|
+
def name(self) -> str:
|
418
|
+
"""The name of the host profile."""
|
419
|
+
return self.controller_profile.name
|
420
|
+
|
400
421
|
def get_controller_target_connections(self) -> list[SshConnection]:
|
401
422
|
"""Return SSH connection(s) for accessing the host as a target from the controller."""
|
402
423
|
settings = SshConnectionDetail(
|
@@ -425,6 +446,11 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
|
|
425
446
|
command_privileged: bool
|
426
447
|
expected_mounts: tuple[CGroupMount, ...]
|
427
448
|
|
449
|
+
@property
|
450
|
+
def name(self) -> str:
|
451
|
+
"""The name of the host profile."""
|
452
|
+
return self.config.name
|
453
|
+
|
428
454
|
@property
|
429
455
|
def container_name(self) -> t.Optional[str]:
|
430
456
|
"""Return the stored container name, if any, otherwise None."""
|
@@ -976,6 +1002,8 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
|
|
976
1002
|
|
977
1003
|
def deprovision(self) -> None:
|
978
1004
|
"""Deprovision the host after delegation has completed."""
|
1005
|
+
super().deprovision()
|
1006
|
+
|
979
1007
|
container_exists = False
|
980
1008
|
|
981
1009
|
if self.container_name:
|
@@ -1025,10 +1053,10 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
|
|
1025
1053
|
|
1026
1054
|
raise HostConnectionError(f'Timeout waiting for {self.config.name} container {self.container_name}.', callback)
|
1027
1055
|
|
1028
|
-
def
|
1029
|
-
"""Return SSH connection
|
1056
|
+
def get_ssh_connection_detail(self, host_type: str) -> SshConnectionDetail:
|
1057
|
+
"""Return SSH connection detail for the specified host type."""
|
1030
1058
|
containers = get_container_database(self.args)
|
1031
|
-
access = containers.data[
|
1059
|
+
access = containers.data[host_type]['__test_hosts__'][self.container_name]
|
1032
1060
|
|
1033
1061
|
host = access.host_ip
|
1034
1062
|
port = dict(access.port_map())[22]
|
@@ -1046,7 +1074,11 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
|
|
1046
1074
|
enable_rsa_sha1='centos6' in self.config.image,
|
1047
1075
|
)
|
1048
1076
|
|
1049
|
-
return
|
1077
|
+
return settings
|
1078
|
+
|
1079
|
+
def get_controller_target_connections(self) -> list[SshConnection]:
|
1080
|
+
"""Return SSH connection(s) for accessing the host as a target from the controller."""
|
1081
|
+
return [SshConnection(self.args, self.get_ssh_connection_detail(HostType.control))]
|
1050
1082
|
|
1051
1083
|
def get_origin_controller_connection(self) -> DockerConnection:
|
1052
1084
|
"""Return a connection for accessing the host as a controller from the origin."""
|
@@ -1116,6 +1148,11 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
|
|
1116
1148
|
class NetworkInventoryProfile(HostProfile[NetworkInventoryConfig]):
|
1117
1149
|
"""Host profile for a network inventory."""
|
1118
1150
|
|
1151
|
+
@property
|
1152
|
+
def name(self) -> str:
|
1153
|
+
"""The name of the host profile."""
|
1154
|
+
return self.config.path
|
1155
|
+
|
1119
1156
|
|
1120
1157
|
class NetworkRemoteProfile(RemoteProfile[NetworkRemoteConfig]):
|
1121
1158
|
"""Host profile for a network remote instance."""
|
@@ -1197,6 +1234,11 @@ class NetworkRemoteProfile(RemoteProfile[NetworkRemoteConfig]):
|
|
1197
1234
|
class OriginProfile(ControllerHostProfile[OriginConfig]):
|
1198
1235
|
"""Host profile for origin."""
|
1199
1236
|
|
1237
|
+
@property
|
1238
|
+
def name(self) -> str:
|
1239
|
+
"""The name of the host profile."""
|
1240
|
+
return 'origin'
|
1241
|
+
|
1200
1242
|
def get_origin_controller_connection(self) -> LocalConnection:
|
1201
1243
|
"""Return a connection for accessing the host as a controller from the origin."""
|
1202
1244
|
return LocalConnection(self.args)
|
@@ -1317,6 +1359,11 @@ class PosixRemoteProfile(ControllerHostProfile[PosixRemoteConfig], RemoteProfile
|
|
1317
1359
|
class PosixSshProfile(SshTargetHostProfile[PosixSshConfig], PosixProfile[PosixSshConfig]):
|
1318
1360
|
"""Host profile for a POSIX SSH instance."""
|
1319
1361
|
|
1362
|
+
@property
|
1363
|
+
def name(self) -> str:
|
1364
|
+
"""The name of the host profile."""
|
1365
|
+
return self.config.host
|
1366
|
+
|
1320
1367
|
def get_controller_target_connections(self) -> list[SshConnection]:
|
1321
1368
|
"""Return SSH connection(s) for accessing the host as a target from the controller."""
|
1322
1369
|
settings = SshConnectionDetail(
|
@@ -1334,6 +1381,11 @@ class PosixSshProfile(SshTargetHostProfile[PosixSshConfig], PosixProfile[PosixSs
|
|
1334
1381
|
class WindowsInventoryProfile(SshTargetHostProfile[WindowsInventoryConfig]):
|
1335
1382
|
"""Host profile for a Windows inventory."""
|
1336
1383
|
|
1384
|
+
@property
|
1385
|
+
def name(self) -> str:
|
1386
|
+
"""The name of the host profile."""
|
1387
|
+
return self.config.path
|
1388
|
+
|
1337
1389
|
def get_controller_target_connections(self) -> list[SshConnection]:
|
1338
1390
|
"""Return SSH connection(s) for accessing the host as a target from the controller."""
|
1339
1391
|
inventory = parse_inventory(self.args, self.config.path)
|
@@ -1436,9 +1488,9 @@ def get_config_profile_type_map() -> dict[t.Type[HostConfig], t.Type[HostProfile
|
|
1436
1488
|
def create_host_profile(
|
1437
1489
|
args: EnvironmentConfig,
|
1438
1490
|
config: HostConfig,
|
1439
|
-
controller:
|
1491
|
+
controller: ControllerHostProfile | None,
|
1440
1492
|
) -> HostProfile:
|
1441
1493
|
"""Create and return a host profile from the given host configuration."""
|
1442
1494
|
profile_type = get_config_profile_type_map()[type(config)]
|
1443
|
-
profile = profile_type(args=args, config=config,
|
1495
|
+
profile = profile_type(args=args, config=config, controller=controller)
|
1444
1496
|
return profile
|
@@ -116,9 +116,11 @@ def prepare_profiles(
|
|
116
116
|
else:
|
117
117
|
run_pypi_proxy(args, targets_use_pypi)
|
118
118
|
|
119
|
+
controller_host_profile = t.cast(ControllerHostProfile, create_host_profile(args, args.controller, None))
|
120
|
+
|
119
121
|
host_state = HostState(
|
120
|
-
controller_profile=
|
121
|
-
target_profiles=[create_host_profile(args, target,
|
122
|
+
controller_profile=controller_host_profile,
|
123
|
+
target_profiles=[create_host_profile(args, target, controller_host_profile) for target in args.targets],
|
122
124
|
)
|
123
125
|
|
124
126
|
if args.prime_containers:
|
@@ -137,7 +139,9 @@ def prepare_profiles(
|
|
137
139
|
if not skip_setup:
|
138
140
|
profile.setup()
|
139
141
|
|
140
|
-
dispatch_jobs(
|
142
|
+
dispatch_jobs(
|
143
|
+
[(profile, WrappedThread(functools.partial(provision, profile), f'Provision: {profile}')) for profile in host_state.profiles]
|
144
|
+
)
|
141
145
|
|
142
146
|
host_state.controller_profile.configure()
|
143
147
|
|
@@ -157,7 +161,9 @@ def prepare_profiles(
|
|
157
161
|
if requirements:
|
158
162
|
requirements(profile)
|
159
163
|
|
160
|
-
dispatch_jobs(
|
164
|
+
dispatch_jobs(
|
165
|
+
[(profile, WrappedThread(functools.partial(configure, profile), f'Configure: {profile}')) for profile in host_state.target_profiles]
|
166
|
+
)
|
161
167
|
|
162
168
|
return host_state
|
163
169
|
|
ansible_test/_internal/ssh.py
CHANGED
@@ -13,7 +13,6 @@ import shlex
|
|
13
13
|
import typing as t
|
14
14
|
|
15
15
|
from .encoding import (
|
16
|
-
to_bytes,
|
17
16
|
to_text,
|
18
17
|
)
|
19
18
|
|
@@ -223,13 +222,10 @@ def run_ssh_command(
|
|
223
222
|
cmd_show = shlex.join(cmd)
|
224
223
|
display.info('Run background command: %s' % cmd_show, verbosity=1, truncate=True)
|
225
224
|
|
226
|
-
cmd_bytes = [to_bytes(arg) for arg in cmd]
|
227
|
-
env_bytes = dict((to_bytes(k), to_bytes(v)) for k, v in env.items())
|
228
|
-
|
229
225
|
if args.explain:
|
230
226
|
process = SshProcess(None)
|
231
227
|
else:
|
232
|
-
process = SshProcess(subprocess.Popen(
|
228
|
+
process = SshProcess(subprocess.Popen(cmd, env=env, bufsize=-1, # pylint: disable=consider-using-with
|
233
229
|
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE))
|
234
230
|
|
235
231
|
return process
|
ansible_test/_internal/thread.py
CHANGED
@@ -17,11 +17,12 @@ TCallable = t.TypeVar('TCallable', bound=t.Callable[..., t.Any])
|
|
17
17
|
class WrappedThread(threading.Thread):
|
18
18
|
"""Wrapper around Thread which captures results and exceptions."""
|
19
19
|
|
20
|
-
def __init__(self, action: c.Callable[[], t.Any]) -> None:
|
20
|
+
def __init__(self, action: c.Callable[[], t.Any], name: str) -> None:
|
21
21
|
super().__init__()
|
22
22
|
self._result: queue.Queue[t.Any] = queue.Queue()
|
23
23
|
self.action = action
|
24
24
|
self.result = None
|
25
|
+
self.name = name
|
25
26
|
|
26
27
|
def run(self) -> None:
|
27
28
|
"""
|
@@ -126,6 +126,6 @@ def configure_test_timeout(args: TestConfig) -> None:
|
|
126
126
|
|
127
127
|
signal.signal(signal.SIGUSR1, timeout_handler)
|
128
128
|
|
129
|
-
instance = WrappedThread(functools.partial(timeout_waiter, timeout_remaining.total_seconds()))
|
129
|
+
instance = WrappedThread(functools.partial(timeout_waiter, timeout_remaining.total_seconds()), 'Timeout Watchdog')
|
130
130
|
instance.daemon = True
|
131
131
|
instance.start()
|
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
|
|
@@ -513,16 +533,23 @@ def raw_command(
|
|
513
533
|
|
514
534
|
try:
|
515
535
|
try:
|
516
|
-
|
517
|
-
env_bytes = dict((to_bytes(k), to_bytes(v)) for k, v in env.items())
|
518
|
-
process = subprocess.Popen(cmd_bytes, env=env_bytes, stdin=stdin, stdout=stdout, stderr=stderr, cwd=cwd) # pylint: disable=consider-using-with
|
536
|
+
process = subprocess.Popen(cmd, env=env, stdin=stdin, stdout=stdout, stderr=stderr, cwd=cwd) # pylint: disable=consider-using-with
|
519
537
|
except FileNotFoundError as ex:
|
520
538
|
raise ApplicationError('Required program "%s" not found.' % cmd[0]) from ex
|
521
539
|
|
522
540
|
if communicate:
|
523
541
|
data_bytes = to_optional_bytes(data)
|
524
|
-
|
525
|
-
|
542
|
+
|
543
|
+
stdout_bytes, stderr_bytes = communicate_with_process(
|
544
|
+
name=cmd[0],
|
545
|
+
process=process,
|
546
|
+
stdin=data_bytes,
|
547
|
+
stdout=stdout == subprocess.PIPE,
|
548
|
+
stderr=stderr == subprocess.PIPE,
|
549
|
+
capture=capture,
|
550
|
+
output_stream=output_stream,
|
551
|
+
)
|
552
|
+
|
526
553
|
stdout_text = to_optional_text(stdout_bytes, str_errors) or ''
|
527
554
|
stderr_text = to_optional_text(stderr_bytes, str_errors) or ''
|
528
555
|
else:
|
@@ -546,6 +573,7 @@ def raw_command(
|
|
546
573
|
|
547
574
|
|
548
575
|
def communicate_with_process(
|
576
|
+
name: str,
|
549
577
|
process: subprocess.Popen,
|
550
578
|
stdin: t.Optional[bytes],
|
551
579
|
stdout: bool,
|
@@ -563,16 +591,16 @@ def communicate_with_process(
|
|
563
591
|
reader = OutputThread
|
564
592
|
|
565
593
|
if stdin is not None:
|
566
|
-
threads.append(WriterThread(process.stdin, stdin))
|
594
|
+
threads.append(WriterThread(process.stdin, stdin, name))
|
567
595
|
|
568
596
|
if stdout:
|
569
|
-
stdout_reader = reader(process.stdout, output_stream.get_buffer(sys.stdout.buffer))
|
597
|
+
stdout_reader = reader(process.stdout, output_stream.get_buffer(sys.stdout.buffer), name)
|
570
598
|
threads.append(stdout_reader)
|
571
599
|
else:
|
572
600
|
stdout_reader = None
|
573
601
|
|
574
602
|
if stderr:
|
575
|
-
stderr_reader = reader(process.stderr, output_stream.get_buffer(sys.stderr.buffer))
|
603
|
+
stderr_reader = reader(process.stderr, output_stream.get_buffer(sys.stderr.buffer), name)
|
576
604
|
threads.append(stderr_reader)
|
577
605
|
else:
|
578
606
|
stderr_reader = None
|
@@ -604,8 +632,8 @@ def communicate_with_process(
|
|
604
632
|
class WriterThread(WrappedThread):
|
605
633
|
"""Thread to write data to stdin of a subprocess."""
|
606
634
|
|
607
|
-
def __init__(self, handle: t.IO[bytes], data: bytes) -> None:
|
608
|
-
super().__init__(self._run)
|
635
|
+
def __init__(self, handle: t.IO[bytes], data: bytes, name: str) -> None:
|
636
|
+
super().__init__(self._run, f'{self.__class__.__name__}: {name}')
|
609
637
|
|
610
638
|
self.handle = handle
|
611
639
|
self.data = data
|
@@ -622,8 +650,8 @@ class WriterThread(WrappedThread):
|
|
622
650
|
class ReaderThread(WrappedThread, metaclass=abc.ABCMeta):
|
623
651
|
"""Thread to read stdout from a subprocess."""
|
624
652
|
|
625
|
-
def __init__(self, handle: t.IO[bytes], buffer: t.BinaryIO) -> None:
|
626
|
-
super().__init__(self._run)
|
653
|
+
def __init__(self, handle: t.IO[bytes], buffer: t.BinaryIO, name: str) -> None:
|
654
|
+
super().__init__(self._run, f'{self.__class__.__name__}: {name}')
|
627
655
|
|
628
656
|
self.handle = handle
|
629
657
|
self.buffer = buffer
|
@@ -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,
|
@@ -176,7 +176,6 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker):
|
|
176
176
|
def __init__(self, *args, **kwargs) -> None:
|
177
177
|
super().__init__(*args, **kwargs)
|
178
178
|
|
179
|
-
self.inference_context = astroid.context.InferenceContext()
|
180
179
|
self.module_cache: dict[str, astroid.Module] = {}
|
181
180
|
|
182
181
|
@functools.cached_property
|
@@ -241,7 +240,7 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker):
|
|
241
240
|
inferred: astroid.typing.InferenceResult | None = None
|
242
241
|
|
243
242
|
while target:
|
244
|
-
if inferred := astroid.util.safe_infer(target
|
243
|
+
if inferred := astroid.util.safe_infer(target):
|
245
244
|
break
|
246
245
|
|
247
246
|
if isinstance(target, astroid.Call):
|
@@ -263,6 +262,20 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker):
|
|
263
262
|
break
|
264
263
|
|
265
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
|
+
|
266
279
|
if not isinstance(inferred, (astroid.Module, astroid.ClassDef)):
|
267
280
|
inferred = None
|
268
281
|
break
|
@@ -282,25 +295,46 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker):
|
|
282
295
|
def infer_name(self, node: astroid.Name) -> astroid.NodeNG | None:
|
283
296
|
"""Infer the node referenced by the given name, or `None` if it cannot be unambiguously inferred."""
|
284
297
|
scope = node.scope()
|
285
|
-
|
298
|
+
inferred: astroid.NodeNG | None = None
|
299
|
+
name = node.name
|
286
300
|
|
287
301
|
while scope:
|
288
302
|
try:
|
289
|
-
assignment = scope[
|
303
|
+
assignment = scope[name]
|
290
304
|
except KeyError:
|
291
305
|
scope = scope.parent.scope() if scope.parent else None
|
292
306
|
continue
|
293
307
|
|
294
308
|
if isinstance(assignment, astroid.AssignName) and isinstance(assignment.parent, astroid.Assign):
|
295
|
-
|
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
|
296
329
|
elif isinstance(assignment, astroid.ImportFrom):
|
297
330
|
if module := self.get_module(assignment):
|
331
|
+
name = assignment.real_name(name)
|
298
332
|
scope = module.scope()
|
299
333
|
continue
|
300
334
|
|
301
335
|
break
|
302
336
|
|
303
|
-
return
|
337
|
+
return inferred
|
304
338
|
|
305
339
|
def get_module(self, node: astroid.ImportFrom) -> astroid.Module | None:
|
306
340
|
"""Import the requested module if possible and cache the result."""
|
@@ -480,7 +514,27 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker):
|
|
480
514
|
|
481
515
|
raise TypeError(type(value))
|
482
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
|
+
|
483
537
|
|
484
538
|
def register(linter: pylint.lint.PyLinter) -> None:
|
485
539
|
"""Required method to auto-register this checker."""
|
486
|
-
|
540
|
+
AnsibleDeprecatedChecker(linter).register()
|