ipa/0005-freeipa-4.7.0-Refactor-os-release-and-platform-information.patch

455 lines
13 KiB
Diff
Raw Normal View History

commit b8528da5a8e8cf4fdeabb77022cb511043544e9f
Author: Christian Heimes <cheimes@redhat.com>
Date: Wed Aug 29 12:43:03 2018 +0200
Refactor os-release and platform information
Move the /etc/os-release parser and platform detection code out of the
private _importhook module. The ipaplatform module now contains an
osinfo module that provides distribution, os, and vendor information.
See: https://www.freedesktop.org/software/systemd/man/os-release.html
See: https://pagure.io/freeipa/issue/7661
Signed-off-by: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
diff --git a/ipaplatform/__init__.py b/ipaplatform/__init__.py
index 06397fda1..1c22346b0 100644
--- a/ipaplatform/__init__.py
+++ b/ipaplatform/__init__.py
@@ -8,4 +8,4 @@ ignore.
"""
__import__('pkg_resources').declare_namespace(__name__)
-NAME = None # initialized by IpaMetaImporter
+NAME = None # initialized by ipaplatform.osinfo
diff --git a/ipaplatform/_importhook.py b/ipaplatform/_importhook.py
index 77c4e0d87..3f84e81fd 100644
--- a/ipaplatform/_importhook.py
+++ b/ipaplatform/_importhook.py
@@ -3,46 +3,14 @@
#
from __future__ import absolute_import
-"""Meta import hook for ipaplatform.
-
-Known Linux distros with /etc/os-release
-----------------------------------------
-
-- alpine
-- centos (like rhel, fedora)
-- debian
-- fedora
-- rhel
-- ubuntu (like debian)
-"""
import importlib
-import io
-import re
import sys
-import warnings
-
-
-import ipaplatform
-try:
- from ipaplatform.override import OVERRIDE
-except ImportError:
- OVERRIDE = None
-
-_osrelease_line = re.compile(
- u"^(?!#)(?P<name>[a-zA-Z0-9_]+)="
- u"(?P<quote>[\"\']?)(?P<value>.+)(?P=quote)$"
-)
+from ipaplatform.osinfo import osinfo
class IpaMetaImporter(object):
- """Meta import hook and platform detector.
-
- The meta import hook uses /etc/os-release to auto-detects the best
- matching ipaplatform provider. It is compatible with external namespace
- packages, too.
- """
modules = {
'ipaplatform.constants',
'ipaplatform.paths',
@@ -50,80 +18,8 @@ class IpaMetaImporter(object):
'ipaplatform.tasks'
}
- bsd_family = (
- 'freebsd',
- 'openbsd',
- 'netbsd',
- 'dragonfly',
- 'gnukfreebsd'
- )
-
- def __init__(self, override=OVERRIDE):
- self.override = override
- self.platform_ids = self._get_platform_ids(self.override)
- self.platform = self._get_platform(self.platform_ids)
-
- def _get_platform_ids(self, override):
- platforms = []
- # allow RPM and Debian packages to override platform
- if override is not None:
- platforms.append(override)
-
- if sys.platform.startswith('linux'):
- # Linux, get distribution from /etc/os-release
- try:
- platforms.extend(self._parse_platform())
- except Exception as e:
- warnings.warn("Failed to read /etc/os-release: {}".format(e))
- elif sys.platform == 'win32':
- # Windows 32 or 64bit platform
- platforms.append('win32')
- elif sys.platform == 'darwin':
- # macOS
- platforms.append('macos')
- elif sys.platform.startswith(self.bsd_family):
- # BSD family, look for e.g. ['freebsd10', 'freebsd']
- platforms.append(sys.platform)
- simple = sys.platform.rstrip('0123456789')
- if simple != sys.platform:
- platforms.append(simple)
-
- if not platforms:
- raise ValueError("Unsupported platform: {}".format(sys.platform))
-
- return platforms
-
- def parse_osrelease(self, filename='/etc/os-release'):
- release = {}
- with io.open(filename, encoding='utf-8') as f:
- for line in f:
- mo = _osrelease_line.match(line)
- if mo is not None:
- release[mo.group('name')] = mo.group('value')
- return release
-
- def _parse_platform(self, filename='/etc/os-release'):
- release = self.parse_osrelease(filename)
- platforms = [
- release['ID'],
- ]
- if "ID_LIKE" in release:
- platforms.extend(
- v.strip() for v in release['ID_LIKE'].split(' ') if v.strip()
- )
-
- return platforms
-
- def _get_platform(self, platform_ids):
- for platform in platform_ids:
- try:
- importlib.import_module('ipaplatform.{}'.format(platform))
- except ImportError:
- pass
- else:
- return platform
- raise ImportError('No ipaplatform available for "{}"'.format(
- ', '.join(platform_ids)))
+ def __init__(self, platform):
+ self.platform = platform
def find_module(self, fullname, path=None):
"""Meta importer hook"""
@@ -148,8 +44,7 @@ class IpaMetaImporter(object):
return platform_mod
-metaimporter = IpaMetaImporter()
+metaimporter = IpaMetaImporter(osinfo.platform)
sys.meta_path.insert(0, metaimporter)
fixup_module = metaimporter.load_module
-ipaplatform.NAME = metaimporter.platform
diff --git a/ipaplatform/osinfo.py b/ipaplatform/osinfo.py
new file mode 100644
index 000000000..a38165d01
--- /dev/null
+++ b/ipaplatform/osinfo.py
@@ -0,0 +1,214 @@
+#
+# Copyright (C) 2018 FreeIPA Contributors see COPYING for license
+#
+"""Distribution information
+
+Known Linux distros with /etc/os-release
+----------------------------------------
+
+- alpine
+- centos (like rhel, fedora)
+- debian
+- fedora
+- rhel
+- ubuntu (like debian)
+"""
+from __future__ import absolute_import
+
+import importlib
+import io
+import re
+import sys
+import warnings
+
+import six
+
+import ipaplatform
+try:
+ from ipaplatform.override import OVERRIDE
+except ImportError:
+ OVERRIDE = None
+
+
+# pylint: disable=no-name-in-module, import-error
+if six.PY3:
+ from collections.abc import Mapping
+else:
+ from collections import Mapping
+# pylint: enable=no-name-in-module, import-error
+
+_osrelease_line = re.compile(
+ u"^(?!#)(?P<name>[a-zA-Z0-9_]+)="
+ u"(?P<quote>[\"\']?)(?P<value>.+)(?P=quote)$"
+)
+
+
+def _parse_osrelease(filename='/etc/os-release'):
+ """Parser for /etc/os-release for Linux distributions
+
+ https://www.freedesktop.org/software/systemd/man/os-release.html
+ """
+ release = {}
+ with io.open(filename, encoding='utf-8') as f:
+ for line in f:
+ mo = _osrelease_line.match(line)
+ if mo is not None:
+ release[mo.group('name')] = mo.group('value')
+ if 'ID_LIKE' in release:
+ release['ID_LIKE'] = tuple(
+ v.strip()
+ for v in release['ID_LIKE'].split(' ')
+ if v.strip()
+ )
+ else:
+ release["ID_LIKE"] = ()
+ # defaults
+ release.setdefault('NAME', 'Linux')
+ release.setdefault('ID', 'linux')
+ release.setdefault('VERSION', '')
+ release.setdefault('VERSION_ID', '')
+ return release
+
+
+class OSInfo(Mapping):
+ __slots__ = ('_info', '_platform')
+
+ bsd_family = (
+ 'freebsd',
+ 'openbsd',
+ 'netbsd',
+ 'dragonfly',
+ 'gnukfreebsd'
+ )
+
+ def __init__(self):
+ if sys.platform.startswith('linux'):
+ # Linux, get distribution from /etc/os-release
+ info = self._handle_linux()
+ elif sys.platform == 'win32':
+ info = self._handle_win32()
+ elif sys.platform == 'darwin':
+ info = self._handle_darwin()
+ elif sys.platform.startswith(self.bsd_family):
+ info = self._handle_bsd()
+ else:
+ raise ValueError("Unsupported platform: {}".format(sys.platform))
+ self._info = info
+ self._platform = None
+
+ def _handle_linux(self):
+ """Detect Linux distribution from /etc/os-release
+ """
+ try:
+ return _parse_osrelease()
+ except Exception as e:
+ warnings.warn("Failed to read /etc/os-release: {}".format(e))
+ return {
+ 'NAME': 'Linux',
+ 'ID': 'linux',
+ }
+
+ def _handle_win32(self):
+ """Windows 32 or 64bit platform
+ """
+ return {
+ 'NAME': 'Windows',
+ 'ID': 'win32',
+ }
+
+ def _handle_darwin(self):
+ """Handle macOS / Darwin platform
+ """
+ return {
+ 'NAME': 'macOS',
+ 'ID': 'macos',
+ }
+
+ def _handle_bsd(self):
+ """Handle BSD-like platforms
+ """
+ platform = sys.platform
+ simple = platform.rstrip('0123456789')
+ id_like = []
+ if simple != platform:
+ id_like.append(simple)
+ return {
+ 'NAME': platform,
+ 'ID': platform,
+ 'ID_LIKE': tuple(id_like),
+ }
+
+ def __getitem__(self, item):
+ return self._info[item]
+
+ def __iter__(self):
+ return iter(self._info)
+
+ def __len__(self):
+ return len(self._info)
+
+ @property
+ def name(self):
+ """OS name (user)
+ """
+ return self._info['NAME']
+
+ @property
+ def id(self):
+ """Lower case OS identifier
+ """
+ return self._info['ID']
+
+ @property
+ def id_like(self):
+ """Related / similar OS
+ """
+ return self._info.get('ID_LIKE', ())
+
+ @property
+ def version(self):
+ """Version number and name of OS (for user)
+ """
+ return self._info.get('VERSION')
+
+ @property
+ def version_id(self):
+ """Version identifier
+ """
+ return self._info.get('VERSION_ID')
+
+ @property
+ def platform_ids(self):
+ """Ordered tuple of detected platforms (including override)
+ """
+ platforms = []
+ if OVERRIDE is not None:
+ # allow RPM and Debian packages to override platform
+ platforms.append(OVERRIDE)
+ if OVERRIDE != self.id:
+ platforms.append(self.id)
+ platforms.extend(self.id_like)
+ return tuple(platforms)
+
+ @property
+ def platform(self):
+ if self._platform is not None:
+ return self._platform
+ for platform in self.platform_ids:
+ try:
+ importlib.import_module('ipaplatform.{}'.format(platform))
+ except ImportError:
+ pass
+ else:
+ self._platform = platform
+ return platform
+ raise ImportError('No ipaplatform available for "{}"'.format(
+ ', '.join(self.platform_ids)))
+
+
+osinfo = OSInfo()
+ipaplatform.NAME = osinfo.platform
+
+if __name__ == '__main__':
+ import pprint
+ pprint.pprint(dict(osinfo))
diff --git a/ipatests/test_ipaplatform/test_importhook.py b/ipatests/test_ipaplatform/test_importhook.py
index c7d2626d6..eeb351ba7 100644
--- a/ipatests/test_ipaplatform/test_importhook.py
+++ b/ipatests/test_ipaplatform/test_importhook.py
@@ -13,6 +13,7 @@ import ipaplatform.paths
import ipaplatform.services
import ipaplatform.tasks
from ipaplatform._importhook import metaimporter
+from ipaplatform.osinfo import osinfo, _parse_osrelease
try:
from ipaplatform.override import OVERRIDE
except ImportError:
@@ -26,8 +27,8 @@ DATA = os.path.join(HERE, 'data')
@pytest.mark.skipif(OVERRIDE is None,
reason='test requires override')
def test_override():
- assert OVERRIDE == metaimporter.platform_ids[0]
- assert OVERRIDE == metaimporter.platform
+ assert OVERRIDE == osinfo.platform_ids[0]
+ assert OVERRIDE == osinfo.platform
@pytest.mark.parametrize('mod, name', [
@@ -46,11 +47,12 @@ def test_importhook(mod, name):
assert mod.__dict__ == sys.modules[override].__dict__
-@pytest.mark.parametrize('filename, expected_platforms', [
- (os.path.join(DATA, 'os-release-centos'), ['centos', 'rhel', 'fedora']),
- (os.path.join(DATA, 'os-release-fedora'), ['fedora']),
- (os.path.join(DATA, 'os-release-ubuntu'), ['ubuntu', 'debian']),
+@pytest.mark.parametrize('filename, id_, id_like', [
+ (os.path.join(DATA, 'os-release-centos'), 'centos', ('rhel', 'fedora')),
+ (os.path.join(DATA, 'os-release-fedora'), 'fedora', ()),
+ (os.path.join(DATA, 'os-release-ubuntu'), 'ubuntu', ('debian',)),
])
-def test_parse_os_release(filename, expected_platforms):
- parsed = metaimporter._parse_platform(filename)
- assert parsed == expected_platforms
+def test_parse_os_release(filename, id_, id_like):
+ parsed = _parse_osrelease(filename)
+ assert parsed['ID'] == id_
+ assert parsed['ID_LIKE'] == id_like
diff --git a/ipatests/test_ipapython/test_certdb.py b/ipatests/test_ipapython/test_certdb.py
index 42edfb5a9..42d48d51a 100644
--- a/ipatests/test_ipapython/test_certdb.py
+++ b/ipatests/test_ipapython/test_certdb.py
@@ -5,13 +5,12 @@ import os
import pytest
from ipapython.certdb import NSSDatabase, TRUSTED_PEER_TRUST_FLAGS
-from ipaplatform._importhook import metaimporter
+from ipaplatform.osinfo import osinfo
-OSRELEASE = metaimporter.parse_osrelease()
CERTNICK = 'testcert'
-if OSRELEASE['ID'] == 'fedora':
- if int(OSRELEASE['VERSION_ID']) >= 28:
+if osinfo.id == 'fedora':
+ if int(osinfo.version_id) >= 28:
NSS_DEFAULT = 'sql'
else:
NSS_DEFAULT = 'dbm'