diff --git a/changelogs/fragments/skip-handlers-tagged-play.yml b/changelogs/fragments/skip-handlers-tagged-play.yml new file mode 100644 index 00000000000..755308eafbe --- /dev/null +++ b/changelogs/fragments/skip-handlers-tagged-play.yml @@ -0,0 +1,2 @@ +bugfixes: + - "Do not run implicit ``flush_handlers`` meta tasks when the whole play is excluded from the run due to tags specified." diff --git a/lib/ansible/playbook/play.py b/lib/ansible/playbook/play.py index 7dc256e22dc..877af1e0d5f 100644 --- a/lib/ansible/playbook/play.py +++ b/lib/ansible/playbook/play.py @@ -282,19 +282,30 @@ class Play(Base, Taggable, CollectionSearch): roles (which are themselves compiled recursively) and/or the list of tasks specified in the play. ''' - # create a block containing a single flush handlers meta # task, so we can be sure to run handlers at certain points # of the playbook execution - flush_block = Block.load( - data={'meta': 'flush_handlers'}, - play=self, - variable_manager=self._variable_manager, - loader=self._loader - ) - - for task in flush_block.block: - task.implicit = True + flush_block = Block(play=self) + + t = Task() + t.action = 'meta' + t.resolved_action = 'ansible.builtin.meta' + t.args['_raw_params'] = 'flush_handlers' + t.implicit = True + t.set_loader(self._loader) + + if self.tags: + # Avoid calling flush_handlers in case the whole play is skipped on tags, + # this could be performance improvement since calling flush_handlers on + # large inventories could be expensive even if no hosts are notified + # since we call flush_handlers per host. + # Block.filter_tagged_tasks ignores evaluating tags on implicit meta + # tasks so we need to explicitly call Task.evaluate_tags here. + t.tags = self.tags + if t.evaluate_tags(self.only_tags, self.skip_tags, all_vars=self.vars): + flush_block.block = [t] + else: + flush_block.block = [t] block_list = [] if self.force_handlers: diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py index af7665ed731..ce4ae897445 100644 --- a/lib/ansible/plugins/strategy/__init__.py +++ b/lib/ansible/plugins/strategy/__init__.py @@ -1090,8 +1090,10 @@ class StrategyBase: else: result['changed'] = False - if not task.implicit: - header = skip_reason if skipped else msg + header = skip_reason if skipped else msg + if task.implicit: + display.debug(f"META: {header}") + else: display.vv(f"META: {header}") res = TaskResult(target_host, task, result) diff --git a/test/integration/targets/handlers/runme.sh b/test/integration/targets/handlers/runme.sh index 0bb2ac7f781..9e7ebb482d3 100755 --- a/test/integration/targets/handlers/runme.sh +++ b/test/integration/targets/handlers/runme.sh @@ -228,3 +228,7 @@ ansible-playbook handler_notify_earlier_handler.yml "$@" 2>&1 | tee out.txt [ "$(grep out.txt -ce 'h2_ran')" = "1" ] [ "$(grep out.txt -ce 'h3_ran')" = "1" ] [ "$(grep out.txt -ce 'h4_ran')" = "1" ] + +ANSIBLE_DEBUG=1 ansible-playbook tagged_play.yml --skip-tags the_whole_play "$@" 2>&1 | tee out.txt +[ "$(grep out.txt -ce 'META: triggered running handlers')" = "0" ] +[ "$(grep out.txt -ce 'handler_ran')" = "0" ] diff --git a/test/integration/targets/handlers/tagged_play.yml b/test/integration/targets/handlers/tagged_play.yml new file mode 100644 index 00000000000..e96348dcd12 --- /dev/null +++ b/test/integration/targets/handlers/tagged_play.yml @@ -0,0 +1,10 @@ +- hosts: localhost + gather_facts: false + tags: the_whole_play + tasks: + - command: echo + notify: h + handlers: + - name: h + debug: + msg: handler_ran