Repository: ansible/ansible
Pull request: Fix notifying handlers by using an exact match
##### SUMMARY
Fixes #55575

Introduced in 2a469fd9597ac36099e991a1757193e6d3016e63.

'string' in 'stringwithmore' is True, so make sure to wrap listeners in a list to get only exact matches: 'string' in ['stringwithmore'] is False.

##### ISSUE TYPE
- Bugfix Pull Request



Problem statement:
notify calls an unexpected listener
<!--- Verify first that your issue is not already reported on GitHub -->
<!--- Also test if the latest release and devel branch are affected too -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
<!--- Explain the problem briefly below -->

Call an unexpected handler.

The string specified by notify seems to match as an intermediate match of the listener string.

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below, use your best guess if unsure -->
handler, notify, listener

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes -->
```paste below
$ ansible --version
ansible 2.8.0b1
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/vagrant/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /home/vagrant/ansible2800/lib/python2.7/site-packages/ansible
  executable location = /home/vagrant/ansible2800/bin/ansible
  python version = 2.7.5 (default, Oct 30 2018, 23:45:53) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]
```

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```paste below
$ ansible-config dump --only-changed
HOST_KEY_CHECKING(/etc/ansible/ansible.cfg) = False
RETRY_FILES_ENABLED(/etc/ansible/ansible.cfg) = False
```

##### OS / ENVIRONMENT
<!--- Provide all relevant information below, e.g. target OS versions, network device firmware, etc. -->
```
$ cat /etc/redhat-release 
CentOS Linux release 7.6.1810 (Core) 
```

##### STEPS TO REPRODUCE
<!--- Describe exactly how to reproduce the problem, using a minimal test-case -->

Run the following Playbook.

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- hosts: localhost
  gather_facts: no
  connection: local

  tasks:
    - name:
      command: echo "changed"
      notify: handler

  handlers:
    - name: h
      debug:
        msg: notified handler
      listen: handler

    - name: h2
      debug:
        msg: notified handler2
      listen: handler2

    - name: h3
      debug:
        msg: notified handler3
      listen:
        - handler3
```

<!--- HINT: You can paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- Describe what you expected to happen when running the steps above -->
I expect that it notified only listen: handler. (likes in ansible 2.7.10)
```
$ ansible-playbook -i localhost, listener.yml 

PLAY [localhost] **************************************************************************

TASK [command] ****************************************************************************
changed: [localhost]

RUNNING HANDLER [h] ***********************************************************************
ok: [localhost] => {
    "msg": "notified handler"
}

PLAY RECAP ********************************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
```

##### ACTUAL RESULTS
<!--- Describe what actually happened. If possible run with extra verbosity (-vvvv) -->
But it notified `listen: handler` and `listen: handler2`.

<!--- Paste verbatim command output between quotes -->
```paste below
$ ansible-playbook -i localhost, listener.yml 

PLAY [localhost] **************************************************************************

TASK [command] ****************************************************************************
changed: [localhost]

RUNNING HANDLER [h] ***********************************************************************
ok: [localhost] => {
    "msg": "notified handler"
}

RUNNING HANDLER [h2] **********************************************************************
ok: [localhost] => {
    "msg": "notified handler2"
}

PLAY RECAP ********************************************************************************
localhost                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
```
If notify is specified as a list rather than a string, it seems an exact match.

Hints from issue discussion:
Files identified in the description:
* [lib/ansible/playbook/handler.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/playbook/handler.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->

Commit message: Fix notifying handlers by using an exact match rather than a string subset if listen is text rather than a list

Patch to review:
diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py
index 6a38edc2828b3a..f125b9db71edfb 100644
--- a/lib/ansible/plugins/strategy/__init__.py
+++ b/lib/ansible/plugins/strategy/__init__.py
@@ -530,6 +530,8 @@ def search_handler_blocks_by_name(handler_name, handler_blocks):
                                 for listening_handler_block in iterator._play.handlers:
                                     for listening_handler in listening_handler_block.block:
                                         listeners = getattr(listening_handler, 'listen', []) or []
+                                        if not isinstance(listeners, list):
+                                            listeners = [listeners]
                                         if handler_name not in listeners:
                                             continue
                                         else:


Code review comments:
diff_hunk: @@ -530,6 +530,8 @@ def search_handler_blocks_by_name(handler_name, handler_blocks):
                                 for listening_handler_block in iterator._play.handlers:
                                     for listening_handler in listening_handler_block.block:
                                         listeners = getattr(listening_handler, 'listen', []) or []
+                                        if not isinstance(listeners, list):
path: lib/ansible/plugins/strategy/__init__.py
text: @user1:
i would specifically check for 'string_types'  and then `elif its not list`, raise an error.

@user1:
`listen = FieldAttribute(isa='list', default=list)` <= i would also check why this is not working as intended

@author:
Good thought. As a result of consolidating the handler tracking dictionaries suddenly became viable too
```
listen:
  handler: new_workflow
```
this fixes that now

~WIP as I'm looking into https://github.com/ansible/ansible/pull/55624#discussion_r277435621~