Repository: ansible/ansible

Issue:
Cannot use include in task handlers (AttributeError)
##### ISSUE TYPE
- Bug Report
##### ANSIBLE VERSION

**Affected :**

```
ansible 2.2.0
  config file = /home/pistache/.ansible.cfg
  configured module search path = Default w/o overrides
```

```
ansible 2.1.0.0
  config file = /home/pistache/.ansible.cfg
  configured module search path = Default w/o overrides

```

**Not affected :** Ansible 2.0.2
##### CONFIGURATION

None
##### OS / ENVIRONMENT

Debian Unstable (on my workstation, bug was also reproduced with Ansible 2.1 on Jessie)
##### SUMMARY

I cannot use `include` in a task handler with Ansible 2.1 or 2.2 (`stable-2.1` and `devel`), the same code that was working with Ansible 2.0 now raises an AttributeError.

It seems like the task handler code was modified in such a way that it does not allow `include` anymore.
##### STEPS TO REPRODUCE

Create a role, and use `include` in a task handler.

`handlers/main.yml`

```
- name: Test
  include: test.yml
```

`handlers/test.yml`

```
- debug: msg="Test handler notified."
```

`tasks/main.yml`

```
- name: Test task
  debug: msg="Notifying test handler"
  changed_when: yes
  notify:
    - Test
```
##### EXPECTED RESULTS

No errors, should print out "Test handler notified."
##### ACTUAL RESULTS

```
ERROR! Unexpected Exception: 'TaskInclude' object has no attribute 'has_triggered'
the full traceback was:

Traceback (most recent call last):
  File "/usr/local/bin/ansible-playbook", line 92, in <module>
    exit_code = cli.run()
  File "/usr/local/lib/python2.7/dist-packages/ansible/cli/playbook.py", line 154, in run
    results = pbex.run()
  File "/usr/local/lib/python2.7/dist-packages/ansible/executor/playbook_executor.py", line 142, in run
    result = self._tqm.run(play=play)
  File "/usr/local/lib/python2.7/dist-packages/ansible/executor/task_queue_manager.py", line 253, in run
    play_return = strategy.run(iterator, play_context)
  File "/usr/local/lib/python2.7/dist-packages/ansible/plugins/strategy/linear.py", line 215, in run
    self._execute_meta(task, play_context, iterator)
  File "/usr/local/lib/python2.7/dist-packages/ansible/plugins/strategy/__init__.py", line 707, in _execute_meta
    self.run_handlers(iterator, play_context)
  File "/usr/local/lib/python2.7/dist-packages/ansible/plugins/strategy/__init__.py", line 589, in run_handlers
    result = self._do_handler_run(handler, handler_name, iterator=iterator, play_context=play_context)
  File "/usr/local/lib/python2.7/dist-packages/ansible/plugins/strategy/__init__.py", line 621, in _do_handler_run
    if not handler.has_triggered(host) and (host.name not in self._tqm._failed_hosts or play_context.force_handlers):
AttributeError: 'TaskInclude' object has no attribute 'has_triggered'

```

Cannot use include in task handlers (AttributeError)
##### ISSUE TYPE
- Bug Report
##### ANSIBLE VERSION

**Affected :**

```
ansible 2.2.0
  config file = /home/pistache/.ansible.cfg
  configured module search path = Default w/o overrides
```

```
ansible 2.1.0.0
  config file = /home/pistache/.ansible.cfg
  configured module search path = Default w/o overrides

```

**Not affected :** Ansible 2.0.2
##### CONFIGURATION

None
##### OS / ENVIRONMENT

Debian Unstable (on my workstation, bug was also reproduced with Ansible 2.1 on Jessie)
##### SUMMARY

I cannot use `include` in a task handler with Ansible 2.1 or 2.2 (`stable-2.1` and `devel`), the same code that was working with Ansible 2.0 now raises an AttributeError.

It seems like the task handler code was modified in such a way that it does not allow `include` anymore.
##### STEPS TO REPRODUCE

Create a role, and use `include` in a task handler.

`handlers/main.yml`

```
- name: Test
  include: test.yml
```

`handlers/test.yml`

```
- debug: msg="Test handler notified."
```

`tasks/main.yml`

```
- name: Test task
  debug: msg="Notifying test handler"
  changed_when: yes
  notify:
    - Test
```
##### EXPECTED RESULTS

No errors, should print out "Test handler notified."
##### ACTUAL RESULTS

