Allow tasks to notify a fqcn handler name (#68213)

* Allow tasks to notify a fqcn handler name

* Add tests. Fixes #68181

* Add changelog fragment

* Add test to ensure handlers are deduped properly with fqcn, role, and just handler names

* Add some docs about new special vars
pull/68135/head
Matt Martz 6 years ago committed by GitHub
parent 4f8e98d322
commit 087be1da50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,6 @@
bugfixes:
- Allow tasks to notify a fqcn handler name (https://github.com/ansible/ansible/issues/68181)
minor_changes:
- Add new magic variable ``ansible_collection`` that contains the collection name
- Add new magic variable ``ansible_role_name`` that contains the FQCN of the role
- Updates ``ansible_role_names``, ``ansible_play_role_names``, and ``ansible_dependent_role_names`` to include the FQCN

@ -67,6 +67,12 @@ ansible_role_names
The names of the roles currently imported into the current play, or roles referenced as dependencies of the roles The names of the roles currently imported into the current play, or roles referenced as dependencies of the roles
imported into the current play. imported into the current play.
ansible_role_name
The fully qualified collection role name, in the format of ``namespace.collection.role_name``
ansible_collection_name
The name of the collection the task that is executing is a part of. In the format of ``namespace.collection``
ansible_run_tags ansible_run_tags
Contents of the ``--tags`` CLI option, which specifies which tags will be included for the current run. Contents of the ``--tags`` CLI option, which specifies which tags will be included for the current run.

@ -128,7 +128,9 @@ class Role(Base, Conditional, Taggable, CollectionSearch):
def __repr__(self): def __repr__(self):
return self.get_name() return self.get_name()
def get_name(self): def get_name(self, include_role_fqcn=True):
if include_role_fqcn:
return '.'.join(x for x in (self._role_collection, self._role_name) if x)
return self._role_name return self._role_name
@staticmethod @staticmethod
@ -155,8 +157,8 @@ class Role(Base, Conditional, Taggable, CollectionSearch):
params['from_include'] = from_include params['from_include'] = from_include
hashed_params = hash_params(params) hashed_params = hash_params(params)
if role_include.role in play.ROLE_CACHE: if role_include.get_name() in play.ROLE_CACHE:
for (entry, role_obj) in iteritems(play.ROLE_CACHE[role_include.role]): for (entry, role_obj) in iteritems(play.ROLE_CACHE[role_include.get_name()]):
if hashed_params == entry: if hashed_params == entry:
if parent_role: if parent_role:
role_obj.add_parent(parent_role) role_obj.add_parent(parent_role)
@ -169,11 +171,11 @@ class Role(Base, Conditional, Taggable, CollectionSearch):
r = Role(play=play, from_files=from_files, from_include=from_include) r = Role(play=play, from_files=from_files, from_include=from_include)
r._load_role_data(role_include, parent_role=parent_role) r._load_role_data(role_include, parent_role=parent_role)
if role_include.role not in play.ROLE_CACHE: if role_include.get_name() not in play.ROLE_CACHE:
play.ROLE_CACHE[role_include.role] = dict() play.ROLE_CACHE[role_include.get_name()] = dict()
# FIXME: how to handle cache keys for collection-based roles, since they're technically adjustable per task? # 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 play.ROLE_CACHE[role_include.get_name()][hashed_params] = r
return r return r
except RuntimeError: except RuntimeError:

@ -231,3 +231,8 @@ class RoleDefinition(Base, Conditional, Taggable, CollectionSearch):
def get_role_path(self): def get_role_path(self):
return self._role_path return self._role_path
def get_name(self, include_role_fqcn=True):
if include_role_fqcn:
return '.'.join(x for x in (self._role_collection, self.role) if x)
return self.role

@ -111,16 +111,19 @@ class Task(Base, Conditional, Taggable, CollectionSearch):
path = "%s:%s" % (self._parent._play._ds._data_source, self._parent._play._ds._line_number) path = "%s:%s" % (self._parent._play._ds._data_source, self._parent._play._ds._line_number)
return path return path
def get_name(self): def get_name(self, include_role_fqcn=True):
''' return the name of the task ''' ''' return the name of the task '''
if self._role and self.name and ("%s : " % self._role._role_name) not in self.name: if self._role:
return "%s : %s" % (self._role.get_name(), self.name) role_name = self._role.get_name(include_role_fqcn=include_role_fqcn)
if self._role and self.name and role_name not in self.name:
return "%s : %s" % (role_name, self.name)
elif self.name: elif self.name:
return self.name return self.name
else: else:
if self._role: if self._role:
return "%s : %s" % (self._role.get_name(), self.action) return "%s : %s" % (role_name, self.action)
else: else:
return "%s" % (self.action,) return "%s" % (self.action,)

@ -460,10 +460,13 @@ class StrategyBase:
# include the role name (if the handler is from a role). If that # include the role name (if the handler is from a role). If that
# is not found, we resort to the simple name field, which doesn't # is not found, we resort to the simple name field, which doesn't
# have anything extra added to it. # have anything extra added to it.
if handler_task.name == handler_name: candidates = (
return handler_task handler_task.name,
else: handler_task.get_name(include_role_fqcn=False),
if handler_task.get_name() == handler_name: handler_task.get_name(include_role_fqcn=True),
)
if handler_name in candidates:
return handler_task return handler_task
except (UndefinedError, AnsibleUndefinedVariable): except (UndefinedError, AnsibleUndefinedVariable):
# We skip this handler due to the fact that it may be using # We skip this handler due to the fact that it may be using
@ -705,7 +708,7 @@ class StrategyBase:
if original_task._role is not None and role_ran: # TODO: and original_task.action != 'include_role':? if original_task._role is not None and role_ran: # TODO: and original_task.action != 'include_role':?
# lookup the role in the ROLE_CACHE to make sure we're dealing # lookup the role in the ROLE_CACHE to make sure we're dealing
# with the correct object and mark it as executed # with the correct object and mark it as executed
for (entry, role_obj) in iteritems(iterator._play.ROLE_CACHE[original_task._role._role_name]): for (entry, role_obj) in iteritems(iterator._play.ROLE_CACHE[original_task._role.get_name()]):
if role_obj._uuid == original_task._role._uuid: if role_obj._uuid == original_task._role._uuid:
role_obj._had_task_run[original_host.name] = True role_obj._had_task_run[original_host.name] = True

@ -457,9 +457,9 @@ class VariableManager:
if play: if play:
# This is a list of all role names of all dependencies for all roles for this play # This is a list of all role names of all dependencies for all roles for this play
dependency_role_names = list(set([d._role_name for r in play.roles for d in r.get_all_dependencies()])) dependency_role_names = list(set([d.get_name() for r in play.roles for d in r.get_all_dependencies()]))
# This is a list of all role names of all roles for this play # This is a list of all role names of all roles for this play
play_role_names = [r._role_name for r in play.roles] play_role_names = [r.get_name() for r in play.roles]
# ansible_role_names includes all role names, dependent or directly referenced by the play # ansible_role_names includes all role names, dependent or directly referenced by the play
variables['ansible_role_names'] = list(set(dependency_role_names + play_role_names)) variables['ansible_role_names'] = list(set(dependency_role_names + play_role_names))
@ -477,9 +477,11 @@ class VariableManager:
if task: if task:
if task._role: if task._role:
variables['role_name'] = task._role.get_name() variables['role_name'] = task._role.get_name(include_role_fqcn=False)
variables['role_path'] = task._role._role_path variables['role_path'] = task._role._role_path
variables['role_uuid'] = text_type(task._role._uuid) variables['role_uuid'] = text_type(task._role._uuid)
variables['ansible_collection_name'] = task._role._role_collection
variables['ansible_role_name'] = task._role.get_name()
if self._inventory is not None: if self._inventory is not None:
variables['groups'] = self._inventory.get_groups_dict() variables['groups'] = self._inventory.get_groups_dict()

@ -0,0 +1,6 @@
# This handler should only be called 1 time, if it's called more than once
# this task should fail on subsequent executions
- name: test_fqcn_handler
set_fact:
handler_counter: '{{ handler_counter|int + 1 }}'
failed_when: handler_counter|int > 1

@ -0,0 +1,7 @@
- debug:
msg: Fire fqcn handler
changed_when: true
notify:
- 'testns.testcoll.common_handlers : test_fqcn_handler'
- 'common_handlers : test_fqcn_handler'
- 'test_fqcn_handler'

@ -394,3 +394,10 @@
that: that:
- hostvars['dynamic_host_a'] is defined - hostvars['dynamic_host_a'] is defined
- hostvars['dynamic_host_a'].connection_out.stdout == "localconn ran echo 'hello world'" - hostvars['dynamic_host_a'].connection_out.stdout == "localconn ran echo 'hello world'"
- name: Test FQCN handlers
hosts: testhost
vars:
handler_counter: 0
roles:
- testns.testcoll.test_fqcn_handlers

Loading…
Cancel
Save