mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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>
This commit is contained in:
committed by
Tibor Dudlák
parent
8af6accfa5
commit
b8528da5a8
@@ -8,4 +8,4 @@ ignore.
|
|||||||
"""
|
"""
|
||||||
__import__('pkg_resources').declare_namespace(__name__)
|
__import__('pkg_resources').declare_namespace(__name__)
|
||||||
|
|
||||||
NAME = None # initialized by IpaMetaImporter
|
NAME = None # initialized by ipaplatform.osinfo
|
||||||
|
|||||||
@@ -3,46 +3,14 @@
|
|||||||
#
|
#
|
||||||
from __future__ import absolute_import
|
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 importlib
|
||||||
import io
|
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
|
||||||
|
|
||||||
|
from ipaplatform.osinfo import osinfo
|
||||||
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)$"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class IpaMetaImporter(object):
|
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 = {
|
modules = {
|
||||||
'ipaplatform.constants',
|
'ipaplatform.constants',
|
||||||
'ipaplatform.paths',
|
'ipaplatform.paths',
|
||||||
@@ -50,80 +18,8 @@ class IpaMetaImporter(object):
|
|||||||
'ipaplatform.tasks'
|
'ipaplatform.tasks'
|
||||||
}
|
}
|
||||||
|
|
||||||
bsd_family = (
|
def __init__(self, platform):
|
||||||
'freebsd',
|
self.platform = platform
|
||||||
'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 find_module(self, fullname, path=None):
|
def find_module(self, fullname, path=None):
|
||||||
"""Meta importer hook"""
|
"""Meta importer hook"""
|
||||||
@@ -148,8 +44,7 @@ class IpaMetaImporter(object):
|
|||||||
return platform_mod
|
return platform_mod
|
||||||
|
|
||||||
|
|
||||||
metaimporter = IpaMetaImporter()
|
metaimporter = IpaMetaImporter(osinfo.platform)
|
||||||
sys.meta_path.insert(0, metaimporter)
|
sys.meta_path.insert(0, metaimporter)
|
||||||
|
|
||||||
fixup_module = metaimporter.load_module
|
fixup_module = metaimporter.load_module
|
||||||
ipaplatform.NAME = metaimporter.platform
|
|
||||||
|
|||||||
214
ipaplatform/osinfo.py
Normal file
214
ipaplatform/osinfo.py
Normal file
@@ -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))
|
||||||
@@ -13,6 +13,7 @@ import ipaplatform.paths
|
|||||||
import ipaplatform.services
|
import ipaplatform.services
|
||||||
import ipaplatform.tasks
|
import ipaplatform.tasks
|
||||||
from ipaplatform._importhook import metaimporter
|
from ipaplatform._importhook import metaimporter
|
||||||
|
from ipaplatform.osinfo import osinfo, _parse_osrelease
|
||||||
try:
|
try:
|
||||||
from ipaplatform.override import OVERRIDE
|
from ipaplatform.override import OVERRIDE
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -26,8 +27,8 @@ DATA = os.path.join(HERE, 'data')
|
|||||||
@pytest.mark.skipif(OVERRIDE is None,
|
@pytest.mark.skipif(OVERRIDE is None,
|
||||||
reason='test requires override')
|
reason='test requires override')
|
||||||
def test_override():
|
def test_override():
|
||||||
assert OVERRIDE == metaimporter.platform_ids[0]
|
assert OVERRIDE == osinfo.platform_ids[0]
|
||||||
assert OVERRIDE == metaimporter.platform
|
assert OVERRIDE == osinfo.platform
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('mod, name', [
|
@pytest.mark.parametrize('mod, name', [
|
||||||
@@ -46,11 +47,12 @@ def test_importhook(mod, name):
|
|||||||
assert mod.__dict__ == sys.modules[override].__dict__
|
assert mod.__dict__ == sys.modules[override].__dict__
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('filename, expected_platforms', [
|
@pytest.mark.parametrize('filename, id_, id_like', [
|
||||||
(os.path.join(DATA, 'os-release-centos'), ['centos', 'rhel', 'fedora']),
|
(os.path.join(DATA, 'os-release-centos'), 'centos', ('rhel', 'fedora')),
|
||||||
(os.path.join(DATA, 'os-release-fedora'), ['fedora']),
|
(os.path.join(DATA, 'os-release-fedora'), 'fedora', ()),
|
||||||
(os.path.join(DATA, 'os-release-ubuntu'), ['ubuntu', 'debian']),
|
(os.path.join(DATA, 'os-release-ubuntu'), 'ubuntu', ('debian',)),
|
||||||
])
|
])
|
||||||
def test_parse_os_release(filename, expected_platforms):
|
def test_parse_os_release(filename, id_, id_like):
|
||||||
parsed = metaimporter._parse_platform(filename)
|
parsed = _parse_osrelease(filename)
|
||||||
assert parsed == expected_platforms
|
assert parsed['ID'] == id_
|
||||||
|
assert parsed['ID_LIKE'] == id_like
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ import os
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ipapython.certdb import NSSDatabase, TRUSTED_PEER_TRUST_FLAGS
|
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'
|
CERTNICK = 'testcert'
|
||||||
|
|
||||||
if OSRELEASE['ID'] == 'fedora':
|
if osinfo.id == 'fedora':
|
||||||
if int(OSRELEASE['VERSION_ID']) >= 28:
|
if int(osinfo.version_id) >= 28:
|
||||||
NSS_DEFAULT = 'sql'
|
NSS_DEFAULT = 'sql'
|
||||||
else:
|
else:
|
||||||
NSS_DEFAULT = 'dbm'
|
NSS_DEFAULT = 'dbm'
|
||||||
|
|||||||
Reference in New Issue
Block a user