```
ERROR! Unexpected Exception: 'TaskInclude' object has no attribute 'has_triggered'
the full traceback was:

Traceback (most recent call last):
  File "/usr/local/bin/ansible-playbook", line 92, in <module>
    exit_code = cli.run()
  File "/usr/local/lib/python2.7/dist-packages/ansible/cli/playbook.py", line 154, in run
    results = pbex.run()
  File "/usr/local/lib/python2.7/dist-packages/ansible/executor/playbook_executor.py", line 142, in run
    result = self._tqm.run(play=play)
  File "/usr/local/lib/python2.7/dist-packages/ansible/executor/task_queue_manager.py", line 253, in run
    play_return = strategy.run(iterator, play_context)
  File "/usr/local/lib/python2.7/dist-packages/ansible/plugins/strategy/linear.py", line 215, in run
    self._execute_meta(task, play_context, iterator)
  File "/usr/local/lib/python2.7/dist-packages/ansible/plugins/strategy/__init__.py", line 707, in _execute_meta
    self.run_handlers(iterator, play_context)
  File "/usr/local/lib/python2.7/dist-packages/ansible/plugins/strategy/__init__.py", line 589, in run_handlers
    result = self._do_handler_run(handler, handler_name, iterator=iterator, play_context=play_context)
  File "/usr/local/lib/python2.7/dist-packages/ansible/plugins/strategy/__init__.py", line 621, in _do_handler_run
    if not handler.has_triggered(host) and (host.name not in self._tqm._failed_hosts or play_context.force_handlers):
AttributeError: 'TaskInclude' object has no attribute 'has_triggered'

```



Discussion:

Resolving patch:
diff --git a/lib/ansible/playbook/handler_task_include.py b/lib/ansible/playbook/handler_task_include.py
new file mode 100644
--- /dev/null
+++ b/lib/ansible/playbook/handler_task_include.py
@@ -0,0 +1,33 @@
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from ansible.errors import AnsibleError
+#from ansible.inventory.host import Host
+from ansible.playbook.task_include import TaskInclude
+from ansible.playbook.handler import Handler
+
+class HandlerTaskInclude(Handler, TaskInclude):
+
+    @staticmethod
+    def load(data, block=None, role=None, task_include=None, variable_manager=None, loader=None):
+        t = HandlerTaskInclude(block=block, role=role, task_include=task_include)
+        return t.load_data(data, variable_manager=variable_manager, loader=loader)
+
diff --git a/lib/ansible/playbook/helpers.py b/lib/ansible/playbook/helpers.py
--- a/lib/ansible/playbook/helpers.py
+++ b/lib/ansible/playbook/helpers.py
@@ -38,7 +38,7 @@ def load_list_of_blocks(ds, play, parent_block=None, role=None, task_include=Non
     return a list of Block() objects, where implicit blocks
     are created for each bare Task.
     '''
- 
+
     # we import here to prevent a circular dependency with imports
     from ansible.playbook.block import Block
 
@@ -81,6 +81,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
     from ansible.playbook.handler import Handler
     from ansible.playbook.task import Task
     from ansible.playbook.task_include import TaskInclude
+    from ansible.playbook.handler_task_include import HandlerTaskInclude
     from ansible.template import Templar
 
     assert isinstance(ds, list)
@@ -103,8 +104,10 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
             task_list.append(t)
         else:
             if 'include' in task_ds:
-                t = TaskInclude.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
-
+                if use_handlers:
+                    t = HandlerTaskInclude.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
+                else:
+                    t = TaskInclude.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
                 all_vars = variable_manager.get_vars(loader=loader, play=play, task=t)
                 templar = Templar(loader=loader, variables=all_vars)
 
@@ -121,7 +124,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
                     if t.loop is not None:
                         raise AnsibleParserError("You cannot use 'static' on an include with a loop", obj=task_ds)
 
-                    # FIXME: all of this code is very similar (if not identical) to that in 
+                    # FIXME: all of this code is very similar (if not identical) to that in
                     #        plugins/strategy/__init__.py, and should be unified to avoid
                     #        patches only being applied to one or the other location
                     if task_include:
@@ -233,11 +236,11 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
                         task_list.extend(included_blocks)
                 else:
                     task_list.append(t)
-            elif use_handlers:
-                t = Handler.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
-                task_list.append(t)
             else:
-                t = Task.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
+                if use_handlers:
+                    t = Handler.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
+                else:
+                    t = Task.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
                 task_list.append(t)
 
     return task_list


Test patch: