Repository: ansible/ansible

Issue:
eos_banner not idempotent
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
eos_banner

##### ANSIBLE VERSION
```
029ccf9aa0cd490224bfcf403ed94bc397e3af63
```

##### SUMMARY

##### STEPS TO REPRODUCE
```yaml
- name: Set login
  eos_banner:
    banner: login
    text: |
      this is my login banner
      that has a multiline
      string
    state: present
    authorize: yes 
  register: result

- debug:
    msg: "{{ result }}"

- assert:
    that:
      - "result.changed == true"
      - "'that has a multiline' in result.commands" # does this break due to "contains?"
      # Ensure sessions contains epoc. Will fail after 18th May 2033
      - "'ansible_1' in result.session_name"

- name: Set login again (idempotent)
  eos_banner:
    banner: login
    text: |
      this is my login banner
      that has a multiline
      string
    state: present
    authorize: yes 
  register: result

```


##### EXPECTED RESULTS
2nd run shouldn't report changed
##### ACTUAL RESULTS
```yaml
 changed: [veos01] => {
    "changed": true, 
    "commands": [
        "banner login", 
        "this is my login banner", 
        "that has a multiline", 
        "string", 
        "EOF"
    ], 
    "invocation": {
        "module_args": {
            "auth_pass": null, 
            "authorize": true, 
            "banner": "login", 
            "host": null, 
            "password": null, 
            "port": null, 
            "provider": null, 
            "ssh_keyfile": null, 
            "state": "present", 
            "text": "this is my login banner\nthat has a multiline\nstring\n", 
            "timeout": null, 
            "transport": null, 
            "use_ssl": null, 
            "username": null, 
            "validate_certs": null
        }
    }, 
    "session_name": "ansible_1488455488"
}

TASK [eos_banner : debug] ************************************************************************************************************************************************************************************************************************
task path: /home/johnb/git/ansible-inc/ansible-workspace-2/test/integration/targets/eos_banner/tests/cli/eos_banner_basic.yaml:27
ok: [veos01] => {
    "changed": false, 
    "msg": {
        "changed": true, 
        "commands": [
            "banner login", 
            "this is my login banner", 
            "that has a multiline", 
            "string", 
            "EOF"
        ], 
        "session_name": "ansible_1488455488", 
        "warnings": [
            "argument authorize has been deprecated and will be removed in a future version"
        ]
    }
}

TASK [eos_banner : assert] ***********************************************************************************************************************************************************************************************************************
task path: /home/johnb/git/ansible-inc/ansible-workspace-2/test/integration/targets/eos_banner/tests/cli/eos_banner_basic.yaml:30
ok: [veos01] => {
    "changed": false, 
    "msg": "All assertions passed"
}

TASK [eos_banner : Set login again (idempotent)] *************************************************************************************************************************************************************************************************
task path: /home/johnb/git/ansible-inc/ansible-workspace-2/test/integration/targets/eos_banner/tests/cli/eos_banner_basic.yaml:37
<veos01> connection transport is cli
<veos01> socket_path: /home/johnb/.ansible/pc/ca5960d27a
Using module file /home/johnb/git/ansible-inc/ansible-workspace-2/lib/ansible/modules/network/eos/eos_banner.py
<veos01> ESTABLISH LOCAL CONNECTION FOR USER: johnb
<veos01> EXEC /bin/sh -c 'echo ~ && sleep 0'
<veos01> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/johnb/.ansible/tmp/ansible-tmp-1488455491.69-116354442763630 `" && echo ansible-tmp-1488455491.69-116354442763630="` echo /home/johnb/.ansible/tmp/ansible-tmp-1488455491.69-116354442763630 `" ) && sleep 0'
<veos01> PUT /tmp/tmpyLoa44 TO /home/johnb/.ansible/tmp/ansible-tmp-1488455491.69-116354442763630/eos_banner.py
<veos01> EXEC /bin/sh -c 'chmod u+x /home/johnb/.ansible/tmp/ansible-tmp-1488455491.69-116354442763630/ /home/johnb/.ansible/tmp/ansible-tmp-1488455491.69-116354442763630/eos_banner.py && sleep 0'
<veos01> EXEC /bin/sh -c 'python /home/johnb/.ansible/tmp/ansible-tmp-1488455491.69-116354442763630/eos_banner.py; rm -rf "/home/johnb/.ansible/tmp/ansible-tmp-1488455491.69-116354442763630/" > /dev/null 2>&1 && sleep 0'
changed: [veos01] => {
    "changed": true, 
    "commands": [
        "banner login", 
        "this is my login banner", 
        "that has a multiline", 
        "string", 
        "EOF"
    ], 
    "invocation": {
        "module_args": {
            "auth_pass": null, 
            "authorize": true, 
            "banner": "login", 
            "host": null, 
            "password": null, 
            "port": null, 
            "provider": null, 
            "ssh_keyfile": null, 
            "state": "present", 
            "text": "this is my login banner\nthat has a multiline\nstring\n", 
            "timeout": null, 
            "transport": null, 
            "use_ssl": null, 
            "username": null, 
            "validate_certs": null
        }
    }, 
    "session_name": "ansible_1488455492"
}


```



Discussion:
cc @privateip @Qalthos
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Also when this is fixed should `.commands` exist for runs where nothing changes?
This also applies to `state: absent`
P3

Resolving patch:
diff --git a/lib/ansible/modules/network/eos/eos_banner.py b/lib/ansible/modules/network/eos/eos_banner.py
--- a/lib/ansible/modules/network/eos/eos_banner.py
+++ b/lib/ansible/modules/network/eos/eos_banner.py
@@ -102,7 +102,7 @@ def map_obj_to_commands(updates, module):
     want, have = updates
     state = module.params['state']
 
-    if state == 'absent':
+    if state == 'absent' and have['text']:
         commands.append('no banner %s' % module.params['banner'])
 
     elif state == 'present':
@@ -117,7 +117,7 @@ def map_config_to_obj(module):
     output = run_commands(module, ['show banner %s' % module.params['banner']])
     obj = {'banner': module.params['banner'], 'state': 'absent'}
     if output:
-        obj['text'] = output
+        obj['text'] = output[0]
         obj['state'] = 'present'
     return obj
 
@@ -155,7 +155,6 @@ def main():
     result = {'changed': False}
     if warnings:
         result['warnings'] = warnings
-
     want = map_params_to_obj(module)
     have = map_config_to_obj(module)
 


Test patch:
diff --git a/test/units/modules/network/eos/test_eos_banner.py b/test/units/modules/network/eos/test_eos_banner.py
--- a/test/units/modules/network/eos/test_eos_banner.py
+++ b/test/units/modules/network/eos/test_eos_banner.py
@@ -40,7 +40,7 @@ def tearDown(self):
         self.mock_load_config.stop()
 
     def load_fixtures(self, commands=None):
-        self.run_commands.return_value = load_fixture('eos_banner_show_banner.txt').strip()
+        self.run_commands.return_value = [load_fixture('eos_banner_show_banner.txt').strip()]
         self.load_config.return_value = dict(diff=None, session='session')
 
     def test_eos_banner_create(self):