Repository: ansible/ansible

Issue:
eos_banner fails to configure login prompt
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
eos_banner
##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
2.4
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.
-->

##### SUMMARY
<!--- Explain the problem briefly -->

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: Set login
  eos_banner:
    banner: login
    text: |
      this is my login banner
      that has a multiline
      string
    state: present
    authorize: yes
    provider: 
      transport: eapi
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
Should configure login prompt

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
The full traceback is:
Traceback (most recent call last):
  File "/var/folders/bp/pf3l672j2s79st6d4ps6q7200000gn/T/ansible__CH5Gb/ansible_module_eos_banner.py", line 197, in <module>
    main()
  File "/var/folders/bp/pf3l672j2s79st6d4ps6q7200000gn/T/ansible__CH5Gb/ansible_module_eos_banner.py", line 183, in main
    commands = map_obj_to_commands((want, have), module)
  File "/var/folders/bp/pf3l672j2s79st6d4ps6q7200000gn/T/ansible__CH5Gb/ansible_module_eos_banner.py", line 115, in map_obj_to_commands
    have_text = have['text'].get('loginBanner') or have['text'].get('motd')
AttributeError: 'unicode' object has no attribute 'get'

fatal: [veos01]: FAILED! => {
    "changed": false,
    "failed": true,
    "module_stderr": "Traceback (most recent call last):\n  File \"/var/folders/bp/pf3l672j2s79st6d4ps6q7200000gn/T/ansible__CH5Gb/ansible_module_eos_banner.py\", line 197, in <module>\n    main()\n  File \"/var/folders/bp/pf3l672j2s79st6d4ps6q7200000gn/T/ansible__CH5Gb/ansible_module_eos_banner.py\", line 183, in main\n    commands = map_obj_to_commands((want, have), module)\n  File \"/var/folders/bp/pf3l672j2s79st6d4ps6q7200000gn/T/ansible__CH5Gb/ansible_module_eos_banner.py\", line 115, in map_obj_to_commands\n    have_text = have['text'].get('loginBanner') or have['text'].get('motd')\nAttributeError: 'unicode' object has no attribute 'get'\n",
    "module_stdout": "",
    "msg": "MODULE FAILURE",
    "rc": 0
}


```



Discussion:

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
@@ -92,8 +92,10 @@
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.eos import load_config, run_commands
 from ansible.module_utils.eos import eos_argument_spec, check_args
+from ansible.module_utils.six import string_types
 from ansible.module_utils._text import to_text
 
+
 def map_obj_to_commands(updates, module):
     commands = list()
     want, have = updates
@@ -106,7 +108,7 @@ def map_obj_to_commands(updates, module):
             commands.append({'cmd': 'no banner %s' % module.params['banner']})
 
     elif state == 'present':
-        if isinstance(have['text'], str):
+        if isinstance(have['text'], string_types):
             if want['text'] != have['text']:
                 commands.append('banner %s' % module.params['banner'])
                 commands.extend(want['text'].strip().split('\n'))
@@ -122,7 +124,6 @@ def map_obj_to_commands(updates, module):
                 commands.append({'cmd': 'banner %s' % module.params['banner'],
                                  'input': want['text'].strip('\n')})
 
-
     return commands
 
 def map_config_to_obj(module):
@@ -139,7 +140,7 @@ def map_config_to_obj(module):
             else:
                 banner_response_key = 'motd'
             if isinstance(output[0], dict) and banner_response_key in output[0].keys():
-                obj['text'] = output[0][banner_response_key].strip('\n')
+                obj['text'] = output[0]
         obj['state'] = 'present'
     return obj
 


Test patch:
diff --git a/test/units/modules/network/eos/eos_module.py b/test/units/modules/network/eos/eos_module.py
--- a/test/units/modules/network/eos/eos_module.py
+++ b/test/units/modules/network/eos/eos_module.py
@@ -64,7 +64,7 @@ class AnsibleFailJson(Exception):
 
 class TestEosModule(unittest.TestCase):
 
-    def execute_module(self, failed=False, changed=False, commands=None, sort=True, defaults=False, transport='cli'):
+    def execute_module(self, failed=False, changed=False, commands=None, inputs=None, sort=True, defaults=False, transport='cli'):
 
         self.load_fixtures(commands, transport=transport)
 
@@ -76,10 +76,24 @@ def execute_module(self, failed=False, changed=False, commands=None, sort=True,
             self.assertEqual(result['changed'], changed, result)
 
         if commands is not None:
-            if sort:
-                self.assertEqual(sorted(commands), sorted(result['commands']), result['commands'])
+            if transport == 'eapi':
+                cmd = []
+                value = []
+                for item in result['commands']:
+                    cmd.append(item['cmd'])
+                    if 'input' in item:
+                        value.append(item['input'])
+                if sort:
+                    self.assertEqual(sorted(commands), sorted(cmd), cmd)
+                else:
+                    self.assertEqual(commands, cmd, cmd)
+                if inputs:
+                    self.assertEqual(inputs, value, value)
             else:
-                self.assertEqual(commands, result['commands'], result['commands'])
+                if sort:
+                    self.assertEqual(sorted(commands), sorted(result['commands']), result['commands'])
+                else:
+                    self.assertEqual(commands, result['commands'], result['commands'])
 
         return result
 
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
@@ -58,6 +58,13 @@ def test_eos_banner_remove_with_cli_transport(self):
         commands = ['no banner login']
         self.execute_module(changed=True, commands=commands)
 
+    def test_eos_banner_create_with_eapi_transport(self):
+        set_module_args(dict(banner='login', text='test\nbanner\nstring',
+                             transport='eapi'))
+        commands = ['banner login']
+        inputs = ['test\nbanner\nstring']
+        self.execute_module(changed=True, commands=commands, inputs=inputs, transport='eapi')
+
     def test_eos_banner_remove_with_eapi_transport(self):
         set_module_args(dict(banner='login', state='absent', transport='eapi'))
         commands = ['no banner login']