ansible-core 2.13.11__py3-none-any.whl → 2.13.12rc1__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.
Potentially problematic release.
This version of ansible-core might be problematic. Click here for more details.
- ansible/cli/config.py +1 -0
- ansible/cli/galaxy.py +6 -2
- ansible/cli/inventory.py +1 -1
- ansible/galaxy/role.py +33 -1
- ansible/module_utils/ansible_release.py +1 -1
- ansible/release.py +1 -1
- ansible_core-2.13.12rc1.dist-info/METADATA +128 -0
- {ansible_core-2.13.11.dist-info → ansible_core-2.13.12rc1.dist-info}/RECORD +39 -38
- {ansible_core-2.13.11.dist-info → ansible_core-2.13.12rc1.dist-info}/WHEEL +1 -1
- ansible_test/_data/completion/remote.txt +2 -2
- ansible_test/_data/requirements/sanity.ansible-doc.txt +2 -0
- ansible_test/_data/requirements/sanity.changelog.txt +2 -0
- ansible_test/_data/requirements/sanity.import.plugin.txt +2 -0
- ansible_test/_data/requirements/sanity.import.txt +2 -0
- ansible_test/_data/requirements/sanity.integration-aliases.txt +2 -0
- ansible_test/_data/requirements/sanity.pylint.txt +2 -0
- ansible_test/_data/requirements/sanity.runtime-metadata.txt +2 -0
- ansible_test/_data/requirements/sanity.validate-modules.txt +2 -0
- ansible_test/_data/requirements/sanity.yamllint.txt +2 -0
- ansible_test/_internal/ansible_util.py +57 -4
- ansible_test/_internal/classification/__init__.py +6 -13
- ansible_test/_internal/classification/python.py +0 -1
- ansible_test/_internal/commands/sanity/__init__.py +35 -1
- ansible_test/_internal/commands/sanity/bin_symlinks.py +102 -0
- ansible_test/_internal/commands/sanity/integration_aliases.py +401 -0
- ansible_test/_internal/commands/sanity/validate_modules.py +5 -1
- ansible_test/_internal/constants.py +1 -0
- ansible_test/_internal/delegation.py +5 -2
- ansible_test/_internal/provider/layout/ansible.py +1 -1
- ansible_test/_internal/provider/source/unversioned.py +0 -3
- ansible_test/_internal/python_requirements.py +7 -0
- ansible_test/_internal/util.py +0 -2
- ansible_test/_util/target/setup/ConfigureRemotingForAnsible.ps1 +1 -1
- ansible_test/_util/target/setup/bootstrap.sh +2 -0
- ansible_test/_util/target/setup/requirements.py +63 -0
- ansible_core-2.13.11.dist-info/METADATA +0 -154
- ansible_test/_internal/commands/sanity/sanity_docs.py +0 -60
- {ansible_core-2.13.11.data → ansible_core-2.13.12rc1.data}/scripts/ansible-test +0 -0
- {ansible_core-2.13.11.dist-info → ansible_core-2.13.12rc1.dist-info}/COPYING +0 -0
- {ansible_core-2.13.11.dist-info → ansible_core-2.13.12rc1.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.13.11.dist-info → ansible_core-2.13.12rc1.dist-info}/top_level.txt +0 -0
|
@@ -3,9 +3,11 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
|
+
import shutil
|
|
6
7
|
import typing as t
|
|
7
8
|
|
|
8
9
|
from .constants import (
|
|
10
|
+
ANSIBLE_BIN_SYMLINK_MAP,
|
|
9
11
|
SOFT_RLIMIT_NOFILE,
|
|
10
12
|
)
|
|
11
13
|
|
|
@@ -17,12 +19,15 @@ from .util import (
|
|
|
17
19
|
common_environment,
|
|
18
20
|
ApplicationError,
|
|
19
21
|
ANSIBLE_LIB_ROOT,
|
|
22
|
+
ANSIBLE_TEST_ROOT,
|
|
20
23
|
ANSIBLE_TEST_DATA_ROOT,
|
|
21
|
-
|
|
24
|
+
ANSIBLE_ROOT,
|
|
22
25
|
ANSIBLE_SOURCE_ROOT,
|
|
23
26
|
ANSIBLE_TEST_TOOLS_ROOT,
|
|
27
|
+
MODE_FILE_EXECUTE,
|
|
24
28
|
get_ansible_version,
|
|
25
29
|
raw_command,
|
|
30
|
+
verified_chmod,
|
|
26
31
|
)
|
|
27
32
|
|
|
28
33
|
from .util_common import (
|
|
@@ -78,8 +83,10 @@ def ansible_environment(args, color=True, ansible_config=None): # type: (Common
|
|
|
78
83
|
env = common_environment()
|
|
79
84
|
path = env['PATH']
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
ansible_bin_path = get_ansible_bin_path(args)
|
|
87
|
+
|
|
88
|
+
if not path.startswith(ansible_bin_path + os.path.pathsep):
|
|
89
|
+
path = ansible_bin_path + os.path.pathsep + path
|
|
83
90
|
|
|
84
91
|
if not ansible_config:
|
|
85
92
|
# use the default empty configuration unless one has been provided
|
|
@@ -196,6 +203,52 @@ def configure_plugin_paths(args): # type: (CommonConfig) -> t.Dict[str, str]
|
|
|
196
203
|
return env
|
|
197
204
|
|
|
198
205
|
|
|
206
|
+
@mutex
|
|
207
|
+
def get_ansible_bin_path(args: CommonConfig) -> str:
|
|
208
|
+
"""
|
|
209
|
+
Return a directory usable for PATH, containing only the ansible entry points.
|
|
210
|
+
If a temporary directory is required, it will be cached for the lifetime of the process and cleaned up at exit.
|
|
211
|
+
"""
|
|
212
|
+
try:
|
|
213
|
+
return get_ansible_bin_path.bin_path # type: ignore[attr-defined]
|
|
214
|
+
except AttributeError:
|
|
215
|
+
pass
|
|
216
|
+
|
|
217
|
+
if ANSIBLE_SOURCE_ROOT:
|
|
218
|
+
# when running from source there is no need for a temporary directory since we already have known entry point scripts
|
|
219
|
+
bin_path = os.path.join(ANSIBLE_ROOT, 'bin')
|
|
220
|
+
else:
|
|
221
|
+
# when not running from source the installed entry points cannot be relied upon
|
|
222
|
+
# doing so would require using the interpreter specified by those entry points, which conflicts with using our interpreter and injector
|
|
223
|
+
# instead a temporary directory is created which contains only ansible entry points
|
|
224
|
+
# symbolic links cannot be used since the files are likely not executable
|
|
225
|
+
bin_path = create_temp_dir(prefix='ansible-test-', suffix='-bin')
|
|
226
|
+
bin_links = {os.path.join(bin_path, name): get_cli_path(path) for name, path in ANSIBLE_BIN_SYMLINK_MAP.items()}
|
|
227
|
+
|
|
228
|
+
if not args.explain:
|
|
229
|
+
for dst, src in bin_links.items():
|
|
230
|
+
shutil.copy(src, dst)
|
|
231
|
+
verified_chmod(dst, MODE_FILE_EXECUTE)
|
|
232
|
+
|
|
233
|
+
get_ansible_bin_path.bin_path = bin_path # type: ignore[attr-defined]
|
|
234
|
+
|
|
235
|
+
return bin_path
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def get_cli_path(path: str) -> str:
|
|
239
|
+
"""Return the absolute path to the CLI script from the given path which is relative to the `bin` directory of the original source tree layout."""
|
|
240
|
+
path_rewrite = {
|
|
241
|
+
'../lib/ansible/': ANSIBLE_LIB_ROOT,
|
|
242
|
+
'../test/lib/ansible_test/': ANSIBLE_TEST_ROOT,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
for prefix, destination in path_rewrite.items():
|
|
246
|
+
if path.startswith(prefix):
|
|
247
|
+
return os.path.join(destination, path[len(prefix):])
|
|
248
|
+
|
|
249
|
+
raise RuntimeError(path)
|
|
250
|
+
|
|
251
|
+
|
|
199
252
|
@mutex
|
|
200
253
|
def get_ansible_python_path(args): # type: (CommonConfig) -> str
|
|
201
254
|
"""
|
|
@@ -231,7 +284,7 @@ def generate_egg_info(path): # type: (str) -> None
|
|
|
231
284
|
# minimal PKG-INFO stub following the format defined in PEP 241
|
|
232
285
|
# required for older setuptools versions to avoid a traceback when importing pkg_resources from packages like cryptography
|
|
233
286
|
# newer setuptools versions are happy with an empty directory
|
|
234
|
-
# including a stub here means we don't need to locate the existing file or
|
|
287
|
+
# including a stub here means we don't need to locate the existing file or run any tools to generate it when running from source
|
|
235
288
|
pkg_info = '''
|
|
236
289
|
Metadata-Version: 1.0
|
|
237
290
|
Name: ansible
|
|
@@ -720,17 +720,6 @@ class PathMapper:
|
|
|
720
720
|
if path.startswith('changelogs/'):
|
|
721
721
|
return minimal
|
|
722
722
|
|
|
723
|
-
if path.startswith('docs/'):
|
|
724
|
-
return minimal
|
|
725
|
-
|
|
726
|
-
if path.startswith('examples/'):
|
|
727
|
-
if path == 'examples/scripts/ConfigureRemotingForAnsible.ps1':
|
|
728
|
-
return {
|
|
729
|
-
'windows-integration': 'connection_winrm',
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
return minimal
|
|
733
|
-
|
|
734
723
|
if path.startswith('hacking/'):
|
|
735
724
|
return minimal
|
|
736
725
|
|
|
@@ -752,8 +741,12 @@ class PathMapper:
|
|
|
752
741
|
return minimal
|
|
753
742
|
|
|
754
743
|
if path.startswith('packaging/'):
|
|
755
|
-
|
|
756
|
-
|
|
744
|
+
packaging_target = f'packaging_{os.path.splitext(path.split(os.path.sep)[1])[0]}'
|
|
745
|
+
|
|
746
|
+
if packaging_target in self.integration_targets_by_name:
|
|
747
|
+
return {
|
|
748
|
+
'integration': packaging_target,
|
|
749
|
+
}
|
|
757
750
|
|
|
758
751
|
return minimal
|
|
759
752
|
|
|
@@ -257,7 +257,6 @@ class ModuleUtilFinder(ast.NodeVisitor):
|
|
|
257
257
|
# The mapping is a tuple consisting of a path pattern to match and a replacement path.
|
|
258
258
|
# During analyis, any relative imports not covered here will result in warnings, which can be fixed by adding the appropriate entry.
|
|
259
259
|
path_map = (
|
|
260
|
-
('^hacking/build_library/build_ansible/', 'build_ansible/'),
|
|
261
260
|
('^lib/ansible/', 'ansible/'),
|
|
262
261
|
('^test/lib/ansible_test/_util/controller/sanity/validate-modules/', 'validate_modules/'),
|
|
263
262
|
('^test/units/', 'test/units/'),
|
|
@@ -70,6 +70,7 @@ from ...executor import (
|
|
|
70
70
|
)
|
|
71
71
|
|
|
72
72
|
from ...python_requirements import (
|
|
73
|
+
PipCommand,
|
|
73
74
|
PipInstall,
|
|
74
75
|
collect_requirements,
|
|
75
76
|
run_pip,
|
|
@@ -1125,7 +1126,7 @@ def create_sanity_virtualenv(
|
|
|
1125
1126
|
# The path to the virtual environment must be kept short to avoid the 127 character shebang length limit on Linux.
|
|
1126
1127
|
# If the limit is exceeded, generated entry point scripts from pip installed packages will fail with syntax errors.
|
|
1127
1128
|
virtualenv_install = json.dumps([command.serialize() for command in commands], indent=4)
|
|
1128
|
-
virtualenv_hash =
|
|
1129
|
+
virtualenv_hash = hash_pip_commands(commands)
|
|
1129
1130
|
virtualenv_cache = os.path.join(os.path.expanduser('~/.ansible/test/venv'))
|
|
1130
1131
|
virtualenv_path = os.path.join(virtualenv_cache, label, f'{python.version}', virtualenv_hash)
|
|
1131
1132
|
virtualenv_marker = os.path.join(virtualenv_path, 'marker.txt')
|
|
@@ -1165,6 +1166,39 @@ def create_sanity_virtualenv(
|
|
|
1165
1166
|
return virtualenv_python
|
|
1166
1167
|
|
|
1167
1168
|
|
|
1169
|
+
def hash_pip_commands(commands: list[PipCommand]) -> str:
|
|
1170
|
+
"""Return a short hash unique to the given list of pip commands, suitable for identifying the resulting sanity test environment."""
|
|
1171
|
+
serialized_commands = json.dumps([make_pip_command_hashable(command) for command in commands], indent=4)
|
|
1172
|
+
|
|
1173
|
+
return hashlib.sha256(to_bytes(serialized_commands)).hexdigest()[:8]
|
|
1174
|
+
|
|
1175
|
+
|
|
1176
|
+
def make_pip_command_hashable(command: PipCommand) -> tuple[str, dict[str, t.Any]]:
|
|
1177
|
+
"""Return a serialized version of the given pip command that is suitable for hashing."""
|
|
1178
|
+
if isinstance(command, PipInstall):
|
|
1179
|
+
# The pre-build instructions for pip installs must be omitted, so they do not affect the hash.
|
|
1180
|
+
# This is allows the pre-build commands to be added without breaking sanity venv caching.
|
|
1181
|
+
# It is safe to omit these from the hash since they only affect packages used during builds, not what is installed in the venv.
|
|
1182
|
+
command = PipInstall(
|
|
1183
|
+
requirements=[omit_pre_build_from_requirement(*req) for req in command.requirements],
|
|
1184
|
+
constraints=list(command.constraints),
|
|
1185
|
+
packages=list(command.packages),
|
|
1186
|
+
)
|
|
1187
|
+
|
|
1188
|
+
return command.serialize()
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
def omit_pre_build_from_requirement(path: str, requirements: str) -> tuple[str, str]:
|
|
1192
|
+
"""Return the given requirements with pre-build instructions omitted."""
|
|
1193
|
+
lines = requirements.splitlines(keepends=True)
|
|
1194
|
+
|
|
1195
|
+
# CAUTION: This code must be kept in sync with the code which processes pre-build instructions in:
|
|
1196
|
+
# test/lib/ansible_test/_util/target/setup/requirements.py
|
|
1197
|
+
lines = [line for line in lines if not line.startswith('# pre-build ')]
|
|
1198
|
+
|
|
1199
|
+
return path, ''.join(lines)
|
|
1200
|
+
|
|
1201
|
+
|
|
1168
1202
|
def check_sanity_virtualenv_yaml(python): # type: (VirtualPythonConfig) -> t.Optional[bool]
|
|
1169
1203
|
"""Return True if PyYAML has libyaml support for the given sanity virtual environment, False if it does not and None if it was not found."""
|
|
1170
1204
|
virtualenv_path = os.path.dirname(os.path.dirname(python.path))
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Sanity test for symlinks in the bin directory."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import typing as t
|
|
6
|
+
|
|
7
|
+
from . import (
|
|
8
|
+
SanityVersionNeutral,
|
|
9
|
+
SanityMessage,
|
|
10
|
+
SanityFailure,
|
|
11
|
+
SanitySuccess,
|
|
12
|
+
SanityTargets,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from ...constants import (
|
|
16
|
+
__file__ as symlink_map_full_path,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from ...test import (
|
|
20
|
+
TestResult,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from ...config import (
|
|
24
|
+
SanityConfig,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from ...data import (
|
|
28
|
+
data_context,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
from ...payload import (
|
|
32
|
+
ANSIBLE_BIN_SYMLINK_MAP,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from ...util import (
|
|
36
|
+
ANSIBLE_SOURCE_ROOT,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BinSymlinksTest(SanityVersionNeutral):
|
|
41
|
+
"""Sanity test for symlinks in the bin directory."""
|
|
42
|
+
ansible_only = True
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def can_ignore(self): # type: () -> bool
|
|
46
|
+
"""True if the test supports ignore entries."""
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def no_targets(self): # type: () -> bool
|
|
51
|
+
"""True if the test does not use test targets. Mutually exclusive with all_targets."""
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
def test(self, args, targets): # type: (SanityConfig, SanityTargets) -> TestResult
|
|
55
|
+
bin_root = os.path.join(ANSIBLE_SOURCE_ROOT, 'bin')
|
|
56
|
+
bin_names = os.listdir(bin_root)
|
|
57
|
+
bin_paths = sorted(os.path.join(bin_root, path) for path in bin_names)
|
|
58
|
+
|
|
59
|
+
errors = [] # type: t.List[t.Tuple[str, str]]
|
|
60
|
+
|
|
61
|
+
symlink_map_path = os.path.relpath(symlink_map_full_path, data_context().content.root)
|
|
62
|
+
|
|
63
|
+
for bin_path in bin_paths:
|
|
64
|
+
if not os.path.islink(bin_path):
|
|
65
|
+
errors.append((bin_path, 'not a symbolic link'))
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
dest = os.readlink(bin_path)
|
|
69
|
+
|
|
70
|
+
if not os.path.exists(bin_path):
|
|
71
|
+
errors.append((bin_path, 'points to non-existent path "%s"' % dest))
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
if not os.path.isfile(bin_path):
|
|
75
|
+
errors.append((bin_path, 'points to non-file "%s"' % dest))
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
map_dest = ANSIBLE_BIN_SYMLINK_MAP.get(os.path.basename(bin_path))
|
|
79
|
+
|
|
80
|
+
if not map_dest:
|
|
81
|
+
errors.append((bin_path, 'missing from ANSIBLE_BIN_SYMLINK_MAP in file "%s"' % symlink_map_path))
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
if dest != map_dest:
|
|
85
|
+
errors.append((bin_path, 'points to "%s" instead of "%s" from ANSIBLE_BIN_SYMLINK_MAP in file "%s"' % (dest, map_dest, symlink_map_path)))
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
if not os.access(bin_path, os.X_OK):
|
|
89
|
+
errors.append((bin_path, 'points to non-executable file "%s"' % dest))
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
for bin_name, dest in ANSIBLE_BIN_SYMLINK_MAP.items():
|
|
93
|
+
if bin_name not in bin_names:
|
|
94
|
+
bin_path = os.path.join(bin_root, bin_name)
|
|
95
|
+
errors.append((bin_path, 'missing symlink to "%s" defined in ANSIBLE_BIN_SYMLINK_MAP in file "%s"' % (dest, symlink_map_path)))
|
|
96
|
+
|
|
97
|
+
messages = [SanityMessage(message=message, path=os.path.relpath(path, data_context().content.root), confidence=100) for path, message in errors]
|
|
98
|
+
|
|
99
|
+
if errors:
|
|
100
|
+
return SanityFailure(self.name, messages=messages)
|
|
101
|
+
|
|
102
|
+
return SanitySuccess(self.name)
|