summaryrefslogtreecommitdiff
path: root/lib/ansible/playbook/role/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible/playbook/role/__init__.py')
-rw-r--r--lib/ansible/playbook/role/__init__.py167
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 = []