# # Copyright (C) 2017 FreeIPA Contributors see COPYING for license # """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[a-zA-Z0-9_]+)=" u"(?P[\"\']?)(?P.+)(?P=quote)$" ) 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', 'ipaplatform.services', '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 find_module(self, fullname, path=None): """Meta importer hook""" if fullname in self.modules: return self return None def load_module(self, fullname): """Meta importer hook""" suffix = fullname.split('.', 1)[1] alias = 'ipaplatform.{}.{}'.format(self.platform, suffix) platform_mod = importlib.import_module(alias) base_mod = sys.modules.get(fullname) if base_mod is not None: # module has been imported before, update its __dict__ base_mod.__dict__.update(platform_mod.__dict__) for key in list(base_mod.__dict__): if not hasattr(platform_mod, key): delattr(base_mod, key) else: sys.modules[fullname] = platform_mod return platform_mod metaimporter = IpaMetaImporter() sys.meta_path.insert(0, metaimporter) fixup_module = metaimporter.load_module ipaplatform.NAME = metaimporter.platform