ansible-core 2.19.2rc1__py3-none-any.whl → 2.20.0b1__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/_internal/__init__.py +1 -4
- ansible/_internal/_ansiballz/_builder.py +1 -3
- ansible/_internal/_collection_proxy.py +7 -9
- ansible/_internal/_display_utils.py +145 -0
- ansible/_internal/_json/__init__.py +3 -4
- ansible/_internal/_templating/_engine.py +1 -1
- ansible/_internal/_templating/_jinja_plugins.py +1 -2
- ansible/_internal/_wrapt.py +105 -301
- ansible/cli/__init__.py +11 -10
- ansible/cli/adhoc.py +1 -2
- ansible/cli/arguments/option_helpers.py +1 -1
- ansible/cli/config.py +5 -6
- ansible/cli/doc.py +67 -67
- ansible/cli/galaxy.py +15 -24
- ansible/cli/inventory.py +0 -1
- ansible/cli/playbook.py +0 -1
- ansible/cli/pull.py +0 -1
- ansible/cli/scripts/ansible_connection_cli_stub.py +1 -1
- ansible/config/base.yml +1 -25
- ansible/config/manager.py +0 -2
- ansible/executor/play_iterator.py +42 -20
- ansible/executor/playbook_executor.py +0 -9
- ansible/executor/powershell/async_watchdog.ps1 +24 -4
- ansible/executor/task_executor.py +32 -22
- ansible/executor/task_queue_manager.py +1 -3
- ansible/galaxy/api.py +33 -80
- ansible/galaxy/collection/__init__.py +4 -17
- ansible/galaxy/dependency_resolution/dataclasses.py +0 -10
- ansible/galaxy/dependency_resolution/providers.py +1 -2
- ansible/galaxy/role.py +1 -33
- ansible/inventory/manager.py +2 -3
- ansible/keyword_desc.yml +0 -3
- ansible/module_utils/_internal/_datatag/__init__.py +2 -10
- ansible/module_utils/_internal/_no_six.py +86 -0
- ansible/module_utils/_text.py +28 -8
- ansible/module_utils/ansible_release.py +2 -2
- ansible/module_utils/basic.py +27 -24
- ansible/module_utils/common/_collections_compat.py +11 -2
- ansible/module_utils/common/collections.py +8 -3
- ansible/module_utils/common/dict_transformations.py +1 -2
- ansible/module_utils/common/network.py +4 -2
- ansible/module_utils/common/parameters.py +32 -41
- ansible/module_utils/common/text/converters.py +109 -23
- ansible/module_utils/common/text/formatters.py +6 -2
- ansible/module_utils/common/validation.py +11 -9
- ansible/module_utils/connection.py +8 -3
- ansible/module_utils/facts/hardware/linux.py +23 -7
- ansible/module_utils/facts/hardware/netbsd.py +1 -1
- ansible/module_utils/facts/hardware/sunos.py +2 -1
- ansible/module_utils/facts/packages.py +6 -2
- ansible/module_utils/facts/system/distribution.py +2 -1
- ansible/module_utils/facts/system/env.py +6 -3
- ansible/module_utils/facts/system/local.py +3 -1
- ansible/module_utils/parsing/convert_bool.py +6 -2
- ansible/module_utils/service.py +2 -3
- ansible/module_utils/six/__init__.py +11 -6
- ansible/module_utils/urls.py +6 -2
- ansible/module_utils/yumdnf.py +0 -5
- ansible/modules/apt.py +18 -13
- ansible/modules/apt_repository.py +1 -1
- ansible/modules/assemble.py +5 -9
- ansible/modules/blockinfile.py +39 -23
- ansible/modules/cron.py +26 -35
- ansible/modules/deb822_repository.py +83 -12
- ansible/modules/dnf.py +3 -7
- ansible/modules/dnf5.py +4 -6
- ansible/modules/expect.py +0 -3
- ansible/modules/find.py +1 -2
- ansible/modules/get_url.py +1 -1
- ansible/modules/git.py +4 -5
- ansible/modules/include_vars.py +1 -1
- ansible/modules/lineinfile.py +71 -63
- ansible/modules/package_facts.py +1 -1
- ansible/modules/pip.py +8 -2
- ansible/modules/replace.py +6 -6
- ansible/modules/service.py +3 -4
- ansible/modules/stat.py +20 -0
- ansible/modules/uri.py +9 -10
- ansible/modules/user.py +1 -2
- ansible/modules/wait_for.py +2 -2
- ansible/modules/wait_for_connection.py +2 -1
- ansible/modules/yum_repository.py +1 -16
- ansible/parsing/dataloader.py +24 -31
- ansible/parsing/mod_args.py +3 -0
- ansible/parsing/vault/__init__.py +1 -2
- ansible/playbook/base.py +8 -56
- ansible/playbook/block.py +0 -60
- ansible/playbook/collectionsearch.py +1 -2
- ansible/playbook/handler.py +1 -7
- ansible/playbook/helpers.py +0 -7
- ansible/playbook/included_file.py +1 -1
- ansible/playbook/play.py +103 -37
- ansible/playbook/play_context.py +4 -0
- ansible/playbook/role/__init__.py +10 -65
- ansible/playbook/role/definition.py +3 -4
- ansible/playbook/role/include.py +2 -3
- ansible/playbook/role/metadata.py +1 -12
- ansible/playbook/role/requirement.py +1 -2
- ansible/playbook/role_include.py +1 -2
- ansible/playbook/taggable.py +16 -5
- ansible/playbook/task.py +51 -55
- ansible/plugins/action/__init__.py +20 -19
- ansible/plugins/action/add_host.py +1 -2
- ansible/plugins/action/fetch.py +2 -4
- ansible/plugins/action/group_by.py +1 -2
- ansible/plugins/action/include_vars.py +20 -22
- ansible/plugins/action/script.py +1 -3
- ansible/plugins/action/template.py +1 -2
- ansible/plugins/action/uri.py +4 -2
- ansible/plugins/cache/__init__.py +1 -0
- ansible/plugins/callback/__init__.py +13 -6
- ansible/plugins/connection/__init__.py +3 -7
- ansible/plugins/connection/local.py +2 -3
- ansible/plugins/connection/psrp.py +0 -2
- ansible/plugins/connection/ssh.py +2 -7
- ansible/plugins/connection/winrm.py +0 -2
- ansible/plugins/doc_fragments/result_format_callback.py +15 -0
- ansible/plugins/filter/core.py +4 -5
- ansible/plugins/filter/encryption.py +3 -27
- ansible/plugins/filter/mathstuff.py +1 -2
- ansible/plugins/filter/to_nice_yaml.yml +31 -3
- ansible/plugins/filter/to_yaml.yml +29 -12
- ansible/plugins/inventory/__init__.py +1 -2
- ansible/plugins/inventory/script.py +2 -1
- ansible/plugins/inventory/toml.py +3 -6
- ansible/plugins/inventory/yaml.py +1 -2
- ansible/plugins/list.py +10 -3
- ansible/plugins/loader.py +6 -6
- ansible/plugins/lookup/password.py +1 -2
- ansible/plugins/lookup/subelements.py +2 -3
- ansible/plugins/lookup/url.py +1 -1
- ansible/plugins/lookup/varnames.py +1 -2
- ansible/plugins/shell/__init__.py +9 -4
- ansible/plugins/shell/powershell.py +8 -24
- ansible/plugins/strategy/__init__.py +6 -3
- ansible/plugins/test/core.py +4 -1
- ansible/plugins/test/regex.yml +18 -6
- ansible/release.py +2 -2
- ansible/template/__init__.py +3 -7
- ansible/utils/collection_loader/_collection_config.py +5 -0
- ansible/utils/collection_loader/_collection_finder.py +11 -14
- ansible/utils/context_objects.py +7 -4
- ansible/utils/display.py +28 -167
- ansible/utils/encrypt.py +0 -5
- ansible/utils/helpers.py +6 -2
- ansible/utils/jsonrpc.py +7 -3
- ansible/utils/plugin_docs.py +49 -38
- ansible/utils/ssh_functions.py +0 -19
- ansible/utils/unsafe_proxy.py +7 -7
- ansible/vars/clean.py +2 -3
- ansible/vars/manager.py +27 -20
- ansible/vars/plugins.py +1 -31
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/METADATA +3 -3
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/RECORD +200 -200
- ansible_test/_data/completion/docker.txt +7 -7
- ansible_test/_data/completion/network.txt +0 -1
- ansible_test/_data/completion/remote.txt +4 -4
- ansible_test/_data/requirements/ansible-test.txt +1 -1
- ansible_test/_data/requirements/sanity.changelog.txt +1 -1
- ansible_test/_data/requirements/sanity.pep8.txt +1 -1
- ansible_test/_data/requirements/sanity.pylint.txt +4 -4
- ansible_test/_internal/cache.py +2 -5
- ansible_test/_internal/cli/compat.py +1 -1
- ansible_test/_internal/commands/coverage/combine.py +1 -3
- ansible_test/_internal/commands/integration/__init__.py +3 -7
- ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
- ansible_test/_internal/commands/integration/coverage.py +1 -3
- ansible_test/_internal/commands/integration/filters.py +5 -10
- ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
- ansible_test/_internal/commands/units/__init__.py +1 -13
- ansible_test/_internal/completion.py +2 -5
- ansible_test/_internal/config.py +2 -7
- ansible_test/_internal/coverage_util.py +1 -1
- ansible_test/_internal/delegation.py +2 -0
- ansible_test/_internal/docker_util.py +1 -1
- ansible_test/_internal/host_profiles.py +6 -11
- ansible_test/_internal/provider/__init__.py +2 -5
- ansible_test/_internal/provisioning.py +2 -5
- ansible_test/_internal/pypi_proxy.py +1 -1
- ansible_test/_internal/target.py +2 -6
- ansible_test/_internal/thread.py +1 -4
- ansible_test/_internal/util.py +9 -14
- ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
- ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +30 -27
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
- ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
- ansible_test/_util/target/common/constants.py +2 -2
- ansible_test/_util/target/setup/bootstrap.sh +0 -6
- ansible/utils/py3compat.py +0 -27
- ansible_test/_data/pytest/config/legacy.ini +0 -4
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/top_level.txt +0 -0
ansible/playbook/play.py
CHANGED
|
@@ -17,12 +17,15 @@
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
import functools as _functools
|
|
21
|
+
import pathlib as _pathlib
|
|
22
|
+
|
|
20
23
|
from ansible import constants as C
|
|
21
24
|
from ansible import context
|
|
22
25
|
from ansible.errors import AnsibleError
|
|
23
|
-
from ansible.errors import AnsibleParserError, AnsibleAssertionError
|
|
26
|
+
from ansible.errors import AnsibleParserError, AnsibleAssertionError, AnsibleValueOmittedError
|
|
24
27
|
from ansible.module_utils.common.collections import is_sequence
|
|
25
|
-
from ansible.module_utils.
|
|
28
|
+
from ansible.module_utils.common.yaml import yaml_dump
|
|
26
29
|
from ansible.playbook.attribute import NonInheritableFieldAttribute
|
|
27
30
|
from ansible.playbook.base import Base
|
|
28
31
|
from ansible.playbook.block import Block
|
|
@@ -34,6 +37,8 @@ from ansible.playbook.taggable import Taggable
|
|
|
34
37
|
from ansible.parsing.vault import EncryptedString
|
|
35
38
|
from ansible.utils.display import Display
|
|
36
39
|
|
|
40
|
+
from ansible._internal._templating._engine import TemplateEngine as _TE
|
|
41
|
+
|
|
37
42
|
display = Display()
|
|
38
43
|
|
|
39
44
|
|
|
@@ -53,11 +58,11 @@ class Play(Base, Taggable, CollectionSearch):
|
|
|
53
58
|
"""
|
|
54
59
|
|
|
55
60
|
# =================================================================================
|
|
56
|
-
hosts = NonInheritableFieldAttribute(isa='list', required=True, listof=
|
|
61
|
+
hosts = NonInheritableFieldAttribute(isa='list', required=True, listof=(str,), always_post_validate=True, priority=-2)
|
|
57
62
|
|
|
58
63
|
# Facts
|
|
59
64
|
gather_facts = NonInheritableFieldAttribute(isa='bool', default=None, always_post_validate=True)
|
|
60
|
-
gather_subset = NonInheritableFieldAttribute(isa='list', default=None, listof=
|
|
65
|
+
gather_subset = NonInheritableFieldAttribute(isa='list', default=None, listof=(str,), always_post_validate=True)
|
|
61
66
|
gather_timeout = NonInheritableFieldAttribute(isa='int', default=None, always_post_validate=True)
|
|
62
67
|
fact_path = NonInheritableFieldAttribute(isa='string', default=None)
|
|
63
68
|
|
|
@@ -65,6 +70,8 @@ class Play(Base, Taggable, CollectionSearch):
|
|
|
65
70
|
vars_files = NonInheritableFieldAttribute(isa='list', default=list, priority=99)
|
|
66
71
|
vars_prompt = NonInheritableFieldAttribute(isa='list', default=list, always_post_validate=False)
|
|
67
72
|
|
|
73
|
+
validate_argspec = NonInheritableFieldAttribute(isa='string', always_post_validate=True)
|
|
74
|
+
|
|
68
75
|
# Role Attributes
|
|
69
76
|
roles = NonInheritableFieldAttribute(isa='list', default=list, priority=90)
|
|
70
77
|
|
|
@@ -120,10 +127,10 @@ class Play(Base, Taggable, CollectionSearch):
|
|
|
120
127
|
for entry in value:
|
|
121
128
|
if entry is None:
|
|
122
129
|
raise AnsibleParserError("Hosts list cannot contain values of 'None'. Please check your playbook")
|
|
123
|
-
elif not isinstance(entry, (
|
|
130
|
+
elif not isinstance(entry, (bytes, str)):
|
|
124
131
|
raise AnsibleParserError("Hosts list contains an invalid host value: '{host!s}'".format(host=entry))
|
|
125
132
|
|
|
126
|
-
elif not isinstance(value, (
|
|
133
|
+
elif not isinstance(value, (bytes, str, EncryptedString)):
|
|
127
134
|
raise AnsibleParserError("Hosts list must be a sequence or string. Please check your playbook.")
|
|
128
135
|
|
|
129
136
|
def get_name(self):
|
|
@@ -303,7 +310,7 @@ class Play(Base, Taggable, CollectionSearch):
|
|
|
303
310
|
|
|
304
311
|
t = Task(block=flush_block)
|
|
305
312
|
t.action = 'meta'
|
|
306
|
-
t.
|
|
313
|
+
t._resolved_action = 'ansible.builtin.meta'
|
|
307
314
|
t.args['_raw_params'] = 'flush_handlers'
|
|
308
315
|
t.implicit = True
|
|
309
316
|
t.set_loader(self._loader)
|
|
@@ -400,36 +407,6 @@ class Play(Base, Taggable, CollectionSearch):
|
|
|
400
407
|
tasklist.append(task)
|
|
401
408
|
return tasklist
|
|
402
409
|
|
|
403
|
-
def serialize(self):
|
|
404
|
-
data = super(Play, self).serialize()
|
|
405
|
-
|
|
406
|
-
roles = []
|
|
407
|
-
for role in self.get_roles():
|
|
408
|
-
roles.append(role.serialize())
|
|
409
|
-
data['roles'] = roles
|
|
410
|
-
data['included_path'] = self._included_path
|
|
411
|
-
data['action_groups'] = self._action_groups
|
|
412
|
-
data['group_actions'] = self._group_actions
|
|
413
|
-
|
|
414
|
-
return data
|
|
415
|
-
|
|
416
|
-
def deserialize(self, data):
|
|
417
|
-
super(Play, self).deserialize(data)
|
|
418
|
-
|
|
419
|
-
self._included_path = data.get('included_path', None)
|
|
420
|
-
self._action_groups = data.get('action_groups', {})
|
|
421
|
-
self._group_actions = data.get('group_actions', {})
|
|
422
|
-
if 'roles' in data:
|
|
423
|
-
role_data = data.get('roles', [])
|
|
424
|
-
roles = []
|
|
425
|
-
for role in role_data:
|
|
426
|
-
r = Role()
|
|
427
|
-
r.deserialize(role)
|
|
428
|
-
roles.append(r)
|
|
429
|
-
|
|
430
|
-
setattr(self, 'roles', roles)
|
|
431
|
-
del data['roles']
|
|
432
|
-
|
|
433
410
|
def copy(self):
|
|
434
411
|
new_me = super(Play, self).copy()
|
|
435
412
|
new_me.role_cache = self.role_cache.copy()
|
|
@@ -438,3 +415,92 @@ class Play(Base, Taggable, CollectionSearch):
|
|
|
438
415
|
new_me._action_groups = self._action_groups
|
|
439
416
|
new_me._group_actions = self._group_actions
|
|
440
417
|
return new_me
|
|
418
|
+
|
|
419
|
+
def _post_validate_validate_argspec(self, attr: NonInheritableFieldAttribute, value: object, templar: _TE) -> str | None:
|
|
420
|
+
"""Validate user input is a bool or string, and return the corresponding argument spec name."""
|
|
421
|
+
|
|
422
|
+
# Ensure the configuration is valid
|
|
423
|
+
if isinstance(value, str):
|
|
424
|
+
try:
|
|
425
|
+
value = templar.template(value)
|
|
426
|
+
except AnsibleValueOmittedError:
|
|
427
|
+
value = False
|
|
428
|
+
|
|
429
|
+
if not isinstance(value, (str, bool)):
|
|
430
|
+
raise AnsibleParserError(f"validate_argspec must be a boolean or string, not {type(value)}", obj=value)
|
|
431
|
+
|
|
432
|
+
# Short-circuit if configuration is turned off or inapplicable
|
|
433
|
+
if not value or self._origin is None:
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
# Use the requested argument spec or fall back to the play name
|
|
437
|
+
argspec_name = None
|
|
438
|
+
if isinstance(value, str):
|
|
439
|
+
argspec_name = value
|
|
440
|
+
elif self._ds.get("name"):
|
|
441
|
+
argspec_name = self.name
|
|
442
|
+
|
|
443
|
+
metadata_err = argspec_err = ""
|
|
444
|
+
if not argspec_name:
|
|
445
|
+
argspec_err = (
|
|
446
|
+
"A play name is required when validate_argspec is True. "
|
|
447
|
+
"Alternatively, set validate_argspec to the name of an argument spec."
|
|
448
|
+
)
|
|
449
|
+
if self._metadata_path is None:
|
|
450
|
+
metadata_err = "A playbook meta file is required. Considered:\n - "
|
|
451
|
+
metadata_err += "\n - ".join([path.as_posix() for path in self._metadata_candidate_paths])
|
|
452
|
+
|
|
453
|
+
if metadata_err or argspec_err:
|
|
454
|
+
error = f"{argspec_err + (' ' if argspec_err else '')}{metadata_err}"
|
|
455
|
+
raise AnsibleParserError(error, obj=self._origin)
|
|
456
|
+
|
|
457
|
+
metadata = self._loader.load_from_file(self._metadata_path)
|
|
458
|
+
|
|
459
|
+
try:
|
|
460
|
+
metadata = metadata['argument_specs']
|
|
461
|
+
metadata = metadata[argspec_name]
|
|
462
|
+
options = metadata['options']
|
|
463
|
+
except (TypeError, KeyError):
|
|
464
|
+
options = None
|
|
465
|
+
|
|
466
|
+
if not isinstance(options, dict):
|
|
467
|
+
raise AnsibleParserError(
|
|
468
|
+
f"No argument spec named '{argspec_name}' in {self._metadata_path}. Minimally expected:\n"
|
|
469
|
+
+ yaml_dump({"argument_specs": {f"{argspec_name!s}": {"options": {}}}}),
|
|
470
|
+
obj=metadata,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
return argspec_name
|
|
474
|
+
|
|
475
|
+
@property
|
|
476
|
+
def _metadata_candidate_paths(self) -> list[_pathlib.Path]:
|
|
477
|
+
"""A list of possible playbook.meta paths in configured order."""
|
|
478
|
+
extensions = C.config.get_config_value("YAML_FILENAME_EXTENSIONS")
|
|
479
|
+
if self._origin.path.endswith(tuple(extensions)):
|
|
480
|
+
playbook_without_ext = self._origin.path.rsplit('.', 1)[0]
|
|
481
|
+
else:
|
|
482
|
+
playbook_without_ext = self._origin.path
|
|
483
|
+
|
|
484
|
+
return [_pathlib.Path(playbook_without_ext + ".meta" + ext) for ext in extensions + ['']]
|
|
485
|
+
|
|
486
|
+
@_functools.cached_property
|
|
487
|
+
def _metadata_path(self) -> str | None:
|
|
488
|
+
"""Locate playbook meta path:
|
|
489
|
+
|
|
490
|
+
playbook{ext?} -> playbook.meta{ext?}
|
|
491
|
+
"""
|
|
492
|
+
if self._origin is None:
|
|
493
|
+
# adhoc, ansible-console don't have an associated playbook
|
|
494
|
+
return None
|
|
495
|
+
for candidate in self._metadata_candidate_paths:
|
|
496
|
+
if candidate.is_file():
|
|
497
|
+
return candidate.as_posix()
|
|
498
|
+
return None
|
|
499
|
+
|
|
500
|
+
@property
|
|
501
|
+
def argument_spec(self) -> dict:
|
|
502
|
+
"""Retrieve the argument spec if one is configured."""
|
|
503
|
+
if not self.validate_argspec:
|
|
504
|
+
return {}
|
|
505
|
+
|
|
506
|
+
return self._loader.load_from_file(self._metadata_path)['argument_specs'][self.validate_argspec]['options']
|
ansible/playbook/play_context.py
CHANGED
|
@@ -325,3 +325,7 @@ class PlayContext(Base):
|
|
|
325
325
|
variables[var_opt] = var_val
|
|
326
326
|
except AttributeError:
|
|
327
327
|
continue
|
|
328
|
+
|
|
329
|
+
def deserialize(self, data):
|
|
330
|
+
"""Do not use this method. Backward compatibility for network connections plugins that rely on it."""
|
|
331
|
+
self.from_attrs(data)
|
|
@@ -27,7 +27,6 @@ from ansible import constants as C
|
|
|
27
27
|
from ansible.errors import AnsibleError, AnsibleParserError, AnsibleAssertionError
|
|
28
28
|
from ansible.module_utils.common.sentinel import Sentinel
|
|
29
29
|
from ansible.module_utils.common.text.converters import to_text
|
|
30
|
-
from ansible.module_utils.six import binary_type, text_type
|
|
31
30
|
from ansible.playbook.base import Base
|
|
32
31
|
from ansible.playbook.collectionsearch import CollectionSearch
|
|
33
32
|
from ansible.playbook.conditional import Conditional
|
|
@@ -37,6 +36,7 @@ from ansible.playbook.role.metadata import RoleMetadata
|
|
|
37
36
|
from ansible.playbook.taggable import Taggable
|
|
38
37
|
from ansible.plugins.loader import add_all_plugin_dirs
|
|
39
38
|
from ansible.utils.collection_loader import AnsibleCollectionConfig
|
|
39
|
+
from ansible.utils.display import Display
|
|
40
40
|
from ansible.utils.path import is_subpath
|
|
41
41
|
from ansible.utils.vars import combine_vars
|
|
42
42
|
|
|
@@ -52,14 +52,12 @@ if _t.TYPE_CHECKING:
|
|
|
52
52
|
|
|
53
53
|
__all__ = ['Role', 'hash_params']
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
# the role due to the fact that it would require the use of self
|
|
57
|
-
# in a static method. This is also used in the base class for
|
|
58
|
-
# strategies (ansible/plugins/strategy/__init__.py)
|
|
55
|
+
_display = Display()
|
|
59
56
|
|
|
60
57
|
|
|
61
58
|
def hash_params(params):
|
|
62
59
|
"""
|
|
60
|
+
DEPRECATED
|
|
63
61
|
Construct a data structure of parameters that is hashable.
|
|
64
62
|
|
|
65
63
|
This requires changing any mutable data structures into immutable ones.
|
|
@@ -71,10 +69,16 @@ def hash_params(params):
|
|
|
71
69
|
1) There shouldn't be any unhashable scalars specified in the yaml
|
|
72
70
|
2) Our only choice would be to return an error anyway.
|
|
73
71
|
"""
|
|
72
|
+
|
|
73
|
+
_display.deprecated(
|
|
74
|
+
msg="The hash_params function is deprecated as its consumers have moved to internal alternatives",
|
|
75
|
+
version='2.24',
|
|
76
|
+
help_text='Contact the plugin author to update their code',
|
|
77
|
+
)
|
|
74
78
|
# Any container is unhashable if it contains unhashable items (for
|
|
75
79
|
# instance, tuple() is a Hashable subclass but if it contains a dict, it
|
|
76
80
|
# cannot be hashed)
|
|
77
|
-
if isinstance(params, Container) and not isinstance(params, (
|
|
81
|
+
if isinstance(params, Container) and not isinstance(params, (str, bytes)):
|
|
78
82
|
if isinstance(params, Mapping):
|
|
79
83
|
try:
|
|
80
84
|
# Optimistically hope the contents are all hashable
|
|
@@ -651,65 +655,6 @@ class Role(Base, Conditional, Taggable, CollectionSearch, Delegatable):
|
|
|
651
655
|
|
|
652
656
|
return block_list
|
|
653
657
|
|
|
654
|
-
def serialize(self, include_deps=True):
|
|
655
|
-
res = super(Role, self).serialize()
|
|
656
|
-
|
|
657
|
-
res['_role_name'] = self._role_name
|
|
658
|
-
res['_role_path'] = self._role_path
|
|
659
|
-
res['_role_vars'] = self._role_vars
|
|
660
|
-
res['_role_params'] = self._role_params
|
|
661
|
-
res['_default_vars'] = self._default_vars
|
|
662
|
-
res['_had_task_run'] = self._had_task_run.copy()
|
|
663
|
-
res['_completed'] = self._completed.copy()
|
|
664
|
-
|
|
665
|
-
res['_metadata'] = self._metadata.serialize()
|
|
666
|
-
|
|
667
|
-
if include_deps:
|
|
668
|
-
deps = []
|
|
669
|
-
for role in self.get_direct_dependencies():
|
|
670
|
-
deps.append(role.serialize())
|
|
671
|
-
res['_dependencies'] = deps
|
|
672
|
-
|
|
673
|
-
parents = []
|
|
674
|
-
for parent in self._parents:
|
|
675
|
-
parents.append(parent.serialize(include_deps=False))
|
|
676
|
-
res['_parents'] = parents
|
|
677
|
-
|
|
678
|
-
return res
|
|
679
|
-
|
|
680
|
-
def deserialize(self, data, include_deps=True):
|
|
681
|
-
self._role_name = data.get('_role_name', '')
|
|
682
|
-
self._role_path = data.get('_role_path', '')
|
|
683
|
-
self._role_vars = data.get('_role_vars', dict())
|
|
684
|
-
self._role_params = data.get('_role_params', dict())
|
|
685
|
-
self._default_vars = data.get('_default_vars', dict())
|
|
686
|
-
self._had_task_run = data.get('_had_task_run', dict())
|
|
687
|
-
self._completed = data.get('_completed', dict())
|
|
688
|
-
|
|
689
|
-
if include_deps:
|
|
690
|
-
deps = []
|
|
691
|
-
for dep in data.get('_dependencies', []):
|
|
692
|
-
r = Role()
|
|
693
|
-
r.deserialize(dep)
|
|
694
|
-
deps.append(r)
|
|
695
|
-
setattr(self, '_dependencies', deps)
|
|
696
|
-
|
|
697
|
-
parent_data = data.get('_parents', [])
|
|
698
|
-
parents = []
|
|
699
|
-
for parent in parent_data:
|
|
700
|
-
r = Role()
|
|
701
|
-
r.deserialize(parent, include_deps=False)
|
|
702
|
-
parents.append(r)
|
|
703
|
-
setattr(self, '_parents', parents)
|
|
704
|
-
|
|
705
|
-
metadata_data = data.get('_metadata')
|
|
706
|
-
if metadata_data:
|
|
707
|
-
m = RoleMetadata()
|
|
708
|
-
m.deserialize(metadata_data)
|
|
709
|
-
self._metadata = m
|
|
710
|
-
|
|
711
|
-
super(Role, self).deserialize(data)
|
|
712
|
-
|
|
713
658
|
def set_loader(self, loader):
|
|
714
659
|
self._loader = loader
|
|
715
660
|
for parent in self._parents:
|
|
@@ -22,7 +22,6 @@ import os
|
|
|
22
22
|
from ansible import constants as C
|
|
23
23
|
from ansible.errors import AnsibleError, AnsibleAssertionError
|
|
24
24
|
from ansible.module_utils._internal._datatag import AnsibleTagHelper
|
|
25
|
-
from ansible.module_utils.six import string_types
|
|
26
25
|
from ansible.playbook.attribute import NonInheritableFieldAttribute
|
|
27
26
|
from ansible.playbook.base import Base
|
|
28
27
|
from ansible.playbook.collectionsearch import CollectionSearch
|
|
@@ -70,7 +69,7 @@ class RoleDefinition(Base, Conditional, Taggable, CollectionSearch):
|
|
|
70
69
|
if isinstance(ds, int):
|
|
71
70
|
ds = "%s" % ds
|
|
72
71
|
|
|
73
|
-
if not isinstance(ds, dict) and not isinstance(ds,
|
|
72
|
+
if not isinstance(ds, dict) and not isinstance(ds, str):
|
|
74
73
|
raise AnsibleAssertionError()
|
|
75
74
|
|
|
76
75
|
if isinstance(ds, dict):
|
|
@@ -113,11 +112,11 @@ class RoleDefinition(Base, Conditional, Taggable, CollectionSearch):
|
|
|
113
112
|
string), just that string
|
|
114
113
|
"""
|
|
115
114
|
|
|
116
|
-
if isinstance(ds,
|
|
115
|
+
if isinstance(ds, str):
|
|
117
116
|
return ds
|
|
118
117
|
|
|
119
118
|
role_name = ds.get('role', ds.get('name'))
|
|
120
|
-
if not role_name or not isinstance(role_name,
|
|
119
|
+
if not role_name or not isinstance(role_name, str):
|
|
121
120
|
raise AnsibleError('role definitions must contain a role name', obj=ds)
|
|
122
121
|
|
|
123
122
|
# if we have the required datastructures, and if the role_name
|
ansible/playbook/role/include.py
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
from ansible.errors import AnsibleError, AnsibleParserError
|
|
21
|
-
from ansible.module_utils.six import string_types
|
|
22
21
|
from ansible.playbook.delegatable import Delegatable
|
|
23
22
|
from ansible.playbook.role.definition import RoleDefinition
|
|
24
23
|
|
|
@@ -40,10 +39,10 @@ class RoleInclude(RoleDefinition, Delegatable):
|
|
|
40
39
|
@staticmethod
|
|
41
40
|
def load(data, play, current_role_path=None, parent_role=None, variable_manager=None, loader=None, collection_list=None):
|
|
42
41
|
|
|
43
|
-
if not (isinstance(data,
|
|
42
|
+
if not (isinstance(data, str) or isinstance(data, dict)):
|
|
44
43
|
raise AnsibleParserError("Invalid role definition.", obj=data)
|
|
45
44
|
|
|
46
|
-
if isinstance(data,
|
|
45
|
+
if isinstance(data, str) and ',' in data:
|
|
47
46
|
raise AnsibleError("Invalid old style role requirement: %s" % data)
|
|
48
47
|
|
|
49
48
|
ri = RoleInclude(play=play, role_basedir=current_role_path, variable_manager=variable_manager, loader=loader, collection_list=collection_list)
|
|
@@ -20,7 +20,6 @@ from __future__ import annotations
|
|
|
20
20
|
import os
|
|
21
21
|
|
|
22
22
|
from ansible.errors import AnsibleParserError, AnsibleError
|
|
23
|
-
from ansible.module_utils.six import string_types
|
|
24
23
|
from ansible.playbook.attribute import NonInheritableFieldAttribute
|
|
25
24
|
from ansible.playbook.base import Base
|
|
26
25
|
from ansible.playbook.collectionsearch import CollectionSearch
|
|
@@ -70,7 +69,7 @@ class RoleMetadata(Base, CollectionSearch):
|
|
|
70
69
|
|
|
71
70
|
for role_def in ds:
|
|
72
71
|
# FIXME: consolidate with ansible-galaxy to keep this in sync
|
|
73
|
-
if isinstance(role_def,
|
|
72
|
+
if isinstance(role_def, str) or 'role' in role_def or 'name' in role_def:
|
|
74
73
|
roles.append(role_def)
|
|
75
74
|
continue
|
|
76
75
|
try:
|
|
@@ -106,13 +105,3 @@ class RoleMetadata(Base, CollectionSearch):
|
|
|
106
105
|
collection_search_list=collection_search_list)
|
|
107
106
|
except AssertionError as ex:
|
|
108
107
|
raise AnsibleParserError("A malformed list of role dependencies was encountered.", obj=self._ds) from ex
|
|
109
|
-
|
|
110
|
-
def serialize(self):
|
|
111
|
-
return dict(
|
|
112
|
-
allow_duplicates=self._allow_duplicates,
|
|
113
|
-
dependencies=self._dependencies
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
def deserialize(self, data):
|
|
117
|
-
setattr(self, 'allow_duplicates', data.get('allow_duplicates', False))
|
|
118
|
-
setattr(self, 'dependencies', data.get('dependencies', []))
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
from ansible.errors import AnsibleError
|
|
21
|
-
from ansible.module_utils.six import string_types
|
|
22
21
|
from ansible.playbook.role.definition import RoleDefinition
|
|
23
22
|
from ansible.utils.display import Display
|
|
24
23
|
from ansible.utils.galaxy import scm_archive_resource
|
|
@@ -65,7 +64,7 @@ class RoleRequirement(RoleDefinition):
|
|
|
65
64
|
@staticmethod
|
|
66
65
|
def role_yaml_parse(role):
|
|
67
66
|
|
|
68
|
-
if isinstance(role,
|
|
67
|
+
if isinstance(role, str):
|
|
69
68
|
name = None
|
|
70
69
|
scm = None
|
|
71
70
|
src = None
|
ansible/playbook/role_include.py
CHANGED
|
@@ -23,7 +23,6 @@ from ansible.playbook.task_include import TaskInclude
|
|
|
23
23
|
from ansible.playbook.role import Role
|
|
24
24
|
from ansible.playbook.role.include import RoleInclude
|
|
25
25
|
from ansible.utils.display import Display
|
|
26
|
-
from ansible.module_utils.six import string_types
|
|
27
26
|
from ansible._internal._templating._engine import TemplateEngine
|
|
28
27
|
|
|
29
28
|
__all__ = ['IncludeRole']
|
|
@@ -137,7 +136,7 @@ class IncludeRole(TaskInclude):
|
|
|
137
136
|
for key in my_arg_names.intersection(IncludeRole.FROM_ARGS):
|
|
138
137
|
from_key = key.removesuffix('_from')
|
|
139
138
|
args_value = ir.args.get(key)
|
|
140
|
-
if not isinstance(args_value,
|
|
139
|
+
if not isinstance(args_value, str):
|
|
141
140
|
raise AnsibleParserError('Expected a string for %s but got %s instead' % (key, type(args_value)))
|
|
142
141
|
ir._from_files[from_key] = args_value
|
|
143
142
|
|
ansible/playbook/taggable.py
CHANGED
|
@@ -19,11 +19,14 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import typing as t
|
|
21
21
|
|
|
22
|
+
from ansible._internal._templating._engine import TemplateEngine
|
|
22
23
|
from ansible.errors import AnsibleError
|
|
23
24
|
from ansible.module_utils.common.sentinel import Sentinel
|
|
24
25
|
from ansible.module_utils._internal._datatag import AnsibleTagHelper
|
|
25
26
|
from ansible.playbook.attribute import FieldAttribute
|
|
26
|
-
from ansible.
|
|
27
|
+
from ansible.utils.display import Display
|
|
28
|
+
|
|
29
|
+
_display = Display()
|
|
27
30
|
|
|
28
31
|
|
|
29
32
|
def _flatten_tags(tags: list[str | int]) -> list[str | int]:
|
|
@@ -38,17 +41,25 @@ def _flatten_tags(tags: list[str | int]) -> list[str | int]:
|
|
|
38
41
|
|
|
39
42
|
class Taggable:
|
|
40
43
|
|
|
44
|
+
_RESERVED = frozenset(['tagged', 'all', 'untagged'])
|
|
41
45
|
untagged = frozenset(['untagged'])
|
|
42
46
|
tags = FieldAttribute(isa='list', default=list, listof=(str, int), extend=True)
|
|
43
47
|
|
|
44
48
|
def _load_tags(self, attr, ds):
|
|
49
|
+
|
|
50
|
+
tags = None
|
|
45
51
|
if isinstance(ds, list):
|
|
46
|
-
|
|
52
|
+
tags = ds
|
|
53
|
+
elif isinstance(ds, str):
|
|
54
|
+
tags = [AnsibleTagHelper.tag_copy(ds, item.strip()) for item in ds.split(',')]
|
|
55
|
+
|
|
56
|
+
if tags is None:
|
|
57
|
+
raise AnsibleError('tags must be specified as a list', obj=ds)
|
|
47
58
|
|
|
48
|
-
if
|
|
49
|
-
|
|
59
|
+
if found := self._RESERVED.intersection(tags):
|
|
60
|
+
_display.warning(f"Found reserved tagnames in tags: {list(found)!r}, we do not recommend doing this as it might give unexpected results", obj=ds)
|
|
50
61
|
|
|
51
|
-
|
|
62
|
+
return tags
|
|
52
63
|
|
|
53
64
|
def _get_all_taggable_objects(self) -> t.Iterable[Taggable]:
|
|
54
65
|
obj = self
|
ansible/playbook/task.py
CHANGED
|
@@ -25,7 +25,6 @@ from ansible.errors import AnsibleError, AnsibleParserError, AnsibleUndefinedVar
|
|
|
25
25
|
from ansible.executor.module_common import _get_action_arg_defaults
|
|
26
26
|
from ansible.module_utils.common.text.converters import to_native
|
|
27
27
|
from ansible.module_utils._internal._datatag import AnsibleTagHelper
|
|
28
|
-
from ansible.module_utils.six import string_types
|
|
29
28
|
from ansible.parsing.mod_args import ModuleArgsParser, RAW_PARAM_MODULES
|
|
30
29
|
from ansible.plugins.action import ActionBase
|
|
31
30
|
from ansible.plugins.loader import action_loader, module_loader, lookup_loader
|
|
@@ -37,11 +36,10 @@ from ansible.playbook.conditional import Conditional
|
|
|
37
36
|
from ansible.playbook.delegatable import Delegatable
|
|
38
37
|
from ansible.playbook.loop_control import LoopControl
|
|
39
38
|
from ansible.playbook.notifiable import Notifiable
|
|
40
|
-
from ansible.playbook.role import Role
|
|
41
39
|
from ansible.playbook.taggable import Taggable
|
|
42
40
|
from ansible._internal import _task
|
|
43
41
|
from ansible._internal._templating import _marker_behaviors
|
|
44
|
-
from ansible._internal._templating._jinja_bits import is_possibly_all_template
|
|
42
|
+
from ansible._internal._templating._jinja_bits import is_possibly_all_template, is_possibly_template
|
|
45
43
|
from ansible._internal._templating._engine import TemplateEngine, TemplateOptions
|
|
46
44
|
from ansible.utils.collection_loader import AnsibleCollectionConfig
|
|
47
45
|
from ansible.utils.display import Display
|
|
@@ -101,7 +99,7 @@ class Task(Base, Conditional, Taggable, CollectionSearch, Notifiable, Delegatabl
|
|
|
101
99
|
self._role = role
|
|
102
100
|
self._parent = None
|
|
103
101
|
self.implicit = False
|
|
104
|
-
self.
|
|
102
|
+
self._resolved_action: str | None = None
|
|
105
103
|
|
|
106
104
|
if task_include:
|
|
107
105
|
self._parent = task_include
|
|
@@ -110,6 +108,38 @@ class Task(Base, Conditional, Taggable, CollectionSearch, Notifiable, Delegatabl
|
|
|
110
108
|
|
|
111
109
|
super(Task, self).__init__()
|
|
112
110
|
|
|
111
|
+
_resolved_action_warning = (
|
|
112
|
+
"A plugin is sampling the task's resolved_action when it is not resolved. "
|
|
113
|
+
"This can be caused by callback plugins using the resolved_action attribute too "
|
|
114
|
+
"early (such as in v2_playbook_on_task_start for a task using the action/local_action "
|
|
115
|
+
"keyword), or too late (such as in v2_runner_on_ok for a task with a loop). "
|
|
116
|
+
"To maximize compatibility with user features, callback plugins should "
|
|
117
|
+
"only use this attribute in v2_runner_on_ok/v2_runner_on_failed for tasks "
|
|
118
|
+
"without a loop, and v2_runner_item_on_ok/v2_runner_item_on_failed otherwise."
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def resolved_action(self) -> str | None:
|
|
123
|
+
"""The templated and resolved FQCN of the task action or None.
|
|
124
|
+
|
|
125
|
+
If the action is a template, callback plugins can only use this value in certain methods.
|
|
126
|
+
- v2_runner_on_ok and v2_runner_on_failed if there's no task loop
|
|
127
|
+
- v2_runner_item_on_ok and v2_runner_item_on_failed if there is a task loop
|
|
128
|
+
"""
|
|
129
|
+
# Consider deprecating this because it's difficult to use?
|
|
130
|
+
# Moving it to the task result would improve the no-loop limitation on v2_runner_on_ok
|
|
131
|
+
# but then wouldn't be accessible to v2_playbook_on_task_start, *_on_skipped, etc.
|
|
132
|
+
if self._resolved_action is not None:
|
|
133
|
+
return self._resolved_action
|
|
134
|
+
if not is_possibly_template(self.action):
|
|
135
|
+
try:
|
|
136
|
+
return self._resolve_action(self.action)
|
|
137
|
+
except AnsibleParserError:
|
|
138
|
+
display.warning(self._resolved_action_warning, obj=self.action)
|
|
139
|
+
else:
|
|
140
|
+
display.warning(self._resolved_action_warning, obj=self.action)
|
|
141
|
+
return None
|
|
142
|
+
|
|
113
143
|
def get_name(self, include_role_fqcn=True):
|
|
114
144
|
""" return the name of the task """
|
|
115
145
|
|
|
@@ -129,7 +159,7 @@ class Task(Base, Conditional, Taggable, CollectionSearch, Notifiable, Delegatabl
|
|
|
129
159
|
def _merge_kv(self, ds):
|
|
130
160
|
if ds is None:
|
|
131
161
|
return ""
|
|
132
|
-
elif isinstance(ds,
|
|
162
|
+
elif isinstance(ds, str):
|
|
133
163
|
return ds
|
|
134
164
|
elif isinstance(ds, dict):
|
|
135
165
|
buf = ""
|
|
@@ -168,7 +198,7 @@ class Task(Base, Conditional, Taggable, CollectionSearch, Notifiable, Delegatabl
|
|
|
168
198
|
else:
|
|
169
199
|
module_or_action_context = action_context.plugin_load_context
|
|
170
200
|
|
|
171
|
-
self.
|
|
201
|
+
self._resolved_action = module_or_action_context.resolved_fqcn
|
|
172
202
|
|
|
173
203
|
action_type: type[ActionBase] = action_context.object
|
|
174
204
|
|
|
@@ -282,6 +312,9 @@ class Task(Base, Conditional, Taggable, CollectionSearch, Notifiable, Delegatabl
|
|
|
282
312
|
# But if it wasn't, we can add the yaml object now to get more detail
|
|
283
313
|
raise AnsibleParserError("Error parsing task arguments.", obj=ds) from ex
|
|
284
314
|
|
|
315
|
+
if args_parser._resolved_action is not None:
|
|
316
|
+
self._resolved_action = args_parser._resolved_action
|
|
317
|
+
|
|
285
318
|
new_ds['action'] = action
|
|
286
319
|
new_ds['args'] = args
|
|
287
320
|
new_ds['delegate_to'] = delegate_to
|
|
@@ -465,58 +498,11 @@ class Task(Base, Conditional, Taggable, CollectionSearch, Notifiable, Delegatabl
|
|
|
465
498
|
new_me._role = self._role
|
|
466
499
|
|
|
467
500
|
new_me.implicit = self.implicit
|
|
468
|
-
new_me.
|
|
501
|
+
new_me._resolved_action = self._resolved_action
|
|
469
502
|
new_me._uuid = self._uuid
|
|
470
503
|
|
|
471
504
|
return new_me
|
|
472
505
|
|
|
473
|
-
def serialize(self):
|
|
474
|
-
data = super(Task, self).serialize()
|
|
475
|
-
|
|
476
|
-
if not self._squashed and not self._finalized:
|
|
477
|
-
if self._parent:
|
|
478
|
-
data['parent'] = self._parent.serialize()
|
|
479
|
-
data['parent_type'] = self._parent.__class__.__name__
|
|
480
|
-
|
|
481
|
-
if self._role:
|
|
482
|
-
data['role'] = self._role.serialize()
|
|
483
|
-
|
|
484
|
-
data['implicit'] = self.implicit
|
|
485
|
-
data['resolved_action'] = self.resolved_action
|
|
486
|
-
|
|
487
|
-
return data
|
|
488
|
-
|
|
489
|
-
def deserialize(self, data):
|
|
490
|
-
|
|
491
|
-
# import is here to avoid import loops
|
|
492
|
-
from ansible.playbook.task_include import TaskInclude
|
|
493
|
-
from ansible.playbook.handler_task_include import HandlerTaskInclude
|
|
494
|
-
|
|
495
|
-
parent_data = data.get('parent', None)
|
|
496
|
-
if parent_data:
|
|
497
|
-
parent_type = data.get('parent_type')
|
|
498
|
-
if parent_type == 'Block':
|
|
499
|
-
p = Block()
|
|
500
|
-
elif parent_type == 'TaskInclude':
|
|
501
|
-
p = TaskInclude()
|
|
502
|
-
elif parent_type == 'HandlerTaskInclude':
|
|
503
|
-
p = HandlerTaskInclude()
|
|
504
|
-
p.deserialize(parent_data)
|
|
505
|
-
self._parent = p
|
|
506
|
-
del data['parent']
|
|
507
|
-
|
|
508
|
-
role_data = data.get('role')
|
|
509
|
-
if role_data:
|
|
510
|
-
r = Role()
|
|
511
|
-
r.deserialize(role_data)
|
|
512
|
-
self._role = r
|
|
513
|
-
del data['role']
|
|
514
|
-
|
|
515
|
-
self.implicit = data.get('implicit', False)
|
|
516
|
-
self.resolved_action = data.get('resolved_action')
|
|
517
|
-
|
|
518
|
-
super(Task, self).deserialize(data)
|
|
519
|
-
|
|
520
506
|
def set_loader(self, loader):
|
|
521
507
|
"""
|
|
522
508
|
Sets the loader on this object and recursively on parent, child objects.
|
|
@@ -591,9 +577,19 @@ class Task(Base, Conditional, Taggable, CollectionSearch, Notifiable, Delegatabl
|
|
|
591
577
|
def dump_attrs(self):
|
|
592
578
|
"""Override to smuggle important non-FieldAttribute values back to the controller."""
|
|
593
579
|
attrs = super().dump_attrs()
|
|
594
|
-
attrs.update(
|
|
580
|
+
attrs.update(_resolved_action=self._resolved_action)
|
|
595
581
|
return attrs
|
|
596
582
|
|
|
583
|
+
def from_attrs(self, attrs):
|
|
584
|
+
super().from_attrs(attrs)
|
|
585
|
+
|
|
586
|
+
# from_attrs is only used to create a finalized task
|
|
587
|
+
# from attrs from the Worker/TaskExecutor
|
|
588
|
+
# Those attrs are finalized and squashed in the TE
|
|
589
|
+
# and controller side use needs to reflect that
|
|
590
|
+
self._finalized = True
|
|
591
|
+
self._squashed = True
|
|
592
|
+
|
|
597
593
|
def _resolve_conditional(
|
|
598
594
|
self,
|
|
599
595
|
conditional: list[str | bool],
|