ansible-core 2.19.0b5__py3-none-any.whl → 2.19.0b7__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/_ansiballz/__init__.py +0 -0
- ansible/_internal/_ansiballz/_builder.py +101 -0
- ansible/_internal/{_ansiballz.py → _ansiballz/_wrapper.py} +11 -11
- ansible/_internal/_templating/_jinja_bits.py +22 -4
- ansible/_internal/_templating/_jinja_common.py +1 -1
- ansible/_internal/_templating/_jinja_plugins.py +5 -2
- ansible/_internal/_templating/_template_vars.py +72 -0
- ansible/_internal/_templating/_transform.py +6 -0
- ansible/_internal/_yaml/_constructor.py +4 -4
- ansible/_internal/_yaml/_dumper.py +26 -18
- ansible/cli/__init__.py +9 -14
- ansible/cli/adhoc.py +6 -3
- ansible/cli/arguments/option_helpers.py +1 -1
- ansible/cli/console.py +2 -2
- ansible/cli/doc.py +4 -4
- ansible/cli/inventory.py +5 -7
- ansible/config/base.yml +33 -6
- ansible/errors/__init__.py +2 -1
- ansible/executor/module_common.py +75 -44
- ansible/executor/powershell/psrp_put_file.ps1 +1 -1
- ansible/executor/process/worker.py +2 -2
- 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 +3 -6
- ansible/galaxy/collection/__init__.py +1 -6
- ansible/galaxy/collection/concrete_artifact_manager.py +4 -10
- ansible/galaxy/dependency_resolution/providers.py +3 -3
- ansible/galaxy/role.py +2 -2
- ansible/inventory/group.py +6 -1
- ansible/inventory/host.py +6 -1
- ansible/module_utils/_internal/__init__.py +7 -4
- 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} +10 -38
- 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 +23 -1
- ansible/module_utils/_internal/_deprecator.py +39 -34
- ansible/module_utils/_internal/_json/_profiles/__init__.py +1 -0
- ansible/module_utils/_internal/_messages.py +26 -2
- ansible/module_utils/_internal/_plugin_info.py +14 -1
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +58 -70
- ansible/module_utils/common/respawn.py +4 -41
- ansible/module_utils/common/yaml.py +1 -1
- ansible/module_utils/connection.py +8 -11
- 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/hardware/linux.py +1 -1
- ansible/module_utils/facts/other/facter.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 +2 -2
- ansible/module_utils/facts/system/local.py +1 -1
- ansible/module_utils/facts/virtual/linux.py +1 -1
- 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/service.py +1 -1
- ansible/module_utils/urls.py +5 -5
- ansible/modules/apt.py +9 -3
- ansible/modules/apt_repository.py +10 -10
- ansible/modules/assemble.py +7 -5
- ansible/modules/async_wrapper.py +7 -17
- ansible/modules/command.py +3 -3
- ansible/modules/copy.py +4 -4
- ansible/modules/cron.py +1 -1
- ansible/modules/expect.py +5 -5
- 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 +2 -2
- ansible/modules/known_hosts.py +12 -14
- ansible/modules/package.py +6 -0
- ansible/modules/pip.py +9 -11
- ansible/modules/raw.py +2 -2
- ansible/modules/replace.py +2 -2
- ansible/modules/slurp.py +10 -13
- ansible/modules/stat.py +6 -8
- ansible/modules/unarchive.py +6 -6
- ansible/modules/user.py +1 -1
- ansible/modules/wait_for.py +38 -33
- ansible/modules/yum_repository.py +4 -3
- ansible/parsing/dataloader.py +2 -2
- ansible/parsing/mod_args.py +38 -20
- ansible/parsing/vault/__init__.py +9 -13
- ansible/playbook/base.py +7 -4
- ansible/playbook/helpers.py +1 -1
- ansible/playbook/included_file.py +3 -1
- ansible/playbook/play_context.py +2 -0
- ansible/playbook/playbook_include.py +23 -56
- ansible/playbook/role/__init__.py +38 -21
- ansible/playbook/taggable.py +19 -5
- ansible/playbook/task.py +2 -0
- ansible/plugins/action/__init__.py +2 -2
- ansible/plugins/action/assemble.py +2 -1
- ansible/plugins/action/assert.py +2 -2
- ansible/plugins/action/fetch.py +3 -3
- ansible/plugins/action/script.py +5 -4
- ansible/plugins/action/template.py +9 -3
- ansible/plugins/cache/__init__.py +17 -19
- ansible/plugins/callback/__init__.py +77 -87
- ansible/plugins/callback/default.py +0 -3
- ansible/plugins/callback/junit.py +0 -6
- ansible/plugins/callback/tree.py +5 -5
- ansible/plugins/connection/local.py +4 -4
- ansible/plugins/connection/paramiko_ssh.py +5 -5
- ansible/plugins/connection/ssh.py +9 -7
- ansible/plugins/connection/winrm.py +1 -1
- ansible/plugins/filter/core.py +19 -21
- ansible/plugins/filter/encryption.py +10 -2
- 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/list.py +5 -4
- ansible/plugins/loader.py +5 -0
- ansible/plugins/lookup/password.py +4 -6
- ansible/plugins/lookup/template.py +9 -4
- ansible/plugins/shell/powershell.py +3 -2
- ansible/plugins/shell/sh.py +3 -2
- ansible/plugins/strategy/__init__.py +3 -3
- ansible/plugins/test/core.py +2 -2
- ansible/release.py +1 -1
- ansible/template/__init__.py +9 -53
- ansible/utils/collection_loader/_collection_finder.py +3 -3
- ansible/utils/display.py +38 -37
- ansible/utils/galaxy.py +2 -2
- ansible/utils/hashing.py +6 -7
- ansible/utils/path.py +6 -8
- ansible/utils/py3compat.py +2 -1
- ansible/utils/ssh_functions.py +3 -2
- ansible/utils/vars.py +4 -1
- ansible/vars/manager.py +6 -3
- ansible/vars/plugins.py +3 -3
- ansible/vars/reserved.py +6 -4
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/RECORD +184 -173
- 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/integration/coverage.py +7 -2
- 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 +259 -16
- ansible_test/_internal/inventory.py +4 -0
- ansible_test/_internal/metadata.py +94 -4
- ansible_test/_internal/processes.py +80 -0
- ansible_test/_internal/provisioning.py +10 -4
- ansible_test/_internal/python_requirements.py +27 -0
- ansible_test/_internal/ssh.py +1 -5
- ansible_test/_internal/target.py +8 -0
- ansible_test/_internal/thread.py +2 -1
- ansible_test/_internal/timeout.py +1 -1
- ansible_test/_internal/util.py +20 -12
- ansible_test/_internal/util_common.py +13 -3
- ansible_test/_util/target/injector/python.py +8 -0
- ansible_test/_util/target/setup/requirements.py +3 -9
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/top_level.txt +0 -0
ansible/modules/replace.py
CHANGED
@@ -256,8 +256,8 @@ def main():
|
|
256
256
|
try:
|
257
257
|
with open(path, 'rb') as f:
|
258
258
|
contents = to_text(f.read(), errors='surrogate_or_strict', encoding=encoding)
|
259
|
-
except
|
260
|
-
|
259
|
+
except OSError as ex:
|
260
|
+
raise Exception(f"Unable to read the contents of {path!r}.") from ex
|
261
261
|
|
262
262
|
pattern = u''
|
263
263
|
if params['after'] and params['before']:
|
ansible/modules/slurp.py
CHANGED
@@ -85,7 +85,6 @@ import base64
|
|
85
85
|
import errno
|
86
86
|
|
87
87
|
from ansible.module_utils.basic import AnsibleModule
|
88
|
-
from ansible.module_utils.common.text.converters import to_native
|
89
88
|
|
90
89
|
|
91
90
|
def main():
|
@@ -99,20 +98,18 @@ def main():
|
|
99
98
|
|
100
99
|
try:
|
101
100
|
with open(source, 'rb') as source_fh:
|
102
|
-
|
103
|
-
except
|
104
|
-
if
|
105
|
-
msg = "
|
106
|
-
elif
|
107
|
-
msg = "
|
108
|
-
elif
|
109
|
-
msg = "
|
101
|
+
data = base64.b64encode(source_fh.read())
|
102
|
+
except OSError as ex:
|
103
|
+
if ex.errno == errno.ENOENT:
|
104
|
+
msg = f"File not found: {source}"
|
105
|
+
elif ex.errno == errno.EACCES:
|
106
|
+
msg = f"File is not readable: {source}"
|
107
|
+
elif ex.errno == errno.EISDIR:
|
108
|
+
msg = f"Source is a directory and must be a file: {source}"
|
110
109
|
else:
|
111
|
-
msg = "
|
110
|
+
msg = "Unable to slurp file: {source}"
|
112
111
|
|
113
|
-
module.fail_json(msg)
|
114
|
-
|
115
|
-
data = base64.b64encode(source_content)
|
112
|
+
module.fail_json(msg, exception=ex)
|
116
113
|
|
117
114
|
module.exit_json(content=data, source=source, encoding='base64')
|
118
115
|
|
ansible/modules/stat.py
CHANGED
@@ -354,7 +354,6 @@ stat:
|
|
354
354
|
version_added: 2.3
|
355
355
|
"""
|
356
356
|
|
357
|
-
import errno
|
358
357
|
import grp
|
359
358
|
import os
|
360
359
|
import pwd
|
@@ -409,7 +408,7 @@ def format_output(module, path, st):
|
|
409
408
|
('st_blksize', 'block_size'),
|
410
409
|
('st_rdev', 'device_type'),
|
411
410
|
('st_flags', 'flags'),
|
412
|
-
# Some
|
411
|
+
# Some Berkeley based
|
413
412
|
('st_gen', 'generation'),
|
414
413
|
('st_birthtime', 'birthtime'),
|
415
414
|
# RISCOS
|
@@ -456,12 +455,11 @@ def main():
|
|
456
455
|
st = os.stat(b_path)
|
457
456
|
else:
|
458
457
|
st = os.lstat(b_path)
|
459
|
-
except
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
module.fail_json(msg=e.strerror)
|
458
|
+
except FileNotFoundError:
|
459
|
+
output = {'exists': False}
|
460
|
+
module.exit_json(changed=False, stat=output)
|
461
|
+
except OSError as ex:
|
462
|
+
module.fail_json(msg=ex.strerror, exception=ex)
|
465
463
|
|
466
464
|
# process base results
|
467
465
|
output = format_output(module, path, st)
|
ansible/modules/unarchive.py
CHANGED
@@ -1132,8 +1132,8 @@ def main():
|
|
1132
1132
|
res_args['extract_results'] = handler.unarchive()
|
1133
1133
|
if res_args['extract_results']['rc'] != 0:
|
1134
1134
|
module.fail_json(msg="failed to unpack %s to %s" % (src, dest), **res_args)
|
1135
|
-
except
|
1136
|
-
module.fail_json(
|
1135
|
+
except OSError as ex:
|
1136
|
+
module.fail_json(f"Failed to unpack {src!r} to {dest!r}.", exception=ex, **res_args)
|
1137
1137
|
else:
|
1138
1138
|
res_args['changed'] = True
|
1139
1139
|
|
@@ -1150,8 +1150,8 @@ def main():
|
|
1150
1150
|
|
1151
1151
|
try:
|
1152
1152
|
res_args['changed'] = module.set_fs_attributes_if_different(file_args, res_args['changed'], expand=False)
|
1153
|
-
except
|
1154
|
-
module.fail_json(
|
1153
|
+
except OSError as ex:
|
1154
|
+
module.fail_json("Unexpected error when accessing exploded file.", exception=ex, **res_args)
|
1155
1155
|
|
1156
1156
|
if '/' in filename:
|
1157
1157
|
top_folder_path = filename.split('/')[0]
|
@@ -1165,8 +1165,8 @@ def main():
|
|
1165
1165
|
file_args['path'] = "%s/%s" % (dest, f)
|
1166
1166
|
try:
|
1167
1167
|
res_args['changed'] = module.set_fs_attributes_if_different(file_args, res_args['changed'], expand=False)
|
1168
|
-
except
|
1169
|
-
module.fail_json(
|
1168
|
+
except OSError as ex:
|
1169
|
+
module.fail_json("Unexpected error when accessing exploded file.", exception=ex, **res_args)
|
1170
1170
|
|
1171
1171
|
if module.params['list_files']:
|
1172
1172
|
res_args['files'] = handler.files_in_archive
|
ansible/modules/user.py
CHANGED
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
|
@@ -380,31 +387,29 @@ class LinuxTCPConnectionInfo(TCPConnectionInfo):
|
|
380
387
|
if not os.path.isfile(self.source_file[family]):
|
381
388
|
continue
|
382
389
|
try:
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
except
|
390
|
+
with open(self.source_file[family]) as f:
|
391
|
+
for tcp_connection in f.readlines():
|
392
|
+
tcp_connection = tcp_connection.strip().split()
|
393
|
+
if tcp_connection[self.local_address_field] == 'local_address':
|
394
|
+
continue
|
395
|
+
if (tcp_connection[self.connection_state_field] not in
|
396
|
+
[get_connection_state_id(_connection_state) for _connection_state in self.module.params['active_connection_states']]):
|
397
|
+
continue
|
398
|
+
(local_ip, local_port) = tcp_connection[self.local_address_field].split(':')
|
399
|
+
if self.port != local_port:
|
400
|
+
continue
|
401
|
+
(remote_ip, remote_port) = tcp_connection[self.remote_address_field].split(':')
|
402
|
+
if (family, remote_ip) in self.exclude_ips:
|
403
|
+
continue
|
404
|
+
if any((
|
405
|
+
(family, local_ip) in self.ips,
|
406
|
+
(family, self.match_all_ips[family]) in self.ips,
|
407
|
+
local_ip.startswith(self.ipv4_mapped_ipv6_address['prefix']) and
|
408
|
+
(family, self.ipv4_mapped_ipv6_address['match_all']) in self.ips,
|
409
|
+
)):
|
410
|
+
active_connections += 1
|
411
|
+
except OSError:
|
405
412
|
pass
|
406
|
-
finally:
|
407
|
-
f.close()
|
408
413
|
|
409
414
|
return active_connections
|
410
415
|
|
@@ -549,7 +554,7 @@ def main():
|
|
549
554
|
try:
|
550
555
|
if not os.access(b_path, os.F_OK):
|
551
556
|
break
|
552
|
-
except
|
557
|
+
except OSError:
|
553
558
|
break
|
554
559
|
elif port:
|
555
560
|
try:
|
@@ -609,7 +614,7 @@ def main():
|
|
609
614
|
break
|
610
615
|
except Exception as e:
|
611
616
|
module.warn('wait_for failed on "%s", unexpected exception(%s): %s.).' % (path, to_native(e.__class__), to_native(e)))
|
612
|
-
except
|
617
|
+
except OSError:
|
613
618
|
pass
|
614
619
|
elif port:
|
615
620
|
alt_connect_timeout = math.ceil(
|
@@ -648,8 +653,8 @@ def main():
|
|
648
653
|
# Shutdown the client socket
|
649
654
|
try:
|
650
655
|
s.shutdown(socket.SHUT_RDWR)
|
651
|
-
except
|
652
|
-
if
|
656
|
+
except OSError as ex:
|
657
|
+
if ex.errno != errno.ENOTCONN:
|
653
658
|
raise
|
654
659
|
# else, the server broke the connection on its end, assume it's not ready
|
655
660
|
else:
|
@@ -661,8 +666,8 @@ def main():
|
|
661
666
|
# Connection established, success!
|
662
667
|
try:
|
663
668
|
s.shutdown(socket.SHUT_RDWR)
|
664
|
-
except
|
665
|
-
if
|
669
|
+
except OSError as ex:
|
670
|
+
if ex.errno != errno.ENOTCONN:
|
666
671
|
raise
|
667
672
|
# else, the server broke the connection on its end, assume it's not ready
|
668
673
|
else:
|
@@ -502,10 +502,11 @@ class YumRepo:
|
|
502
502
|
try:
|
503
503
|
with open(self.dest, 'w') as fd:
|
504
504
|
self.repofile.write(fd)
|
505
|
-
except
|
505
|
+
except OSError as ex:
|
506
506
|
self.module.fail_json(
|
507
|
-
msg=f"Problems handling file {self.dest}.",
|
508
|
-
details=
|
507
|
+
msg=f"Problems handling file {self.dest!r}.",
|
508
|
+
details=str(ex),
|
509
|
+
exception=ex,
|
509
510
|
)
|
510
511
|
else:
|
511
512
|
try:
|
ansible/parsing/dataloader.py
CHANGED
@@ -217,7 +217,7 @@ class DataLoader:
|
|
217
217
|
except FileNotFoundError as ex:
|
218
218
|
# DTFIX-FUTURE: why not just let the builtin one fly?
|
219
219
|
raise AnsibleFileNotFound("Unable to retrieve file contents.", file_name=file_name) from ex
|
220
|
-
except
|
220
|
+
except OSError as ex:
|
221
221
|
raise AnsibleParserError(f"An error occurred while trying to read the file {file_name!r}.") from ex
|
222
222
|
|
223
223
|
data = Origin(path=file_name).tag(data)
|
@@ -448,7 +448,7 @@ class DataLoader:
|
|
448
448
|
|
449
449
|
return real_path
|
450
450
|
|
451
|
-
except
|
451
|
+
except OSError as ex:
|
452
452
|
raise AnsibleParserError(f"an error occurred while trying to read the file {to_text(real_path)!r}.") from ex
|
453
453
|
|
454
454
|
def cleanup_tmp_file(self, file_path: str) -> None:
|
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
|
@@ -17,7 +17,6 @@
|
|
17
17
|
|
18
18
|
from __future__ import annotations
|
19
19
|
|
20
|
-
import errno
|
21
20
|
import fcntl
|
22
21
|
import functools
|
23
22
|
import os
|
@@ -414,8 +413,8 @@ class FileVaultSecret(VaultSecret):
|
|
414
413
|
try:
|
415
414
|
with open(filename, "rb") as f:
|
416
415
|
vault_pass = f.read().strip()
|
417
|
-
except
|
418
|
-
raise AnsibleError("Could not read vault password file
|
416
|
+
except OSError as ex:
|
417
|
+
raise AnsibleError(f"Could not read vault password file {filename!r}.") from ex
|
419
418
|
|
420
419
|
b_vault_data, dummy = self.loader._decrypt_if_vault_data(vault_pass)
|
421
420
|
|
@@ -571,8 +570,8 @@ def match_encrypt_secret(secrets, encrypt_vault_id=None):
|
|
571
570
|
return match_encrypt_vault_id_secret(secrets,
|
572
571
|
encrypt_vault_id=encrypt_vault_id)
|
573
572
|
|
574
|
-
# Find the best/first secret from secrets since we
|
575
|
-
# 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
|
576
575
|
_vault_id_matchers = [_vault_id for _vault_id, dummy in secrets]
|
577
576
|
best_secret = match_best_secret(secrets, _vault_id_matchers)
|
578
577
|
|
@@ -1071,13 +1070,10 @@ class VaultEditor:
|
|
1071
1070
|
try:
|
1072
1071
|
# create file with secure permissions
|
1073
1072
|
fd = os.open(thefile, os.O_CREAT | os.O_EXCL | os.O_RDWR | os.O_TRUNC, mode)
|
1074
|
-
except
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
raise AnsibleError('Vault file got recreated while we were operating on it: %s' % to_native(ose))
|
1079
|
-
|
1080
|
-
raise AnsibleError('Problem creating temporary vault file: %s' % to_native(ose))
|
1073
|
+
except FileExistsError as ex:
|
1074
|
+
raise AnsibleError('Vault file got recreated while we were operating on it.') from ex
|
1075
|
+
except OSError as ex:
|
1076
|
+
raise AnsibleError('Problem creating temporary vault file.') from ex
|
1081
1077
|
|
1082
1078
|
try:
|
1083
1079
|
# now write to the file and ensure ours is only data in it
|
@@ -1417,7 +1413,7 @@ class EncryptedString(AnsibleTaggedObject):
|
|
1417
1413
|
'ljust',
|
1418
1414
|
'lower',
|
1419
1415
|
'lstrip',
|
1420
|
-
'maketrans', # static, but implemented for
|
1416
|
+
'maketrans', # static, but implemented for simplicity/consistency
|
1421
1417
|
'partition',
|
1422
1418
|
'removeprefix',
|
1423
1419
|
'removesuffix',
|
ansible/playbook/base.py
CHANGED
@@ -83,6 +83,11 @@ class _ClassProperty:
|
|
83
83
|
|
84
84
|
class FieldAttributeBase:
|
85
85
|
|
86
|
+
_post_validate_object = False
|
87
|
+
"""
|
88
|
+
`False` skips FieldAttribute post-validation on intermediate objects and mixins for attributes without `always_post_validate`.
|
89
|
+
Leaf objects (e.g., `Task`) should set this attribute `True` to opt-in to post-validation.
|
90
|
+
"""
|
86
91
|
fattributes = _ClassProperty()
|
87
92
|
|
88
93
|
@classmethod
|
@@ -216,8 +221,6 @@ class FieldAttributeBase:
|
|
216
221
|
|
217
222
|
def validate(self, all_vars=None):
|
218
223
|
""" validation that is done at parse time, not load time """
|
219
|
-
all_vars = {} if all_vars is None else all_vars
|
220
|
-
|
221
224
|
if not self._validated:
|
222
225
|
# walk all fields in the object
|
223
226
|
for (name, attribute) in self.fattributes.items():
|
@@ -566,8 +569,8 @@ class FieldAttributeBase:
|
|
566
569
|
# only import_role is checked here because import_tasks never reaches this point
|
567
570
|
return Sentinel
|
568
571
|
|
569
|
-
#
|
570
|
-
if not attribute.always_post_validate and self.
|
572
|
+
# Skip post validation unless always_post_validate is True, or the object requires post validation.
|
573
|
+
if not attribute.always_post_validate and not self._post_validate_object:
|
571
574
|
# Intermediate objects like Play() won't have their fields validated by
|
572
575
|
# default, as their values are often inherited by other objects and validated
|
573
576
|
# later, so we don't want them to fail out early
|
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.
|
@@ -144,7 +144,9 @@ class IncludedFile:
|
|
144
144
|
parent_include_dir = parent_include._role_path
|
145
145
|
else:
|
146
146
|
try:
|
147
|
-
|
147
|
+
# FUTURE: Since the parent include path has already been resolved, it should be used here.
|
148
|
+
# Unfortunately it's not currently stored anywhere, so it must be calculated again.
|
149
|
+
parent_include_dir = os.path.dirname(templar.template(parent_include.args.get('_raw_params')))
|
148
150
|
except AnsibleError as e:
|
149
151
|
parent_include_dir = ''
|
150
152
|
display.warning(
|
ansible/playbook/play_context.py
CHANGED