Repository: RedHatInsights/insights-core

Issue:
Redhat Release parser fails to parse some RHEL 6 and Beta product
The RedhatRelease parser has some problems parsing systems that look like this:

```
 'Red Hat Enterprise Linux Server release 6.10(Santiago)',
 'Red Hat Enterprise Linux release 8.4 Beta (Ootpa)',
```
The parser doesn't fail, but in the first example the `minor` attribute throws an exception and in the second example the `parsed['code_name']` attribute will be `"Beta (Ootpa"`.


Discussion:

Resolving patch:
diff --git a/insights/parsers/redhat_release.py b/insights/parsers/redhat_release.py
--- a/insights/parsers/redhat_release.py
+++ b/insights/parsers/redhat_release.py
@@ -8,23 +8,24 @@
 
     Red Hat Enterprise Linux Server release 7.2 (Maipo)
 
-This module parses the file content and stores data in the dict ``self.parsed``.
-The version info can also be get via ``obj.major`` and ``obj.minor``.
-Property ``is_rhel`` and ``is_hypervisor`` specifies the host type.
+This module parses the file contents and stores data in the class
+attributes described below.
 
 Examples:
-    >>> rh_rls_content = '''
-    ... Red Hat Enterprise Linux Server release 7.2 (Maipo)
-    ... '''.strip()
-    >>> from insights.tests import context_wrap
-    >>> shared = {RedhatRelease: RedhatRelease(context_wrap(rh_rls_content))}
-    >>> release = shared[RedhatRelease]
-    >>> assert release.raw == rh_rls_content
-    >>> assert release.major == 7
-    >>> assert release.minor == 2
-    >>> assert release.version == "7.2"
-    >>> assert release.is_rhel
-    >>> assert release.product == "Red Hat Enterprise Linux Server"
+    >>> type(rh_release)
+    <class 'insights.parsers.redhat_release.RedhatRelease'>
+    >>> rh_release.raw
+    'Red Hat Enterprise Linux Server release 7.2 (Maipo)'
+    >>> rh_release.major
+    7
+    >>> rh_release.minor
+    2
+    >>> rh_release.version
+    '7.2'
+    >>> rh_release.is_rhel
+    True
+    >>> rh_release.product
+    'Red Hat Enterprise Linux Server'
 """
 from .. import Parser, parser
 from ..specs import Specs
@@ -32,43 +33,68 @@
 
 @parser(Specs.redhat_release)
 class RedhatRelease(Parser):
-    """Parses the content of file ``/etc/redhat-release``."""
+    """Parses the content of file ``/etc/redhat-release``
+
+    Attributes:
+        is_beta(bool): True if this is a Beta release
+        is_centos(bool): True if this release is CentOS
+        is_fedora(bool): True if this release is Fedora
+        is_rhel(bool): True if this release is Red Hat Enterprise Linux
+        major(int): Major release number or None
+        minor(int): Minor release number or None
+        parsed(dict): Dictionary containing the parsed strings for ``product``, ``version``, and ``code_name``
+        raw(string): Unparsed redhat-release string
+    """
 
     def parse_content(self, content):
         self.raw = content[0]
+        self.is_beta = False
         product, _, version_name = [v.strip() for v in content[0].partition("release")]
-        version_name_split = [v.strip() for v in version_name.split(None, 1)]
-        code_name = (version_name_split[1].strip("()")
-                        if len(version_name_split) > 1 else None)
+        if 'Beta' in version_name:
+            # Red Hat Enterprise Linux release 8.5 Beta (Ootpa)
+            version_number, code_name = version_name.split('Beta', 1)
+            self.is_beta = True
+        elif '(' in version_name:
+            # Red Hat Enterprise Linux Workstation release 6.10(Santiago)
+            # Red Hat Enterprise Linux Workstation release 6.10 (Santiago)
+            version_number, code_name = version_name.split('(', 1)
+        else:
+            # Red Hat Enterprise Linux Workstation release 6.10
+            version_number = version_name
+            code_name = None
+
         self.parsed = {
             "product": product,
-            "version": version_name_split[0],
-            "code_name": code_name
+            "version": version_number.strip(),
+            "code_name": code_name.strip().strip('()') if code_name is not None else None
         }
 
-    @property
-    def major(self):
-        """int: the major version of this OS."""
-        return int(self.parsed["version"].split(".")[0])
+        self.is_rhel = 'red hat enterprise linux' in self.parsed['product'].lower()
+        self.is_centos = 'centos' in self.parsed['product'].lower()
+        self.is_fedora = 'fedora' in self.parsed['product'].lower()
 
-    @property
-    def minor(self):
-        """int: the minor version of this OS."""
-        s = self.parsed["version"].split("-", 1)[0].split(".")
-        if len(s) > 1:
-            return int(s[1])
+        v_parts = self.parsed['version'].split('.')
+        self.major = int(v_parts[0]) if v_parts[0].isdigit() else None
+        if len(v_parts) >= 2:
+            if '-' in v_parts[1]:
+                minor = v_parts[1].split('-')[0]
+            else:
+                minor = v_parts[1]
+            self.minor = int(minor) if minor.isdigit() else None
+        else:
+            self.minor = None
 
     @property
     def version(self):
         """string: version of this OS."""
         return self.parsed["version"]
 
-    @property
-    def is_rhel(self):
-        """bool: True if this OS belong to RHEL, else False."""
-        return "Red Hat Enterprise Linux" in self.parsed["product"]
-
     @property
     def product(self):
         """string: product of this OS."""
         return self.parsed["product"]
+
+    @property
+    def code_name(self):
+        """string: code name of this OS or None."""
+        return self.parsed["code_name"]


Test patch:
diff --git a/insights/parsers/tests/test_redhat_release.py b/insights/parsers/tests/test_redhat_release.py
--- a/insights/parsers/tests/test_redhat_release.py
+++ b/insights/parsers/tests/test_redhat_release.py
@@ -1,3 +1,6 @@
+import doctest
+
+from insights.parsers import redhat_release
 from insights.parsers.redhat_release import RedhatRelease
 from insights.tests import context_wrap
 
@@ -26,6 +29,26 @@
 Fedora release 23 (Twenty Three)
 """.strip()
 
