diff options
Diffstat (limited to 'lib/ansible/playbook/role/__init__.py')
-rw-r--r-- | lib/ansible/playbook/role/__init__.py | 167 |
1 files changed, 104 insertions, 63 deletions
diff --git a/lib/ansible/playbook/role/__init__.py b/lib/ansible/playbook/role/__init__.py index 0409609f..34d8ba99 100644 --- a/lib/ansible/playbook/role/__init__.py +++ b/lib/ansible/playbook/role/__init__.py @@ -22,15 +22,17 @@ __metaclass__ = type import os from collections.abc import Container, Mapping, Set, Sequence +from types import MappingProxyType from ansible import constants as C from ansible.errors import AnsibleError, AnsibleParserError, AnsibleAssertionError -from ansible.module_utils._text import to_text +from ansible.module_utils.common.text.converters import to_text from ansible.module_utils.six import binary_type, text_type from ansible.playbook.attribute import FieldAttribute from ansible.playbook.base import Base from ansible.playbook.collectionsearch import CollectionSearch from ansible.playbook.conditional import Conditional +from ansible.playbook.delegatable import Delegatable from ansible.playbook.helpers import load_list_of_blocks from ansible.playbook.role.metadata import RoleMetadata from ansible.playbook.taggable import Taggable @@ -96,22 +98,32 @@ def hash_params(params): return frozenset((params,)) -class Role(Base, Conditional, Taggable, CollectionSearch): +class Role(Base, Conditional, Taggable, CollectionSearch, Delegatable): - delegate_to = FieldAttribute(isa='string') - delegate_facts = FieldAttribute(isa='bool') - - def __init__(self, play=None, from_files=None, from_include=False, validate=True): + def __init__(self, play=None, from_files=None, from_include=False, validate=True, public=None, static=True): self._role_name = None self._role_path = None self._role_collection = None self._role_params = dict() self._loader = None + self.static = static + + # includes (static=false) default to private, while imports (static=true) default to public + # but both can be overriden by global config if set + if public is None: + global_private, origin = C.config.get_config_value_and_origin('DEFAULT_PRIVATE_ROLE_VARS') + if origin == 'default': + self.public = static + else: + self.public = not global_private + else: + self.public = public - self._metadata = None + self._metadata = RoleMetadata() self._play = play self._parents = [] self._dependencies = [] + self._all_dependencies = None self._task_blocks = [] self._handler_blocks = [] self._compiled_handler_blocks = None @@ -128,6 +140,8 @@ class Role(Base, Conditional, Taggable, CollectionSearch): # Indicates whether this role was included via include/import_role self.from_include = from_include + self._hash = None + super(Role, self).__init__() def __repr__(self): @@ -138,49 +152,54 @@ class Role(Base, Conditional, Taggable, CollectionSearch): return '.'.join(x for x in (self._role_collection, self._role_name) if x) return self._role_name - @staticmethod - def load(role_include, play, parent_role=None, from_files=None, from_include=False, validate=True): + def get_role_path(self): + # Purposefully using realpath for canonical path + return os.path.realpath(self._role_path) + + def _get_hash_dict(self): + if self._hash: + return self._hash + self._hash = MappingProxyType( + { + 'name': self.get_name(), + 'path': self.get_role_path(), + 'params': MappingProxyType(self.get_role_params()), + 'when': self.when, + 'tags': self.tags, + 'from_files': MappingProxyType(self._from_files), + 'vars': MappingProxyType(self.vars), + 'from_include': self.from_include, + } + ) + return self._hash + + def __eq__(self, other): + if not isinstance(other, Role): + return False + + return self._get_hash_dict() == other._get_hash_dict() + @staticmethod + def load(role_include, play, parent_role=None, from_files=None, from_include=False, validate=True, public=None, static=True): if from_files is None: from_files = {} try: - # The ROLE_CACHE is a dictionary of role names, with each entry - # containing another dictionary corresponding to a set of parameters - # specified for a role as the key and the Role() object itself. - # We use frozenset to make the dictionary hashable. - - params = role_include.get_role_params() - if role_include.when is not None: - params['when'] = role_include.when - if role_include.tags is not None: - params['tags'] = role_include.tags - if from_files is not None: - params['from_files'] = from_files - if role_include.vars: - params['vars'] = role_include.vars - - params['from_include'] = from_include - - hashed_params = hash_params(params) - if role_include.get_name() in play.ROLE_CACHE: - for (entry, role_obj) in play.ROLE_CACHE[role_include.get_name()].items(): - if hashed_params == entry: - if parent_role: - role_obj.add_parent(parent_role) - return role_obj - # TODO: need to fix cycle detection in role load (maybe use an empty dict # for the in-flight in role cache as a sentinel that we're already trying to load # that role?) # see https://github.com/ansible/ansible/issues/61527 - r = Role(play=play, from_files=from_files, from_include=from_include, validate=validate) + r = Role(play=play, from_files=from_files, from_include=from_include, validate=validate, public=public, static=static) r._load_role_data(role_include, parent_role=parent_role) - if role_include.get_name() not in play.ROLE_CACHE: - play.ROLE_CACHE[role_include.get_name()] = dict() + role_path = r.get_role_path() + if role_path not in play.role_cache: + play.role_cache[role_path] = [] + + # Using the role path as a cache key is done to improve performance when a large number of roles + # are in use in the play + if r not in play.role_cache[role_path]: + play.role_cache[role_path].append(r) - # FIXME: how to handle cache keys for collection-based roles, since they're technically adjustable per task? - play.ROLE_CACHE[role_include.get_name()][hashed_params] = r return r except RuntimeError: @@ -221,8 +240,6 @@ class Role(Base, Conditional, Taggable, CollectionSearch): if metadata: self._metadata = RoleMetadata.load(metadata, owner=self, variable_manager=self._variable_manager, loader=self._loader) self._dependencies = self._load_dependencies() - 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 @@ -421,10 +438,9 @@ class Role(Base, Conditional, Taggable, CollectionSearch): ''' deps = [] - if self._metadata: - for role_include in self._metadata.dependencies: - r = Role.load(role_include, play=self._play, parent_role=self) - deps.append(r) + for role_include in self._metadata.dependencies: + r = Role.load(role_include, play=self._play, parent_role=self, static=self.static) + deps.append(r) return deps @@ -441,6 +457,13 @@ class Role(Base, Conditional, Taggable, CollectionSearch): def get_parents(self): return self._parents + def get_dep_chain(self): + dep_chain = [] + for parent in self._parents: + dep_chain.extend(parent.get_dep_chain()) + dep_chain.append(parent) + return dep_chain + def get_default_vars(self, dep_chain=None): dep_chain = [] if dep_chain is None else dep_chain @@ -453,14 +476,15 @@ class Role(Base, Conditional, Taggable, CollectionSearch): default_vars = combine_vars(default_vars, self._default_vars) return default_vars - def get_inherited_vars(self, dep_chain=None): + def get_inherited_vars(self, dep_chain=None, only_exports=False): dep_chain = [] if dep_chain is None else dep_chain inherited_vars = dict() if dep_chain: for parent in dep_chain: - inherited_vars = combine_vars(inherited_vars, parent.vars) + if not only_exports: + inherited_vars = combine_vars(inherited_vars, parent.vars) inherited_vars = combine_vars(inherited_vars, parent._role_vars) return inherited_vars @@ -474,18 +498,36 @@ class Role(Base, Conditional, Taggable, CollectionSearch): params = combine_vars(params, self._role_params) return params - def get_vars(self, dep_chain=None, include_params=True): + def get_vars(self, dep_chain=None, include_params=True, only_exports=False): dep_chain = [] if dep_chain is None else dep_chain - all_vars = self.get_inherited_vars(dep_chain) + all_vars = {} - for dep in self.get_all_dependencies(): - all_vars = combine_vars(all_vars, dep.get_vars(include_params=include_params)) + # get role_vars: from parent objects + # TODO: is this right precedence for inherited role_vars? + all_vars = self.get_inherited_vars(dep_chain, only_exports=only_exports) - all_vars = combine_vars(all_vars, self.vars) + # get exported variables from meta/dependencies + seen = [] + for dep in self.get_all_dependencies(): + # Avoid reruning dupe deps since they can have vars from previous invocations and they accumulate in deps + # TODO: re-examine dep loading to see if we are somehow improperly adding the same dep too many times + if dep not in seen: + # only take 'exportable' vars from deps + all_vars = combine_vars(all_vars, dep.get_vars(include_params=False, only_exports=True)) + seen.append(dep) + + # role_vars come from vars/ in a role all_vars = combine_vars(all_vars, self._role_vars) - if include_params: - all_vars = combine_vars(all_vars, self.get_role_params(dep_chain=dep_chain)) + + if not only_exports: + # include_params are 'inline variables' in role invocation. - {role: x, varname: value} + if include_params: + # TODO: add deprecation notice + all_vars = combine_vars(all_vars, self.get_role_params(dep_chain=dep_chain)) + + # these come from vars: keyword in role invocation. - {role: x, vars: {varname: value}} + all_vars = combine_vars(all_vars, self.vars) return all_vars @@ -497,15 +539,15 @@ class Role(Base, Conditional, Taggable, CollectionSearch): Returns a list of all deps, built recursively from all child dependencies, in the proper order in which they should be executed or evaluated. ''' + if self._all_dependencies is None: - child_deps = [] - - for dep in self.get_direct_dependencies(): - for child_dep in dep.get_all_dependencies(): - child_deps.append(child_dep) - child_deps.append(dep) + self._all_dependencies = [] + for dep in self.get_direct_dependencies(): + for child_dep in dep.get_all_dependencies(): + self._all_dependencies.append(child_dep) + self._all_dependencies.append(dep) - return child_deps + return self._all_dependencies def get_task_blocks(self): return self._task_blocks[:] @@ -607,8 +649,7 @@ class Role(Base, Conditional, Taggable, CollectionSearch): res['_had_task_run'] = self._had_task_run.copy() res['_completed'] = self._completed.copy() - if self._metadata: - res['_metadata'] = self._metadata.serialize() + res['_metadata'] = self._metadata.serialize() if include_deps: deps = [] |