ansible-core 2.15.0b1__py3-none-any.whl → 2.15.0b3__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/arguments/option_helpers.py +0 -2
- ansible/cli/playbook.py +2 -0
- ansible/executor/play_iterator.py +12 -0
- ansible/executor/task_executor.py +12 -0
- ansible/galaxy/api.py +1 -2
- ansible/galaxy/collection/__init__.py +3 -0
- 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/basic.py +47 -41
- ansible/modules/dnf5.py +7 -6
- ansible/plugins/strategy/__init__.py +80 -84
- ansible/plugins/strategy/linear.py +1 -5
- ansible/release.py +1 -1
- {ansible_core-2.15.0b1.dist-info → ansible_core-2.15.0b3.dist-info}/METADATA +1 -1
- {ansible_core-2.15.0b1.dist-info → ansible_core-2.15.0b3.dist-info}/RECORD +28 -28
- ansible_test/_data/completion/docker.txt +2 -2
- ansible_test/_data/requirements/sanity.validate-modules.in +1 -0
- ansible_test/_data/requirements/sanity.validate-modules.txt +1 -0
- ansible_test/_internal/cli/argparsing/argcompletion.py +20 -5
- ansible_test/_util/controller/sanity/mypy/ansible-test.ini +3 -0
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +27 -27
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +56 -139
- {ansible_core-2.15.0b1.data → ansible_core-2.15.0b3.data}/scripts/ansible-test +0 -0
- {ansible_core-2.15.0b1.dist-info → ansible_core-2.15.0b3.dist-info}/COPYING +0 -0
- {ansible_core-2.15.0b1.dist-info → ansible_core-2.15.0b3.dist-info}/WHEEL +0 -0
- {ansible_core-2.15.0b1.dist-info → ansible_core-2.15.0b3.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.15.0b1.dist-info → ansible_core-2.15.0b3.dist-info}/top_level.txt +0 -0
|
@@ -235,8 +235,6 @@ def add_check_options(parser):
|
|
|
235
235
|
"""Add options for commands which can run with diagnostic information of tasks"""
|
|
236
236
|
parser.add_argument("-C", "--check", default=False, dest='check', action='store_true',
|
|
237
237
|
help="don't make any changes; instead, try to predict some of the changes that may occur")
|
|
238
|
-
parser.add_argument('--syntax-check', dest='syntax', action='store_true',
|
|
239
|
-
help="perform a syntax check on the playbook, but do not execute it")
|
|
240
238
|
parser.add_argument("-D", "--diff", default=C.DIFF_ALWAYS, dest='diff', action='store_true',
|
|
241
239
|
help="when changing (small) files and templates, show the differences in those"
|
|
242
240
|
" files; works great with --check")
|
ansible/cli/playbook.py
CHANGED
|
@@ -54,6 +54,8 @@ class PlaybookCLI(CLI):
|
|
|
54
54
|
opt_help.add_module_options(self.parser)
|
|
55
55
|
|
|
56
56
|
# ansible playbook specific opts
|
|
57
|
+
self.parser.add_argument('--syntax-check', dest='syntax', action='store_true',
|
|
58
|
+
help="perform a syntax check on the playbook, but do not execute it")
|
|
57
59
|
self.parser.add_argument('--list-tasks', dest='listtasks', action='store_true',
|
|
58
60
|
help="list all tasks that would be executed")
|
|
59
61
|
self.parser.add_argument('--list-tags', dest='listtags', action='store_true',
|
|
@@ -60,6 +60,8 @@ class HostState:
|
|
|
60
60
|
self._blocks = blocks[:]
|
|
61
61
|
self.handlers = []
|
|
62
62
|
|
|
63
|
+
self.handler_notifications = []
|
|
64
|
+
|
|
63
65
|
self.cur_block = 0
|
|
64
66
|
self.cur_regular_task = 0
|
|
65
67
|
self.cur_rescue_task = 0
|
|
@@ -120,6 +122,7 @@ class HostState:
|
|
|
120
122
|
def copy(self):
|
|
121
123
|
new_state = HostState(self._blocks)
|
|
122
124
|
new_state.handlers = self.handlers[:]
|
|
125
|
+
new_state.handler_notifications = self.handler_notifications[:]
|
|
123
126
|
new_state.cur_block = self.cur_block
|
|
124
127
|
new_state.cur_regular_task = self.cur_regular_task
|
|
125
128
|
new_state.cur_rescue_task = self.cur_rescue_task
|
|
@@ -650,3 +653,12 @@ class PlayIterator:
|
|
|
650
653
|
if not isinstance(fail_state, FailedStates):
|
|
651
654
|
raise AnsibleAssertionError('Expected fail_state to be a FailedStates but was %s' % (type(fail_state)))
|
|
652
655
|
self._host_states[hostname].fail_state = fail_state
|
|
656
|
+
|
|
657
|
+
def add_notification(self, hostname: str, notification: str) -> None:
|
|
658
|
+
# preserve order
|
|
659
|
+
host_state = self._host_states[hostname]
|
|
660
|
+
if notification not in host_state.handler_notifications:
|
|
661
|
+
host_state.handler_notifications.append(notification)
|
|
662
|
+
|
|
663
|
+
def clear_notification(self, hostname: str, notification: str) -> None:
|
|
664
|
+
self._host_states[hostname].handler_notifications.remove(notification)
|
|
@@ -137,6 +137,12 @@ class TaskExecutor:
|
|
|
137
137
|
self._task.ignore_errors = item_ignore
|
|
138
138
|
elif self._task.ignore_errors and not item_ignore:
|
|
139
139
|
self._task.ignore_errors = item_ignore
|
|
140
|
+
if 'unreachable' in item and item['unreachable']:
|
|
141
|
+
item_ignore_unreachable = item.pop('_ansible_ignore_unreachable')
|
|
142
|
+
if not res.get('unreachable'):
|
|
143
|
+
self._task.ignore_unreachable = item_ignore_unreachable
|
|
144
|
+
elif self._task.ignore_unreachable and not item_ignore_unreachable:
|
|
145
|
+
self._task.ignore_unreachable = item_ignore_unreachable
|
|
140
146
|
|
|
141
147
|
# ensure to accumulate these
|
|
142
148
|
for array in ['warnings', 'deprecations']:
|
|
@@ -277,6 +283,7 @@ class TaskExecutor:
|
|
|
277
283
|
u" to something else to avoid variable collisions and unexpected behavior." % (self._task, loop_var))
|
|
278
284
|
|
|
279
285
|
ran_once = False
|
|
286
|
+
task_fields = None
|
|
280
287
|
no_log = False
|
|
281
288
|
items_len = len(items)
|
|
282
289
|
results = []
|
|
@@ -348,6 +355,7 @@ class TaskExecutor:
|
|
|
348
355
|
|
|
349
356
|
res['_ansible_item_result'] = True
|
|
350
357
|
res['_ansible_ignore_errors'] = task_fields.get('ignore_errors')
|
|
358
|
+
res['_ansible_ignore_unreachable'] = task_fields.get('ignore_unreachable')
|
|
351
359
|
|
|
352
360
|
# gets templated here unlike rest of loop_control fields, depends on loop_var above
|
|
353
361
|
try:
|
|
@@ -392,6 +400,10 @@ class TaskExecutor:
|
|
|
392
400
|
del task_vars[var]
|
|
393
401
|
|
|
394
402
|
self._task.no_log = no_log
|
|
403
|
+
# NOTE: run_once cannot contain loop vars because it's templated earlier also
|
|
404
|
+
# This is saving the post-validated field from the last loop so the strategy can use the templated value post task execution
|
|
405
|
+
self._task.run_once = task_fields.get('run_once')
|
|
406
|
+
self._task.action = task_fields.get('action')
|
|
395
407
|
|
|
396
408
|
return results
|
|
397
409
|
|
ansible/galaxy/api.py
CHANGED
|
@@ -926,8 +926,7 @@ class GalaxyAPI:
|
|
|
926
926
|
try:
|
|
927
927
|
signatures = data["signatures"]
|
|
928
928
|
except KeyError:
|
|
929
|
-
|
|
930
|
-
display.vvvvvv(f"Server {self.api_server} has not signed {namespace}.{name}:{version}")
|
|
929
|
+
display.vvvv(f"Server {self.api_server} has not signed {namespace}.{name}:{version}")
|
|
931
930
|
return []
|
|
932
931
|
else:
|
|
933
932
|
return [signature_info["signature"] for signature_info in signatures]
|
|
@@ -769,6 +769,9 @@ def install_collections(
|
|
|
769
769
|
"Skipping signature verification."
|
|
770
770
|
)
|
|
771
771
|
|
|
772
|
+
if concrete_coll_pin.type == 'galaxy':
|
|
773
|
+
concrete_coll_pin = concrete_coll_pin.with_signatures_repopulated()
|
|
774
|
+
|
|
772
775
|
try:
|
|
773
776
|
install(concrete_coll_pin, output_path, artifacts_manager)
|
|
774
777
|
except AnsibleError as err:
|
|
@@ -27,7 +27,7 @@ if t.TYPE_CHECKING:
|
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
from ansible.errors import AnsibleError
|
|
30
|
+
from ansible.errors import AnsibleError, AnsibleAssertionError
|
|
31
31
|
from ansible.galaxy.api import GalaxyAPI
|
|
32
32
|
from ansible.galaxy.collection import HAS_PACKAGING, PkgReq
|
|
33
33
|
from ansible.module_utils._text import to_bytes, to_native, to_text
|
|
@@ -584,3 +584,13 @@ class Candidate(
|
|
|
584
584
|
|
|
585
585
|
def __init__(self, *args, **kwargs):
|
|
586
586
|
super(Candidate, self).__init__()
|
|
587
|
+
|
|
588
|
+
def with_signatures_repopulated(self): # type: (Candidate) -> Candidate
|
|
589
|
+
"""Populate a new Candidate instance with Galaxy signatures.
|
|
590
|
+
:raises AnsibleAssertionError: If the supplied candidate is not sourced from a Galaxy-like index.
|
|
591
|
+
"""
|
|
592
|
+
if self.type != 'galaxy':
|
|
593
|
+
raise AnsibleAssertionError(f"Invalid collection type for {self!r}: unable to get signatures from a galaxy server.")
|
|
594
|
+
|
|
595
|
+
signatures = self.src.get_collection_signatures(self.namespace, self.name, self.ver)
|
|
596
|
+
return self.__class__(self.fqcn, self.ver, self.src, self.type, frozenset([*self.signatures, *signatures]))
|
|
@@ -392,7 +392,6 @@ class CollectionDependencyProviderBase(AbstractProvider):
|
|
|
392
392
|
|
|
393
393
|
if not unsatisfied:
|
|
394
394
|
if self._include_signatures:
|
|
395
|
-
signatures = src_server.get_collection_signatures(first_req.namespace, first_req.name, version)
|
|
396
395
|
for extra_source in extra_signature_sources:
|
|
397
396
|
signatures.append(get_signature_from_source(extra_source))
|
|
398
397
|
latest_matches.append(
|
ansible/module_utils/basic.py
CHANGED
|
@@ -2043,53 +2043,59 @@ class AnsibleModule(object):
|
|
|
2043
2043
|
# Select PollSelector which is supported by major platforms
|
|
2044
2044
|
selector = selectors.PollSelector()
|
|
2045
2045
|
|
|
2046
|
-
selector.register(cmd.stdout, selectors.EVENT_READ)
|
|
2047
|
-
selector.register(cmd.stderr, selectors.EVENT_READ)
|
|
2048
|
-
if os.name == 'posix':
|
|
2049
|
-
fcntl.fcntl(cmd.stdout.fileno(), fcntl.F_SETFL, fcntl.fcntl(cmd.stdout.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK)
|
|
2050
|
-
fcntl.fcntl(cmd.stderr.fileno(), fcntl.F_SETFL, fcntl.fcntl(cmd.stderr.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK)
|
|
2051
|
-
|
|
2052
2046
|
if data:
|
|
2053
2047
|
if not binary_data:
|
|
2054
2048
|
data += '\n'
|
|
2055
2049
|
if isinstance(data, text_type):
|
|
2056
2050
|
data = to_bytes(data)
|
|
2057
|
-
cmd.stdin.write(data)
|
|
2058
|
-
cmd.stdin.close()
|
|
2059
|
-
|
|
2060
|
-
while True:
|
|
2061
|
-
events = selector.select(1)
|
|
2062
|
-
for key, event in events:
|
|
2063
|
-
b_chunk = key.fileobj.read()
|
|
2064
|
-
if b_chunk == b(''):
|
|
2065
|
-
selector.unregister(key.fileobj)
|
|
2066
|
-
if key.fileobj == cmd.stdout:
|
|
2067
|
-
stdout += b_chunk
|
|
2068
|
-
elif key.fileobj == cmd.stderr:
|
|
2069
|
-
stderr += b_chunk
|
|
2070
|
-
# if we're checking for prompts, do it now
|
|
2071
|
-
if prompt_re:
|
|
2072
|
-
if prompt_re.search(stdout) and not data:
|
|
2073
|
-
if encoding:
|
|
2074
|
-
stdout = to_native(stdout, encoding=encoding, errors=errors)
|
|
2075
|
-
return (257, stdout, "A prompt was encountered while running a command, but no input data was specified")
|
|
2076
|
-
# only break out if no pipes are left to read or
|
|
2077
|
-
# the pipes are completely read and
|
|
2078
|
-
# the process is terminated
|
|
2079
|
-
if (not events or not selector.get_map()) and cmd.poll() is not None:
|
|
2080
|
-
break
|
|
2081
|
-
# No pipes are left to read but process is not yet terminated
|
|
2082
|
-
# Only then it is safe to wait for the process to be finished
|
|
2083
|
-
# NOTE: Actually cmd.poll() is always None here if no selectors are left
|
|
2084
|
-
elif not selector.get_map() and cmd.poll() is None:
|
|
2085
|
-
cmd.wait()
|
|
2086
|
-
# The process is terminated. Since no pipes to read from are
|
|
2087
|
-
# left, there is no need to call select() again.
|
|
2088
|
-
break
|
|
2089
2051
|
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2052
|
+
if not prompt_re:
|
|
2053
|
+
stdout, stderr = cmd.communicate(input=data)
|
|
2054
|
+
else:
|
|
2055
|
+
# We only need this to look for a prompt, to abort instead of hanging
|
|
2056
|
+
selector.register(cmd.stdout, selectors.EVENT_READ)
|
|
2057
|
+
selector.register(cmd.stderr, selectors.EVENT_READ)
|
|
2058
|
+
if os.name == 'posix':
|
|
2059
|
+
fcntl.fcntl(cmd.stdout.fileno(), fcntl.F_SETFL, fcntl.fcntl(cmd.stdout.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK)
|
|
2060
|
+
fcntl.fcntl(cmd.stderr.fileno(), fcntl.F_SETFL, fcntl.fcntl(cmd.stderr.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK)
|
|
2061
|
+
|
|
2062
|
+
if data:
|
|
2063
|
+
cmd.stdin.write(data)
|
|
2064
|
+
cmd.stdin.close()
|
|
2065
|
+
|
|
2066
|
+
while True:
|
|
2067
|
+
events = selector.select(1)
|
|
2068
|
+
for key, event in events:
|
|
2069
|
+
b_chunk = key.fileobj.read()
|
|
2070
|
+
if b_chunk == b(''):
|
|
2071
|
+
selector.unregister(key.fileobj)
|
|
2072
|
+
if key.fileobj == cmd.stdout:
|
|
2073
|
+
stdout += b_chunk
|
|
2074
|
+
elif key.fileobj == cmd.stderr:
|
|
2075
|
+
stderr += b_chunk
|
|
2076
|
+
# if we're checking for prompts, do it now
|
|
2077
|
+
if prompt_re:
|
|
2078
|
+
if prompt_re.search(stdout) and not data:
|
|
2079
|
+
if encoding:
|
|
2080
|
+
stdout = to_native(stdout, encoding=encoding, errors=errors)
|
|
2081
|
+
return (257, stdout, "A prompt was encountered while running a command, but no input data was specified")
|
|
2082
|
+
# only break out if no pipes are left to read or
|
|
2083
|
+
# the pipes are completely read and
|
|
2084
|
+
# the process is terminated
|
|
2085
|
+
if (not events or not selector.get_map()) and cmd.poll() is not None:
|
|
2086
|
+
break
|
|
2087
|
+
# No pipes are left to read but process is not yet terminated
|
|
2088
|
+
# Only then it is safe to wait for the process to be finished
|
|
2089
|
+
# NOTE: Actually cmd.poll() is always None here if no selectors are left
|
|
2090
|
+
elif not selector.get_map() and cmd.poll() is None:
|
|
2091
|
+
cmd.wait()
|
|
2092
|
+
# The process is terminated. Since no pipes to read from are
|
|
2093
|
+
# left, there is no need to call select() again.
|
|
2094
|
+
break
|
|
2095
|
+
|
|
2096
|
+
cmd.stdout.close()
|
|
2097
|
+
cmd.stderr.close()
|
|
2098
|
+
selector.close()
|
|
2093
2099
|
|
|
2094
2100
|
rc = cmd.returncode
|
|
2095
2101
|
except (OSError, IOError) as e:
|
ansible/modules/dnf5.py
CHANGED
|
@@ -445,8 +445,8 @@ class Dnf5Module(YumDnf):
|
|
|
445
445
|
|
|
446
446
|
# done all we can do, something is just broken (auto-install isn't useful anymore with respawn, so it was removed)
|
|
447
447
|
self.module.fail_json(
|
|
448
|
-
msg="Could not import the
|
|
449
|
-
"Please install
|
|
448
|
+
msg="Could not import the libdnf5 python module using {0} ({1}). "
|
|
449
|
+
"Please install python3-libdnf5 package or ensure you have specified the "
|
|
450
450
|
"correct ansible_python_interpreter. (attempted {2})".format(
|
|
451
451
|
sys.executable, sys.version.replace("\n", ""), system_interpreters
|
|
452
452
|
),
|
|
@@ -629,11 +629,12 @@ class Dnf5Module(YumDnf):
|
|
|
629
629
|
|
|
630
630
|
if transaction.get_problems():
|
|
631
631
|
failures = []
|
|
632
|
-
for
|
|
633
|
-
if
|
|
634
|
-
|
|
632
|
+
for log_event in transaction.get_resolve_logs():
|
|
633
|
+
if log_event.get_problem() == libdnf5.base.GoalProblem_NOT_FOUND and self.state in {"install", "present", "latest"}:
|
|
634
|
+
# NOTE dnf module compat
|
|
635
|
+
failures.append("No package {} available.".format(log_event.get_spec()))
|
|
635
636
|
else:
|
|
636
|
-
failures.append(
|
|
637
|
+
failures.append(log_event.to_string())
|
|
637
638
|
|
|
638
639
|
if transaction.get_problems() & libdnf5.base.GoalProblem_SOLVER_ERROR != 0:
|
|
639
640
|
msg = "Depsolve Error occurred"
|
|
@@ -27,6 +27,7 @@ import queue
|
|
|
27
27
|
import sys
|
|
28
28
|
import threading
|
|
29
29
|
import time
|
|
30
|
+
import typing as t
|
|
30
31
|
|
|
31
32
|
from collections import deque
|
|
32
33
|
from multiprocessing import Lock
|
|
@@ -37,7 +38,7 @@ from ansible import constants as C
|
|
|
37
38
|
from ansible import context
|
|
38
39
|
from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleUndefinedVariable, AnsibleParserError
|
|
39
40
|
from ansible.executor import action_write_locks
|
|
40
|
-
from ansible.executor.play_iterator import IteratingStates
|
|
41
|
+
from ansible.executor.play_iterator import IteratingStates, PlayIterator
|
|
41
42
|
from ansible.executor.process.worker import WorkerProcess
|
|
42
43
|
from ansible.executor.task_result import TaskResult
|
|
43
44
|
from ansible.executor.task_queue_manager import CallbackSend, DisplaySend, PromptSend
|
|
@@ -506,6 +507,57 @@ class StrategyBase:
|
|
|
506
507
|
|
|
507
508
|
return task_result
|
|
508
509
|
|
|
510
|
+
def search_handlers_by_notification(self, notification: str, iterator: PlayIterator) -> t.Generator[Handler, None, None]:
|
|
511
|
+
templar = Templar(None)
|
|
512
|
+
# iterate in reversed order since last handler loaded with the same name wins
|
|
513
|
+
for handler in (h for b in reversed(iterator._play.handlers) for h in b.block if h.name):
|
|
514
|
+
if not handler.cached_name:
|
|
515
|
+
if templar.is_template(handler.name):
|
|
516
|
+
templar.available_variables = self._variable_manager.get_vars(
|
|
517
|
+
play=iterator._play,
|
|
518
|
+
task=handler,
|
|
519
|
+
_hosts=self._hosts_cache,
|
|
520
|
+
_hosts_all=self._hosts_cache_all
|
|
521
|
+
)
|
|
522
|
+
try:
|
|
523
|
+
handler.name = templar.template(handler.name)
|
|
524
|
+
except (UndefinedError, AnsibleUndefinedVariable) as e:
|
|
525
|
+
# We skip this handler due to the fact that it may be using
|
|
526
|
+
# a variable in the name that was conditionally included via
|
|
527
|
+
# set_fact or some other method, and we don't want to error
|
|
528
|
+
# out unnecessarily
|
|
529
|
+
if not handler.listen:
|
|
530
|
+
display.warning(
|
|
531
|
+
"Handler '%s' is unusable because it has no listen topics and "
|
|
532
|
+
"the name could not be templated (host-specific variables are "
|
|
533
|
+
"not supported in handler names). The error: %s" % (handler.name, to_text(e))
|
|
534
|
+
)
|
|
535
|
+
continue
|
|
536
|
+
handler.cached_name = True
|
|
537
|
+
|
|
538
|
+
# first we check with the full result of get_name(), which may
|
|
539
|
+
# include the role name (if the handler is from a role). If that
|
|
540
|
+
# is not found, we resort to the simple name field, which doesn't
|
|
541
|
+
# have anything extra added to it.
|
|
542
|
+
if notification in {
|
|
543
|
+
handler.name,
|
|
544
|
+
handler.get_name(include_role_fqcn=False),
|
|
545
|
+
handler.get_name(include_role_fqcn=True),
|
|
546
|
+
}:
|
|
547
|
+
yield handler
|
|
548
|
+
break
|
|
549
|
+
|
|
550
|
+
templar.available_variables = {}
|
|
551
|
+
for handler in (h for b in iterator._play.handlers for h in b.block):
|
|
552
|
+
if listeners := handler.listen:
|
|
553
|
+
if notification in handler.get_validated_value(
|
|
554
|
+
'listen',
|
|
555
|
+
handler.fattributes.get('listen'),
|
|
556
|
+
listeners,
|
|
557
|
+
templar,
|
|
558
|
+
):
|
|
559
|
+
yield handler
|
|
560
|
+
|
|
509
561
|
@debug_closure
|
|
510
562
|
def _process_pending_results(self, iterator, one_pass=False, max_passes=None):
|
|
511
563
|
'''
|
|
@@ -516,46 +568,6 @@ class StrategyBase:
|
|
|
516
568
|
ret_results = []
|
|
517
569
|
handler_templar = Templar(self._loader)
|
|
518
570
|
|
|
519
|
-
def search_handler_blocks_by_name(handler_name, handler_blocks):
|
|
520
|
-
# iterate in reversed order since last handler loaded with the same name wins
|
|
521
|
-
for handler_block in reversed(handler_blocks):
|
|
522
|
-
for handler_task in handler_block.block:
|
|
523
|
-
if handler_task.name:
|
|
524
|
-
try:
|
|
525
|
-
if not handler_task.cached_name:
|
|
526
|
-
if handler_templar.is_template(handler_task.name):
|
|
527
|
-
handler_templar.available_variables = self._variable_manager.get_vars(play=iterator._play,
|
|
528
|
-
task=handler_task,
|
|
529
|
-
_hosts=self._hosts_cache,
|
|
530
|
-
_hosts_all=self._hosts_cache_all)
|
|
531
|
-
handler_task.name = handler_templar.template(handler_task.name)
|
|
532
|
-
handler_task.cached_name = True
|
|
533
|
-
|
|
534
|
-
# first we check with the full result of get_name(), which may
|
|
535
|
-
# include the role name (if the handler is from a role). If that
|
|
536
|
-
# is not found, we resort to the simple name field, which doesn't
|
|
537
|
-
# have anything extra added to it.
|
|
538
|
-
candidates = (
|
|
539
|
-
handler_task.name,
|
|
540
|
-
handler_task.get_name(include_role_fqcn=False),
|
|
541
|
-
handler_task.get_name(include_role_fqcn=True),
|
|
542
|
-
)
|
|
543
|
-
|
|
544
|
-
if handler_name in candidates:
|
|
545
|
-
return handler_task
|
|
546
|
-
except (UndefinedError, AnsibleUndefinedVariable) as e:
|
|
547
|
-
# We skip this handler due to the fact that it may be using
|
|
548
|
-
# a variable in the name that was conditionally included via
|
|
549
|
-
# set_fact or some other method, and we don't want to error
|
|
550
|
-
# out unnecessarily
|
|
551
|
-
if not handler_task.listen:
|
|
552
|
-
display.warning(
|
|
553
|
-
"Handler '%s' is unusable because it has no listen topics and "
|
|
554
|
-
"the name could not be templated (host-specific variables are "
|
|
555
|
-
"not supported in handler names). The error: %s" % (handler_task.name, to_text(e))
|
|
556
|
-
)
|
|
557
|
-
continue
|
|
558
|
-
|
|
559
571
|
cur_pass = 0
|
|
560
572
|
while True:
|
|
561
573
|
try:
|
|
@@ -636,49 +648,24 @@ class StrategyBase:
|
|
|
636
648
|
result_items = [task_result._result]
|
|
637
649
|
|
|
638
650
|
for result_item in result_items:
|
|
639
|
-
if '_ansible_notify' in result_item:
|
|
640
|
-
if
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
for listening_handler_block in iterator._play.handlers:
|
|
658
|
-
for listening_handler in listening_handler_block.block:
|
|
659
|
-
listeners = getattr(listening_handler, 'listen', []) or []
|
|
660
|
-
if not listeners:
|
|
661
|
-
continue
|
|
662
|
-
|
|
663
|
-
listeners = listening_handler.get_validated_value(
|
|
664
|
-
'listen', listening_handler.fattributes.get('listen'), listeners, handler_templar
|
|
665
|
-
)
|
|
666
|
-
if handler_name not in listeners:
|
|
667
|
-
continue
|
|
668
|
-
else:
|
|
669
|
-
found = True
|
|
670
|
-
|
|
671
|
-
if listening_handler.notify_host(original_host):
|
|
672
|
-
self._tqm.send_callback('v2_playbook_on_notify', listening_handler, original_host)
|
|
673
|
-
|
|
674
|
-
# and if none were found, then we raise an error
|
|
675
|
-
if not found:
|
|
676
|
-
msg = ("The requested handler '%s' was not found in either the main handlers list nor in the listening "
|
|
677
|
-
"handlers list" % handler_name)
|
|
678
|
-
if C.ERROR_ON_MISSING_HANDLER:
|
|
679
|
-
raise AnsibleError(msg)
|
|
680
|
-
else:
|
|
681
|
-
display.warning(msg)
|
|
651
|
+
if '_ansible_notify' in result_item and task_result.is_changed():
|
|
652
|
+
# only ensure that notified handlers exist, if so save the notifications for when
|
|
653
|
+
# handlers are actually flushed so the last defined handlers are exexcuted,
|
|
654
|
+
# otherwise depending on the setting either error or warn
|
|
655
|
+
for notification in result_item['_ansible_notify']:
|
|
656
|
+
if any(self.search_handlers_by_notification(notification, iterator)):
|
|
657
|
+
iterator.add_notification(original_host.name, notification)
|
|
658
|
+
display.vv(f"Notification for handler {notification} has been saved.")
|
|
659
|
+
continue
|
|
660
|
+
|
|
661
|
+
msg = (
|
|
662
|
+
f"The requested handler '{notification}' was not found in either the main handlers"
|
|
663
|
+
" list nor in the listening handlers list"
|
|
664
|
+
)
|
|
665
|
+
if C.ERROR_ON_MISSING_HANDLER:
|
|
666
|
+
raise AnsibleError(msg)
|
|
667
|
+
else:
|
|
668
|
+
display.warning(msg)
|
|
682
669
|
|
|
683
670
|
if 'add_host' in result_item:
|
|
684
671
|
# this task added a new host (add_host module)
|
|
@@ -957,6 +944,15 @@ class StrategyBase:
|
|
|
957
944
|
elif meta_action == 'flush_handlers':
|
|
958
945
|
if _evaluate_conditional(target_host):
|
|
959
946
|
host_state = iterator.get_state_for_host(target_host.name)
|
|
947
|
+
# actually notify proper handlers based on all notifications up to this point
|
|
948
|
+
for notification in list(host_state.handler_notifications):
|
|
949
|
+
for handler in self.search_handlers_by_notification(notification, iterator):
|
|
950
|
+
if not handler.notify_host(target_host):
|
|
951
|
+
# NOTE even with notifications deduplicated this can still happen in case of handlers being
|
|
952
|
+
# notified multiple times using different names, like role name or fqcn
|
|
953
|
+
self._tqm.send_callback('v2_playbook_on_notify', handler, target_host)
|
|
954
|
+
iterator.clear_notification(target_host.name, notification)
|
|
955
|
+
|
|
960
956
|
if host_state.run_state == IteratingStates.HANDLERS:
|
|
961
957
|
raise AnsibleError('flush_handlers cannot be used as a handler')
|
|
962
958
|
if target_host.name not in self._tqm._unreachable_hosts:
|
|
@@ -35,7 +35,6 @@ from ansible import constants as C
|
|
|
35
35
|
from ansible.errors import AnsibleError, AnsibleAssertionError, AnsibleParserError
|
|
36
36
|
from ansible.executor.play_iterator import IteratingStates, FailedStates
|
|
37
37
|
from ansible.module_utils._text import to_text
|
|
38
|
-
from ansible.module_utils.parsing.convert_bool import boolean
|
|
39
38
|
from ansible.playbook.handler import Handler
|
|
40
39
|
from ansible.playbook.included_file import IncludedFile
|
|
41
40
|
from ansible.playbook.task import Task
|
|
@@ -214,10 +213,7 @@ class StrategyModule(StrategyBase):
|
|
|
214
213
|
skip_rest = True
|
|
215
214
|
break
|
|
216
215
|
|
|
217
|
-
|
|
218
|
-
setattr(task, 'run_once', boolean(templar.template(task.run_once), strict=True))
|
|
219
|
-
|
|
220
|
-
run_once = task.run_once or action and getattr(action, 'BYPASS_HOST_LOOP', False)
|
|
216
|
+
run_once = templar.template(task.run_once) or action and getattr(action, 'BYPASS_HOST_LOOP', False)
|
|
221
217
|
|
|
222
218
|
if (task.any_errors_fatal or run_once) and not task.ignore_errors:
|
|
223
219
|
any_errors_fatal = True
|
ansible/release.py
CHANGED
|
@@ -3,7 +3,7 @@ ansible/__main__.py,sha256=IvyRvY64pT0on94qCLibxgDJ0-7_2CRoaZ5kfGOl54Q,1395
|
|
|
3
3
|
ansible/constants.py,sha256=JLIDnuSz3_PbtXWsL4vnvVBbxlh3lSrJREd7T73atEI,8293
|
|
4
4
|
ansible/context.py,sha256=OzSlaA_GgGRyyf5I209sy19_eGOX6HXn441W9w_FcvU,2018
|
|
5
5
|
ansible/keyword_desc.yml,sha256=FYY0Ld1Xc3AxJ_Tefz78kRSYzIKGS8qcPtVk370J118,7367
|
|
6
|
-
ansible/release.py,sha256=
|
|
6
|
+
ansible/release.py,sha256=P-fXbdy9zVITuZdHA46usN4gSEjuWpRFm8Vf8lcZFtc,920
|
|
7
7
|
ansible/_vendor/__init__.py,sha256=wJRKH7kI9OzYVY9hgSchOsTNTmTnugpPLGYj9Y5akX0,2086
|
|
8
8
|
ansible/cli/__init__.py,sha256=ZK8bKuMmeRqeAcePriGtJ0tMuoDur3sN-ySBmOzAF3c,28687
|
|
9
9
|
ansible/cli/adhoc.py,sha256=pGW6eysaireovp4sVsUuntg-l1o7DSujuhxVhVC2zsM,8230
|
|
@@ -12,11 +12,11 @@ ansible/cli/console.py,sha256=rc-6s-Exf9b8lead40RyfugZdU1-cMoN-kA1iI8Uhs8,21941
|
|
|
12
12
|
ansible/cli/doc.py,sha256=x7LNU10RiJJejxHxbZg0xd6cdJarxTEK5LfWmQMc3y0,64153
|
|
13
13
|
ansible/cli/galaxy.py,sha256=BwKVIeErmdPIN7V77vzrGtlbO0n72_PYvh1bvVSuSLk,91107
|
|
14
14
|
ansible/cli/inventory.py,sha256=6aZ9n8GrRHVPSYbrEezfIJO62pcdH8RUNIayA-iNTKs,17693
|
|
15
|
-
ansible/cli/playbook.py,sha256=
|
|
15
|
+
ansible/cli/playbook.py,sha256=2MNTSu99nKVO7b7ZeyA0PkR5ML8kuBCeCDMjd5YPh4g,10901
|
|
16
16
|
ansible/cli/pull.py,sha256=TI3xfqcO-f8I60gRvVdiAEELghSq5Mlb8YPX6SdiitM,17010
|
|
17
17
|
ansible/cli/vault.py,sha256=8od9BPi570xO7CqiG82G5HHa_6oFRGDxlQ5bdCZkkjQ,22645
|
|
18
18
|
ansible/cli/arguments/__init__.py,sha256=CL2cOeYgVnD4r0iJTzEjjldSkJjGKPZc_t44UKSF4n8,221
|
|
19
|
-
ansible/cli/arguments/option_helpers.py,sha256=
|
|
19
|
+
ansible/cli/arguments/option_helpers.py,sha256=zu0VTuuUHCvw5X_nvcNe5qjCPmjJPNdHVmOUgDRsa7E,18107
|
|
20
20
|
ansible/cli/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
ansible/cli/scripts/ansible_connection_cli_stub.py,sha256=bpaNmBnYGiTXVtZsM-Rw-jOE0LR9M5rL9WiZdX5X4vI,13505
|
|
22
22
|
ansible/collections/__init__.py,sha256=t8x1TZiQQa9vaQAm7ECYoP3MCdzquEtAlPzG9HzEV50,814
|
|
@@ -34,10 +34,10 @@ ansible/executor/__init__.py,sha256=1lMXN1i2fFqslda4BmeI5tpYMFP95D5Wpr1AjDJi-SQ,
|
|
|
34
34
|
ansible/executor/action_write_locks.py,sha256=Up2n3cwFCr4T4IvttHpe3QOxRBF_9NgWJ1tFm9CHpfM,1915
|
|
35
35
|
ansible/executor/interpreter_discovery.py,sha256=0Pad_qYo8OSfA0U0AqHOXGt1HPKcqxBw-NfTKq4HmfQ,9926
|
|
36
36
|
ansible/executor/module_common.py,sha256=6R58IqfOLzg0aDQWRWsi0cbohWMSf_Lvdhf_5hTavWg,65820
|
|
37
|
-
ansible/executor/play_iterator.py,sha256=
|
|
37
|
+
ansible/executor/play_iterator.py,sha256=WmByZKIcBYx1gT5ybsIv9yiwkimmljR_NnbBVtYs2X8,31562
|
|
38
38
|
ansible/executor/playbook_executor.py,sha256=VQHEIvZbfOFzp388XFD0KjG0e8Ye8yuNPnnHAZmi898,15069
|
|
39
39
|
ansible/executor/stats.py,sha256=757UK8wDzLCXq4ltI9PqpoMNAdtRsd9D9-GS-5Al_Hs,3264
|
|
40
|
-
ansible/executor/task_executor.py,sha256=
|
|
40
|
+
ansible/executor/task_executor.py,sha256=8LUxmZs5Ak3ciU_LayWB3LbCrzu4VkpCGnRkKGBCa-U,60003
|
|
41
41
|
ansible/executor/task_queue_manager.py,sha256=DxmfDMeWAClNvp85qvc1uATor-hilv8KsYno3Pl_Ztk,18758
|
|
42
42
|
ansible/executor/task_result.py,sha256=DvshMci5i9-qCXs0m_vScSa6BJMbPwwNQBV7L2DTCzE,5748
|
|
43
43
|
ansible/executor/discovery/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -56,11 +56,11 @@ ansible/executor/powershell/module_wrapper.ps1,sha256=JkCL_6aAZoXgQmFjU5hgEzTCMd
|
|
|
56
56
|
ansible/executor/process/__init__.py,sha256=1lMXN1i2fFqslda4BmeI5tpYMFP95D5Wpr1AjDJi-SQ,833
|
|
57
57
|
ansible/executor/process/worker.py,sha256=k5aTaoCUu_ZUmdnPgIGqGsD7013_o3srzwxNpAjD1SY,9415
|
|
58
58
|
ansible/galaxy/__init__.py,sha256=_ccTedn8dUGGtkmHcQLIkeje_YD0TYSXlvCl1AOY5fE,2533
|
|
59
|
-
ansible/galaxy/api.py,sha256=
|
|
59
|
+
ansible/galaxy/api.py,sha256=deSYsFinaJodT2Y9-XnOerWIwYY8V2AWQ_9kZI0pWCE,39872
|
|
60
60
|
ansible/galaxy/role.py,sha256=roEhuloz2-UHLdNwK7pqRCYsOLpu_Xg6sC_nyE5A30w,19086
|
|
61
61
|
ansible/galaxy/token.py,sha256=K0dAwD3Fjkn3Zs2N9sG98UesSWfAukie47QGyYpIf0M,6167
|
|
62
62
|
ansible/galaxy/user_agent.py,sha256=x7cJzzpnTngHcwqSUd2hg0i28Dv0tbAyBdke5CSiNhM,813
|
|
63
|
-
ansible/galaxy/collection/__init__.py,sha256=
|
|
63
|
+
ansible/galaxy/collection/__init__.py,sha256=ImRjgWK5DTAI1BsIYwK3p4c7E5wkrtqTZnfPU1UGGPE,77683
|
|
64
64
|
ansible/galaxy/collection/concrete_artifact_manager.py,sha256=Zw59WkR7kHaN1GnJDmzfiTSH31eZ4dEBhyzeOrvT5zs,28978
|
|
65
65
|
ansible/galaxy/collection/galaxy_api_proxy.py,sha256=HWnMiWIEt1YW7srbnFXjRsgpSC-3Iwj7-wkrkmVtXkA,7972
|
|
66
66
|
ansible/galaxy/collection/gpg.py,sha256=1wk22RJnX--FsB-4h_EdaT05PWlx9AMxhfH3H7db1i4,7312
|
|
@@ -126,9 +126,9 @@ ansible/galaxy/data/network/tests/inventory,sha256=4CIzgZsaCYREEFSRkYE_fMe6Ng8hK
|
|
|
126
126
|
ansible/galaxy/data/network/tests/test.yml.j2,sha256=rjvtKhlkGT_CI_dgBLHX14eI41S6jFPypqESN8Uztgg,199
|
|
127
127
|
ansible/galaxy/data/network/vars/main.yml.j2,sha256=3qtsgmeZTZ37OdmQNpmI9R6XxOEzJcOhjjGQYGFb85w,36
|
|
128
128
|
ansible/galaxy/dependency_resolution/__init__.py,sha256=HDqJKqsDP4UHTCnoGq6c3rGpVwOI2DdYaH-4s7tkL0A,2196
|
|
129
|
-
ansible/galaxy/dependency_resolution/dataclasses.py,sha256=
|
|
129
|
+
ansible/galaxy/dependency_resolution/dataclasses.py,sha256=08Sjja6PupKzK0WdmRTRjmIt8Vo4e8seKR3TOuQNSGA,22456
|
|
130
130
|
ansible/galaxy/dependency_resolution/errors.py,sha256=3YatCcrKVHeXTkMQBEFnTEHZBUTpd9ySEJ4r-3MbEak,750
|
|
131
|
-
ansible/galaxy/dependency_resolution/providers.py,sha256=
|
|
131
|
+
ansible/galaxy/dependency_resolution/providers.py,sha256=9p0-DGy_imfAQJ1xjo_tO_CwmfW6TVcvC7NM7G3Z4B0,24946
|
|
132
132
|
ansible/galaxy/dependency_resolution/reporters.py,sha256=q-jyfsRu5qve8nikZ0_cBQar3dOX4_WMSAUXZd9xlqg,687
|
|
133
133
|
ansible/galaxy/dependency_resolution/resolvers.py,sha256=XHBYqltTS7AV4mSiAF2ImXP53W6Yadog_rrqKxrpZU0,676
|
|
134
134
|
ansible/galaxy/dependency_resolution/versioning.py,sha256=fGmuNhgwHaCKfHb6uH91A7ztuB86nDgiMW3htSI06DY,1779
|
|
@@ -140,9 +140,9 @@ ansible/inventory/host.py,sha256=wXJp6kpSaZtDr4JNsgdAuhi5MzQ9LTQzaAH10zoVbIA,505
|
|
|
140
140
|
ansible/inventory/manager.py,sha256=tGwhBR6poLuG_i4jZ5RGOG-rH4gu4DBfT0-4iLLZZMs,29490
|
|
141
141
|
ansible/module_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
142
142
|
ansible/module_utils/_text.py,sha256=F_YfeaxhwmTI16HICAzQS9ZmlKgBDdQ4mqR-Kh--okg,597
|
|
143
|
-
ansible/module_utils/ansible_release.py,sha256=
|
|
143
|
+
ansible/module_utils/ansible_release.py,sha256=P-fXbdy9zVITuZdHA46usN4gSEjuWpRFm8Vf8lcZFtc,920
|
|
144
144
|
ansible/module_utils/api.py,sha256=BTo7stVOANbtd-ngZslaqx70r9t5gfvo44cKyu5SFjU,5837
|
|
145
|
-
ansible/module_utils/basic.py,sha256=
|
|
145
|
+
ansible/module_utils/basic.py,sha256=KwFTKMws6bPfSEP1fIc7Srvan6b34EEbZ5nedfhhiTw,87493
|
|
146
146
|
ansible/module_utils/connection.py,sha256=XHxMlyAdwLiXDSo8jBMkV61-lz_0FDJUYH1B152UGJU,8430
|
|
147
147
|
ansible/module_utils/errors.py,sha256=LYv9EWkvBRAmYW6qQY4Vz2hMULqkAGkMG2Opf86Ep4M,3396
|
|
148
148
|
ansible/module_utils/json_utils.py,sha256=IR_bSwrYK1Ie36dCQSHyN4mahkrZkzIIkH3DdwtIi6Q,3456
|
|
@@ -292,7 +292,7 @@ ansible/modules/deb822_repository.py,sha256=9XBk-w4zFTXi2A3hkivEMTbUnZSKqCmxh2wp
|
|
|
292
292
|
ansible/modules/debconf.py,sha256=uCnyPYGhNPHq7cBUVzfxHp_5-N8RauBL8zlc19NHQic,7732
|
|
293
293
|
ansible/modules/debug.py,sha256=_wSyvOmlhpDeiD40oC_uwbRisSJLVbfIbEN98TJxVfs,2958
|
|
294
294
|
ansible/modules/dnf.py,sha256=OqX1KZlkop-pW5ye1kSUQqbn4y41wgDcVTdSf-VPJX0,59955
|
|
295
|
-
ansible/modules/dnf5.py,sha256=
|
|
295
|
+
ansible/modules/dnf5.py,sha256=JlUeTq2iV8UNkJ5msd25bCJOFszeNOrMD5ihVDR76wQ,26452
|
|
296
296
|
ansible/modules/dpkg_selections.py,sha256=X3owrI7nDVM2tYOiVgLPkzBEZxfwkfBuomjbAfzG1GQ,2408
|
|
297
297
|
ansible/modules/expect.py,sha256=pdPmPmPLc0HbfcVTIIJFZq2NwcTwhP-O_S3bRwSpG70,8518
|
|
298
298
|
ansible/modules/fail.py,sha256=AI4gNQC7E5U2Vs7QiIlFk7PcWozN0tXtiVPa_LJrQpk,1710
|
|
@@ -580,11 +580,11 @@ ansible/plugins/shell/__init__.py,sha256=Rj-H2AhfBZAWZ_Hy8D-1ypvjXTMP3pTbByOYlPM
|
|
|
580
580
|
ansible/plugins/shell/cmd.py,sha256=fswLtU2XVNb1T5tF0BIM9msViObs5dXzo9k6sNN4dao,2207
|
|
581
581
|
ansible/plugins/shell/powershell.py,sha256=qnpEZ9uOJF_4gExheFCAYT_tSR_KQtQeilU1WKXJymA,11376
|
|
582
582
|
ansible/plugins/shell/sh.py,sha256=1nhiMv0_c8zu2MaDHvOCr--dG8b-iUVEPPnpMh_Hx8I,3952
|
|
583
|
-
ansible/plugins/strategy/__init__.py,sha256=
|
|
583
|
+
ansible/plugins/strategy/__init__.py,sha256=ecMkuKVsIZyZHJzcPiFqq-RU74_Ub8bYptw5njge9uI,56268
|
|
584
584
|
ansible/plugins/strategy/debug.py,sha256=GxUS0bSiaWInIK8zgB7rMREEqvgrZhVlFUzOCJtnjFo,1258
|
|
585
585
|
ansible/plugins/strategy/free.py,sha256=eXAvxTFloyb5x6VYANXDMdloWUYxMhbPr1lyY6UPBps,15773
|
|
586
586
|
ansible/plugins/strategy/host_pinned.py,sha256=3-q5l-tpheMlU-BXGm6ZQNgHvQv5IMvOCDZBLibl1L4,1959
|
|
587
|
-
ansible/plugins/strategy/linear.py,sha256=
|
|
587
|
+
ansible/plugins/strategy/linear.py,sha256=pTdVG7cWrcDK1pXViLmiGtEPKMrOcgBWaGUENkd2RYg,20172
|
|
588
588
|
ansible/plugins/terminal/__init__.py,sha256=zGIuxlntye0FHk6Zbl57snHB5d3-w_pr0osRpCRy4co,4438
|
|
589
589
|
ansible/plugins/test/__init__.py,sha256=6DY18LxzSdtO7-fDS6957bo61fg-xG3TDWvtFkhGYOQ,471
|
|
590
590
|
ansible/plugins/test/abs.yml,sha256=-caY4vAMXbhukUTdMQvBa2WYvg6w1AWr8raEfAv0qa8,764
|
|
@@ -682,11 +682,11 @@ ansible/vars/hostvars.py,sha256=dg3jpVmNwSg8EJ4SIvYGT80uxMgRtrOW6vvtDfrQzDU,5152
|
|
|
682
682
|
ansible/vars/manager.py,sha256=qsF6PgAYcon5n7HmXG56P4pmKLyrniuFpAtKWnNaFpw,38284
|
|
683
683
|
ansible/vars/plugins.py,sha256=B7L3fXoSOoBZSXqJ2ulk0adx1g5SpAb8BxyLGPNA7d4,4695
|
|
684
684
|
ansible/vars/reserved.py,sha256=FBD7n2dnA0CW4I0J1LtWwk2hQqvGW0KTRPcxaRtMKWo,2615
|
|
685
|
-
ansible_core-2.15.
|
|
685
|
+
ansible_core-2.15.0b3.data/scripts/ansible-test,sha256=CYIYL99IxWdVTtDIj3avilIJXhGAmtjuKPPWNuLWuc8,1690
|
|
686
686
|
ansible_test/__init__.py,sha256=6e721yAyyyocRKzbCKtQXloAfFP7Aqv0L3zG70uh-4A,190
|
|
687
687
|
ansible_test/_data/ansible.cfg,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
688
688
|
ansible_test/_data/coveragerc,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
689
|
-
ansible_test/_data/completion/docker.txt,sha256=
|
|
689
|
+
ansible_test/_data/completion/docker.txt,sha256=AnhWoIqdBVSn-mPiZiY_87U1MXffmbRBXyTiTuQqCqQ,822
|
|
690
690
|
ansible_test/_data/completion/network.txt,sha256=_-mi013-JeufshKMUmykkOmZPw1cVbakIMaAuweHet8,198
|
|
691
691
|
ansible_test/_data/completion/remote.txt,sha256=q_sCM75PX_Q4lxz4SFRGQZwm885FehFitb9eROgGqcg,1044
|
|
692
692
|
ansible_test/_data/completion/windows.txt,sha256=k02uwXJ2i7cVHZnXS4HRFlvK5QU5BzAV291Cw53FlXA,226
|
|
@@ -726,8 +726,8 @@ ansible_test/_data/requirements/sanity.pylint.in,sha256=CqgyF_s4K3o41RSc6KZVicBl
|
|
|
726
726
|
ansible_test/_data/requirements/sanity.pylint.txt,sha256=VzyfimUEUboaL0g7XgPUx9pS6vf_dE9IQepRY8rradU,293
|
|
727
727
|
ansible_test/_data/requirements/sanity.runtime-metadata.in,sha256=QzOCB5QxVHYuXHXQvkUsa5MwRQzPhI-ZDD-M2htj36s,18
|
|
728
728
|
ansible_test/_data/requirements/sanity.runtime-metadata.txt,sha256=K0Kesnp-H7naekb-cXc0oMAvqNKLwUATh_DgwcIdL3Q,148
|
|
729
|
-
ansible_test/_data/requirements/sanity.validate-modules.in,sha256=
|
|
730
|
-
ansible_test/_data/requirements/sanity.validate-modules.txt,sha256=
|
|
729
|
+
ansible_test/_data/requirements/sanity.validate-modules.in,sha256=XDFnVqB9yhfWhwIOBkTw-Dw8hpMnxqO1hXtettetUIU,117
|
|
730
|
+
ansible_test/_data/requirements/sanity.validate-modules.txt,sha256=WzuOlP9NzdxowbQ1AcL09yt7zQ71n0R6GmcLyJ6cb20,209
|
|
731
731
|
ansible_test/_data/requirements/sanity.yamllint.in,sha256=ivPsPeZUDHOuLbd603ZxKClOQ1bATyMYNx3GfHQmt4g,9
|
|
732
732
|
ansible_test/_data/requirements/sanity.yamllint.txt,sha256=t_5YBcbqw7-c3GCbTE3jh3e5M4YfRENpG0eFtJYpY-A,147
|
|
733
733
|
ansible_test/_data/requirements/units.txt,sha256=ah91xwwRFeY_fpi0WdRGw9GqEiAjm9BbVbnwTrdzn2g,125
|
|
@@ -791,7 +791,7 @@ ansible_test/_internal/cli/environments.py,sha256=M5Vcolpz-GBBUVlv8gVBl7_54RTSJL
|
|
|
791
791
|
ansible_test/_internal/cli/epilog.py,sha256=kzCYlmqDccMZnSCV57iXUITo6Z9FMMUIagjWJHHA0yY,658
|
|
792
792
|
ansible_test/_internal/cli/argparsing/__init__.py,sha256=ravr0Yv7tEOBFv2s2DuZtEl9BPAQNy-KMKcJNSk4dmc,8922
|
|
793
793
|
ansible_test/_internal/cli/argparsing/actions.py,sha256=VplAf5K9G-loJmLXMAZwbRbIsuFJ-yDrRrP4El5p4RM,606
|
|
794
|
-
ansible_test/_internal/cli/argparsing/argcompletion.py,sha256=
|
|
794
|
+
ansible_test/_internal/cli/argparsing/argcompletion.py,sha256=zOZtYVDkqWIdbmuASkyJuMUKrFh4w3MJzYS2O9DoIQA,5166
|
|
795
795
|
ansible_test/_internal/cli/argparsing/parsers.py,sha256=i7bEPWy7q2mcgiBb3sZ0EN5wQ0G5SetOMQKsOMSsw4M,21490
|
|
796
796
|
ansible_test/_internal/cli/commands/__init__.py,sha256=d8FNvVbSVR2JlnyDUxnS-lZDIQqbdEEPU0cqJA9663Q,5436
|
|
797
797
|
ansible_test/_internal/cli/commands/env.py,sha256=-3zKICX4STeo_lObMh6EmvBy3s2ORL53idmLKN2vCHk,1397
|
|
@@ -938,7 +938,7 @@ ansible_test/_util/controller/sanity/code-smell/use-compat-six.py,sha256=CkYomOt
|
|
|
938
938
|
ansible_test/_util/controller/sanity/code-smell/changelog/sphinx.py,sha256=M3aEK_XugBtVJjfUZbeoVc10hzRylxRxNfEiNq1JVWQ,193
|
|
939
939
|
ansible_test/_util/controller/sanity/integration-aliases/yaml_to_json.py,sha256=qxXHZboRVEqISZYOIXrutsAgobEyh6fiUibk133fzhI,299
|
|
940
940
|
ansible_test/_util/controller/sanity/mypy/ansible-core.ini,sha256=B13dYyd5PGoN-BrFShPMhGBCGbV2oiTsBD8TdRDAh3Q,2327
|
|
941
|
-
ansible_test/_util/controller/sanity/mypy/ansible-test.ini,sha256=
|
|
941
|
+
ansible_test/_util/controller/sanity/mypy/ansible-test.ini,sha256=lbBGGRhM-sL7iUdt5f0ctGSAvPmjCXIFeogm3Z22qkA,908
|
|
942
942
|
ansible_test/_util/controller/sanity/mypy/modules.ini,sha256=48N2I3ubw3yAuE8layHQ_d0CTfH_eATuXt-K5Bq-ifw,1694
|
|
943
943
|
ansible_test/_util/controller/sanity/pep8/current-ignore.txt,sha256=9VSaFOsdxN4_8GJVhnmpl5kXos2TPU3M08eC_NRI2Ks,196
|
|
944
944
|
ansible_test/_util/controller/sanity/pslint/pslint.ps1,sha256=h0fLdkwF7JhGGjApvqAsCU87BKy0E_UiFJ_O7MARz6U,1089
|
|
@@ -954,10 +954,10 @@ ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py,sha256=WGG1z4M3N
|
|
|
954
954
|
ansible_test/_util/controller/sanity/shellcheck/exclude.txt,sha256=-idybvpZeOHVfR8usoyCNdNLD5WpaKQeP9mgzganMpQ,21
|
|
955
955
|
ansible_test/_util/controller/sanity/validate-modules/validate.py,sha256=jpNOhA5qJ5LdlWlSOJoJyTUh9H1tepjcSYZXeHdhJRY,114
|
|
956
956
|
ansible_test/_util/controller/sanity/validate-modules/validate_modules/__init__.py,sha256=CRUAj-k-zJye4RAGZ8eR9HvP6weM6VKTwGmFYpI_0Bw,816
|
|
957
|
-
ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py,sha256=
|
|
957
|
+
ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py,sha256=m5vrFrpIX1XVT6vNUZU9gLC2RHWAhNeNGnXVBBSXokY,114134
|
|
958
958
|
ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py,sha256=TlRRjgB2ICrN_4bmQe4MgCTlgMn_su_9Iqi_l5t5OWQ,6611
|
|
959
959
|
ansible_test/_util/controller/sanity/validate-modules/validate_modules/ps_argspec.ps1,sha256=wteIiuD7-UOEGkjdUArKqVVWBpa7A7FU_WwDuMtR2mY,4139
|
|
960
|
-
ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py,sha256=
|
|
960
|
+
ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py,sha256=uXiiQMOv6ej1_AYdP-edKRhIaQ51d24FwwtZLHmDJjA,36172
|
|
961
961
|
ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py,sha256=9AKO67hQe1mEzyHUbKcCfe4f5ScKEPjWjvSG75HUFWs,7037
|
|
962
962
|
ansible_test/_util/controller/sanity/yamllint/yamllinter.py,sha256=TW7moUDCDO2QnlzvNi_MJypQxnKV49AC7QVWaeMKLU4,8369
|
|
963
963
|
ansible_test/_util/controller/sanity/yamllint/config/default.yml,sha256=19ITqd_UW6PVgoxX0C_N65x2zp4L5zQ5xWJ0Y0p4BI4,534
|
|
@@ -999,9 +999,9 @@ ansible_test/config/cloud-config-vultr.ini.template,sha256=XLKHk3lg_8ReQMdWfZzhh
|
|
|
999
999
|
ansible_test/config/config.yml,sha256=wb3knoBmZewG3GWOMnRHoVPQWW4vPixKLPMNS6vJmTc,2620
|
|
1000
1000
|
ansible_test/config/inventory.networking.template,sha256=bFNSk8zNQOaZ_twaflrY0XZ9mLwUbRLuNT0BdIFwvn4,1335
|
|
1001
1001
|
ansible_test/config/inventory.winrm.template,sha256=1QU8W-GFLnYEw8yY9bVIvUAVvJYPM3hyoijf6-M7T00,1098
|
|
1002
|
-
ansible_core-2.15.
|
|
1003
|
-
ansible_core-2.15.
|
|
1004
|
-
ansible_core-2.15.
|
|
1005
|
-
ansible_core-2.15.
|
|
1006
|
-
ansible_core-2.15.
|
|
1007
|
-
ansible_core-2.15.
|
|
1002
|
+
ansible_core-2.15.0b3.dist-info/COPYING,sha256=CuBIWlvTemPmNgNZZBfk6w5lMzT6bH-TLKOg6F1K8ic,35148
|
|
1003
|
+
ansible_core-2.15.0b3.dist-info/METADATA,sha256=XrERIJGjWiZy6gmN7oZxVTmEko90rTP5JsgV9L17KAI,7506
|
|
1004
|
+
ansible_core-2.15.0b3.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
1005
|
+
ansible_core-2.15.0b3.dist-info/entry_points.txt,sha256=0mpmsrIhODChxKl3eS-NcVQCaMetBn8KdPLtVxQgR64,453
|
|
1006
|
+
ansible_core-2.15.0b3.dist-info/top_level.txt,sha256=IFbRLjAvih1DYzJWg3_F6t4sCzEMxRO7TOMNs6GkYHo,21
|
|
1007
|
+
ansible_core-2.15.0b3.dist-info/RECORD,,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
base image=quay.io/ansible/base-test-container:4.1.0 python=3.11,2.7,3.5,3.6,3.7,3.8,3.9,3.10
|
|
2
|
-
default image=quay.io/ansible/default-test-container:7.
|
|
3
|
-
default image=quay.io/ansible/ansible-core-test-container:7.
|
|
2
|
+
default image=quay.io/ansible/default-test-container:7.11.0 python=3.11,2.7,3.5,3.6,3.7,3.8,3.9,3.10 context=collection
|
|
3
|
+
default image=quay.io/ansible/ansible-core-test-container:7.11.0 python=3.11,2.7,3.5,3.6,3.7,3.8,3.9,3.10 context=ansible-core
|
|
4
4
|
alpine3 image=quay.io/ansible/alpine3-test-container:5.0.0 python=3.10 cgroup=none audit=none
|
|
5
5
|
centos7 image=quay.io/ansible/centos7-test-container:5.0.0 python=2.7 cgroup=v1-only
|
|
6
6
|
fedora37 image=quay.io/ansible/fedora37-test-container:5.0.0 python=3.11
|
|
@@ -17,10 +17,19 @@ class Substitute:
|
|
|
17
17
|
try:
|
|
18
18
|
import argcomplete
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
try:
|
|
21
|
+
# argcomplete 3+
|
|
22
|
+
# see: https://github.com/kislyuk/argcomplete/commit/bd781cb08512b94966312377186ebc5550f46ae0
|
|
23
|
+
from argcomplete.finders import (
|
|
24
|
+
CompletionFinder,
|
|
25
|
+
default_validator,
|
|
26
|
+
)
|
|
27
|
+
except ImportError:
|
|
28
|
+
# argcomplete <3
|
|
29
|
+
from argcomplete import (
|
|
30
|
+
CompletionFinder,
|
|
31
|
+
default_validator,
|
|
32
|
+
)
|
|
24
33
|
|
|
25
34
|
warn = argcomplete.warn # pylint: disable=invalid-name
|
|
26
35
|
except ImportError:
|
|
@@ -72,7 +81,13 @@ class CompType(enum.Enum):
|
|
|
72
81
|
def register_safe_action(action_type: t.Type[argparse.Action]) -> None:
|
|
73
82
|
"""Register the given action as a safe action for argcomplete to use during completion if it is not already registered."""
|
|
74
83
|
if argcomplete and action_type not in argcomplete.safe_actions:
|
|
75
|
-
argcomplete.safe_actions
|
|
84
|
+
if isinstance(argcomplete.safe_actions, set):
|
|
85
|
+
# argcomplete 3+
|
|
86
|
+
# see: https://github.com/kislyuk/argcomplete/commit/bd781cb08512b94966312377186ebc5550f46ae0
|
|
87
|
+
argcomplete.safe_actions.add(action_type)
|
|
88
|
+
else:
|
|
89
|
+
# argcomplete <3
|
|
90
|
+
argcomplete.safe_actions += (action_type,)
|
|
76
91
|
|
|
77
92
|
|
|
78
93
|
def get_comp_type() -> t.Optional[CompType]:
|
|
@@ -33,6 +33,9 @@ from collections.abc import Mapping
|
|
|
33
33
|
from contextlib import contextmanager
|
|
34
34
|
from fnmatch import fnmatch
|
|
35
35
|
|
|
36
|
+
from antsibull_docs_parser import dom
|
|
37
|
+
from antsibull_docs_parser.parser import parse, Context
|
|
38
|
+
|
|
36
39
|
import yaml
|
|
37
40
|
|
|
38
41
|
from voluptuous.humanize import humanize_error
|
|
@@ -79,10 +82,6 @@ from .schema import (
|
|
|
79
82
|
ansible_module_kwargs_schema,
|
|
80
83
|
doc_schema,
|
|
81
84
|
return_schema,
|
|
82
|
-
_SEM_OPTION_NAME,
|
|
83
|
-
_SEM_RET_VALUE,
|
|
84
|
-
_check_sem_quoting,
|
|
85
|
-
_parse_prefix,
|
|
86
85
|
)
|
|
87
86
|
|
|
88
87
|
from .utils import CaptureStd, NoArgsAnsibleModule, compare_unordered_lists, parse_yaml, parse_isodate
|
|
@@ -1164,39 +1163,31 @@ class ModuleValidator(Validator):
|
|
|
1164
1163
|
|
|
1165
1164
|
return doc_info, doc
|
|
1166
1165
|
|
|
1167
|
-
def _check_sem_option(self,
|
|
1168
|
-
|
|
1169
|
-
content = _check_sem_quoting(directive, content)
|
|
1170
|
-
plugin_fqcn, plugin_type, option_link, option, value = _parse_prefix(directive, content)
|
|
1171
|
-
except Exception:
|
|
1172
|
-
# Validation errors have already been covered in the schema check
|
|
1166
|
+
def _check_sem_option(self, part: dom.OptionNamePart, current_plugin: dom.PluginIdentifier) -> None:
|
|
1167
|
+
if part.plugin is None or part.plugin != current_plugin:
|
|
1173
1168
|
return
|
|
1174
|
-
if
|
|
1169
|
+
if part.entrypoint is not None:
|
|
1175
1170
|
return
|
|
1176
|
-
if tuple(
|
|
1171
|
+
if tuple(part.link) not in self._all_options:
|
|
1177
1172
|
self.reporter.error(
|
|
1178
1173
|
path=self.object_path,
|
|
1179
1174
|
code='invalid-documentation-markup',
|
|
1180
|
-
msg='Directive "%s" contains a non-existing option "%s"' % (
|
|
1175
|
+
msg='Directive "%s" contains a non-existing option "%s"' % (part.source, part.name)
|
|
1181
1176
|
)
|
|
1182
1177
|
|
|
1183
|
-
def _check_sem_return_value(self,
|
|
1184
|
-
|
|
1185
|
-
content = _check_sem_quoting(directive, content)
|
|
1186
|
-
plugin_fqcn, plugin_type, rv_link, rv, value = _parse_prefix(directive, content)
|
|
1187
|
-
except Exception:
|
|
1188
|
-
# Validation errors have already been covered in the schema check
|
|
1178
|
+
def _check_sem_return_value(self, part: dom.ReturnValuePart, current_plugin: dom.PluginIdentifier) -> None:
|
|
1179
|
+
if part.plugin is None or part.plugin != current_plugin:
|
|
1189
1180
|
return
|
|
1190
|
-
if
|
|
1181
|
+
if part.entrypoint is not None:
|
|
1191
1182
|
return
|
|
1192
|
-
if tuple(
|
|
1183
|
+
if tuple(part.link) not in self._all_return_values:
|
|
1193
1184
|
self.reporter.error(
|
|
1194
1185
|
path=self.object_path,
|
|
1195
1186
|
code='invalid-documentation-markup',
|
|
1196
|
-
msg='Directive "%s" contains a non-existing return value "%s"' % (
|
|
1187
|
+
msg='Directive "%s" contains a non-existing return value "%s"' % (part.source, part.name)
|
|
1197
1188
|
)
|
|
1198
1189
|
|
|
1199
|
-
def _validate_semantic_markup(self, object):
|
|
1190
|
+
def _validate_semantic_markup(self, object) -> None:
|
|
1200
1191
|
# Make sure we operate on strings
|
|
1201
1192
|
if is_iterable(object):
|
|
1202
1193
|
for entry in object:
|
|
@@ -1205,10 +1196,19 @@ class ModuleValidator(Validator):
|
|
|
1205
1196
|
if not isinstance(object, string_types):
|
|
1206
1197
|
return
|
|
1207
1198
|
|
|
1208
|
-
|
|
1209
|
-
self.
|
|
1210
|
-
|
|
1211
|
-
|
|
1199
|
+
if self.collection:
|
|
1200
|
+
fqcn = f'{self.collection_name}.{self.name}'
|
|
1201
|
+
else:
|
|
1202
|
+
fqcn = f'ansible.builtin.{self.name}'
|
|
1203
|
+
current_plugin = dom.PluginIdentifier(fqcn=fqcn, type=self.plugin_type)
|
|
1204
|
+
for par in parse(object, Context(current_plugin=current_plugin), errors='message', add_source=True):
|
|
1205
|
+
for part in par:
|
|
1206
|
+
# Errors are already covered during schema validation, we only check for option and
|
|
1207
|
+
# return value references
|
|
1208
|
+
if part.type == dom.PartType.OPTION_NAME:
|
|
1209
|
+
self._check_sem_option(part, current_plugin)
|
|
1210
|
+
if part.type == dom.PartType.RETURN_VALUE:
|
|
1211
|
+
self._check_sem_return_value(part, current_plugin)
|
|
1212
1212
|
|
|
1213
1213
|
def _validate_semantic_markup_collect(self, destination, sub_key, data, all_paths):
|
|
1214
1214
|
if not isinstance(data, dict):
|
|
@@ -11,7 +11,7 @@ from ansible.module_utils.compat.version import StrictVersion
|
|
|
11
11
|
from functools import partial
|
|
12
12
|
from urllib.parse import urlparse
|
|
13
13
|
|
|
14
|
-
from voluptuous import ALLOW_EXTRA, PREVENT_EXTRA, All, Any, Invalid, Length, Required, Schema, Self, ValueInvalid, Exclusive
|
|
14
|
+
from voluptuous import ALLOW_EXTRA, PREVENT_EXTRA, All, Any, Invalid, Length, MultipleInvalid, Required, Schema, Self, ValueInvalid, Exclusive
|
|
15
15
|
from ansible.constants import DOCUMENTABLE_PLUGINS
|
|
16
16
|
from ansible.module_utils.six import string_types
|
|
17
17
|
from ansible.module_utils.common.collections import is_iterable
|
|
@@ -20,6 +20,9 @@ from ansible.parsing.quoting import unquote
|
|
|
20
20
|
from ansible.utils.version import SemanticVersion
|
|
21
21
|
from ansible.release import __version__
|
|
22
22
|
|
|
23
|
+
from antsibull_docs_parser import dom
|
|
24
|
+
from antsibull_docs_parser.parser import parse, Context
|
|
25
|
+
|
|
23
26
|
from .utils import parse_isodate
|
|
24
27
|
|
|
25
28
|
list_string_types = list(string_types)
|
|
@@ -81,57 +84,8 @@ def date(error_code=None):
|
|
|
81
84
|
return Any(isodate, error_code=error_code)
|
|
82
85
|
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
_LINK = re.compile(r"\bL\(([^)]+)\)")
|
|
87
|
-
_URL = re.compile(r"\bU\(([^)]+)\)")
|
|
88
|
-
_REF = re.compile(r"\bR\(([^)]+)\)")
|
|
89
|
-
|
|
90
|
-
_SEM_PARAMETER_STRING = r"\(((?:[^\\)]+|\\.)+)\)"
|
|
91
|
-
_SEM_OPTION_NAME = re.compile(r"\bO" + _SEM_PARAMETER_STRING)
|
|
92
|
-
_SEM_OPTION_VALUE = re.compile(r"\bV" + _SEM_PARAMETER_STRING)
|
|
93
|
-
_SEM_ENV_VARIABLE = re.compile(r"\bE" + _SEM_PARAMETER_STRING)
|
|
94
|
-
_SEM_RET_VALUE = re.compile(r"\bRV" + _SEM_PARAMETER_STRING)
|
|
95
|
-
|
|
96
|
-
_UNESCAPE = re.compile(r"\\(.)")
|
|
97
|
-
_CONTENT_LINK_SPLITTER_RE = re.compile(r'(?:\[[^\]]*\])?\.')
|
|
98
|
-
_CONTENT_LINK_END_STUB_RE = re.compile(r'\[[^\]]*\]$')
|
|
99
|
-
_FQCN_TYPE_PREFIX_RE = re.compile(r'^([^.]+\.[^.]+\.[^#]+)#([a-z]+):(.*)$')
|
|
100
|
-
_IGNORE_MARKER = 'ignore:'
|
|
101
|
-
_IGNORE_STRING = '(ignore)'
|
|
102
|
-
|
|
103
|
-
_VALID_PLUGIN_TYPES = set(DOCUMENTABLE_PLUGINS)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def _check_module_link(directive, content):
|
|
107
|
-
if not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(content):
|
|
108
|
-
raise _add_ansible_error_code(
|
|
109
|
-
Invalid('Directive "%s" must contain a FQCN' % directive), 'invalid-documentation-markup')
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def _check_plugin_link(directive, content):
|
|
113
|
-
if '#' not in content:
|
|
114
|
-
raise _add_ansible_error_code(
|
|
115
|
-
Invalid('Directive "%s" must contain a "#"' % directive), 'invalid-documentation-markup')
|
|
116
|
-
plugin_fqcn, plugin_type = content.split('#', 1)
|
|
117
|
-
if not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(plugin_fqcn):
|
|
118
|
-
raise _add_ansible_error_code(
|
|
119
|
-
Invalid('Directive "%s" must contain a FQCN; found "%s"' % (directive, plugin_fqcn)),
|
|
120
|
-
'invalid-documentation-markup')
|
|
121
|
-
if plugin_type not in _VALID_PLUGIN_TYPES:
|
|
122
|
-
raise _add_ansible_error_code(
|
|
123
|
-
Invalid('Directive "%s" must contain a valid plugin type; found "%s"' % (directive, plugin_type)),
|
|
124
|
-
'invalid-documentation-markup')
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def _check_link(directive, content):
|
|
128
|
-
if ',' not in content:
|
|
129
|
-
raise _add_ansible_error_code(
|
|
130
|
-
Invalid('Directive "%s" must contain a comma' % directive), 'invalid-documentation-markup')
|
|
131
|
-
idx = content.rindex(',')
|
|
132
|
-
title = content[:idx]
|
|
133
|
-
url = content[idx + 1:].lstrip(' ')
|
|
134
|
-
_check_url(directive, url)
|
|
87
|
+
# Roles can also be referenced by semantic markup
|
|
88
|
+
_VALID_PLUGIN_TYPES = set(DOCUMENTABLE_PLUGINS + ('role', ))
|
|
135
89
|
|
|
136
90
|
|
|
137
91
|
def _check_url(directive, content):
|
|
@@ -139,67 +93,10 @@ def _check_url(directive, content):
|
|
|
139
93
|
parsed_url = urlparse(content)
|
|
140
94
|
if parsed_url.scheme not in ('', 'http', 'https'):
|
|
141
95
|
raise ValueError('Schema must be HTTP, HTTPS, or not specified')
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
def _check_ref(directive, content):
|
|
148
|
-
if ',' not in content:
|
|
149
|
-
raise _add_ansible_error_code(
|
|
150
|
-
Invalid('Directive "%s" must contain a comma' % directive), 'invalid-documentation-markup')
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def _check_sem_quoting(directive, content):
|
|
154
|
-
for m in _UNESCAPE.finditer(content):
|
|
155
|
-
if m.group(1) not in ('\\', ')'):
|
|
156
|
-
raise _add_ansible_error_code(
|
|
157
|
-
Invalid('Directive "%s" contains unnecessarily quoted "%s"' % (directive, m.group(1))),
|
|
158
|
-
'invalid-documentation-markup')
|
|
159
|
-
return _UNESCAPE.sub(r'\1', content)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def _parse_prefix(directive, content):
|
|
163
|
-
value = None
|
|
164
|
-
if '=' in content:
|
|
165
|
-
content, value = content.split('=', 1)
|
|
166
|
-
m = _FQCN_TYPE_PREFIX_RE.match(content)
|
|
167
|
-
if m:
|
|
168
|
-
plugin_fqcn = m.group(1)
|
|
169
|
-
plugin_type = m.group(2)
|
|
170
|
-
content = m.group(3)
|
|
171
|
-
if not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(plugin_fqcn):
|
|
172
|
-
raise _add_ansible_error_code(
|
|
173
|
-
Invalid('Directive "%s" must contain a FQCN; found "%s"' % (directive, plugin_fqcn)),
|
|
174
|
-
'invalid-documentation-markup')
|
|
175
|
-
if plugin_type not in _VALID_PLUGIN_TYPES:
|
|
176
|
-
raise _add_ansible_error_code(
|
|
177
|
-
Invalid('Directive "%s" must contain a valid plugin type; found "%s"' % (directive, plugin_type)),
|
|
178
|
-
'invalid-documentation-markup')
|
|
179
|
-
elif content.startswith(_IGNORE_MARKER):
|
|
180
|
-
content = content[len(_IGNORE_MARKER):]
|
|
181
|
-
plugin_fqcn = plugin_type = _IGNORE_STRING
|
|
182
|
-
else:
|
|
183
|
-
plugin_fqcn = plugin_type = None
|
|
184
|
-
if ':' in content or '#' in content:
|
|
185
|
-
raise _add_ansible_error_code(
|
|
186
|
-
Invalid('Directive "%s" contains wrongly specified FQCN/plugin type' % directive),
|
|
187
|
-
'invalid-documentation-markup')
|
|
188
|
-
content_link = _CONTENT_LINK_SPLITTER_RE.split(content)
|
|
189
|
-
for i, part in enumerate(content_link):
|
|
190
|
-
if i == len(content_link) - 1:
|
|
191
|
-
part = _CONTENT_LINK_END_STUB_RE.sub('', part)
|
|
192
|
-
content_link[i] = part
|
|
193
|
-
if '.' in part or '[' in part or ']' in part:
|
|
194
|
-
raise _add_ansible_error_code(
|
|
195
|
-
Invalid('Directive "%s" contains invalid name "%s"' % (directive, content)),
|
|
196
|
-
'invalid-documentation-markup')
|
|
197
|
-
return plugin_fqcn, plugin_type, content_link, content, value
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
def _check_sem_option_return_value(directive, content):
|
|
201
|
-
content = _check_sem_quoting(directive, content)
|
|
202
|
-
_parse_prefix(directive, content)
|
|
96
|
+
return []
|
|
97
|
+
except ValueError:
|
|
98
|
+
return [_add_ansible_error_code(
|
|
99
|
+
Invalid('Directive %s must contain a valid URL' % directive), 'invalid-documentation-markup')]
|
|
203
100
|
|
|
204
101
|
|
|
205
102
|
def doc_string(v):
|
|
@@ -207,35 +104,55 @@ def doc_string(v):
|
|
|
207
104
|
if not isinstance(v, string_types):
|
|
208
105
|
raise _add_ansible_error_code(
|
|
209
106
|
Invalid('Must be a string'), 'invalid-documentation')
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
107
|
+
errors = []
|
|
108
|
+
for par in parse(v, Context(), errors='message', strict=True, add_source=True):
|
|
109
|
+
for part in par:
|
|
110
|
+
if part.type == dom.PartType.ERROR:
|
|
111
|
+
errors.append(_add_ansible_error_code(Invalid(part.message), 'invalid-documentation-markup'))
|
|
112
|
+
if part.type == dom.PartType.URL:
|
|
113
|
+
errors.extend(_check_url('U()', part.url))
|
|
114
|
+
if part.type == dom.PartType.LINK:
|
|
115
|
+
errors.extend(_check_url('L()', part.url))
|
|
116
|
+
if part.type == dom.PartType.MODULE:
|
|
117
|
+
if not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(part.fqcn):
|
|
118
|
+
errors.append(_add_ansible_error_code(Invalid(
|
|
119
|
+
'Directive "%s" must contain a FQCN; found "%s"' % (part.source, part.fqcn)),
|
|
120
|
+
'invalid-documentation-markup'))
|
|
121
|
+
if part.type == dom.PartType.PLUGIN:
|
|
122
|
+
if not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(part.plugin.fqcn):
|
|
123
|
+
errors.append(_add_ansible_error_code(Invalid(
|
|
124
|
+
'Directive "%s" must contain a FQCN; found "%s"' % (part.source, part.plugin.fqcn)),
|
|
125
|
+
'invalid-documentation-markup'))
|
|
126
|
+
if part.plugin.type not in _VALID_PLUGIN_TYPES:
|
|
127
|
+
errors.append(_add_ansible_error_code(Invalid(
|
|
128
|
+
'Directive "%s" must contain a valid plugin type; found "%s"' % (part.source, part.plugin.type)),
|
|
129
|
+
'invalid-documentation-markup'))
|
|
130
|
+
if part.type == dom.PartType.OPTION_NAME:
|
|
131
|
+
if part.plugin is not None and not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(part.plugin.fqcn):
|
|
132
|
+
errors.append(_add_ansible_error_code(Invalid(
|
|
133
|
+
'Directive "%s" must contain a FQCN; found "%s"' % (part.source, part.plugin.fqcn)),
|
|
134
|
+
'invalid-documentation-markup'))
|
|
135
|
+
if part.plugin is not None and part.plugin.type not in _VALID_PLUGIN_TYPES:
|
|
136
|
+
errors.append(_add_ansible_error_code(Invalid(
|
|
137
|
+
'Directive "%s" must contain a valid plugin type; found "%s"' % (part.source, part.plugin.type)),
|
|
138
|
+
'invalid-documentation-markup'))
|
|
139
|
+
if part.type == dom.PartType.RETURN_VALUE:
|
|
140
|
+
if part.plugin is not None and not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(part.plugin.fqcn):
|
|
141
|
+
errors.append(_add_ansible_error_code(Invalid(
|
|
142
|
+
'Directive "%s" must contain a FQCN; found "%s"' % (part.source, part.plugin.fqcn)),
|
|
143
|
+
'invalid-documentation-markup'))
|
|
144
|
+
if part.plugin is not None and part.plugin.type not in _VALID_PLUGIN_TYPES:
|
|
145
|
+
errors.append(_add_ansible_error_code(Invalid(
|
|
146
|
+
'Directive "%s" must contain a valid plugin type; found "%s"' % (part.source, part.plugin.type)),
|
|
147
|
+
'invalid-documentation-markup'))
|
|
148
|
+
if len(errors) == 1:
|
|
149
|
+
raise errors[0]
|
|
150
|
+
if errors:
|
|
151
|
+
raise MultipleInvalid(errors)
|
|
228
152
|
return v
|
|
229
153
|
|
|
230
154
|
|
|
231
|
-
|
|
232
|
-
"""Match a documentation string, or list of strings."""
|
|
233
|
-
if isinstance(v, string_types):
|
|
234
|
-
return doc_string(v)
|
|
235
|
-
if isinstance(v, (list, tuple)):
|
|
236
|
-
return [doc_string(vv) for vv in v]
|
|
237
|
-
raise _add_ansible_error_code(
|
|
238
|
-
Invalid('Must be a string or list of strings'), 'invalid-documentation')
|
|
155
|
+
doc_string_or_strings = Any(doc_string, [doc_string])
|
|
239
156
|
|
|
240
157
|
|
|
241
158
|
def is_callable(v):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|