diff options
Diffstat (limited to 'lib/ansible/playbook')
-rw-r--r-- | lib/ansible/playbook/__init__.py | 2 | ||||
-rw-r--r-- | lib/ansible/playbook/block.py | 3 | ||||
-rw-r--r-- | lib/ansible/playbook/collectionsearch.py | 26 | ||||
-rw-r--r-- | lib/ansible/playbook/helpers.py | 8 | ||||
-rw-r--r-- | lib/ansible/playbook/play.py | 3 | ||||
-rw-r--r-- | lib/ansible/playbook/role/__init__.py | 32 | ||||
-rw-r--r-- | lib/ansible/playbook/role/definition.py | 43 | ||||
-rw-r--r-- | lib/ansible/playbook/role/include.py | 9 | ||||
-rw-r--r-- | lib/ansible/playbook/role/metadata.py | 10 | ||||
-rw-r--r-- | lib/ansible/playbook/role_include.py | 7 | ||||
-rw-r--r-- | lib/ansible/playbook/task.py | 5 |
11 files changed, 115 insertions, 33 deletions
diff --git a/lib/ansible/playbook/__init__.py b/lib/ansible/playbook/__init__.py index 5c912f6d2a..0321615505 100644 --- a/lib/ansible/playbook/__init__.py +++ b/lib/ansible/playbook/__init__.py @@ -23,7 +23,7 @@ import os from ansible import constants as C from ansible.errors import AnsibleParserError -from ansible.module_utils._text import to_bytes, to_text, to_native +from ansible.module_utils._text import to_text, to_native from ansible.playbook.play import Play from ansible.playbook.playbook_include import PlaybookInclude from ansible.utils.display import Display diff --git a/lib/ansible/playbook/block.py b/lib/ansible/playbook/block.py index 418af42ddf..103b7e4b1f 100644 --- a/lib/ansible/playbook/block.py +++ b/lib/ansible/playbook/block.py @@ -24,13 +24,14 @@ from ansible.playbook.attribute import FieldAttribute from ansible.playbook.base import Base from ansible.playbook.become import Become from ansible.playbook.conditional import Conditional +from ansible.playbook.collectionsearch import CollectionSearch from ansible.playbook.helpers import load_list_of_tasks from ansible.playbook.role import Role from ansible.playbook.taggable import Taggable from ansible.utils.sentinel import Sentinel -class Block(Base, Become, Conditional, Taggable): +class Block(Base, Become, Conditional, CollectionSearch, Taggable): # main block fields containing the task lists _block = FieldAttribute(isa='list', default=list, inherit=False) diff --git a/lib/ansible/playbook/collectionsearch.py b/lib/ansible/playbook/collectionsearch.py new file mode 100644 index 0000000000..245d98117b --- /dev/null +++ b/lib/ansible/playbook/collectionsearch.py @@ -0,0 +1,26 @@ +# Copyright: (c) 2019, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.six import string_types +from ansible.playbook.attribute import FieldAttribute + + +class CollectionSearch: + # this needs to be populated before we can resolve tasks/roles/etc + _collections = FieldAttribute(isa='list', listof=string_types, priority=100) + + def _load_collections(self, attr, ds): + if not ds: + # if empty/None, just return whatever was there; legacy behavior will do the right thing + return ds + + if not isinstance(ds, list): + ds = [ds] + + if 'ansible.builtin' not in ds and 'ansible.legacy' not in ds: + ds.append('ansible.legacy') + + return ds diff --git a/lib/ansible/playbook/helpers.py b/lib/ansible/playbook/helpers.py index 12b18ae62b..f9b15dc4bb 100644 --- a/lib/ansible/playbook/helpers.py +++ b/lib/ansible/playbook/helpers.py @@ -117,7 +117,10 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h ) task_list.append(t) else: - args_parser = ModuleArgsParser(task_ds) + collection_list = task_ds.get('collections') + if collection_list is None and block is not None and block.collections: + collection_list = block.collections + args_parser = ModuleArgsParser(task_ds, collection_list=collection_list) try: (action, args, delegate_to) = args_parser.parse() except AnsibleParserError as e: @@ -382,7 +385,8 @@ def load_list_of_roles(ds, play, current_role_path=None, variable_manager=None, roles = [] for role_def in ds: - i = RoleInclude.load(role_def, play=play, current_role_path=current_role_path, variable_manager=variable_manager, loader=loader) + i = RoleInclude.load(role_def, play=play, current_role_path=current_role_path, variable_manager=variable_manager, + loader=loader, collection_list=play.collections) roles.append(i) return roles diff --git a/lib/ansible/playbook/play.py b/lib/ansible/playbook/play.py index 05c6aea2cb..386f5871c0 100644 --- a/lib/ansible/playbook/play.py +++ b/lib/ansible/playbook/play.py @@ -27,6 +27,7 @@ from ansible.playbook.attribute import FieldAttribute from ansible.playbook.base import Base from ansible.playbook.become import Become from ansible.playbook.block import Block +from ansible.playbook.collectionsearch import CollectionSearch from ansible.playbook.helpers import load_list_of_blocks, load_list_of_roles from ansible.playbook.role import Role from ansible.playbook.taggable import Taggable @@ -39,7 +40,7 @@ display = Display() __all__ = ['Play'] -class Play(Base, Taggable, Become): +class Play(Base, Taggable, Become, CollectionSearch): """ A play is a language feature that represents a list of roles and/or diff --git a/lib/ansible/playbook/role/__init__.py b/lib/ansible/playbook/role/__init__.py index 5a01b453ed..a00db31b59 100644 --- a/lib/ansible/playbook/role/__init__.py +++ b/lib/ansible/playbook/role/__init__.py @@ -27,6 +27,7 @@ from ansible.module_utils.common._collections_compat import Container, Mapping, from ansible.playbook.attribute import FieldAttribute from ansible.playbook.base import Base from ansible.playbook.become import Become +from ansible.playbook.collectionsearch import CollectionSearch from ansible.playbook.conditional import Conditional from ansible.playbook.helpers import load_list_of_blocks from ansible.playbook.role.metadata import RoleMetadata @@ -91,7 +92,7 @@ def hash_params(params): return frozenset((params,)) -class Role(Base, Become, Conditional, Taggable): +class Role(Base, Become, Conditional, Taggable, CollectionSearch): _delegate_to = FieldAttribute(isa='string') _delegate_facts = FieldAttribute(isa='bool') @@ -99,6 +100,7 @@ class Role(Base, Become, Conditional, Taggable): def __init__(self, play=None, from_files=None, from_include=False): self._role_name = None self._role_path = None + self._role_collection = None self._role_params = dict() self._loader = None @@ -166,6 +168,7 @@ class Role(Base, Become, Conditional, Taggable): if role_include.role not in play.ROLE_CACHE: play.ROLE_CACHE[role_include.role] = dict() + # FIXME: how to handle cache keys for collection-based roles, since they're technically adjustable per task? play.ROLE_CACHE[role_include.role][hashed_params] = r return r @@ -176,6 +179,7 @@ class Role(Base, Become, Conditional, Taggable): def _load_role_data(self, role_include, parent_role=None): self._role_name = role_include.role self._role_path = role_include.get_role_path() + self._role_collection = role_include._role_collection self._role_params = role_include.get_role_params() self._variable_manager = role_include.get_variable_manager() self._loader = role_include.get_loader() @@ -194,9 +198,6 @@ class Role(Base, Become, Conditional, Taggable): else: self._attributes[attr_name] = role_include._attributes[attr_name] - # ensure all plugins dirs for this role are added to plugin search path - add_all_plugin_dirs(self._role_path) - # vars and default vars are regular dictionaries self._role_vars = self._load_role_yaml('vars', main=self._from_files.get('vars'), allow_dir=True) if self._role_vars is None: @@ -218,6 +219,29 @@ class Role(Base, Become, Conditional, Taggable): else: self._metadata = RoleMetadata() + # reset collections list; roles do not inherit collections from parents, just use the defaults + # FUTURE: use a private config default for this so we can allow it to be overridden later + self.collections = [] + + # configure plugin/collection loading; either prepend the current role's collection or configure legacy plugin loading + # FIXME: need exception for explicit ansible.legacy? + if self._role_collection: + self.collections.insert(0, self._role_collection) + else: + # legacy role, ensure all plugin dirs under the role are added to plugin search path + add_all_plugin_dirs(self._role_path) + + # collections can be specified in metadata for legacy or collection-hosted roles + if self._metadata.collections: + self.collections.extend(self._metadata.collections) + + # if any collections were specified, ensure that core or legacy synthetic collections are always included + if self.collections: + # default append collection is core for collection-hosted roles, legacy for others + default_append_collection = 'ansible.builtin' if self.collections else 'ansible.legacy' + if 'ansible.builtin' not in self.collections and 'ansible.legacy' not in self.collections: + self.collections.append(default_append_collection) + task_data = self._load_role_yaml('tasks', main=self._from_files.get('tasks')) if task_data: try: diff --git a/lib/ansible/playbook/role/definition.py b/lib/ansible/playbook/role/definition.py index 235a54490e..c5999bb1b3 100644 --- a/lib/ansible/playbook/role/definition.py +++ b/lib/ansible/playbook/role/definition.py @@ -28,9 +28,11 @@ from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping from ansible.playbook.attribute import Attribute, FieldAttribute from ansible.playbook.base import Base from ansible.playbook.become import Become +from ansible.playbook.collectionsearch import CollectionSearch from ansible.playbook.conditional import Conditional from ansible.playbook.taggable import Taggable from ansible.template import Templar +from ansible.utils.collection_loader import get_collection_role_path, is_collection_ref from ansible.utils.path import unfrackpath from ansible.utils.display import Display @@ -39,11 +41,11 @@ __all__ = ['RoleDefinition'] display = Display() -class RoleDefinition(Base, Become, Conditional, Taggable): +class RoleDefinition(Base, Become, Conditional, Taggable, CollectionSearch): _role = FieldAttribute(isa='string') - def __init__(self, play=None, role_basedir=None, variable_manager=None, loader=None): + def __init__(self, play=None, role_basedir=None, variable_manager=None, loader=None, collection_list=None): super(RoleDefinition, self).__init__() @@ -52,8 +54,10 @@ class RoleDefinition(Base, Become, Conditional, Taggable): self._loader = loader self._role_path = None + self._role_collection = None self._role_basedir = role_basedir self._role_params = dict() + self._collection_list = collection_list # def __repr__(self): # return 'ROLEDEF: ' + self._attributes.get('role', '<no name set>') @@ -139,6 +143,31 @@ class RoleDefinition(Base, Become, Conditional, Taggable): append it to the default role path ''' + # create a templar class to template the dependency names, in + # case they contain variables + if self._variable_manager is not None: + all_vars = self._variable_manager.get_vars(play=self._play) + else: + all_vars = dict() + + templar = Templar(loader=self._loader, variables=all_vars) + role_name = templar.template(role_name) + + role_tuple = None + + # try to load as a collection-based role first + if self._collection_list or is_collection_ref(role_name): + role_tuple = get_collection_role_path(role_name, self._collection_list) + + if role_tuple: + # we found it, stash collection data and return the name/path tuple + self._role_collection = role_tuple[2] + return role_tuple[0:2] + + # FUTURE: refactor this to be callable from internal so we can properly order ansible.legacy searches with the collections keyword + if self._collection_list and 'ansible.legacy' not in self._collection_list: + raise AnsibleError("the role '%s' was not found in %s" % (role_name, ":".join(self._collection_list)), obj=self._ds) + # we always start the search for roles in the base directory of the playbook role_search_paths = [ os.path.join(self._loader.get_basedir(), u'roles'), @@ -158,16 +187,6 @@ class RoleDefinition(Base, Become, Conditional, Taggable): # the roles/ dir appended role_search_paths.append(self._loader.get_basedir()) - # create a templar class to template the dependency names, in - # case they contain variables - if self._variable_manager is not None: - all_vars = self._variable_manager.get_vars(play=self._play) - else: - all_vars = dict() - - templar = Templar(loader=self._loader, variables=all_vars) - role_name = templar.template(role_name) - # now iterate through the possible paths and return the first one we find for path in role_search_paths: path = templar.template(path) diff --git a/lib/ansible/playbook/role/include.py b/lib/ansible/playbook/role/include.py index ddcdf80997..1e5d901d96 100644 --- a/lib/ansible/playbook/role/include.py +++ b/lib/ansible/playbook/role/include.py @@ -43,11 +43,12 @@ class RoleInclude(RoleDefinition): _delegate_to = FieldAttribute(isa='string') _delegate_facts = FieldAttribute(isa='bool', default=False) - def __init__(self, play=None, role_basedir=None, variable_manager=None, loader=None): - super(RoleInclude, self).__init__(play=play, role_basedir=role_basedir, variable_manager=variable_manager, loader=loader) + def __init__(self, play=None, role_basedir=None, variable_manager=None, loader=None, collection_list=None): + super(RoleInclude, self).__init__(play=play, role_basedir=role_basedir, variable_manager=variable_manager, + loader=loader, collection_list=collection_list) @staticmethod - def load(data, play, current_role_path=None, parent_role=None, variable_manager=None, loader=None): + def load(data, play, current_role_path=None, parent_role=None, variable_manager=None, loader=None, collection_list=None): if not (isinstance(data, string_types) or isinstance(data, dict) or isinstance(data, AnsibleBaseYAMLObject)): raise AnsibleParserError("Invalid role definition: %s" % to_native(data)) @@ -55,5 +56,5 @@ class RoleInclude(RoleDefinition): if isinstance(data, string_types) and ',' in data: raise AnsibleError("Invalid old style role requirement: %s" % data) - ri = RoleInclude(play=play, role_basedir=current_role_path, variable_manager=variable_manager, loader=loader) + ri = RoleInclude(play=play, role_basedir=current_role_path, variable_manager=variable_manager, loader=loader, collection_list=collection_list) return ri.load_data(data, variable_manager=variable_manager, loader=loader) diff --git a/lib/ansible/playbook/role/metadata.py b/lib/ansible/playbook/role/metadata.py index b50387329c..fd1d873734 100644 --- a/lib/ansible/playbook/role/metadata.py +++ b/lib/ansible/playbook/role/metadata.py @@ -23,17 +23,17 @@ import os from ansible.errors import AnsibleParserError, AnsibleError from ansible.module_utils._text import to_native -from ansible.module_utils.six import iteritems, string_types -from ansible.playbook.attribute import Attribute, FieldAttribute +from ansible.module_utils.six import string_types +from ansible.playbook.attribute import FieldAttribute from ansible.playbook.base import Base +from ansible.playbook.collectionsearch import CollectionSearch from ansible.playbook.helpers import load_list_of_roles -from ansible.playbook.role.include import RoleInclude from ansible.playbook.role.requirement import RoleRequirement __all__ = ['RoleMetadata'] -class RoleMetadata(Base): +class RoleMetadata(Base, CollectionSearch): ''' This class wraps the parsing and validation of the optional metadata within each Role (meta/main.yml). @@ -105,7 +105,7 @@ class RoleMetadata(Base): def serialize(self): return dict( allow_duplicates=self._allow_duplicates, - dependencies=self._dependencies, + dependencies=self._dependencies ) def deserialize(self, data): diff --git a/lib/ansible/playbook/role_include.py b/lib/ansible/playbook/role_include.py index e63c4ac537..870dea80d6 100644 --- a/lib/ansible/playbook/role_include.py +++ b/lib/ansible/playbook/role_include.py @@ -73,7 +73,7 @@ class IncludeRole(TaskInclude): else: myplay = play - ri = RoleInclude.load(self._role_name, play=myplay, variable_manager=variable_manager, loader=loader) + ri = RoleInclude.load(self._role_name, play=myplay, variable_manager=variable_manager, loader=loader, collection_list=self.collections) ri.vars.update(self.vars) # build role @@ -97,9 +97,14 @@ class IncludeRole(TaskInclude): p_block = self.build_parent_block() + # collections value is not inherited; override with the value we calculated during role setup + p_block.collections = actual_role.collections + blocks = actual_role.compile(play=myplay, dep_chain=dep_chain) for b in blocks: b._parent = p_block + # HACK: parent inheritance doesn't seem to have a way to handle this intermediate override until squashed/finalized + b.collections = actual_role.collections # updated available handlers in play handlers = actual_role.get_handler_blocks(play=myplay) diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py index 80854f3fb9..68204b4968 100644 --- a/lib/ansible/playbook/task.py +++ b/lib/ansible/playbook/task.py @@ -32,6 +32,7 @@ from ansible.playbook.attribute import FieldAttribute from ansible.playbook.base import Base from ansible.playbook.become import Become from ansible.playbook.block import Block +from ansible.playbook.collectionsearch import CollectionSearch from ansible.playbook.conditional import Conditional from ansible.playbook.loop_control import LoopControl from ansible.playbook.role import Role @@ -44,7 +45,7 @@ __all__ = ['Task'] display = Display() -class Task(Base, Conditional, Taggable, Become): +class Task(Base, Conditional, Taggable, Become, CollectionSearch): """ A task is a language feature that represents a call to a module, with given arguments and other parameters. @@ -180,7 +181,7 @@ class Task(Base, Conditional, Taggable, Become): # use the args parsing class to determine the action, args, # and the delegate_to value from the various possible forms # supported as legacy - args_parser = ModuleArgsParser(task_ds=ds) + args_parser = ModuleArgsParser(task_ds=ds, collection_list=self.collections) try: (action, args, delegate_to) = args_parser.parse() except AnsibleParserError as e: |