ansible-core 2.19.0b6__py3-none-any.whl → 2.19.0rc1__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/_json/__init__.py +31 -20
- ansible/_internal/_json/_profiles/_legacy.py +1 -1
- ansible/_internal/_templating/_jinja_bits.py +46 -14
- ansible/_internal/_templating/_jinja_common.py +1 -1
- ansible/_internal/_templating/_jinja_plugins.py +5 -2
- ansible/_internal/_templating/_utils.py +2 -1
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/dump_object.py +9 -0
- ansible/cli/__init__.py +2 -2
- ansible/cli/_ssh_askpass.py +37 -30
- ansible/cli/adhoc.py +6 -3
- ansible/cli/console.py +2 -2
- ansible/cli/doc.py +2 -2
- ansible/config/base.yml +9 -6
- ansible/executor/module_common.py +9 -6
- ansible/executor/powershell/psrp_put_file.ps1 +1 -1
- ansible/executor/task_executor.py +2 -2
- ansible/executor/task_queue_manager.py +34 -70
- ansible/executor/task_result.py +1 -1
- ansible/galaxy/api.py +2 -2
- ansible/galaxy/collection/concrete_artifact_manager.py +2 -2
- ansible/galaxy/dependency_resolution/providers.py +3 -3
- ansible/inventory/group.py +6 -1
- ansible/inventory/host.py +6 -1
- ansible/module_utils/_internal/_datatag/__init__.py +6 -1
- ansible/module_utils/_internal/_deprecator.py +12 -1
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +14 -16
- ansible/module_utils/common/yaml.py +1 -1
- ansible/module_utils/csharp/Ansible.Basic.cs +1 -1
- ansible/module_utils/csharp/Ansible.Privilege.cs +2 -2
- ansible/module_utils/facts/hardware/base.py +1 -1
- ansible/module_utils/facts/other/facter.py +1 -1
- ansible/module_utils/facts/system/distribution.py +2 -2
- ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1 +1 -1
- ansible/module_utils/urls.py +1 -1
- ansible/modules/apt.py +9 -3
- ansible/modules/assemble.py +5 -3
- ansible/modules/expect.py +5 -5
- ansible/modules/hostname.py +2 -2
- ansible/modules/pip.py +9 -11
- ansible/modules/raw.py +2 -2
- ansible/modules/stat.py +1 -1
- ansible/modules/systemd.py +1 -1
- ansible/modules/systemd_service.py +1 -1
- ansible/modules/wait_for.py +10 -3
- ansible/parsing/mod_args.py +38 -20
- ansible/parsing/vault/__init__.py +3 -3
- ansible/playbook/base.py +0 -2
- ansible/playbook/helpers.py +1 -1
- ansible/playbook/playbook_include.py +23 -57
- ansible/playbook/role/__init__.py +40 -23
- ansible/plugins/action/__init__.py +2 -2
- ansible/plugins/action/assemble.py +2 -1
- ansible/plugins/action/assert.py +2 -2
- ansible/plugins/action/script.py +5 -4
- ansible/plugins/action/template.py +1 -1
- ansible/plugins/callback/__init__.py +77 -87
- ansible/plugins/callback/default.py +0 -3
- ansible/plugins/callback/junit.py +0 -6
- ansible/plugins/connection/ssh.py +13 -6
- ansible/plugins/filter/pow.yml +1 -1
- ansible/plugins/filter/root.yml +1 -1
- ansible/plugins/filter/strftime.yml +3 -3
- ansible/plugins/filter/to_uuid.yml +1 -1
- ansible/plugins/inventory/script.py +1 -1
- ansible/plugins/loader.py +5 -0
- ansible/plugins/lookup/password.py +4 -6
- ansible/release.py +1 -1
- ansible/utils/display.py +16 -26
- ansible/utils/path.py +1 -1
- ansible/utils/vars.py +6 -2
- ansible/vars/manager.py +6 -3
- ansible/vars/reserved.py +6 -4
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0rc1.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0rc1.dist-info}/RECORD +111 -109
- ansible_test/_internal/__init__.py +5 -0
- ansible_test/_internal/ansible_util.py +1 -1
- ansible_test/_internal/classification/python.py +6 -0
- ansible_test/_internal/cli/commands/__init__.py +0 -5
- ansible_test/_internal/cli/environments.py +51 -5
- ansible_test/_internal/commands/coverage/__init__.py +1 -1
- ansible_test/_internal/commands/integration/__init__.py +18 -5
- ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
- ansible_test/_internal/commands/sanity/__init__.py +3 -1
- ansible_test/_internal/commands/sanity/integration_aliases.py +11 -0
- ansible_test/_internal/commands/shell/__init__.py +43 -4
- ansible_test/_internal/commands/units/__init__.py +4 -1
- ansible_test/_internal/config.py +21 -13
- ansible_test/_internal/debugging.py +166 -0
- ansible_test/_internal/delegation.py +21 -13
- ansible_test/_internal/host_profiles.py +197 -6
- ansible_test/_internal/inventory.py +4 -0
- ansible_test/_internal/metadata.py +94 -4
- ansible_test/_internal/processes.py +80 -0
- ansible_test/_internal/python_requirements.py +27 -0
- ansible_test/_internal/target.py +8 -0
- ansible_test/_internal/util_common.py +13 -3
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +2 -1
- ansible_test/_util/target/injector/python.py +8 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0rc1.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0rc1.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0rc1.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0rc1.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0rc1.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0rc1.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0rc1.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0rc1.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0rc1.dist-info}/top_level.txt +0 -0
ansible/modules/assemble.py
CHANGED
@@ -80,7 +80,7 @@ attributes:
|
|
80
80
|
bypass_host_loop:
|
81
81
|
support: none
|
82
82
|
check_mode:
|
83
|
-
support:
|
83
|
+
support: full
|
84
84
|
diff_mode:
|
85
85
|
support: full
|
86
86
|
platform:
|
@@ -212,6 +212,7 @@ def main():
|
|
212
212
|
decrypt=dict(type='bool', default=True),
|
213
213
|
),
|
214
214
|
add_file_common_args=True,
|
215
|
+
supports_check_mode=True,
|
215
216
|
)
|
216
217
|
|
217
218
|
changed = False
|
@@ -266,12 +267,13 @@ def main():
|
|
266
267
|
if backup and dest_hash is not None:
|
267
268
|
result['backup_file'] = module.backup_local(dest)
|
268
269
|
|
269
|
-
|
270
|
+
if not module.check_mode:
|
271
|
+
module.atomic_move(path, dest, unsafe_writes=module.params['unsafe_writes'])
|
270
272
|
changed = True
|
271
273
|
|
272
274
|
cleanup(module, path, result)
|
273
275
|
|
274
|
-
# handle file permissions
|
276
|
+
# handle file permissions (check mode aware)
|
275
277
|
file_args = module.load_file_common_arguments(module.params)
|
276
278
|
result['changed'] = module.set_fs_attributes_if_different(file_args, changed)
|
277
279
|
|
ansible/modules/expect.py
CHANGED
@@ -218,7 +218,7 @@ def main():
|
|
218
218
|
rc=0
|
219
219
|
)
|
220
220
|
|
221
|
-
|
221
|
+
start_date = datetime.datetime.now()
|
222
222
|
|
223
223
|
try:
|
224
224
|
try:
|
@@ -246,8 +246,8 @@ def main():
|
|
246
246
|
except pexpect.ExceptionPexpect as e:
|
247
247
|
module.fail_json(msg='%s' % to_native(e))
|
248
248
|
|
249
|
-
|
250
|
-
delta =
|
249
|
+
end_date = datetime.datetime.now()
|
250
|
+
delta = end_date - start_date
|
251
251
|
|
252
252
|
if b_out is None:
|
253
253
|
b_out = b''
|
@@ -256,8 +256,8 @@ def main():
|
|
256
256
|
cmd=args,
|
257
257
|
stdout=to_native(b_out).rstrip('\r\n'),
|
258
258
|
rc=rc,
|
259
|
-
start=str(
|
260
|
-
end=str(
|
259
|
+
start=str(start_date),
|
260
|
+
end=str(end_date),
|
261
261
|
delta=str(delta),
|
262
262
|
changed=True,
|
263
263
|
)
|
ansible/modules/hostname.py
CHANGED
@@ -608,8 +608,8 @@ class Hostname(object):
|
|
608
608
|
self.use = module.params['use']
|
609
609
|
|
610
610
|
if self.use is not None:
|
611
|
-
|
612
|
-
self.strategy =
|
611
|
+
strategy = globals()['%sStrategy' % STRATS[self.use]]
|
612
|
+
self.strategy = strategy(module)
|
613
613
|
elif platform.system() == 'Linux' and ServiceMgrFactCollector.is_systemd_managed(module):
|
614
614
|
# This is Linux and systemd is active
|
615
615
|
self.strategy = SystemdStrategy(module)
|
ansible/modules/pip.py
CHANGED
@@ -60,7 +60,7 @@ options:
|
|
60
60
|
virtualenv_python:
|
61
61
|
description:
|
62
62
|
- The Python executable used for creating the virtual environment.
|
63
|
-
For example V(python3.
|
63
|
+
For example V(python3.13). When not specified, the
|
64
64
|
Python version used to run the ansible module is used. This parameter
|
65
65
|
should not be used when O(virtualenv_command) is using V(pyvenv) or
|
66
66
|
the C(-m venv) module.
|
@@ -93,8 +93,8 @@ options:
|
|
93
93
|
description:
|
94
94
|
- The explicit executable or pathname for the C(pip) executable,
|
95
95
|
if different from the Ansible Python interpreter. For
|
96
|
-
example V(pip3.
|
97
|
-
in the system and you want to run pip for the Python 3.
|
96
|
+
example V(pip3.13), if there are multiple Python installations
|
97
|
+
in the system and you want to run pip for the Python 3.13 installation.
|
98
98
|
- Mutually exclusive with O(virtualenv) (added in 2.1).
|
99
99
|
- Does not affect the Ansible Python interpreter.
|
100
100
|
- The C(setuptools) package must be installed for both the Ansible Python interpreter
|
@@ -134,7 +134,7 @@ notes:
|
|
134
134
|
the virtualenv needs to be created.
|
135
135
|
- Although it executes using the Ansible Python interpreter, the pip module shells out to
|
136
136
|
run the actual pip command, so it can use any pip version you specify with O(executable).
|
137
|
-
By default, it uses the pip version for the Ansible Python interpreter.
|
137
|
+
By default, it uses the pip version for the Ansible Python interpreter.
|
138
138
|
- The interpreter used by Ansible
|
139
139
|
(see R(ansible_python_interpreter, ansible_python_interpreter))
|
140
140
|
requires the setuptools package, regardless of the version of pip set with
|
@@ -197,11 +197,11 @@ EXAMPLES = """
|
|
197
197
|
virtualenv: /my_app/venv
|
198
198
|
virtualenv_site_packages: yes
|
199
199
|
|
200
|
-
- name: Install bottle into the specified (virtualenv), using Python
|
200
|
+
- name: Install bottle into the specified (virtualenv), using Python 3.13
|
201
201
|
ansible.builtin.pip:
|
202
202
|
name: bottle
|
203
203
|
virtualenv: /my_app/venv
|
204
|
-
virtualenv_command: virtualenv-
|
204
|
+
virtualenv_command: virtualenv-3.13
|
205
205
|
|
206
206
|
- name: Install bottle within a user home directory
|
207
207
|
ansible.builtin.pip:
|
@@ -227,10 +227,10 @@ EXAMPLES = """
|
|
227
227
|
requirements: /my_app/requirements.txt
|
228
228
|
extra_args: "--no-index --find-links=file:///my_downloaded_packages_dir"
|
229
229
|
|
230
|
-
- name: Install bottle for Python 3.
|
230
|
+
- name: Install bottle for Python 3.13 specifically, using the 'pip3.13' executable
|
231
231
|
ansible.builtin.pip:
|
232
232
|
name: bottle
|
233
|
-
executable: pip3.
|
233
|
+
executable: pip3.13
|
234
234
|
|
235
235
|
- name: Install bottle, forcing reinstallation if it's already installed
|
236
236
|
ansible.builtin.pip:
|
@@ -460,9 +460,7 @@ def _get_pip(module, env=None, executable=None):
|
|
460
460
|
candidate_pip_basenames = (executable,)
|
461
461
|
elif executable is None and env is None and _have_pip_module():
|
462
462
|
# If no executable or virtualenv were specified, use the pip module for the current Python interpreter if available.
|
463
|
-
|
464
|
-
# Without it Python 2.6 gives the following error: pip is a package and cannot be directly executed
|
465
|
-
pip = [sys.executable, '-m', 'pip.__main__']
|
463
|
+
pip = [sys.executable, '-m', 'pip']
|
466
464
|
|
467
465
|
if pip is None:
|
468
466
|
if env is None:
|
ansible/modules/raw.py
CHANGED
@@ -73,8 +73,8 @@ author:
|
|
73
73
|
"""
|
74
74
|
|
75
75
|
EXAMPLES = r"""
|
76
|
-
- name: Bootstrap a host without
|
77
|
-
ansible.builtin.raw: dnf install -y
|
76
|
+
- name: Bootstrap a host without Python installed
|
77
|
+
ansible.builtin.raw: dnf install -y python3 python3-libdnf
|
78
78
|
|
79
79
|
- name: Run a command that uses non-posix shell-isms (in this example /bin/sh doesn't handle redirection and wildcards together but bash does)
|
80
80
|
ansible.builtin.raw: cat < /tmp/*txt
|
ansible/modules/stat.py
CHANGED
@@ -408,7 +408,7 @@ def format_output(module, path, st):
|
|
408
408
|
('st_blksize', 'block_size'),
|
409
409
|
('st_rdev', 'device_type'),
|
410
410
|
('st_flags', 'flags'),
|
411
|
-
# Some
|
411
|
+
# Some Berkeley based
|
412
412
|
('st_gen', 'generation'),
|
413
413
|
('st_birthtime', 'birthtime'),
|
414
414
|
# RISCOS
|
ansible/modules/systemd.py
CHANGED
@@ -34,7 +34,7 @@ options:
|
|
34
34
|
choices: [ reloaded, restarted, started, stopped ]
|
35
35
|
enabled:
|
36
36
|
description:
|
37
|
-
- Whether the unit should start on boot. At least one of O(state)
|
37
|
+
- Whether the unit should start on boot. At least one of O(state) or O(enabled) are required.
|
38
38
|
- If set, requires O(name).
|
39
39
|
type: bool
|
40
40
|
force:
|
@@ -34,7 +34,7 @@ options:
|
|
34
34
|
choices: [ reloaded, restarted, started, stopped ]
|
35
35
|
enabled:
|
36
36
|
description:
|
37
|
-
- Whether the unit should start on boot. At least one of O(state)
|
37
|
+
- Whether the unit should start on boot. At least one of O(state) or O(enabled) are required.
|
38
38
|
- If set, requires O(name).
|
39
39
|
type: bool
|
40
40
|
force:
|
ansible/modules/wait_for.py
CHANGED
@@ -76,6 +76,8 @@ options:
|
|
76
76
|
description:
|
77
77
|
- Can be used to match a string in either a file or a socket connection.
|
78
78
|
- Defaults to a multiline regex.
|
79
|
+
- When inspecting a system log file and a static string, remember that Ansible by default logs its own actions there;
|
80
|
+
see the notes and examples for information.
|
79
81
|
type: str
|
80
82
|
version_added: "1.4"
|
81
83
|
exclude_hosts:
|
@@ -105,13 +107,13 @@ attributes:
|
|
105
107
|
platform:
|
106
108
|
platforms: posix
|
107
109
|
notes:
|
108
|
-
- The ability to use search_regex with a port connection was added in Ansible 1.7.
|
109
|
-
- Prior to Ansible 2.4, testing for the absence of a directory or UNIX socket did not work correctly.
|
110
|
-
- Prior to Ansible 2.4, testing for the presence of a file did not work correctly if the remote user did not have read access to that file.
|
111
110
|
- Under some circumstances when using mandatory access control, a path may always be treated as being absent even if it exists, but
|
112
111
|
can't be modified or created by the remote user either.
|
113
112
|
- When waiting for a path, symbolic links will be followed. Many other modules that manipulate files do not follow symbolic links,
|
114
113
|
so operations on the path using other modules may not work exactly as expected.
|
114
|
+
- When searching a static string within a system log file, it is important to account for potential self-matching against log entries
|
115
|
+
generated by the Ansible modules. To prevent this, add a regular expression construct into the search string. For example, to match
|
116
|
+
a literal string 'this thing', one could use a regular expression like 'this t[h]ing'.
|
115
117
|
seealso:
|
116
118
|
- module: ansible.builtin.wait_for_connection
|
117
119
|
- module: ansible.windows.win_wait_for
|
@@ -156,6 +158,11 @@ EXAMPLES = r"""
|
|
156
158
|
path: /tmp/foo
|
157
159
|
search_regex: completed
|
158
160
|
|
161
|
+
- name: Wait until the string "tomcat up" is in syslog, use regex character set to avoid self match
|
162
|
+
ansible.builtin.wait_for:
|
163
|
+
path: /var/log/syslog
|
164
|
+
search_regex: 'tomcat [u]p'
|
165
|
+
|
159
166
|
- name: Wait until regex pattern matches in the file /tmp/foo and print the matched group
|
160
167
|
ansible.builtin.wait_for:
|
161
168
|
path: /tmp/foo
|
ansible/parsing/mod_args.py
CHANGED
@@ -20,17 +20,17 @@ from __future__ import annotations
|
|
20
20
|
import ansible.constants as C
|
21
21
|
from ansible.errors import AnsibleParserError, AnsibleError, AnsibleAssertionError
|
22
22
|
from ansible.module_utils._internal._datatag import AnsibleTagHelper
|
23
|
-
from ansible.module_utils.six import string_types
|
24
23
|
from ansible.module_utils.common.sentinel import Sentinel
|
25
24
|
from ansible.module_utils.common.text.converters import to_text
|
26
25
|
from ansible.parsing.splitter import parse_kv, split_args
|
27
26
|
from ansible.parsing.vault import EncryptedString
|
28
27
|
from ansible.plugins.loader import module_loader, action_loader
|
29
|
-
from ansible._internal._templating
|
28
|
+
from ansible._internal._templating import _jinja_bits
|
29
|
+
from ansible.utils.display import Display
|
30
30
|
from ansible.utils.fqcn import add_internal_fqcns
|
31
31
|
|
32
32
|
|
33
|
-
# modules
|
33
|
+
# modules formatted for user msg
|
34
34
|
_BUILTIN_RAW_PARAM_MODULES_SIMPLE = set([
|
35
35
|
'include_vars',
|
36
36
|
'include_tasks',
|
@@ -152,38 +152,43 @@ class ModuleArgsParser:
|
|
152
152
|
arguments can be fuzzy. Deal with all the forms.
|
153
153
|
"""
|
154
154
|
|
155
|
-
additional_args = {} if additional_args is None else additional_args
|
156
|
-
|
157
155
|
# final args are the ones we'll eventually return, so first update
|
158
156
|
# them with any additional args specified, which have lower priority
|
159
157
|
# than those which may be parsed/normalized next
|
160
158
|
final_args = dict()
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
final_args['_variable_params'] = additional_args
|
166
|
-
else:
|
167
|
-
raise AnsibleParserError("Complex args containing variables cannot use bare variables (without Jinja2 delimiters), "
|
168
|
-
"and must use the full variable style ('{{var_name}}')")
|
159
|
+
|
160
|
+
if additional_args is not Sentinel:
|
161
|
+
if isinstance(additional_args, str) and _jinja_bits.is_possibly_all_template(additional_args):
|
162
|
+
final_args['_variable_params'] = additional_args
|
169
163
|
elif isinstance(additional_args, dict):
|
170
164
|
final_args.update(additional_args)
|
165
|
+
elif additional_args is None:
|
166
|
+
Display().deprecated(
|
167
|
+
msg="Ignoring empty task `args` keyword.",
|
168
|
+
version="2.23",
|
169
|
+
help_text='A mapping or template which resolves to a mapping is required.',
|
170
|
+
obj=self._task_ds,
|
171
|
+
)
|
171
172
|
else:
|
172
|
-
raise AnsibleParserError(
|
173
|
+
raise AnsibleParserError(
|
174
|
+
message='The value of the task `args` keyword is invalid.',
|
175
|
+
help_text='A mapping or template which resolves to a mapping is required.',
|
176
|
+
obj=additional_args,
|
177
|
+
)
|
173
178
|
|
174
179
|
# how we normalize depends if we figured out what the module name is
|
175
180
|
# yet. If we have already figured it out, it's a 'new style' invocation.
|
176
181
|
# otherwise, it's not
|
177
182
|
|
178
183
|
if action is not None:
|
179
|
-
args = self._normalize_new_style_args(thing, action)
|
184
|
+
args = self._normalize_new_style_args(thing, action, additional_args)
|
180
185
|
else:
|
181
186
|
(action, args) = self._normalize_old_style_args(thing)
|
182
187
|
|
183
188
|
# this can occasionally happen, simplify
|
184
189
|
if args and 'args' in args:
|
185
190
|
tmp_args = args.pop('args')
|
186
|
-
if isinstance(tmp_args,
|
191
|
+
if isinstance(tmp_args, str):
|
187
192
|
tmp_args = parse_kv(tmp_args)
|
188
193
|
args.update(tmp_args)
|
189
194
|
|
@@ -206,7 +211,7 @@ class ModuleArgsParser:
|
|
206
211
|
|
207
212
|
return (action, final_args)
|
208
213
|
|
209
|
-
def _normalize_new_style_args(self, thing, action):
|
214
|
+
def _normalize_new_style_args(self, thing, action, additional_args):
|
210
215
|
"""
|
211
216
|
deals with fuzziness in new style module invocations
|
212
217
|
accepting key=value pairs and dictionaries, and returns
|
@@ -222,11 +227,23 @@ class ModuleArgsParser:
|
|
222
227
|
if isinstance(thing, dict):
|
223
228
|
# form is like: { xyz: { x: 2, y: 3 } }
|
224
229
|
args = thing
|
225
|
-
elif isinstance(thing,
|
230
|
+
elif isinstance(thing, str):
|
226
231
|
# form is like: copy: src=a dest=b
|
227
232
|
check_raw = action in FREEFORM_ACTIONS
|
228
233
|
args = parse_kv(thing, check_raw=check_raw)
|
234
|
+
args_keys = set(args) - {'_raw_params'}
|
235
|
+
|
236
|
+
if args_keys and additional_args is not Sentinel:
|
237
|
+
kv_args = ', '.join(repr(arg) for arg in sorted(args_keys))
|
238
|
+
|
239
|
+
Display().deprecated(
|
240
|
+
msg=f"Merging legacy k=v args ({kv_args}) into task args.",
|
241
|
+
help_text="Include all task args in the task `args` mapping.",
|
242
|
+
version="2.23",
|
243
|
+
obj=thing,
|
244
|
+
)
|
229
245
|
elif isinstance(thing, EncryptedString):
|
246
|
+
# k=v parsing intentionally omitted
|
230
247
|
args = dict(_raw_params=thing)
|
231
248
|
elif thing is None:
|
232
249
|
# this can happen with modules which take no params, like ping:
|
@@ -253,6 +270,7 @@ class ModuleArgsParser:
|
|
253
270
|
|
254
271
|
if isinstance(thing, dict):
|
255
272
|
# form is like: action: { module: 'copy', src: 'a', dest: 'b' }
|
273
|
+
Display().deprecated("Using a mapping for `action` is deprecated.", version='2.23', help_text='Use a string value for `action`.', obj=thing)
|
256
274
|
thing = thing.copy()
|
257
275
|
if 'module' in thing:
|
258
276
|
action, module_args = self._split_module_string(thing['module'])
|
@@ -261,7 +279,7 @@ class ModuleArgsParser:
|
|
261
279
|
args.update(parse_kv(module_args, check_raw=check_raw))
|
262
280
|
del args['module']
|
263
281
|
|
264
|
-
elif isinstance(thing,
|
282
|
+
elif isinstance(thing, str):
|
265
283
|
# form is like: action: copy src=a dest=b
|
266
284
|
(action, args) = self._split_module_string(thing)
|
267
285
|
check_raw = action in FREEFORM_ACTIONS
|
@@ -287,7 +305,7 @@ class ModuleArgsParser:
|
|
287
305
|
# This is the standard YAML form for command-type modules. We grab
|
288
306
|
# the args and pass them in as additional arguments, which can/will
|
289
307
|
# be overwritten via dict updates from the other arg sources below
|
290
|
-
additional_args = self._task_ds.get('args',
|
308
|
+
additional_args = self._task_ds.get('args', Sentinel)
|
291
309
|
|
292
310
|
# We can have one of action, local_action, or module specified
|
293
311
|
# action
|
@@ -570,8 +570,8 @@ def match_encrypt_secret(secrets, encrypt_vault_id=None):
|
|
570
570
|
return match_encrypt_vault_id_secret(secrets,
|
571
571
|
encrypt_vault_id=encrypt_vault_id)
|
572
572
|
|
573
|
-
# Find the best/first secret from secrets since we
|
574
|
-
# ie, consider all
|
573
|
+
# Find the best/first secret from secrets since we didn't specify otherwise
|
574
|
+
# ie, consider all the available secrets as matches
|
575
575
|
_vault_id_matchers = [_vault_id for _vault_id, dummy in secrets]
|
576
576
|
best_secret = match_best_secret(secrets, _vault_id_matchers)
|
577
577
|
|
@@ -1413,7 +1413,7 @@ class EncryptedString(AnsibleTaggedObject):
|
|
1413
1413
|
'ljust',
|
1414
1414
|
'lower',
|
1415
1415
|
'lstrip',
|
1416
|
-
'maketrans', # static, but implemented for
|
1416
|
+
'maketrans', # static, but implemented for simplicity/consistency
|
1417
1417
|
'partition',
|
1418
1418
|
'removeprefix',
|
1419
1419
|
'removesuffix',
|
ansible/playbook/base.py
CHANGED
@@ -221,8 +221,6 @@ class FieldAttributeBase:
|
|
221
221
|
|
222
222
|
def validate(self, all_vars=None):
|
223
223
|
""" validation that is done at parse time, not load time """
|
224
|
-
all_vars = {} if all_vars is None else all_vars
|
225
|
-
|
226
224
|
if not self._validated:
|
227
225
|
# walk all fields in the object
|
228
226
|
for (name, attribute) in self.fattributes.items():
|
ansible/playbook/helpers.py
CHANGED
@@ -122,7 +122,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
|
|
122
122
|
except AnsibleParserError as ex:
|
123
123
|
# if the raises exception was created with obj=ds args, then it includes the detail
|
124
124
|
# so we dont need to add it so we can just re raise.
|
125
|
-
if ex.obj:
|
125
|
+
if ex.obj is not None:
|
126
126
|
raise
|
127
127
|
# But if it wasn't, we can add the yaml object now to get more detail
|
128
128
|
# DTFIX-FUTURE: this *should* be unnecessary- check code coverage.
|
@@ -19,12 +19,7 @@ from __future__ import annotations
|
|
19
19
|
|
20
20
|
import os
|
21
21
|
|
22
|
-
import ansible.constants as C
|
23
|
-
from ansible.errors import AnsibleParserError, AnsibleAssertionError
|
24
22
|
from ansible.module_utils.common.text.converters import to_bytes
|
25
|
-
from ansible.module_utils._internal._datatag import AnsibleTagHelper
|
26
|
-
from ansible.module_utils.six import string_types
|
27
|
-
from ansible.parsing.splitter import split_args
|
28
23
|
from ansible.playbook.attribute import NonInheritableFieldAttribute
|
29
24
|
from ansible.playbook.base import Base
|
30
25
|
from ansible.playbook.conditional import Conditional
|
@@ -32,15 +27,27 @@ from ansible.playbook.taggable import Taggable
|
|
32
27
|
from ansible.utils.collection_loader import AnsibleCollectionConfig
|
33
28
|
from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path, _get_collection_playbook_path
|
34
29
|
from ansible._internal._templating._engine import TemplateEngine
|
35
|
-
from ansible.
|
36
|
-
|
37
|
-
display = Display()
|
30
|
+
from ansible.errors import AnsibleError
|
31
|
+
from ansible import constants as C
|
38
32
|
|
39
33
|
|
40
34
|
class PlaybookInclude(Base, Conditional, Taggable):
|
41
35
|
|
42
|
-
import_playbook = NonInheritableFieldAttribute(isa='string')
|
43
|
-
|
36
|
+
import_playbook = NonInheritableFieldAttribute(isa='string', required=True)
|
37
|
+
|
38
|
+
_post_validate_object = True # manually post_validate to get free arg validation/coercion
|
39
|
+
|
40
|
+
def preprocess_data(self, ds):
|
41
|
+
keys = {action for action in C._ACTION_IMPORT_PLAYBOOK if action in ds}
|
42
|
+
|
43
|
+
if len(keys) != 1:
|
44
|
+
raise AnsibleError(f'Found conflicting import_playbook actions: {", ".join(sorted(keys))}')
|
45
|
+
|
46
|
+
key = next(iter(keys))
|
47
|
+
|
48
|
+
ds['import_playbook'] = ds.pop(key)
|
49
|
+
|
50
|
+
return ds
|
44
51
|
|
45
52
|
@staticmethod
|
46
53
|
def load(data, basedir, variable_manager=None, loader=None):
|
@@ -62,18 +69,22 @@ class PlaybookInclude(Base, Conditional, Taggable):
|
|
62
69
|
new_obj = super(PlaybookInclude, self).load_data(ds, variable_manager, loader)
|
63
70
|
|
64
71
|
all_vars = self.vars.copy()
|
72
|
+
|
65
73
|
if variable_manager:
|
66
74
|
all_vars |= variable_manager.get_vars()
|
67
75
|
|
68
76
|
templar = TemplateEngine(loader=loader, variables=all_vars)
|
69
77
|
|
78
|
+
new_obj.post_validate(templar)
|
79
|
+
|
70
80
|
# then we use the object to load a Playbook
|
71
81
|
pb = Playbook(loader=loader)
|
72
82
|
|
73
|
-
file_name =
|
83
|
+
file_name = new_obj.import_playbook
|
74
84
|
|
75
85
|
# check for FQCN
|
76
86
|
resource = _get_collection_playbook_path(file_name)
|
87
|
+
|
77
88
|
if resource is not None:
|
78
89
|
playbook = resource[1]
|
79
90
|
playbook_collection = resource[2]
|
@@ -92,6 +103,7 @@ class PlaybookInclude(Base, Conditional, Taggable):
|
|
92
103
|
else:
|
93
104
|
# it is NOT a collection playbook, setup adjacent paths
|
94
105
|
AnsibleCollectionConfig.playbook_paths.append(os.path.dirname(os.path.abspath(to_bytes(playbook, errors='surrogate_or_strict'))))
|
106
|
+
# broken, see: https://github.com/ansible/ansible/issues/85357
|
95
107
|
|
96
108
|
pb._load_playbook_data(file_name=playbook, variable_manager=variable_manager, vars=self.vars.copy())
|
97
109
|
|
@@ -120,49 +132,3 @@ class PlaybookInclude(Base, Conditional, Taggable):
|
|
120
132
|
task_block._when = new_obj.when[:] + task_block.when[:]
|
121
133
|
|
122
134
|
return pb
|
123
|
-
|
124
|
-
def preprocess_data(self, ds):
|
125
|
-
"""
|
126
|
-
Reorganizes the data for a PlaybookInclude datastructure to line
|
127
|
-
up with what we expect the proper attributes to be
|
128
|
-
"""
|
129
|
-
|
130
|
-
if not isinstance(ds, dict):
|
131
|
-
raise AnsibleAssertionError('ds (%s) should be a dict but was a %s' % (ds, type(ds)))
|
132
|
-
|
133
|
-
# the new, cleaned datastructure, which will have legacy items reduced to a standard structure suitable for the
|
134
|
-
# attributes of the task class; copy any tagged data to preserve things like origin
|
135
|
-
new_ds = AnsibleTagHelper.tag_copy(ds, {})
|
136
|
-
|
137
|
-
for (k, v) in ds.items():
|
138
|
-
if k in C._ACTION_IMPORT_PLAYBOOK:
|
139
|
-
self._preprocess_import(ds, new_ds, k, v)
|
140
|
-
else:
|
141
|
-
# some basic error checking, to make sure vars are properly
|
142
|
-
# formatted and do not conflict with k=v parameters
|
143
|
-
if k == 'vars':
|
144
|
-
if 'vars' in new_ds:
|
145
|
-
raise AnsibleParserError("import_playbook parameters cannot be mixed with 'vars' entries for import statements", obj=ds)
|
146
|
-
elif not isinstance(v, dict):
|
147
|
-
raise AnsibleParserError("vars for import_playbook statements must be specified as a dictionary", obj=ds)
|
148
|
-
new_ds[k] = v
|
149
|
-
|
150
|
-
return super(PlaybookInclude, self).preprocess_data(new_ds)
|
151
|
-
|
152
|
-
def _preprocess_import(self, ds, new_ds, k, v):
|
153
|
-
"""
|
154
|
-
Splits the playbook import line up into filename and parameters
|
155
|
-
"""
|
156
|
-
if v is None:
|
157
|
-
raise AnsibleParserError("playbook import parameter is missing", obj=ds)
|
158
|
-
elif not isinstance(v, string_types):
|
159
|
-
raise AnsibleParserError("playbook import parameter must be a string indicating a file path, got %s instead" % type(v), obj=ds)
|
160
|
-
|
161
|
-
# The import_playbook line must include at least one item, which is the filename
|
162
|
-
# to import. Anything after that should be regarded as a parameter to the import
|
163
|
-
items = split_args(v)
|
164
|
-
if len(items) == 0:
|
165
|
-
raise AnsibleParserError("import_playbook statements must specify the file name to import", obj=ds)
|
166
|
-
|
167
|
-
# DTFIX3: investigate this as a possible "problematic strip"
|
168
|
-
new_ds['import_playbook'] = AnsibleTagHelper.tag_copy(v, items[0].strip())
|
@@ -18,6 +18,7 @@
|
|
18
18
|
from __future__ import annotations
|
19
19
|
|
20
20
|
import os
|
21
|
+
import typing as _t
|
21
22
|
|
22
23
|
from collections.abc import Container, Mapping, Set, Sequence
|
23
24
|
from types import MappingProxyType
|
@@ -39,6 +40,16 @@ from ansible.utils.collection_loader import AnsibleCollectionConfig
|
|
39
40
|
from ansible.utils.path import is_subpath
|
40
41
|
from ansible.utils.vars import combine_vars
|
41
42
|
|
43
|
+
# NOTE: This import is only needed for the type-checking in __init__. While there's an alternative
|
44
|
+
# available by using forward references this seems not to work well with commonly used IDEs.
|
45
|
+
# Therefore the TYPE_CHECKING hack seems to be a more universal approach, even if not being very elegant.
|
46
|
+
# References:
|
47
|
+
# * https://stackoverflow.com/q/39740632/199513
|
48
|
+
# * https://peps.python.org/pep-0484/#forward-references
|
49
|
+
if _t.TYPE_CHECKING:
|
50
|
+
from ansible.playbook.block import Block
|
51
|
+
from ansible.playbook.play import Play
|
52
|
+
|
42
53
|
__all__ = ['Role', 'hash_params']
|
43
54
|
|
44
55
|
# TODO: this should be a utility function, but can't be a member of
|
@@ -97,13 +108,19 @@ def hash_params(params):
|
|
97
108
|
|
98
109
|
class Role(Base, Conditional, Taggable, CollectionSearch, Delegatable):
|
99
110
|
|
100
|
-
def __init__(self,
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
111
|
+
def __init__(self,
|
112
|
+
play: Play = None,
|
113
|
+
from_files: dict[str, list[str]] = None,
|
114
|
+
from_include: bool = False,
|
115
|
+
validate: bool = True,
|
116
|
+
public: bool = None,
|
117
|
+
static: bool = True) -> None:
|
118
|
+
self._role_name: str = None
|
119
|
+
self._role_path: str = None
|
120
|
+
self._role_collection: str = None
|
121
|
+
self._role_params: dict[str, dict[str, str]] = dict()
|
105
122
|
self._loader = None
|
106
|
-
self.static = static
|
123
|
+
self.static: bool = static
|
107
124
|
|
108
125
|
# includes (static=false) default to private, while imports (static=true) default to public
|
109
126
|
# but both can be overridden by global config if set
|
@@ -116,26 +133,26 @@ class Role(Base, Conditional, Taggable, CollectionSearch, Delegatable):
|
|
116
133
|
else:
|
117
134
|
self.public = public
|
118
135
|
|
119
|
-
self._metadata = RoleMetadata()
|
120
|
-
self._play = play
|
121
|
-
self._parents = []
|
122
|
-
self._dependencies = []
|
123
|
-
self._all_dependencies = None
|
124
|
-
self._task_blocks = []
|
125
|
-
self._handler_blocks = []
|
126
|
-
self._compiled_handler_blocks = None
|
127
|
-
self._default_vars = dict()
|
128
|
-
self._role_vars = dict()
|
129
|
-
self._had_task_run = dict()
|
130
|
-
self._completed = dict()
|
131
|
-
self._should_validate = validate
|
136
|
+
self._metadata: RoleMetadata = RoleMetadata()
|
137
|
+
self._play: Play = play
|
138
|
+
self._parents: list[Role] = []
|
139
|
+
self._dependencies: list[Role] = []
|
140
|
+
self._all_dependencies: list[Role] | None = None
|
141
|
+
self._task_blocks: list[Block] = []
|
142
|
+
self._handler_blocks: list[Block] = []
|
143
|
+
self._compiled_handler_blocks: list[Block] | None = None
|
144
|
+
self._default_vars: dict[str, str] | None = dict()
|
145
|
+
self._role_vars: dict[str, str] | None = dict()
|
146
|
+
self._had_task_run: dict[str, bool] = dict()
|
147
|
+
self._completed: dict[str, bool] = dict()
|
148
|
+
self._should_validate: bool = validate
|
132
149
|
|
133
150
|
if from_files is None:
|
134
151
|
from_files = {}
|
135
|
-
self._from_files = from_files
|
152
|
+
self._from_files: dict[str, list[str]] = from_files
|
136
153
|
|
137
154
|
# Indicates whether this role was included via include/import_role
|
138
|
-
self.from_include = from_include
|
155
|
+
self.from_include: bool = from_include
|
139
156
|
|
140
157
|
self._hash = None
|
141
158
|
|
@@ -357,8 +374,8 @@ class Role(Base, Conditional, Taggable, CollectionSearch, Delegatable):
|
|
357
374
|
task_name = task_name + ' - ' + argument_spec['short_description']
|
358
375
|
|
359
376
|
return {
|
360
|
-
'action':
|
361
|
-
|
377
|
+
'action': 'ansible.builtin.validate_argument_spec',
|
378
|
+
'args': {
|
362
379
|
# Pass only the 'options' portion of the arg spec to the module.
|
363
380
|
'argument_spec': argument_spec.get('options', {}),
|
364
381
|
'provided_arguments': self._role_params,
|
@@ -103,7 +103,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
103
103
|
self._display = display
|
104
104
|
|
105
105
|
@abstractmethod
|
106
|
-
def run(self, tmp=None, task_vars=None):
|
106
|
+
def run(self, tmp: str | None = None, task_vars: dict[str, t.Any] | None = None) -> dict[str, t.Any]:
|
107
107
|
""" Action Plugins should implement this method to perform their
|
108
108
|
tasks. Everything else in this base class is a helper method for the
|
109
109
|
action plugin to do that.
|
@@ -120,7 +120,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
120
120
|
* Module parameters. These are stored in self._task.args
|
121
121
|
"""
|
122
122
|
# does not default to {'changed': False, 'failed': False}, as it used to break async
|
123
|
-
result = {}
|
123
|
+
result: dict[str, t.Any] = {}
|
124
124
|
|
125
125
|
if tmp is not None:
|
126
126
|
display.warning('ActionModule.run() no longer honors the tmp parameter. Action'
|