Repository: RedHatInsights/insights-core

Issue:
Unable to run ethtool parser without root permissions
I want to get supported links mode for different interfaces. But I have a problem.
```python3
from insights import run
from insights.parsers import ethtool as ep

results2 = run(ep.Ethtool)
ept = results2[ep.Ethtool] # <-- ept should contains info that I need (including supported links mode).
```
This works perfectly fine when run python as root. But when run without root, I get the exception (ethtool: unrecognised first line).

Example of ethtool output (ethtool version 5.14):
```
$ ethtool enp3s0
Settings for enp3s0:
        Supported ports: [ TP ]
        Supported link modes:   10baseT/Half 10baseT/Full
                                100baseT/Half 100baseT/Full
                                1000baseT/Full
        Supported pause frame use: No
        Supports auto-negotiation: Yes
        Supported FEC modes: Not reported
        Advertised link modes:  10baseT/Half 10baseT/Full
                                100baseT/Half 100baseT/Full
                                1000baseT/Full
        Advertised pause frame use: No
        Advertised auto-negotiation: Yes
        Advertised FEC modes: Not reported
        Speed: 1000Mb/s
        Duplex: Full
        Auto-negotiation: on
        Port: Twisted Pair
        PHYAD: 1
        Transceiver: internal
        MDI-X: off (auto)
netlink error: Operation not permitted
        Current message level: 0x00000007 (7)
                               drv probe link
        Link detected: yes
```
when run with sudo, it is the same, except it has the info about wake on lan:

```
$ sudo ethtool enp3s0
...
        MDI-X: off (auto)
        Supports Wake-on: pumbg
        Wake-on: g
        Current message level: 0x00000007 (7)
...
```
On the another machine I have line (ethtool version 4.19):
```
        MDI-X: on (auto)
Cannot get wake-on-lan settings: Operation not permitted
        Current message level: 0x00000007 (7)
```

How can I tell the parser to ignore those lines?


Discussion:
Hi @Ashark insights-core is designed to be executed as root, but there are some commands that the output is parseable if not ran as root. As is the case for ethtool it will output most of the data, but will print operation not permitted to stderror for information it can't obtain. In insights stderror is at the top of the content provided to parsers. Currently the parser is setup to raise a ParseException if "Settings for <nic>:" isn't on the first line. I will work on an update that will loop through the content to look for that starting line and start parsing once found.

Resolving patch:
diff --git a/insights/parsers/ethtool.py b/insights/parsers/ethtool.py
--- a/insights/parsers/ethtool.py
+++ b/insights/parsers/ethtool.py
@@ -876,27 +876,36 @@ def parse_content(self, content):
         if "ethtool: bad command line argument(s)" in content[0]:
             raise ParseException('ethtool: bad command line argument for ethtool', content)
 
-        if "Settings for" not in content[0]:
-            raise ParseException("ethtool: unrecognised first line '{l}'".format(l=content[0]))
-
-        self.data['ETHNIC'] = content[0].split()[-1].strip(':')
-
-        if "No data available" in content[1]:
-            raise ParseException('Fake ethnic as ethtool command argument', content)
-
-        key = value = None
-        for line in content[1:]:
+        key = started = None
+        for line in content:
             line = line.strip()
             if line:
-                try:
-                    if ':' in line:
-                        key, value = line.split(':', 1)
-                        key = key.strip()
-                        self.data[key] = [value.strip()]
-                    else:
-                        self.data[key].append(line)
-                except:
-                    raise ParseException('Ethtool unable to parse content', line)
+                # Started is set when a line containing Settings for <nic>:
+                # is found, ignore all other lines prior to this one.
+                if not started:
+                    if "Settings for" in line:
+                        started = 1
+                        self.data['ETHNIC'] = line.split()[-1].strip(':')
+
+                    continue
+                else:
+                    if "No data available" in line:
+                        raise ParseException('Fake ethnic as ethtool command argument', content)
+
+                    try:
+                        if ':' in line:
+                            key, value = line.split(':', 1)
+                            key = key.strip()
+                            self.data[key] = [value.strip()]
+                        else:
+                            self.data[key].append(line)
+                    except KeyError:
+                        raise ParseException('Ethtool unable to parse content', line)
+
+        # If started was never set then there wasn't any
+        # valid lines found so raise a parse exception.
+        if not started:
+            raise ParseException('Ethtool does not contain Settings for <nic>:')
 
         self.supported_link_modes = []
         if 'Supported link modes' in self.data:


Test patch:
diff --git a/insights/parsers/tests/test_ethtool.py b/insights/parsers/tests/test_ethtool.py
--- a/insights/parsers/tests/test_ethtool.py
+++ b/insights/parsers/tests/test_ethtool.py
@@ -732,6 +732,8 @@ def test_extract_from_path_1():
 '''
 
 ETHTOOL_INFO = """
+Cannot get wake-on-lan settings: Operation not permitted
+Cannot get link status: Operation not permitted
 Settings for eth1:
     Supported ports: [ TP MII ]
     Supported link modes: 10baseT/Half 10baseT/Full
@@ -815,7 +817,7 @@ def test_ethtool_fail():
 
     with pytest.raises(ParseException) as e:
         ethtool.Ethtool(context_wrap(ETHTOOL_INFO_BAD_2, path="ethtool_eth1"))
-    assert "ethtool: unrecognised first line " in str(e.value)
+    assert "Ethtool does not contain Settings for <nic>:" in str(e.value)
 
     with pytest.raises(ParseException) as e:
         ethtool.Ethtool(context_wrap(ETHTOOL_INFO_BAD_3, path="ethtool_eth1"))