+REDHAT_RELEASE8 = """
+Red Hat Enterprise Linux release 8.2 (Ootpa)
+""".strip()
+
+REDHAT_RELEASE10 = """
+Red Hat Enterprise Linux Server release 6.10(Santiago)
+""".strip()
+
+REDHAT_RELEASE_BETA = """
+Red Hat Enterprise Linux Server release 8.5 Beta (Ootpa)
+""".strip()
+
+CENTOS_STREAM = """
+CentOS Stream release 8
+""".strip()
+
+CENTOS_7 = """
+CentOS Linux release 7.6.1810 (Core)
+""".strip()
+
 
 def test_rhe6():
     release = RedhatRelease(context_wrap(REDHAT_RELEASE1))
@@ -84,4 +107,65 @@ def test_fedora23():
     assert release.minor is None
     assert release.version == "23"
     assert not release.is_rhel
+    assert release.is_fedora
     assert release.product == "Fedora"
+
+
+def test_rhel6_10():
+    release = RedhatRelease(context_wrap(REDHAT_RELEASE10))
+    assert release.raw == REDHAT_RELEASE10
+    assert release.major == 6
+    assert release.minor == 10
+    assert release.version == "6.10"
+    assert release.is_rhel
+    assert release.product == "Red Hat Enterprise Linux Server"
+
+
+def test_rhel8():
+    release = RedhatRelease(context_wrap(REDHAT_RELEASE8))
+    assert release.raw == REDHAT_RELEASE8
+    assert release.major == 8
+    assert release.minor == 2
+    assert release.version == "8.2"
+    assert release.is_rhel
+    assert release.product == "Red Hat Enterprise Linux"
+
+
+def test_rhel_beta():
+    release = RedhatRelease(context_wrap(REDHAT_RELEASE_BETA))
+    assert release.raw == REDHAT_RELEASE_BETA
+    assert release.major == 8
+    assert release.minor == 5
+    assert release.version == "8.5"
+    assert release.is_rhel
+    assert release.is_beta
+    assert release.parsed['code_name'] == 'Ootpa'
+    assert release.product == "Red Hat Enterprise Linux Server"
+
+
+def test_centos_stream():
+    release = RedhatRelease(context_wrap(CENTOS_STREAM))
+    assert release.major == 8
+    assert release.minor is None
+    assert release.product == 'CentOS Stream'
+    assert release.is_centos
+    assert not release.is_rhel
+
+
+def test_centos_7():
+    release = RedhatRelease(context_wrap(CENTOS_7))
+    assert release.major == 7
+    assert release.minor == 6
+    assert release.product == 'CentOS Linux'
+    assert release.code_name == 'Core'
+    assert release.is_centos
+    assert not release.is_rhel
+
+
+def test_examples():
+    release = RedhatRelease(context_wrap(REDHAT_RELEASE2))
+    globs = {
+        'rh_release': release
+    }
+    failed, tested = doctest.testmod(redhat_release, globs=globs)
+    assert failed == 0