ansible-core 2.13.8rc1__py3-none-any.whl → 2.13.9rc1__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/doc.py +10 -4
- ansible/galaxy/api.py +29 -10
- ansible/galaxy/collection/__init__.py +3 -0
- ansible/galaxy/collection/concrete_artifact_manager.py +34 -17
- ansible/galaxy/dependency_resolution/dataclasses.py +11 -1
- ansible/galaxy/dependency_resolution/providers.py +0 -1
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/api.py +14 -1
- ansible/module_utils/csharp/Ansible.Basic.cs +265 -7
- ansible/plugins/lookup/password.py +79 -39
- ansible/release.py +1 -1
- ansible/utils/encrypt.py +9 -6
- {ansible_core-2.13.8rc1.dist-info → ansible_core-2.13.9rc1.dist-info}/METADATA +1 -1
- {ansible_core-2.13.8rc1.dist-info → ansible_core-2.13.9rc1.dist-info}/RECORD +43 -43
- {ansible_core-2.13.8rc1.dist-info → ansible_core-2.13.9rc1.dist-info}/WHEEL +1 -1
- ansible_test/_internal/ci/__init__.py +2 -2
- ansible_test/_internal/ci/azp.py +12 -8
- ansible_test/_internal/ci/local.py +2 -2
- ansible_test/_internal/classification/__init__.py +51 -43
- ansible_test/_internal/cli/argparsing/argcompletion.py +20 -5
- ansible_test/_internal/cli/commands/sanity.py +0 -15
- ansible_test/_internal/commands/coverage/combine.py +3 -1
- ansible_test/_internal/commands/integration/__init__.py +6 -2
- ansible_test/_internal/commands/integration/cloud/__init__.py +3 -1
- ansible_test/_internal/commands/sanity/__init__.py +7 -0
- ansible_test/_internal/commands/sanity/pylint.py +11 -0
- ansible_test/_internal/commands/sanity/validate_modules.py +66 -5
- ansible_test/_internal/config.py +6 -12
- ansible_test/_internal/core_ci.py +8 -1
- ansible_test/_internal/data.py +17 -8
- ansible_test/_internal/delegation.py +1 -2
- ansible_test/_internal/metadata.py +4 -0
- ansible_test/_internal/payload.py +75 -6
- ansible_test/_internal/python_requirements.py +15 -0
- ansible_test/_internal/target.py +3 -7
- ansible_test/_internal/test.py +1 -1
- ansible_test/_internal/util.py +17 -0
- ansible_test/_util/controller/sanity/mypy/ansible-test.ini +3 -0
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +92 -126
- {ansible_core-2.13.8rc1.data → ansible_core-2.13.9rc1.data}/scripts/ansible-test +0 -0
- {ansible_core-2.13.8rc1.dist-info → ansible_core-2.13.9rc1.dist-info}/COPYING +0 -0
- {ansible_core-2.13.8rc1.dist-info → ansible_core-2.13.9rc1.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.13.8rc1.dist-info → ansible_core-2.13.9rc1.dist-info}/top_level.txt +0 -0
|
@@ -661,21 +661,58 @@ class PathMapper:
|
|
|
661
661
|
|
|
662
662
|
def _classify_ansible(self, path): # type: (str) -> t.Optional[t.Dict[str, str]]
|
|
663
663
|
"""Return the classification for the given path using rules specific to Ansible."""
|
|
664
|
+
dirname = os.path.dirname(path)
|
|
665
|
+
filename = os.path.basename(path)
|
|
666
|
+
name, ext = os.path.splitext(filename)
|
|
667
|
+
|
|
668
|
+
minimal: dict[str, str] = {}
|
|
669
|
+
|
|
670
|
+
packaging = {
|
|
671
|
+
'integration': 'packaging/',
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
# Early classification that needs to occur before common classification belongs here.
|
|
675
|
+
|
|
664
676
|
if path.startswith('test/units/compat/'):
|
|
665
677
|
return {
|
|
666
678
|
'units': 'test/units/',
|
|
667
679
|
}
|
|
668
680
|
|
|
681
|
+
if dirname == '.azure-pipelines/commands':
|
|
682
|
+
test_map = {
|
|
683
|
+
'cloud.sh': 'integration:cloud/',
|
|
684
|
+
'linux.sh': 'integration:all',
|
|
685
|
+
'network.sh': 'network-integration:all',
|
|
686
|
+
'remote.sh': 'integration:all',
|
|
687
|
+
'sanity.sh': 'sanity:all',
|
|
688
|
+
'units.sh': 'units:all',
|
|
689
|
+
'windows.sh': 'windows-integration:all',
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
test_match = test_map.get(filename)
|
|
693
|
+
|
|
694
|
+
if test_match:
|
|
695
|
+
test_command, test_target = test_match.split(':')
|
|
696
|
+
|
|
697
|
+
return {
|
|
698
|
+
test_command: test_target,
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
cloud_target = f'cloud/{name}/'
|
|
702
|
+
|
|
703
|
+
if cloud_target in self.integration_targets_by_alias:
|
|
704
|
+
return {
|
|
705
|
+
'integration': cloud_target,
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
# Classification common to both ansible and collections.
|
|
709
|
+
|
|
669
710
|
result = self._classify_common(path)
|
|
670
711
|
|
|
671
712
|
if result is not None:
|
|
672
713
|
return result
|
|
673
714
|
|
|
674
|
-
|
|
675
|
-
filename = os.path.basename(path)
|
|
676
|
-
name, ext = os.path.splitext(filename)
|
|
677
|
-
|
|
678
|
-
minimal = {} # type: t.Dict[str, str]
|
|
715
|
+
# Classification here is specific to ansible, and runs after common classification.
|
|
679
716
|
|
|
680
717
|
if path.startswith('bin/'):
|
|
681
718
|
return all_tests(self.args) # broad impact, run all tests
|
|
@@ -715,6 +752,9 @@ class PathMapper:
|
|
|
715
752
|
return minimal
|
|
716
753
|
|
|
717
754
|
if path.startswith('packaging/'):
|
|
755
|
+
if path.startswith('packaging/pep517_backend/'):
|
|
756
|
+
return packaging
|
|
757
|
+
|
|
718
758
|
return minimal
|
|
719
759
|
|
|
720
760
|
if path.startswith('test/ansible_test/'):
|
|
@@ -791,39 +831,6 @@ class PathMapper:
|
|
|
791
831
|
if path.startswith('test/support/'):
|
|
792
832
|
return all_tests(self.args) # test infrastructure, run all tests
|
|
793
833
|
|
|
794
|
-
if path.startswith('test/utils/shippable/'):
|
|
795
|
-
if dirname == 'test/utils/shippable':
|
|
796
|
-
test_map = {
|
|
797
|
-
'cloud.sh': 'integration:cloud/',
|
|
798
|
-
'linux.sh': 'integration:all',
|
|
799
|
-
'network.sh': 'network-integration:all',
|
|
800
|
-
'remote.sh': 'integration:all',
|
|
801
|
-
'sanity.sh': 'sanity:all',
|
|
802
|
-
'units.sh': 'units:all',
|
|
803
|
-
'windows.sh': 'windows-integration:all',
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
test_match = test_map.get(filename)
|
|
807
|
-
|
|
808
|
-
if test_match:
|
|
809
|
-
test_command, test_target = test_match.split(':')
|
|
810
|
-
|
|
811
|
-
return {
|
|
812
|
-
test_command: test_target,
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
cloud_target = 'cloud/%s/' % name
|
|
816
|
-
|
|
817
|
-
if cloud_target in self.integration_targets_by_alias:
|
|
818
|
-
return {
|
|
819
|
-
'integration': cloud_target,
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
return all_tests(self.args) # test infrastructure, run all tests
|
|
823
|
-
|
|
824
|
-
if path.startswith('test/utils/'):
|
|
825
|
-
return minimal
|
|
826
|
-
|
|
827
834
|
if '/' not in path:
|
|
828
835
|
if path in (
|
|
829
836
|
'.gitattributes',
|
|
@@ -835,16 +842,17 @@ class PathMapper:
|
|
|
835
842
|
return minimal
|
|
836
843
|
|
|
837
844
|
if path in (
|
|
838
|
-
|
|
845
|
+
'MANIFEST.in',
|
|
846
|
+
'pyproject.toml',
|
|
847
|
+
'requirements.txt',
|
|
848
|
+
'setup.cfg',
|
|
849
|
+
'setup.py',
|
|
839
850
|
):
|
|
840
|
-
return
|
|
851
|
+
return packaging
|
|
841
852
|
|
|
842
853
|
if ext in (
|
|
843
|
-
'.in',
|
|
844
854
|
'.md',
|
|
845
855
|
'.rst',
|
|
846
|
-
'.toml',
|
|
847
|
-
'.txt',
|
|
848
856
|
):
|
|
849
857
|
return minimal
|
|
850
858
|
|
|
@@ -16,10 +16,19 @@ class Substitute:
|
|
|
16
16
|
try:
|
|
17
17
|
import argcomplete
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
try:
|
|
20
|
+
# argcomplete 3+
|
|
21
|
+
# see: https://github.com/kislyuk/argcomplete/commit/bd781cb08512b94966312377186ebc5550f46ae0
|
|
22
|
+
from argcomplete.finders import (
|
|
23
|
+
CompletionFinder,
|
|
24
|
+
default_validator,
|
|
25
|
+
)
|
|
26
|
+
except ImportError:
|
|
27
|
+
# argcomplete <3
|
|
28
|
+
from argcomplete import (
|
|
29
|
+
CompletionFinder,
|
|
30
|
+
default_validator,
|
|
31
|
+
)
|
|
23
32
|
|
|
24
33
|
warn = argcomplete.warn # pylint: disable=invalid-name
|
|
25
34
|
except ImportError:
|
|
@@ -70,7 +79,13 @@ class CompType(enum.Enum):
|
|
|
70
79
|
def register_safe_action(action_type): # type: (t.Type[argparse.Action]) -> None
|
|
71
80
|
"""Register the given action as a safe action for argcomplete to use during completion if it is not already registered."""
|
|
72
81
|
if argcomplete and action_type not in argcomplete.safe_actions:
|
|
73
|
-
argcomplete.safe_actions
|
|
82
|
+
if isinstance(argcomplete.safe_actions, set):
|
|
83
|
+
# argcomplete 3+
|
|
84
|
+
# see: https://github.com/kislyuk/argcomplete/commit/bd781cb08512b94966312377186ebc5550f46ae0
|
|
85
|
+
argcomplete.safe_actions.add(action_type)
|
|
86
|
+
else:
|
|
87
|
+
# argcomplete <3
|
|
88
|
+
argcomplete.safe_actions += (action_type,)
|
|
74
89
|
|
|
75
90
|
|
|
76
91
|
def get_comp_type(): # type: () -> t.Optional[CompType]
|
|
@@ -16,10 +16,6 @@ from ...target import (
|
|
|
16
16
|
walk_sanity_targets,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
-
from ...data import (
|
|
20
|
-
data_context,
|
|
21
|
-
)
|
|
22
|
-
|
|
23
19
|
from ..environments import (
|
|
24
20
|
CompositeActionCompletionFinder,
|
|
25
21
|
ControllerMode,
|
|
@@ -81,17 +77,6 @@ def do_sanity(
|
|
|
81
77
|
help='enable optional errors',
|
|
82
78
|
)
|
|
83
79
|
|
|
84
|
-
if data_context().content.is_ansible:
|
|
85
|
-
sanity.add_argument(
|
|
86
|
-
'--keep-git',
|
|
87
|
-
action='store_true',
|
|
88
|
-
help='transfer git related files to the remote host/container',
|
|
89
|
-
)
|
|
90
|
-
else:
|
|
91
|
-
sanity.set_defaults(
|
|
92
|
-
keep_git=False,
|
|
93
|
-
)
|
|
94
|
-
|
|
95
80
|
sanity.add_argument(
|
|
96
81
|
'--lint',
|
|
97
82
|
action='store_true',
|
|
@@ -33,6 +33,7 @@ from ...executor import (
|
|
|
33
33
|
|
|
34
34
|
from ...data import (
|
|
35
35
|
data_context,
|
|
36
|
+
PayloadConfig,
|
|
36
37
|
)
|
|
37
38
|
|
|
38
39
|
from ...host_configs import (
|
|
@@ -81,9 +82,10 @@ def combine_coverage_files(args, host_state): # type: (CoverageCombineConfig, H
|
|
|
81
82
|
|
|
82
83
|
pairs = [(path, os.path.relpath(path, data_context().content.root)) for path in exported_paths]
|
|
83
84
|
|
|
84
|
-
def coverage_callback(
|
|
85
|
+
def coverage_callback(payload_config: PayloadConfig) -> None:
|
|
85
86
|
"""Add the coverage files to the payload file list."""
|
|
86
87
|
display.info('Including %d exported coverage file(s) in payload.' % len(pairs), verbosity=1)
|
|
88
|
+
files = payload_config.files
|
|
87
89
|
files.extend(pairs)
|
|
88
90
|
|
|
89
91
|
data_context().register_payload_callback(coverage_callback)
|
|
@@ -89,6 +89,7 @@ from .cloud import (
|
|
|
89
89
|
|
|
90
90
|
from ...data import (
|
|
91
91
|
data_context,
|
|
92
|
+
PayloadConfig,
|
|
92
93
|
)
|
|
93
94
|
|
|
94
95
|
from ...host_configs import (
|
|
@@ -213,11 +214,13 @@ def delegate_inventory(args, inventory_path_src): # type: (IntegrationConfig, s
|
|
|
213
214
|
if isinstance(args, PosixIntegrationConfig):
|
|
214
215
|
return
|
|
215
216
|
|
|
216
|
-
def inventory_callback(
|
|
217
|
+
def inventory_callback(payload_config: PayloadConfig) -> None:
|
|
217
218
|
"""
|
|
218
219
|
Add the inventory file to the payload file list.
|
|
219
220
|
This will preserve the file during delegation even if it is ignored or is outside the content and install roots.
|
|
220
221
|
"""
|
|
222
|
+
files = payload_config.files
|
|
223
|
+
|
|
221
224
|
inventory_path = get_inventory_relative_path(args)
|
|
222
225
|
inventory_tuple = inventory_path_src, inventory_path
|
|
223
226
|
|
|
@@ -940,11 +943,12 @@ def command_integration_filter(args, # type: TIntegrationConfig
|
|
|
940
943
|
vars_file_src = os.path.join(data_context().content.root, data_context().content.integration_vars_path)
|
|
941
944
|
|
|
942
945
|
if os.path.exists(vars_file_src):
|
|
943
|
-
def integration_config_callback(
|
|
946
|
+
def integration_config_callback(payload_config: PayloadConfig) -> None:
|
|
944
947
|
"""
|
|
945
948
|
Add the integration config vars file to the payload file list.
|
|
946
949
|
This will preserve the file during delegation even if the file is ignored by source control.
|
|
947
950
|
"""
|
|
951
|
+
files = payload_config.files
|
|
948
952
|
files.append((vars_file_src, data_context().content.integration_vars_path))
|
|
949
953
|
|
|
950
954
|
data_context().register_payload_callback(integration_config_callback)
|
|
@@ -47,6 +47,7 @@ from ....ci import (
|
|
|
47
47
|
|
|
48
48
|
from ....data import (
|
|
49
49
|
data_context,
|
|
50
|
+
PayloadConfig,
|
|
50
51
|
)
|
|
51
52
|
|
|
52
53
|
from ....docker_util import (
|
|
@@ -189,13 +190,14 @@ class CloudBase(metaclass=abc.ABCMeta):
|
|
|
189
190
|
self.args = args
|
|
190
191
|
self.platform = self.__module__.rsplit('.', 1)[-1]
|
|
191
192
|
|
|
192
|
-
def config_callback(
|
|
193
|
+
def config_callback(payload_config: PayloadConfig) -> None:
|
|
193
194
|
"""Add the config file to the payload file list."""
|
|
194
195
|
if self.platform not in self.args.metadata.cloud_config:
|
|
195
196
|
return # platform was initialized, but not used -- such as being skipped due to all tests being disabled
|
|
196
197
|
|
|
197
198
|
if self._get_cloud_config(self._CONFIG_PATH, ''):
|
|
198
199
|
pair = (self.config_path, os.path.relpath(self.config_path, data_context().content.root))
|
|
200
|
+
files = payload_config.files
|
|
199
201
|
|
|
200
202
|
if pair not in files:
|
|
201
203
|
display.info('Including %s config: %s -> %s' % (self.platform, pair[0], pair[1]), verbosity=3)
|
|
@@ -159,6 +159,10 @@ def command_sanity(args): # type: (SanityConfig) -> None
|
|
|
159
159
|
if args.skip_test:
|
|
160
160
|
tests = [target for target in tests if target.name not in args.skip_test]
|
|
161
161
|
|
|
162
|
+
if not args.host_path:
|
|
163
|
+
for test in tests:
|
|
164
|
+
test.origin_hook(args)
|
|
165
|
+
|
|
162
166
|
targets_use_pypi = any(isinstance(test, SanityMultipleVersion) and test.needs_pypi for test in tests) and not args.list_tests
|
|
163
167
|
host_state = prepare_profiles(args, targets_use_pypi=targets_use_pypi) # sanity
|
|
164
168
|
|
|
@@ -756,6 +760,9 @@ class SanityTest(metaclass=abc.ABCMeta):
|
|
|
756
760
|
"""A tuple of supported Python versions or None if the test does not depend on specific Python versions."""
|
|
757
761
|
return CONTROLLER_PYTHON_VERSIONS
|
|
758
762
|
|
|
763
|
+
def origin_hook(self, args: SanityConfig) -> None:
|
|
764
|
+
"""This method is called on the origin, before the test runs or delegation occurs."""
|
|
765
|
+
|
|
759
766
|
def filter_targets(self, targets): # type: (t.List[TestTarget]) -> t.List[TestTarget] # pylint: disable=unused-argument
|
|
760
767
|
"""Return the given list of test targets, filtered to include only those relevant for the test."""
|
|
761
768
|
if self.no_targets:
|
|
@@ -17,6 +17,10 @@ from . import (
|
|
|
17
17
|
SANITY_ROOT,
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
+
from ...io import (
|
|
21
|
+
make_dirs,
|
|
22
|
+
)
|
|
23
|
+
|
|
20
24
|
from ...test import (
|
|
21
25
|
TestResult,
|
|
22
26
|
)
|
|
@@ -40,6 +44,7 @@ from ...ansible_util import (
|
|
|
40
44
|
get_collection_detail,
|
|
41
45
|
CollectionDetail,
|
|
42
46
|
CollectionDetailError,
|
|
47
|
+
ResultType,
|
|
43
48
|
)
|
|
44
49
|
|
|
45
50
|
from ...config import (
|
|
@@ -245,6 +250,12 @@ class PylintTest(SanitySingleVersion):
|
|
|
245
250
|
# expose plugin paths for use in custom plugins
|
|
246
251
|
env.update(dict(('ANSIBLE_TEST_%s_PATH' % k.upper(), os.path.abspath(v) + os.path.sep) for k, v in data_context().content.plugin_paths.items()))
|
|
247
252
|
|
|
253
|
+
# Set PYLINTHOME to prevent pylint from checking for an obsolete directory, which can result in a test failure due to stderr output.
|
|
254
|
+
# See: https://github.com/PyCQA/pylint/blob/e6c6bf5dfd61511d64779f54264b27a368c43100/pylint/constants.py#L148
|
|
255
|
+
pylint_home = os.path.join(ResultType.TMP.path, 'pylint')
|
|
256
|
+
make_dirs(pylint_home)
|
|
257
|
+
env.update(PYLINTHOME=pylint_home)
|
|
258
|
+
|
|
248
259
|
if paths:
|
|
249
260
|
display.info('Checking %d file(s) in context "%s" with config: %s' % (len(paths), context, rcfile), verbosity=1)
|
|
250
261
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"""Sanity test using validate-modules."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import atexit
|
|
4
5
|
import collections
|
|
6
|
+
import contextlib
|
|
5
7
|
import json
|
|
6
8
|
import os
|
|
9
|
+
import tarfile
|
|
7
10
|
import typing as t
|
|
8
11
|
|
|
9
12
|
from . import (
|
|
@@ -16,6 +19,10 @@ from . import (
|
|
|
16
19
|
SANITY_ROOT,
|
|
17
20
|
)
|
|
18
21
|
|
|
22
|
+
from ...io import (
|
|
23
|
+
make_dirs,
|
|
24
|
+
)
|
|
25
|
+
|
|
19
26
|
from ...test import (
|
|
20
27
|
TestResult,
|
|
21
28
|
)
|
|
@@ -30,7 +37,9 @@ from ...util import (
|
|
|
30
37
|
)
|
|
31
38
|
|
|
32
39
|
from ...util_common import (
|
|
40
|
+
process_scoped_temporary_directory,
|
|
33
41
|
run_command,
|
|
42
|
+
ResultType,
|
|
34
43
|
)
|
|
35
44
|
|
|
36
45
|
from ...ansible_util import (
|
|
@@ -49,12 +58,21 @@ from ...ci import (
|
|
|
49
58
|
|
|
50
59
|
from ...data import (
|
|
51
60
|
data_context,
|
|
61
|
+
PayloadConfig,
|
|
52
62
|
)
|
|
53
63
|
|
|
54
64
|
from ...host_configs import (
|
|
55
65
|
PythonConfig,
|
|
56
66
|
)
|
|
57
67
|
|
|
68
|
+
from ...git import (
|
|
69
|
+
Git,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
from ...provider.source import (
|
|
73
|
+
SourceProvider as GitSourceProvider,
|
|
74
|
+
)
|
|
75
|
+
|
|
58
76
|
|
|
59
77
|
class ValidateModulesTest(SanitySingleVersion):
|
|
60
78
|
"""Sanity test using validate-modules."""
|
|
@@ -130,14 +148,17 @@ class ValidateModulesTest(SanitySingleVersion):
|
|
|
130
148
|
except CollectionDetailError as ex:
|
|
131
149
|
display.warning('Skipping validate-modules collection version checks since collection detail loading failed: %s' % ex.reason)
|
|
132
150
|
else:
|
|
133
|
-
|
|
151
|
+
path = self.get_archive_path(args)
|
|
152
|
+
|
|
153
|
+
if os.path.exists(path):
|
|
154
|
+
temp_dir = process_scoped_temporary_directory(args)
|
|
155
|
+
|
|
156
|
+
with tarfile.open(path) as file:
|
|
157
|
+
file.extractall(temp_dir)
|
|
134
158
|
|
|
135
|
-
if base_branch:
|
|
136
159
|
cmd.extend([
|
|
137
|
-
'--
|
|
160
|
+
'--original-plugins', temp_dir,
|
|
138
161
|
])
|
|
139
|
-
else:
|
|
140
|
-
display.warning('Cannot perform module comparison against the base branch because the base branch was not detected.')
|
|
141
162
|
|
|
142
163
|
errors = []
|
|
143
164
|
|
|
@@ -188,3 +209,43 @@ class ValidateModulesTest(SanitySingleVersion):
|
|
|
188
209
|
return SanityFailure(self.name, messages=all_errors)
|
|
189
210
|
|
|
190
211
|
return SanitySuccess(self.name)
|
|
212
|
+
|
|
213
|
+
def origin_hook(self, args: SanityConfig) -> None:
|
|
214
|
+
"""This method is called on the origin, before the test runs or delegation occurs."""
|
|
215
|
+
if not data_context().content.is_ansible:
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
if not isinstance(data_context().source_provider, GitSourceProvider):
|
|
219
|
+
display.warning('The validate-modules sanity test cannot compare against the base commit because git is not being used.')
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
base_commit = args.base_branch or get_ci_provider().get_base_commit(args)
|
|
223
|
+
|
|
224
|
+
if not base_commit:
|
|
225
|
+
display.warning('The validate-modules sanity test cannot compare against the base commit because it was not detected.')
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
path = self.get_archive_path(args)
|
|
229
|
+
|
|
230
|
+
def cleanup() -> None:
|
|
231
|
+
"""Cleanup callback called when the process exits."""
|
|
232
|
+
with contextlib.suppress(FileNotFoundError):
|
|
233
|
+
os.unlink(path)
|
|
234
|
+
|
|
235
|
+
def git_callback(payload_config: PayloadConfig) -> None:
|
|
236
|
+
"""Include the previous plugin content archive in the payload."""
|
|
237
|
+
files = payload_config.files
|
|
238
|
+
files.append((path, os.path.relpath(path, data_context().content.root)))
|
|
239
|
+
|
|
240
|
+
atexit.register(cleanup)
|
|
241
|
+
data_context().register_payload_callback(git_callback)
|
|
242
|
+
|
|
243
|
+
make_dirs(os.path.dirname(path))
|
|
244
|
+
|
|
245
|
+
git = Git()
|
|
246
|
+
git.run_git(['archive', '--output', path, base_commit, 'lib/ansible/modules/', 'lib/ansible/plugins/'])
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
249
|
+
def get_archive_path(args: SanityConfig) -> str:
|
|
250
|
+
"""Return the path to the original plugin content archive."""
|
|
251
|
+
return os.path.join(ResultType.TMP.path, f'validate-modules-{args.metadata.session_id}.tar')
|
ansible_test/_internal/config.py
CHANGED
|
@@ -24,6 +24,7 @@ from .metadata import (
|
|
|
24
24
|
|
|
25
25
|
from .data import (
|
|
26
26
|
data_context,
|
|
27
|
+
PayloadConfig,
|
|
27
28
|
)
|
|
28
29
|
|
|
29
30
|
from .host_configs import (
|
|
@@ -114,7 +115,7 @@ class EnvironmentConfig(CommonConfig):
|
|
|
114
115
|
self.dev_systemd_debug: bool = args.dev_systemd_debug
|
|
115
116
|
self.dev_probe_cgroups: t.Optional[str] = args.dev_probe_cgroups
|
|
116
117
|
|
|
117
|
-
def host_callback(
|
|
118
|
+
def host_callback(payload_config: PayloadConfig) -> None:
|
|
118
119
|
"""Add the host files to the payload file list."""
|
|
119
120
|
config = self
|
|
120
121
|
|
|
@@ -123,6 +124,8 @@ class EnvironmentConfig(CommonConfig):
|
|
|
123
124
|
state_path = os.path.join(config.host_path, 'state.dat')
|
|
124
125
|
config_path = os.path.join(config.host_path, 'config.dat')
|
|
125
126
|
|
|
127
|
+
files = payload_config.files
|
|
128
|
+
|
|
126
129
|
files.append((os.path.abspath(settings_path), settings_path))
|
|
127
130
|
files.append((os.path.abspath(state_path), state_path))
|
|
128
131
|
files.append((os.path.abspath(config_path), config_path))
|
|
@@ -225,9 +228,10 @@ class TestConfig(EnvironmentConfig):
|
|
|
225
228
|
if self.coverage_check:
|
|
226
229
|
self.coverage = True
|
|
227
230
|
|
|
228
|
-
def metadata_callback(
|
|
231
|
+
def metadata_callback(payload_config: PayloadConfig) -> None:
|
|
229
232
|
"""Add the metadata file to the payload file list."""
|
|
230
233
|
config = self
|
|
234
|
+
files = payload_config.files
|
|
231
235
|
|
|
232
236
|
if config.metadata_path:
|
|
233
237
|
files.append((os.path.abspath(config.metadata_path), config.metadata_path))
|
|
@@ -258,20 +262,10 @@ class SanityConfig(TestConfig):
|
|
|
258
262
|
self.list_tests = args.list_tests # type: bool
|
|
259
263
|
self.allow_disabled = args.allow_disabled # type: bool
|
|
260
264
|
self.enable_optional_errors = args.enable_optional_errors # type: bool
|
|
261
|
-
self.keep_git = args.keep_git # type: bool
|
|
262
265
|
self.prime_venvs = args.prime_venvs # type: bool
|
|
263
266
|
|
|
264
267
|
self.display_stderr = self.lint or self.list_tests
|
|
265
268
|
|
|
266
|
-
if self.keep_git:
|
|
267
|
-
def git_callback(files): # type: (t.List[t.Tuple[str, str]]) -> None
|
|
268
|
-
"""Add files from the content root .git directory to the payload file list."""
|
|
269
|
-
for dirpath, _dirnames, filenames in os.walk(os.path.join(data_context().content.root, '.git')):
|
|
270
|
-
paths = [os.path.join(dirpath, filename) for filename in filenames]
|
|
271
|
-
files.extend((path, os.path.relpath(path, data_context().content.root)) for path in paths)
|
|
272
|
-
|
|
273
|
-
data_context().register_payload_callback(git_callback)
|
|
274
|
-
|
|
275
269
|
|
|
276
270
|
class IntegrationConfig(TestConfig):
|
|
277
271
|
"""Configuration for the integration command."""
|
|
@@ -6,6 +6,7 @@ import dataclasses
|
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
8
|
import re
|
|
9
|
+
import stat
|
|
9
10
|
import traceback
|
|
10
11
|
import uuid
|
|
11
12
|
import errno
|
|
@@ -47,6 +48,7 @@ from .ci import (
|
|
|
47
48
|
|
|
48
49
|
from .data import (
|
|
49
50
|
data_context,
|
|
51
|
+
PayloadConfig,
|
|
50
52
|
)
|
|
51
53
|
|
|
52
54
|
|
|
@@ -447,14 +449,19 @@ class SshKey:
|
|
|
447
449
|
key, pub = key_pair
|
|
448
450
|
key_dst, pub_dst = self.get_in_tree_key_pair_paths()
|
|
449
451
|
|
|
450
|
-
def ssh_key_callback(
|
|
452
|
+
def ssh_key_callback(payload_config: PayloadConfig) -> None:
|
|
451
453
|
"""
|
|
452
454
|
Add the SSH keys to the payload file list.
|
|
453
455
|
They are either outside the source tree or in the cache dir which is ignored by default.
|
|
454
456
|
"""
|
|
457
|
+
files = payload_config.files
|
|
458
|
+
permissions = payload_config.permissions
|
|
459
|
+
|
|
455
460
|
files.append((key, os.path.relpath(key_dst, data_context().content.root)))
|
|
456
461
|
files.append((pub, os.path.relpath(pub_dst, data_context().content.root)))
|
|
457
462
|
|
|
463
|
+
permissions[os.path.relpath(key_dst, data_context().content.root)] = stat.S_IRUSR | stat.S_IWUSR
|
|
464
|
+
|
|
458
465
|
data_context().register_payload_callback(ssh_key_callback)
|
|
459
466
|
|
|
460
467
|
self.key, self.pub = key, pub
|
ansible_test/_internal/data.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Context information for the current invocation of ansible-test."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import collections.abc as c
|
|
4
5
|
import dataclasses
|
|
5
6
|
import os
|
|
6
7
|
import typing as t
|
|
@@ -49,6 +50,13 @@ from .provider.layout.unsupported import (
|
|
|
49
50
|
)
|
|
50
51
|
|
|
51
52
|
|
|
53
|
+
@dataclasses.dataclass(frozen=True)
|
|
54
|
+
class PayloadConfig:
|
|
55
|
+
"""Configuration required to build a source tree payload for delegation."""
|
|
56
|
+
files: list[tuple[str, str]]
|
|
57
|
+
permissions: dict[str, int]
|
|
58
|
+
|
|
59
|
+
|
|
52
60
|
class DataContext:
|
|
53
61
|
"""Data context providing details about the current execution environment for ansible-test."""
|
|
54
62
|
def __init__(self):
|
|
@@ -62,16 +70,17 @@ class DataContext:
|
|
|
62
70
|
self.__source_providers = source_providers
|
|
63
71
|
self.__ansible_source = None # type: t.Optional[t.Tuple[t.Tuple[str, str], ...]]
|
|
64
72
|
|
|
65
|
-
self.payload_callbacks
|
|
73
|
+
self.payload_callbacks: list[c.Callable[[PayloadConfig], None]] = []
|
|
66
74
|
|
|
67
75
|
if content_path:
|
|
68
|
-
content = self.__create_content_layout(layout_providers, source_providers, content_path, False)
|
|
76
|
+
content, source_provider = self.__create_content_layout(layout_providers, source_providers, content_path, False)
|
|
69
77
|
elif ANSIBLE_SOURCE_ROOT and is_subdir(current_path, ANSIBLE_SOURCE_ROOT):
|
|
70
|
-
content = self.__create_content_layout(layout_providers, source_providers, ANSIBLE_SOURCE_ROOT, False)
|
|
78
|
+
content, source_provider = self.__create_content_layout(layout_providers, source_providers, ANSIBLE_SOURCE_ROOT, False)
|
|
71
79
|
else:
|
|
72
|
-
content = self.__create_content_layout(layout_providers, source_providers, current_path, True)
|
|
80
|
+
content, source_provider = self.__create_content_layout(layout_providers, source_providers, current_path, True)
|
|
73
81
|
|
|
74
82
|
self.content = content # type: ContentLayout
|
|
83
|
+
self.source_provider = source_provider
|
|
75
84
|
|
|
76
85
|
def create_collection_layouts(self): # type: () -> t.List[ContentLayout]
|
|
77
86
|
"""
|
|
@@ -99,7 +108,7 @@ class DataContext:
|
|
|
99
108
|
if collection_path == os.path.join(collection.root, collection.directory):
|
|
100
109
|
collection_layout = layout
|
|
101
110
|
else:
|
|
102
|
-
collection_layout = self.__create_content_layout(self.__layout_providers, self.__source_providers, collection_path, False)
|
|
111
|
+
collection_layout = self.__create_content_layout(self.__layout_providers, self.__source_providers, collection_path, False)[0]
|
|
103
112
|
|
|
104
113
|
file_count = len(collection_layout.all_files())
|
|
105
114
|
|
|
@@ -116,7 +125,7 @@ class DataContext:
|
|
|
116
125
|
source_providers, # type: t.List[t.Type[SourceProvider]]
|
|
117
126
|
root, # type: str
|
|
118
127
|
walk, # type: bool
|
|
119
|
-
)
|
|
128
|
+
) -> t.Tuple[ContentLayout, SourceProvider]:
|
|
120
129
|
"""Create a content layout using the given providers and root path."""
|
|
121
130
|
try:
|
|
122
131
|
layout_provider = find_path_provider(LayoutProvider, layout_providers, root, walk)
|
|
@@ -137,7 +146,7 @@ class DataContext:
|
|
|
137
146
|
|
|
138
147
|
layout = layout_provider.create(layout_provider.root, source_provider.get_paths(layout_provider.root))
|
|
139
148
|
|
|
140
|
-
return layout
|
|
149
|
+
return layout, source_provider
|
|
141
150
|
|
|
142
151
|
def __create_ansible_source(self):
|
|
143
152
|
"""Return a tuple of Ansible source files with both absolute and relative paths."""
|
|
@@ -172,7 +181,7 @@ class DataContext:
|
|
|
172
181
|
|
|
173
182
|
return self.__ansible_source
|
|
174
183
|
|
|
175
|
-
def register_payload_callback(self, callback
|
|
184
|
+
def register_payload_callback(self, callback: c.Callable[[PayloadConfig], None]) -> None:
|
|
176
185
|
"""Register the given payload callback."""
|
|
177
186
|
self.payload_callbacks.append(callback)
|
|
178
187
|
|
|
@@ -172,7 +172,6 @@ def delegate_command(args, host_state, exclude, require): # type: (EnvironmentC
|
|
|
172
172
|
con.run(['mkdir', '-p'] + writable_dirs, capture=True)
|
|
173
173
|
con.run(['chmod', '777'] + writable_dirs, capture=True)
|
|
174
174
|
con.run(['chmod', '755', working_directory], capture=True)
|
|
175
|
-
con.run(['chmod', '644', os.path.join(content_root, args.metadata_path)], capture=True)
|
|
176
175
|
con.run(['useradd', pytest_user, '--create-home'], capture=True)
|
|
177
176
|
|
|
178
177
|
con.run(insert_options(command, options + ['--requirements-mode', 'only']), capture=False)
|
|
@@ -339,7 +338,7 @@ def filter_options(
|
|
|
339
338
|
('--metadata', 1, args.metadata_path),
|
|
340
339
|
('--exclude', 1, exclude),
|
|
341
340
|
('--require', 1, require),
|
|
342
|
-
('--base-branch', 1,
|
|
341
|
+
('--base-branch', 1, False),
|
|
343
342
|
])
|
|
344
343
|
|
|
345
344
|
pass_through_args: list[str] = []
|