diff --git a/changelogs/fragments/role_vars_scope_fix.yml b/changelogs/fragments/role_vars_scope_fix.yml new file mode 100644 index 00000000000..8f45cbbc0ea --- /dev/null +++ b/changelogs/fragments/role_vars_scope_fix.yml @@ -0,0 +1,2 @@ +bugfixes: + - Fix bug in `vars` applied to roles, they were being incorrectly exported among others while only vars/main.yml was meant to be. Also adjusted the precedence to act the same as inline params. diff --git a/lib/ansible/playbook/role/__init__.py b/lib/ansible/playbook/role/__init__.py index 0409609f9e0..7cb9b1a6ce6 100644 --- a/lib/ansible/playbook/role/__init__.py +++ b/lib/ansible/playbook/role/__init__.py @@ -101,12 +101,14 @@ class Role(Base, Conditional, Taggable, CollectionSearch): 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=True): self._role_name = None self._role_path = None self._role_collection = None self._role_params = dict() self._loader = None + self.public = public + self.static = True self._metadata = None self._play = play @@ -139,8 +141,7 @@ class Role(Base, Conditional, Taggable, CollectionSearch): return self._role_name @staticmethod - def load(role_include, play, parent_role=None, from_files=None, from_include=False, validate=True): - + def load(role_include, play, parent_role=None, from_files=None, from_include=False, validate=True, public=True): if from_files is None: from_files = {} try: @@ -173,7 +174,7 @@ class Role(Base, Conditional, Taggable, CollectionSearch): # 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) r._load_role_data(role_include, parent_role=parent_role) if role_include.get_name() not in play.ROLE_CACHE: @@ -453,14 +454,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 +476,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 = set() + 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.add(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 diff --git a/lib/ansible/playbook/role_include.py b/lib/ansible/playbook/role_include.py index 394603729d0..3bce92d2f4b 100644 --- a/lib/ansible/playbook/role_include.py +++ b/lib/ansible/playbook/role_include.py @@ -89,7 +89,7 @@ class IncludeRole(TaskInclude): # build role actual_role = Role.load(ri, myplay, parent_role=self._parent_role, from_files=from_files, - from_include=True, validate=self.rolespec_validate) + from_include=True, validate=self.rolespec_validate, public=self.public) actual_role._metadata.allow_duplicates = self.allow_duplicates if self.statically_loaded or self.public: @@ -129,6 +129,10 @@ class IncludeRole(TaskInclude): ir = IncludeRole(block, role, task_include=task_include).load_data(data, variable_manager=variable_manager, loader=loader) + # dyanmic role! + if ir.action in C._ACTION_INCLUDE_ROLE: + ir.static = False + # Validate options my_arg_names = frozenset(ir.args.keys()) @@ -137,6 +141,7 @@ class IncludeRole(TaskInclude): if ir._role_name is None: raise AnsibleParserError("'name' is a required field for %s." % ir.action, obj=data) + # public is only valid argument for includes, imports are always 'public' (after they run) if 'public' in ir.args and ir.action not in C._ACTION_INCLUDE_ROLE: raise AnsibleParserError('Invalid options for %s: public' % ir.action, obj=data) @@ -145,7 +150,7 @@ class IncludeRole(TaskInclude): if bad_opts: raise AnsibleParserError('Invalid options for %s: %s' % (ir.action, ','.join(list(bad_opts))), obj=data) - # build options for role includes + # build options for role include/import tasks for key in my_arg_names.intersection(IncludeRole.FROM_ARGS): from_key = key.removesuffix('_from') args_value = ir.args.get(key) @@ -153,6 +158,7 @@ class IncludeRole(TaskInclude): raise AnsibleParserError('Expected a string for %s but got %s instead' % (key, type(args_value))) ir._from_files[from_key] = basename(args_value) + # apply is only valid for includes, not imports as they inherit directly apply_attrs = ir.args.get('apply', {}) if apply_attrs and ir.action not in C._ACTION_INCLUDE_ROLE: raise AnsibleParserError('Invalid options for %s: apply' % ir.action, obj=data) diff --git a/lib/ansible/vars/manager.py b/lib/ansible/vars/manager.py index a09704e038b..a80eb25ee38 100644 --- a/lib/ansible/vars/manager.py +++ b/lib/ansible/vars/manager.py @@ -197,10 +197,13 @@ class VariableManager: basedirs = [self._loader.get_basedir()] if play: - # first we compile any vars specified in defaults/main.yml - # for all roles within the specified play - for role in play.get_roles(): - all_vars = _combine_and_track(all_vars, role.get_default_vars(), "role '%s' defaults" % role.name) + if not C.DEFAULT_PRIVATE_ROLE_VARS: + # first we compile any vars specified in defaults/main.yml + # for all roles within the specified play + for role in play.get_roles(): + # role from roles or include_role+public or import_role and completed + if not role.from_include or role.public or (role.static and role._completed.get(to_text(host), False)): + all_vars = _combine_and_track(all_vars, role.get_default_vars(), "role '%s' defaults" % role.name) if task: # set basedirs @@ -215,6 +218,7 @@ class VariableManager: # if we have a task in this context, and that task has a role, make # sure it sees its defaults above any other roles, as we previously # (v1) made sure each task had a copy of its roles default vars + # TODO: investigate why we need play or include_role check? if task._role is not None and (play or task.action in C._ACTION_INCLUDE_ROLE): all_vars = _combine_and_track(all_vars, task._role.get_default_vars(dep_chain=task.get_dep_chain()), "role '%s' defaults" % task._role.name) @@ -383,19 +387,20 @@ class VariableManager: raise AnsibleParserError("Error while reading vars files - please supply a list of file names. " "Got '%s' of type %s" % (vars_files, type(vars_files))) - # By default, we now merge in all vars from all roles in the play, + # By default, we now merge in all exported vars from all roles in the play, # unless the user has disabled this via a config option if not C.DEFAULT_PRIVATE_ROLE_VARS: for role in play.get_roles(): - all_vars = _combine_and_track(all_vars, role.get_vars(include_params=False), "role '%s' vars" % role.name) + if not role.from_include or role.public or (role.static and role._completed.get(to_text(host), False)): + all_vars = _combine_and_track(all_vars, role.get_vars(include_params=False, only_exports=True), "role '%s' exported vars" % role.name) # next, we merge in the vars from the role, which will specifically # follow the role dependency chain, and then we merge in the tasks # vars (which will look at parent blocks/task includes) if task: if task._role: - all_vars = _combine_and_track(all_vars, task._role.get_vars(task.get_dep_chain(), include_params=False), - "role '%s' vars" % task._role.name) + all_vars = _combine_and_track(all_vars, task._role.get_vars(task.get_dep_chain(), include_params=True, only_exports=False), + "role '%s' all vars" % task._role.name) all_vars = _combine_and_track(all_vars, task.get_vars(), "task vars") # next, we merge in the vars cache (include vars) and nonpersistent @@ -408,9 +413,6 @@ class VariableManager: # next, we merge in role params and task include params if task: - if task._role: - all_vars = _combine_and_track(all_vars, task._role.get_role_params(task.get_dep_chain()), "role '%s' params" % task._role.name) - # special case for include tasks, where the include params # may be specified in the vars field for the task, which should # have higher precedence than the vars/np facts above diff --git a/test/integration/targets/include_import/public_exposure/no_bleeding.yml b/test/integration/targets/include_import/public_exposure/no_bleeding.yml index b9db7132903..bce5d90e1a3 100644 --- a/test/integration/targets/include_import/public_exposure/no_bleeding.yml +++ b/test/integration/targets/include_import/public_exposure/no_bleeding.yml @@ -5,8 +5,8 @@ - name: Static imports should expose vars at parse time, not at execution time assert: that: - - static_defaults_var == 'static_defaults' - - static_vars_var == 'static_vars' + - static_defaults_var is not defined + - static_vars_var is not defined - import_role: name: static - assert: diff --git a/test/integration/targets/include_import/public_exposure/playbook.yml b/test/integration/targets/include_import/public_exposure/playbook.yml index 11735e7731b..ea9641974ce 100644 --- a/test/integration/targets/include_import/public_exposure/playbook.yml +++ b/test/integration/targets/include_import/public_exposure/playbook.yml @@ -10,12 +10,16 @@ - name: Static imports should expose vars at parse time, not at execution time assert: that: - - static_defaults_var == 'static_defaults' - - static_vars_var == 'static_vars' + - static_defaults_var is not defined + - static_vars_var is not defined + - static_task_var is not defined - import_role: name: static - assert: that: + - static_vars_var is defined + - static_tasks_var is defined + - static_defaults_var is defined - static_tasks_var == 'static_tasks' - static_defaults_var == 'static_defaults' - static_vars_var == 'static_vars' diff --git a/test/integration/targets/roles/roles/vars_scope/defaults/main.yml b/test/integration/targets/roles/roles/vars_scope/defaults/main.yml new file mode 100644 index 00000000000..27f3e916551 --- /dev/null +++ b/test/integration/targets/roles/roles/vars_scope/defaults/main.yml @@ -0,0 +1,10 @@ +default_only: default +role_vars_only: default +play_and_role_vars: default +play_and_roles_and_role_vars: default +play_and_import_and_role_vars: default +play_and_include_and_role_vars: default +play_and_role_vars_and_role_vars: default +roles_and_role_vars: default +import_and_role_vars: default +include_and_role_vars: default diff --git a/test/integration/targets/roles/roles/vars_scope/tasks/check_vars.yml b/test/integration/targets/roles/roles/vars_scope/tasks/check_vars.yml new file mode 120000 index 00000000000..4e16fcafbc3 --- /dev/null +++ b/test/integration/targets/roles/roles/vars_scope/tasks/check_vars.yml @@ -0,0 +1 @@ +../../../tasks/check_vars.yml \ No newline at end of file diff --git a/test/integration/targets/roles/roles/vars_scope/tasks/main.yml b/test/integration/targets/roles/roles/vars_scope/tasks/main.yml new file mode 100644 index 00000000000..155f362936b --- /dev/null +++ b/test/integration/targets/roles/roles/vars_scope/tasks/main.yml @@ -0,0 +1 @@ +- include_tasks: check_vars.yml diff --git a/test/integration/targets/roles/roles/vars_scope/vars/main.yml b/test/integration/targets/roles/roles/vars_scope/vars/main.yml new file mode 100644 index 00000000000..079353f8c98 --- /dev/null +++ b/test/integration/targets/roles/roles/vars_scope/vars/main.yml @@ -0,0 +1,9 @@ +role_vars_only: role_vars +play_and_role_vars: role_vars +play_and_roles_and_role_vars: role_vars +play_and_import_and_role_vars: role_vars +play_and_include_and_role_vars: role_vars +play_and_role_vars_and_role_vars: role_vars +roles_and_role_vars: role_vars +import_and_role_vars: role_vars +include_and_role_vars: role_vars diff --git a/test/integration/targets/roles/runme.sh b/test/integration/targets/roles/runme.sh index bb98a932920..bad02bc9d0f 100755 --- a/test/integration/targets/roles/runme.sh +++ b/test/integration/targets/roles/runme.sh @@ -26,3 +26,6 @@ else echo "Test failed, expected output from playbook failure is missing, output not shown)." exit 1 fi + +# ensure vars scope is correct +ansible-playbook vars_scope.yml -i ../../inventory "$@" diff --git a/test/integration/targets/roles/tasks/check_vars.yml b/test/integration/targets/roles/tasks/check_vars.yml new file mode 100644 index 00000000000..083415d110f --- /dev/null +++ b/test/integration/targets/roles/tasks/check_vars.yml @@ -0,0 +1,7 @@ +- debug: var={{item}} + loop: '{{possible_vars}}' + +- assert: + that: + - (item in vars and item in defined and vars[item] == defined[item]) or (item not in vars and item not in defined) + loop: '{{possible_vars}}' diff --git a/test/integration/targets/roles/vars/play.yml b/test/integration/targets/roles/vars/play.yml new file mode 100644 index 00000000000..dd84ae22156 --- /dev/null +++ b/test/integration/targets/roles/vars/play.yml @@ -0,0 +1,26 @@ +play_only: play +play_and_roles: play +play_and_import: play +play_and_include: play +play_and_role_vars: play +play_and_roles_and_role_vars: play +play_and_import_and_role_vars: play +play_and_include_and_role_vars: play +possible_vars: + - default_only + - import_and_role_vars + - import_only + - include_and_role_vars + - include_only + - play_and_import + - play_and_import_and_role_vars + - play_and_include + - play_and_include_and_role_vars + - play_and_roles + - play_and_roles_and_role_vars + - play_and_role_vars + - play_and_role_vars_and_role_vars + - play_only + - roles_and_role_vars + - roles_only + - role_vars_only diff --git a/test/integration/targets/roles/vars_scope.yml b/test/integration/targets/roles/vars_scope.yml new file mode 100644 index 00000000000..256723a2302 --- /dev/null +++ b/test/integration/targets/roles/vars_scope.yml @@ -0,0 +1,335 @@ +- name: play and roles + hosts: localhost + gather_facts: false + vars_files: + - vars/play.yml + roles: + - name: vars_scope + vars: + roles_only: roles + roles_and_role_vars: roles + play_and_roles: roles + play_and_roles_and_role_vars: roles + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: roles + play_and_roles_and_role_vars: roles + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: roles + roles_only: roles + role_vars_only: role_vars + + pre_tasks: + - include_tasks: tasks/check_vars.yml + vars: + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + tasks: + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + +- name: play and import + hosts: localhost + gather_facts: false + vars_files: + - vars/play.yml + tasks: + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + play_and_import: play + play_and_import_and_role_vars: play + play_and_include: play + play_and_include_and_role_vars: play + play_and_roles: play + play_and_roles_and_role_vars: play + play_and_role_vars: play + play_only: play + + - name: static import + import_role: + name: vars_scope + vars: + import_only: import + import_and_role_vars: import + play_and_import: import + play_and_import_and_role_vars: import + defined: + default_only: default + import_and_role_vars: import + import_only: import + include_and_role_vars: role_vars + play_and_import: import + play_and_import_and_role_vars: import + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + +- name: play and include + hosts: localhost + gather_facts: false + vars_files: + - vars/play.yml + tasks: + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + play_and_import: play + play_and_import_and_role_vars: play + play_and_include: play + play_and_include_and_role_vars: play + play_and_roles: play + play_and_roles_and_role_vars: play + play_and_role_vars: play + play_only: play + + - name: dynamic include + include_role: + name: vars_scope + vars: + include_only: include + include_and_role_vars: include + play_and_include: include + play_and_include_and_role_vars: include + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: include + include_only: include + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: include + play_and_include_and_role_vars: include + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + play_and_import: play + play_and_import_and_role_vars: play + play_and_include: play + play_and_include_and_role_vars: play + play_and_roles: play + play_and_roles_and_role_vars: play + play_and_role_vars: play + play_only: play + +- name: play and roles and import and include + hosts: localhost + gather_facts: false + vars: + vars_files: + - vars/play.yml + roles: + - name: vars_scope + vars: + roles_only: roles + roles_and_role_vars: roles + play_and_roles: roles + play_and_roles_and_role_vars: roles + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: roles + play_and_roles_and_role_vars: roles + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: roles + roles_only: roles + role_vars_only: role_vars + + pre_tasks: + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + + tasks: + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + + - name: static import + import_role: + name: vars_scope + vars: + import_only: import + import_and_role_vars: import + play_and_import: import + play_and_import_and_role_vars: import + defined: + default_only: default + import_and_role_vars: import + import_only: import + include_and_role_vars: role_vars + play_and_import: import + play_and_import_and_role_vars: import + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + + - name: dynamic include + include_role: + name: vars_scope + vars: + include_only: include + include_and_role_vars: include + play_and_include: include + play_and_include_and_role_vars: include + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: include + include_only: include + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: include + play_and_include_and_role_vars: include + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars diff --git a/test/integration/targets/roles_arg_spec/test.yml b/test/integration/targets/roles_arg_spec/test.yml index 5eca7c73f9c..b2b44c7ffd8 100644 --- a/test/integration/targets/roles_arg_spec/test.yml +++ b/test/integration/targets/roles_arg_spec/test.yml @@ -48,6 +48,7 @@ name: a vars: a_int: "{{ INT_VALUE }}" + a_str: "import_role" - name: "Call role entry point that is defined, but has no spec data" import_role: