Repository: ansible/ansible

Issue:
Task does not fail after the configuration error, using the EAPI connection in eos_config
<!---
Verify first that your issue/request is not already reported on GitHub.
THIS FORM WILL BE READ BY A MACHINE, COMPLETE ALL SECTIONS AS DESCRIBED.
Also test if the latest release, and devel branch are affected too.
ALWAYS add information AFTER (OUTSIDE) these html comments.
Otherwise it may end up being automatically closed by our bot. -->

##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
<!--- Insert, BELOW THIS COMMENT, the name of the module, plugin, task or feature.
Do not include extra details here, e.g. "vyos_command" not "the network module vyos_command" or the full path--> eos_config

##### ANSIBLE VERSION
<!--- Paste, BELOW THIS COMMENT, verbatim output from "ansible --version" between quotes below -->
```
ansible 2.5.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/etc/ansible/virtualenv']
  ansible python module location = /usr/local/lib/python2.7/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.13 (default, Jul 13 2017, 21:39:32) [GCC 4.2.1 Compatible FreeBSD Clang 3.4.1 (tags/RELEASE_34/dot1-final 208032)]
```

##### CONFIGURATION
<!--- If using Ansible 2.4 or above, paste, BELOW THIS COMMENT, the results of "ansible-config dump --only-changed"
Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).-->
ESC[0;33mANSIBLE_SSH_ARGS(/etc/ansible/ansible.cfg) = -s -p 830 netconfESC[0m
ESC[0;33mDEFAULT_HOST_LIST(/etc/ansible/ansible.cfg) = [u'/etc/ansible/hosts']ESC[0m
ESC[0;33mDEFAULT_LOCAL_TMP(/etc/ansible/ansible.cfg) = /local/rootik/.ansible/tmp/ansible-local-4659WZR81GESC[0m
ESC[0;33mDEFAULT_LOG_PATH(/etc/ansible/ansible.cfg) = /var/log/ansible.logESC[0m
ESC[0;33mDEFAULT_MODULE_PATH(/etc/ansible/ansible.cfg) = [u'/etc/ansible/virtualenv']ESC[0m
ESC[0;33mPERSISTENT_COMMAND_TIMEOUT(/etc/ansible/ansible.cfg) = 600ESC[0m

##### OS / ENVIRONMENT
<!--- Mention, BELOW THIS COMMENT, 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.-->
Software image version: 4.20.3F

##### SUMMARY
<!--- Explain the problem briefly -->
The playbook task doesn't fail, when using "eos_config" with EAPI as a transport, after the configuration, which brings up the following error:
% Subnet aa00::/64 overlaps with existing subnet aa00::10:8:20:6/127 of interface Ethernetx/x/x.y

##### 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
Task:
    - name: configure an interface
      eos_config:
       provider: '{{ eapi }}'
       lines:
        - description "dwcarder playground interface 3"
        - ipv6 address aa00::d:a:1:e/64
        - shutdown
       parents: "interface Ethernet4/24/1"
       replace: block

The IP address above should bring this error on Arista router:
(config)#int e4/24/1
(config-if-Et4/24/1)#no switchport 
(config-if-Et4/24/1)#ipv6 address aa00::d:a:1:e/64
% Subnet aa00::/64 overlaps with existing subnet aa00::10:8:20:6/127 of interface Ethernet4/2/2.100

```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
TASK [...] ******************************************************************************************************************************
fatal: [x.x.x.x]: FAILED!

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
TASK [...] ***************************************************************************************************************************
changed: 
<!--- Paste verbatim command output between quotes below -->
```

```



Discussion:
Files identified in the description:
* [lib/ansible/modules/network/eos/eos_config.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/network/eos/eos_config.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 --->
cc @privateip @trishnaguha
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Hi,
can you please provide the output of `ansible-playbook -vvvvv ...` so we can see the actual result and if any errors are reported

needs_info
If you use `cli` does the task fail correctly?
@gundalow Yes, with cli it fails correctly.

Resolving patch:
diff --git a/lib/ansible/module_utils/network/eos/eos.py b/lib/ansible/module_utils/network/eos/eos.py
--- a/lib/ansible/module_utils/network/eos/eos.py
+++ b/lib/ansible/module_utils/network/eos/eos.py
@@ -153,7 +153,7 @@ def load_config(self, commands, commit=False, replace=False):
         try:
             response = conn.edit_config(commands, commit, replace)
         except ConnectionError as exc:
-            message = getattr(exc, 'err', exc)
+            message = getattr(exc, 'err', to_text(exc))
             if "check mode is not supported without configuration session" in message:
                 self._module.warn("EOS can not check config without config session")
                 response = {'changed': True}
@@ -341,7 +341,11 @@ def load_config(self, config, commit=False, replace=False):
             commands = ['configure session %s' % session, 'abort']
             self.send_request(commands)
             err = response['error']
-            self._module.fail_json(msg=err['message'], code=err['code'])
+            error_text = []
+            for data in err['data']:
+                error_text.extend(data.get('errors', []))
+            error_text = '\n'.join(error_text) or err['message']
+            self._module.fail_json(msg=error_text, code=err['code'])
 
         commands = ['configure session %s' % session, 'show session-config diffs']
         if commit:
diff --git a/lib/ansible/plugins/httpapi/eos.py b/lib/ansible/plugins/httpapi/eos.py
--- a/lib/ansible/plugins/httpapi/eos.py
+++ b/lib/ansible/plugins/httpapi/eos.py
@@ -85,7 +85,12 @@ def edit_config(self, config, commit=False, replace=False):
             else:
                 commands.append(command)
 
-        response = self.send_request(commands)
+        try:
+            response = self.send_request(commands)
+        except Exception:
+            commands = ['configure session %s' % session, 'abort']
+            response = self.send_request(commands, output='text')
+            raise
 
         commands = ['configure session %s' % session, 'show session-config diffs']
         if commit:
@@ -152,7 +157,13 @@ def load_config(self, config, commit=False, replace=False):
 def handle_response(response):
     if 'error' in response:
         error = response['error']
-        raise ConnectionError(error['message'], code=error['code'])
+
+        error_text = []
+        for data in error['data']:
+            error_text.extend(data.get('errors', []))
+        error_text = '\n'.join(error_text) or error['message']
+
+        raise ConnectionError(error_text, code=error['code'])
 
     results = []
     for result in response['result']:
diff --git a/lib/ansible/plugins/terminal/eos.py b/lib/ansible/plugins/terminal/eos.py
--- a/lib/ansible/plugins/terminal/eos.py
+++ b/lib/ansible/plugins/terminal/eos.py
@@ -46,7 +46,8 @@ class TerminalModule(TerminalBase):
         re.compile(br"'[^']' +returned error code: ?\d+"),
         re.compile(br"[^\r\n]\/bin\/(?:ba)?sh"),
         re.compile(br"% More than \d+ OSPF instance", re.I),
-        re.compile(br"Maximum number of pending sessions has been reached")
+        re.compile(br"% Subnet [0-9a-f.:/]+ overlaps", re.I),
+        re.compile(br"Maximum number of pending sessions has been reached"),
     ]
 
     def on_open_shell(self):


Test patch: