From 7aefc07431d293e72559eda55dcd3e38728ff9d9 Mon Sep 17 00:00:00 2001
From: Erik Johnson <palehose@gmail.com>
Date: Mon, 22 Feb 2016 16:35:53 -0600
Subject: [PATCH 58/58] Getting the 'os' grain from CPE_NAME inside
 /etc/os-release for SUSE and openSUSE

Add unit test for core grains

Rename core.py to core_test.py

Adding SLES_SAP to OS_FAMILY_MAP

testing if SUSE os grain is set from /etc/os-release

lint fix

osarch mock for unit test

fix: osarch_mock

Test fixed: get OS grain from /etc/os-release if possible

one clause to set OS grain from CPE_NAME

Normalization of osfullname grain for openSUSE

unit tests for SUSE os grains detection

fixes for fopen mock and some os_release_map for SLES11SP3

Fix for openSUSE Tumbleweed

better way to check for openSUSE Leap

some cleanup and renaming
---
 salt/grains/core.py            |  12 +-
 tests/unit/grains/__init__.py  |   1 +
 tests/unit/grains/core_test.py | 374 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 386 insertions(+), 1 deletion(-)
 create mode 100644 tests/unit/grains/__init__.py
 create mode 100644 tests/unit/grains/core_test.py

diff --git a/salt/grains/core.py b/salt/grains/core.py
index f730a65..4dce646 100644
--- a/salt/grains/core.py
+++ b/salt/grains/core.py
@@ -965,6 +965,7 @@ _OS_FAMILY_MAP = {
     'SUSE': 'Suse',
     'openSUSE Leap': 'Suse',
     'openSUSE Tumbleweed': 'Suse',
+    'SLES_SAP': 'Suse',
     'Solaris': 'Solaris',
     'SmartOS': 'Solaris',
     'OpenIndiana Development': 'Solaris',
@@ -1177,6 +1178,14 @@ def os_data():
                         grains['lsb_distrib_release'] = os_release['VERSION_ID']
                     if 'PRETTY_NAME' in os_release:
                         grains['lsb_distrib_codename'] = os_release['PRETTY_NAME']
+                    if 'CPE_NAME' in os_release:
+                        if ":suse:" in os_release['CPE_NAME'] or ":opensuse:" in os_release['CPE_NAME']:
+                            grains['os'] = "SUSE"
+                            # openSUSE `osfullname` grain normalization
+                            if os_release.get("NAME") == "openSUSE Leap":
+                                grains['osfullname'] = "Leap"
+                            elif os_release.get("VERSION") == "Tumbleweed":
+                                grains['osfullname'] = os_release["VERSION"]
                 elif os.path.isfile('/etc/SuSE-release'):
                     grains['lsb_distrib_id'] = 'SUSE'
                     version = ''
@@ -1275,7 +1284,8 @@ def os_data():
         shortname = distroname.replace(' ', '').lower()[:10]
         # this maps the long names from the /etc/DISTRO-release files to the
         # traditional short names that Salt has used.
-        grains['os'] = _OS_NAME_MAP.get(shortname, distroname)
+        if 'os' not in grains:
+            grains['os'] = _OS_NAME_MAP.get(shortname, distroname)
         grains.update(_linux_cpudata())
         grains.update(_linux_gpu_data())
     elif grains['kernel'] == 'SunOS':
diff --git a/tests/unit/grains/__init__.py b/tests/unit/grains/__init__.py
new file mode 100644
index 0000000..40a96af
--- /dev/null
+++ b/tests/unit/grains/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/tests/unit/grains/core_test.py b/tests/unit/grains/core_test.py
new file mode 100644
index 0000000..7e9c4af
--- /dev/null
+++ b/tests/unit/grains/core_test.py
@@ -0,0 +1,374 @@
+# -*- coding: utf-8 -*-
+'''
+    :codeauthor: :email:`Erik Johnson <erik@saltstack.com>`
+'''
+
+# Import Python libs
+from __future__ import absolute_import
+import os
+import platform
+
+# Import Salt Testing Libs
+from salttesting import TestCase, skipIf
+from salttesting.helpers import ensure_in_syspath
+from salttesting.mock import (
+    MagicMock,
+    patch,
+    mock_open,
+    NO_MOCK,
+    NO_MOCK_REASON
+)
+
+ensure_in_syspath('../../')
+
+# Import Salt Libs
+import salt.utils
+from salt.grains import core
+
+# Globals
+core.__salt__ = {}
+
+
+@skipIf(NO_MOCK, NO_MOCK_REASON)
+class CoreGrainsTestCase(TestCase):
+    '''
+    Test cases for core grains
+    '''
+    @skipIf(not salt.utils.is_linux(), 'System is not Linux')
+    def test_gnu_slash_linux_in_os_name(self):
+        '''
+        Test to return a list of all enabled services
+        '''
+        _path_exists_map = {
+            '/proc/1/cmdline': False
+        }
+        _path_isfile_map = {}
+        _cmd_run_map = {
+            'dpkg --print-architecture': 'amd64'
+        }
+
+        path_exists_mock = MagicMock(side_effect=lambda x: _path_exists_map[x])
+        path_isfile_mock = MagicMock(
+            side_effect=lambda x: _path_isfile_map.get(x, False)
+        )
+        cmd_run_mock = MagicMock(
+            side_effect=lambda x: _cmd_run_map[x]
+        )
+        empty_mock = MagicMock(return_value={})
+
+        orig_import = __import__
+
+        def _import_mock(name, *args):
+            if name == 'lsb_release':
+                raise ImportError('No module named lsb_release')
+            return orig_import(name, *args)
+
+        # Skip the first if statement
+        with patch.object(salt.utils, 'is_proxy',
+                          MagicMock(return_value=False)):
+            # Skip the selinux/systemd stuff (not pertinent)
+            with patch.object(core, '_linux_bin_exists',
+                              MagicMock(return_value=False)):
+                # Skip the init grain compilation (not pertinent)
+                with patch.object(os.path, 'exists', path_exists_mock):
+                    # Ensure that lsb_release fails to import
+                    with patch('__builtin__.__import__',
+                               side_effect=_import_mock):
+                        # Skip all the /etc/*-release stuff (not pertinent)
+                        with patch.object(os.path, 'isfile', path_isfile_mock):
+                            # Mock platform.linux_distribution to give us the
+                            # OS name that we want.
+                            distro_mock = MagicMock(
+                                return_value=('Debian GNU/Linux', '8.3', '')
+                            )
+                            with patch.object(
+                                    platform,
+                                    'linux_distribution',
+                                    distro_mock):
+                                # Make a bunch of functions return empty dicts,
+                                # we don't care about these grains for the
+                                # purposes of this test.
+                                with patch.object(
+                                        core,
+                                        '_linux_cpudata',
+                                        empty_mock):
+                                    with patch.object(
+                                            core,
+                                            '_linux_gpu_data',
+                                            empty_mock):
+                                        with patch.object(
+                                                core,
+                                                '_memdata',
+                                                empty_mock):
+                                            with patch.object(
+                                                    core,
+                                                    '_hw_data',
+                                                    empty_mock):
+                                                with patch.object(
+                                                        core,
+                                                        '_virtual',
+                                                        empty_mock):
+                                                    with patch.object(
+                                                            core,
+                                                            '_ps',
+                                                            empty_mock):
+                                                        # Mock the osarch
+                                                        with patch.dict(
+                                                                core.__salt__,
+                                                                {'cmd.run': cmd_run_mock}):
+                                                            os_grains = core.os_data()
+
+        self.assertEqual(os_grains.get('os_family'), 'Debian')
+
+    @skipIf(not salt.utils.is_linux(), 'System is not Linux')
+    def test_suse_os_from_cpe_data(self):
+        '''
+        Test if 'os' grain is parsed from CPE_NAME of /etc/os-release
+        '''
+        _path_exists_map = {
+            '/proc/1/cmdline': False
+        }
+        _path_isfile_map = {
+            '/etc/os-release': True,
+        }
+        _os_release_map = {
+            'NAME': 'SLES',
+            'VERSION': '12-SP1',
+            'VERSION_ID': '12.1',
+            'PRETTY_NAME': 'SUSE Linux Enterprise Server 12 SP1',
+            'ID': 'sles',
+            'ANSI_COLOR': '0;32',
+            'CPE_NAME': 'cpe:/o:suse:sles:12:sp1'
+        }
+
+        path_exists_mock = MagicMock(side_effect=lambda x: _path_exists_map[x])
+        path_isfile_mock = MagicMock(
+            side_effect=lambda x: _path_isfile_map.get(x, False)
+        )
+        empty_mock = MagicMock(return_value={})
+        osarch_mock = MagicMock(return_value="amd64")
+        os_release_mock = MagicMock(return_value=_os_release_map)
+
+        orig_import = __import__
+
+        def _import_mock(name, *args):
+            if name == 'lsb_release':
+                raise ImportError('No module named lsb_release')
+            return orig_import(name, *args)
+
+        # Skip the first if statement
+        with patch.object(salt.utils, 'is_proxy',
+                          MagicMock(return_value=False)):
+            # Skip the selinux/systemd stuff (not pertinent)
+            with patch.object(core, '_linux_bin_exists',
+                              MagicMock(return_value=False)):
+                # Skip the init grain compilation (not pertinent)
+                with patch.object(os.path, 'exists', path_exists_mock):
+                    # Ensure that lsb_release fails to import
+                    with patch('__builtin__.__import__',
+                               side_effect=_import_mock):
+                        # Skip all the /etc/*-release stuff (not pertinent)
+                        with patch.object(os.path, 'isfile', path_isfile_mock):
+                            with patch.object(core, '_parse_os_release', os_release_mock):
+                                # Mock platform.linux_distribution to give us the
+                                # OS name that we want.
+                                distro_mock = MagicMock(
+                                    return_value=('SUSE Linux Enterprise Server ', '12', 'x86_64')
+                                )
+                                with patch.object(platform, 'linux_distribution', distro_mock):
+                                    with patch.object(core, '_linux_gpu_data', empty_mock):
+                                        with patch.object(core, '_linux_cpudata', empty_mock):
+                                            with patch.object(core, '_virtual', empty_mock):
+                                                # Mock the osarch
+                                                with patch.dict(core.__salt__, {'cmd.run': osarch_mock}):
+                                                    os_grains = core.os_data()
+
+        self.assertEqual(os_grains.get('os_family'), 'Suse')
+        self.assertEqual(os_grains.get('os'), 'SUSE')
+
+    def _run_suse_os_grains_tests(self, os_release_map):
+        path_isfile_mock = MagicMock(side_effect=lambda x: x in os_release_map['files'])
+        empty_mock = MagicMock(return_value={})
+        osarch_mock = MagicMock(return_value="amd64")
+        os_release_mock = MagicMock(return_value=os_release_map.get('os_release_file'))
+
+        orig_import = __import__
+
+        def _import_mock(name, *args):
+            if name == 'lsb_release':
+                raise ImportError('No module named lsb_release')
+            return orig_import(name, *args)
+
+        # Skip the first if statement
+        with patch.object(salt.utils, 'is_proxy',
+                          MagicMock(return_value=False)):
+            # Skip the selinux/systemd stuff (not pertinent)
+            with patch.object(core, '_linux_bin_exists',
+                              MagicMock(return_value=False)):
+                # Skip the init grain compilation (not pertinent)
+                with patch.object(os.path, 'exists', path_isfile_mock):
+                    # Ensure that lsb_release fails to import
+                    with patch('__builtin__.__import__',
+                               side_effect=_import_mock):
+                        # Skip all the /etc/*-release stuff (not pertinent)
+                        with patch.object(os.path, 'isfile', path_isfile_mock):
+                            with patch.object(core, '_parse_os_release', os_release_mock):
+                                # Mock platform.linux_distribution to give us the
+                                # OS name that we want.
+                                distro_mock = MagicMock(
+                                    return_value=('SUSE test', 'version', 'arch')
+                                )
+                                with patch("salt.utils.fopen", mock_open()) as suse_release_file:
+                                    suse_release_file.return_value.__iter__.return_value = os_release_map.get('suse_release_file', '').splitlines()
+                                    with patch.object(platform, 'linux_distribution', distro_mock):
+                                        with patch.object(core, '_linux_gpu_data', empty_mock):
+                                            with patch.object(core, '_linux_cpudata', empty_mock):
+                                                with patch.object(core, '_virtual', empty_mock):
+                                                    # Mock the osarch
+                                                    with patch.dict(core.__salt__, {'cmd.run': osarch_mock}):
+                                                        os_grains = core.os_data()
+
+        self.assertEqual(os_grains.get('os'), 'SUSE')
+        self.assertEqual(os_grains.get('os_family'), 'Suse')
+        self.assertEqual(os_grains.get('osfullname'), os_release_map['osfullname'])
+        self.assertEqual(os_grains.get('oscodename'), os_release_map['oscodename'])
+        self.assertEqual(os_grains.get('osrelease'), os_release_map['osrelease'])
+        self.assertListEqual(list(os_grains.get('osrelease_info')), os_release_map['osrelease_info'])
+
+    @skipIf(not salt.utils.is_linux(), 'System is not Linux')
+    def test_suse_os_grains_sles11sp3(self):
+        '''
+        Test if OS grains are parsed correctly in SLES 11 SP3
+        '''
+        _os_release_map = {
+            'suse_release_file': '''SUSE Linux Enterprise Server 11 (x86_64)
+VERSION = 11
+PATCHLEVEL = 3
+''',
+            'oscodename': 'SUSE Linux Enterprise Server 11 SP3',
+            'osfullname': "SLES",
+            'osrelease': '11.3',
+            'osrelease_info': [11, 3],
+            'files': ["/etc/SuSE-release"],
+        }
+        self._run_suse_os_grains_tests(_os_release_map)
+
+    @skipIf(not salt.utils.is_linux(), 'System is not Linux')
+    def test_suse_os_grains_sles11sp4(self):
+        '''
+        Test if OS grains are parsed correctly in SLES 11 SP4
+        '''
+        _os_release_map = {
+            'os_release_file': {
+                'NAME': 'SLES',
+                'VERSION': '11.4',
+                'VERSION_ID': '11.4',
+                'PRETTY_NAME': 'SUSE Linux Enterprise Server 11 SP4',
+                'ID': 'sles',
+                'ANSI_COLOR': '0;32',
+                'CPE_NAME': 'cpe:/o:suse:sles:11:4'
+            },
+            'oscodename': 'SUSE Linux Enterprise Server 11 SP4',
+            'osfullname': "SLES",
+            'osrelease': '11.4',
+            'osrelease_info': [11, 4],
+            'files': ["/etc/os-release"],
+        }
+        self._run_suse_os_grains_tests(_os_release_map)
+
+    @skipIf(not salt.utils.is_linux(), 'System is not Linux')
+    def test_suse_os_grains_sles12(self):
+        '''
+        Test if OS grains are parsed correctly in SLES 12
+        '''
+        _os_release_map = {
+            'os_release_file': {
+                'NAME': 'SLES',
+                'VERSION': '12',
+                'VERSION_ID': '12',
+                'PRETTY_NAME': 'SUSE Linux Enterprise Server 12',
+                'ID': 'sles',
+                'ANSI_COLOR': '0;32',
+                'CPE_NAME': 'cpe:/o:suse:sles:12'
+            },
+            'oscodename': 'SUSE Linux Enterprise Server 12',
+            'osfullname': "SLES",
+            'osrelease': '12',
+            'osrelease_info': [12],
+            'files': ["/etc/os-release"],
+        }
+        self._run_suse_os_grains_tests(_os_release_map)
+
+    @skipIf(not salt.utils.is_linux(), 'System is not Linux')
+    def test_suse_os_grains_sles12sp1(self):
+        '''
+        Test if OS grains are parsed correctly in SLES 12 SP1
+        '''
+        _os_release_map = {
+            'os_release_file': {
+                'NAME': 'SLES',
+                'VERSION': '12-SP1',
+                'VERSION_ID': '12.1',
+                'PRETTY_NAME': 'SUSE Linux Enterprise Server 12 SP1',
+                'ID': 'sles',
+                'ANSI_COLOR': '0;32',
+                'CPE_NAME': 'cpe:/o:suse:sles:12:sp1'
+            },
+            'oscodename': 'SUSE Linux Enterprise Server 12 SP1',
+            'osfullname': "SLES",
+            'osrelease': '12.1',
+            'osrelease_info': [12, 1],
+            'files': ["/etc/os-release"],
+        }
+        self._run_suse_os_grains_tests(_os_release_map)
+
+    @skipIf(not salt.utils.is_linux(), 'System is not Linux')
+    def test_suse_os_grains_opensuse_leap_42_1(self):
+        '''
+        Test if OS grains are parsed correctly in openSUSE Leap 42.1
+        '''
+        _os_release_map = {
+            'os_release_file': {
+                'NAME': 'openSUSE Leap',
+                'VERSION': '42.1',
+                'VERSION_ID': '42.1',
+                'PRETTY_NAME': 'openSUSE Leap 42.1 (x86_64)',
+                'ID': 'opensuse',
+                'ANSI_COLOR': '0;32',
+                'CPE_NAME': 'cpe:/o:opensuse:opensuse:42.1'
+            },
+            'oscodename': 'openSUSE Leap 42.1 (x86_64)',
+            'osfullname': "Leap",
+            'osrelease': '42.1',
+            'osrelease_info': [42, 1],
+            'files': ["/etc/os-release"],
+        }
+        self._run_suse_os_grains_tests(_os_release_map)
+
+    @skipIf(not salt.utils.is_linux(), 'System is not Linux')
+    def test_suse_os_grains_tumbleweed(self):
+        '''
+        Test if OS grains are parsed correctly in openSUSE Tumbleweed
+        '''
+        _os_release_map = {
+            'os_release_file': {
+                'NAME': 'openSUSE',
+                'VERSION': 'Tumbleweed',
+                'VERSION_ID': '20160504',
+                'PRETTY_NAME': 'openSUSE Tumbleweed (20160504) (x86_64)',
+                'ID': 'opensuse',
+                'ANSI_COLOR': '0;32',
+                'CPE_NAME': 'cpe:/o:opensuse:opensuse:20160504'
+            },
+            'oscodename': 'openSUSE Tumbleweed (20160504) (x86_64)',
+            'osfullname': "Tumbleweed",
+            'osrelease': '20160504',
+            'osrelease_info': [20160504],
+            'files': ["/etc/os-release"],
+        }
+        self._run_suse_os_grains_tests(_os_release_map)
+
+
+if __name__ == '__main__':
+    from integration import run_tests
+    run_tests(CoreGrainsTestCase, needs_daemon=False)
-- 
2.8.2

