Path: blob/master/venv/Lib/site-packages/pip/_vendor/distro.py
811 views
# Copyright 2015,2016,2017 Nir Cohen1#2# Licensed under the Apache License, Version 2.0 (the "License");3# you may not use this file except in compliance with the License.4# You may obtain a copy of the License at5#6# http://www.apache.org/licenses/LICENSE-2.07#8# Unless required by applicable law or agreed to in writing, software9# distributed under the License is distributed on an "AS IS" BASIS,10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11# See the License for the specific language governing permissions and12# limitations under the License.1314"""15The ``distro`` package (``distro`` stands for Linux Distribution) provides16information about the Linux distribution it runs on, such as a reliable17machine-readable distro ID, or version information.1819It is the recommended replacement for Python's original20:py:func:`platform.linux_distribution` function, but it provides much more21functionality. An alternative implementation became necessary because Python223.5 deprecated this function, and Python 3.8 will remove it altogether.23Its predecessor function :py:func:`platform.dist` was already24deprecated since Python 2.6 and will also be removed in Python 3.8.25Still, there are many cases in which access to OS distribution information26is needed. See `Python issue 1322 <https://bugs.python.org/issue1322>`_ for27more information.28"""2930import os31import re32import sys33import json34import shlex35import logging36import argparse37import subprocess383940_UNIXCONFDIR = os.environ.get('UNIXCONFDIR', '/etc')41_OS_RELEASE_BASENAME = 'os-release'4243#: Translation table for normalizing the "ID" attribute defined in os-release44#: files, for use by the :func:`distro.id` method.45#:46#: * Key: Value as defined in the os-release file, translated to lower case,47#: with blanks translated to underscores.48#:49#: * Value: Normalized value.50NORMALIZED_OS_ID = {51'ol': 'oracle', # Oracle Linux52}5354#: Translation table for normalizing the "Distributor ID" attribute returned by55#: the lsb_release command, for use by the :func:`distro.id` method.56#:57#: * Key: Value as returned by the lsb_release command, translated to lower58#: case, with blanks translated to underscores.59#:60#: * Value: Normalized value.61NORMALIZED_LSB_ID = {62'enterpriseenterpriseas': 'oracle', # Oracle Enterprise Linux 463'enterpriseenterpriseserver': 'oracle', # Oracle Linux 564'redhatenterpriseworkstation': 'rhel', # RHEL 6, 7 Workstation65'redhatenterpriseserver': 'rhel', # RHEL 6, 7 Server66'redhatenterprisecomputenode': 'rhel', # RHEL 6 ComputeNode67}6869#: Translation table for normalizing the distro ID derived from the file name70#: of distro release files, for use by the :func:`distro.id` method.71#:72#: * Key: Value as derived from the file name of a distro release file,73#: translated to lower case, with blanks translated to underscores.74#:75#: * Value: Normalized value.76NORMALIZED_DISTRO_ID = {77'redhat': 'rhel', # RHEL 6.x, 7.x78}7980# Pattern for content of distro release file (reversed)81_DISTRO_RELEASE_CONTENT_REVERSED_PATTERN = re.compile(82r'(?:[^)]*\)(.*)\()? *(?:STL )?([\d.+\-a-z]*\d) *(?:esaeler *)?(.+)')8384# Pattern for base file name of distro release file85_DISTRO_RELEASE_BASENAME_PATTERN = re.compile(86r'(\w+)[-_](release|version)$')8788# Base file names to be ignored when searching for distro release file89_DISTRO_RELEASE_IGNORE_BASENAMES = (90'debian_version',91'lsb-release',92'oem-release',93_OS_RELEASE_BASENAME,94'system-release',95'plesk-release',96)979899def linux_distribution(full_distribution_name=True):100"""101Return information about the current OS distribution as a tuple102``(id_name, version, codename)`` with items as follows:103104* ``id_name``: If *full_distribution_name* is false, the result of105:func:`distro.id`. Otherwise, the result of :func:`distro.name`.106107* ``version``: The result of :func:`distro.version`.108109* ``codename``: The result of :func:`distro.codename`.110111The interface of this function is compatible with the original112:py:func:`platform.linux_distribution` function, supporting a subset of113its parameters.114115The data it returns may not exactly be the same, because it uses more data116sources than the original function, and that may lead to different data if117the OS distribution is not consistent across multiple data sources it118provides (there are indeed such distributions ...).119120Another reason for differences is the fact that the :func:`distro.id`121method normalizes the distro ID string to a reliable machine-readable value122for a number of popular OS distributions.123"""124return _distro.linux_distribution(full_distribution_name)125126127def id():128"""129Return the distro ID of the current distribution, as a130machine-readable string.131132For a number of OS distributions, the returned distro ID value is133*reliable*, in the sense that it is documented and that it does not change134across releases of the distribution.135136This package maintains the following reliable distro ID values:137138============== =========================================139Distro ID Distribution140============== =========================================141"ubuntu" Ubuntu142"debian" Debian143"rhel" RedHat Enterprise Linux144"centos" CentOS145"fedora" Fedora146"sles" SUSE Linux Enterprise Server147"opensuse" openSUSE148"amazon" Amazon Linux149"arch" Arch Linux150"cloudlinux" CloudLinux OS151"exherbo" Exherbo Linux152"gentoo" GenToo Linux153"ibm_powerkvm" IBM PowerKVM154"kvmibm" KVM for IBM z Systems155"linuxmint" Linux Mint156"mageia" Mageia157"mandriva" Mandriva Linux158"parallels" Parallels159"pidora" Pidora160"raspbian" Raspbian161"oracle" Oracle Linux (and Oracle Enterprise Linux)162"scientific" Scientific Linux163"slackware" Slackware164"xenserver" XenServer165"openbsd" OpenBSD166"netbsd" NetBSD167"freebsd" FreeBSD168"midnightbsd" MidnightBSD169============== =========================================170171If you have a need to get distros for reliable IDs added into this set,172or if you find that the :func:`distro.id` function returns a different173distro ID for one of the listed distros, please create an issue in the174`distro issue tracker`_.175176**Lookup hierarchy and transformations:**177178First, the ID is obtained from the following sources, in the specified179order. The first available and non-empty value is used:180181* the value of the "ID" attribute of the os-release file,182183* the value of the "Distributor ID" attribute returned by the lsb_release184command,185186* the first part of the file name of the distro release file,187188The so determined ID value then passes the following transformations,189before it is returned by this method:190191* it is translated to lower case,192193* blanks (which should not be there anyway) are translated to underscores,194195* a normalization of the ID is performed, based upon196`normalization tables`_. The purpose of this normalization is to ensure197that the ID is as reliable as possible, even across incompatible changes198in the OS distributions. A common reason for an incompatible change is199the addition of an os-release file, or the addition of the lsb_release200command, with ID values that differ from what was previously determined201from the distro release file name.202"""203return _distro.id()204205206def name(pretty=False):207"""208Return the name of the current OS distribution, as a human-readable209string.210211If *pretty* is false, the name is returned without version or codename.212(e.g. "CentOS Linux")213214If *pretty* is true, the version and codename are appended.215(e.g. "CentOS Linux 7.1.1503 (Core)")216217**Lookup hierarchy:**218219The name is obtained from the following sources, in the specified order.220The first available and non-empty value is used:221222* If *pretty* is false:223224- the value of the "NAME" attribute of the os-release file,225226- the value of the "Distributor ID" attribute returned by the lsb_release227command,228229- the value of the "<name>" field of the distro release file.230231* If *pretty* is true:232233- the value of the "PRETTY_NAME" attribute of the os-release file,234235- the value of the "Description" attribute returned by the lsb_release236command,237238- the value of the "<name>" field of the distro release file, appended239with the value of the pretty version ("<version_id>" and "<codename>"240fields) of the distro release file, if available.241"""242return _distro.name(pretty)243244245def version(pretty=False, best=False):246"""247Return the version of the current OS distribution, as a human-readable248string.249250If *pretty* is false, the version is returned without codename (e.g.251"7.0").252253If *pretty* is true, the codename in parenthesis is appended, if the254codename is non-empty (e.g. "7.0 (Maipo)").255256Some distributions provide version numbers with different precisions in257the different sources of distribution information. Examining the different258sources in a fixed priority order does not always yield the most precise259version (e.g. for Debian 8.2, or CentOS 7.1).260261The *best* parameter can be used to control the approach for the returned262version:263264If *best* is false, the first non-empty version number in priority order of265the examined sources is returned.266267If *best* is true, the most precise version number out of all examined268sources is returned.269270**Lookup hierarchy:**271272In all cases, the version number is obtained from the following sources.273If *best* is false, this order represents the priority order:274275* the value of the "VERSION_ID" attribute of the os-release file,276* the value of the "Release" attribute returned by the lsb_release277command,278* the version number parsed from the "<version_id>" field of the first line279of the distro release file,280* the version number parsed from the "PRETTY_NAME" attribute of the281os-release file, if it follows the format of the distro release files.282* the version number parsed from the "Description" attribute returned by283the lsb_release command, if it follows the format of the distro release284files.285"""286return _distro.version(pretty, best)287288289def version_parts(best=False):290"""291Return the version of the current OS distribution as a tuple292``(major, minor, build_number)`` with items as follows:293294* ``major``: The result of :func:`distro.major_version`.295296* ``minor``: The result of :func:`distro.minor_version`.297298* ``build_number``: The result of :func:`distro.build_number`.299300For a description of the *best* parameter, see the :func:`distro.version`301method.302"""303return _distro.version_parts(best)304305306def major_version(best=False):307"""308Return the major version of the current OS distribution, as a string,309if provided.310Otherwise, the empty string is returned. The major version is the first311part of the dot-separated version string.312313For a description of the *best* parameter, see the :func:`distro.version`314method.315"""316return _distro.major_version(best)317318319def minor_version(best=False):320"""321Return the minor version of the current OS distribution, as a string,322if provided.323Otherwise, the empty string is returned. The minor version is the second324part of the dot-separated version string.325326For a description of the *best* parameter, see the :func:`distro.version`327method.328"""329return _distro.minor_version(best)330331332def build_number(best=False):333"""334Return the build number of the current OS distribution, as a string,335if provided.336Otherwise, the empty string is returned. The build number is the third part337of the dot-separated version string.338339For a description of the *best* parameter, see the :func:`distro.version`340method.341"""342return _distro.build_number(best)343344345def like():346"""347Return a space-separated list of distro IDs of distributions that are348closely related to the current OS distribution in regards to packaging349and programming interfaces, for example distributions the current350distribution is a derivative from.351352**Lookup hierarchy:**353354This information item is only provided by the os-release file.355For details, see the description of the "ID_LIKE" attribute in the356`os-release man page357<http://www.freedesktop.org/software/systemd/man/os-release.html>`_.358"""359return _distro.like()360361362def codename():363"""364Return the codename for the release of the current OS distribution,365as a string.366367If the distribution does not have a codename, an empty string is returned.368369Note that the returned codename is not always really a codename. For370example, openSUSE returns "x86_64". This function does not handle such371cases in any special way and just returns the string it finds, if any.372373**Lookup hierarchy:**374375* the codename within the "VERSION" attribute of the os-release file, if376provided,377378* the value of the "Codename" attribute returned by the lsb_release379command,380381* the value of the "<codename>" field of the distro release file.382"""383return _distro.codename()384385386def info(pretty=False, best=False):387"""388Return certain machine-readable information items about the current OS389distribution in a dictionary, as shown in the following example:390391.. sourcecode:: python392393{394'id': 'rhel',395'version': '7.0',396'version_parts': {397'major': '7',398'minor': '0',399'build_number': ''400},401'like': 'fedora',402'codename': 'Maipo'403}404405The dictionary structure and keys are always the same, regardless of which406information items are available in the underlying data sources. The values407for the various keys are as follows:408409* ``id``: The result of :func:`distro.id`.410411* ``version``: The result of :func:`distro.version`.412413* ``version_parts -> major``: The result of :func:`distro.major_version`.414415* ``version_parts -> minor``: The result of :func:`distro.minor_version`.416417* ``version_parts -> build_number``: The result of418:func:`distro.build_number`.419420* ``like``: The result of :func:`distro.like`.421422* ``codename``: The result of :func:`distro.codename`.423424For a description of the *pretty* and *best* parameters, see the425:func:`distro.version` method.426"""427return _distro.info(pretty, best)428429430def os_release_info():431"""432Return a dictionary containing key-value pairs for the information items433from the os-release file data source of the current OS distribution.434435See `os-release file`_ for details about these information items.436"""437return _distro.os_release_info()438439440def lsb_release_info():441"""442Return a dictionary containing key-value pairs for the information items443from the lsb_release command data source of the current OS distribution.444445See `lsb_release command output`_ for details about these information446items.447"""448return _distro.lsb_release_info()449450451def distro_release_info():452"""453Return a dictionary containing key-value pairs for the information items454from the distro release file data source of the current OS distribution.455456See `distro release file`_ for details about these information items.457"""458return _distro.distro_release_info()459460461def uname_info():462"""463Return a dictionary containing key-value pairs for the information items464from the distro release file data source of the current OS distribution.465"""466return _distro.uname_info()467468469def os_release_attr(attribute):470"""471Return a single named information item from the os-release file data source472of the current OS distribution.473474Parameters:475476* ``attribute`` (string): Key of the information item.477478Returns:479480* (string): Value of the information item, if the item exists.481The empty string, if the item does not exist.482483See `os-release file`_ for details about these information items.484"""485return _distro.os_release_attr(attribute)486487488def lsb_release_attr(attribute):489"""490Return a single named information item from the lsb_release command output491data source of the current OS distribution.492493Parameters:494495* ``attribute`` (string): Key of the information item.496497Returns:498499* (string): Value of the information item, if the item exists.500The empty string, if the item does not exist.501502See `lsb_release command output`_ for details about these information503items.504"""505return _distro.lsb_release_attr(attribute)506507508def distro_release_attr(attribute):509"""510Return a single named information item from the distro release file511data source of the current OS distribution.512513Parameters:514515* ``attribute`` (string): Key of the information item.516517Returns:518519* (string): Value of the information item, if the item exists.520The empty string, if the item does not exist.521522See `distro release file`_ for details about these information items.523"""524return _distro.distro_release_attr(attribute)525526527def uname_attr(attribute):528"""529Return a single named information item from the distro release file530data source of the current OS distribution.531532Parameters:533534* ``attribute`` (string): Key of the information item.535536Returns:537538* (string): Value of the information item, if the item exists.539The empty string, if the item does not exist.540"""541return _distro.uname_attr(attribute)542543544class cached_property(object):545"""A version of @property which caches the value. On access, it calls the546underlying function and sets the value in `__dict__` so future accesses547will not re-call the property.548"""549def __init__(self, f):550self._fname = f.__name__551self._f = f552553def __get__(self, obj, owner):554assert obj is not None, 'call {} on an instance'.format(self._fname)555ret = obj.__dict__[self._fname] = self._f(obj)556return ret557558559class LinuxDistribution(object):560"""561Provides information about a OS distribution.562563This package creates a private module-global instance of this class with564default initialization arguments, that is used by the565`consolidated accessor functions`_ and `single source accessor functions`_.566By using default initialization arguments, that module-global instance567returns data about the current OS distribution (i.e. the distro this568package runs on).569570Normally, it is not necessary to create additional instances of this class.571However, in situations where control is needed over the exact data sources572that are used, instances of this class can be created with a specific573distro release file, or a specific os-release file, or without invoking the574lsb_release command.575"""576577def __init__(self,578include_lsb=True,579os_release_file='',580distro_release_file='',581include_uname=True):582"""583The initialization method of this class gathers information from the584available data sources, and stores that in private instance attributes.585Subsequent access to the information items uses these private instance586attributes, so that the data sources are read only once.587588Parameters:589590* ``include_lsb`` (bool): Controls whether the591`lsb_release command output`_ is included as a data source.592593If the lsb_release command is not available in the program execution594path, the data source for the lsb_release command will be empty.595596* ``os_release_file`` (string): The path name of the597`os-release file`_ that is to be used as a data source.598599An empty string (the default) will cause the default path name to600be used (see `os-release file`_ for details).601602If the specified or defaulted os-release file does not exist, the603data source for the os-release file will be empty.604605* ``distro_release_file`` (string): The path name of the606`distro release file`_ that is to be used as a data source.607608An empty string (the default) will cause a default search algorithm609to be used (see `distro release file`_ for details).610611If the specified distro release file does not exist, or if no default612distro release file can be found, the data source for the distro613release file will be empty.614615* ``include_uname`` (bool): Controls whether uname command output is616included as a data source. If the uname command is not available in617the program execution path the data source for the uname command will618be empty.619620Public instance attributes:621622* ``os_release_file`` (string): The path name of the623`os-release file`_ that is actually used as a data source. The624empty string if no distro release file is used as a data source.625626* ``distro_release_file`` (string): The path name of the627`distro release file`_ that is actually used as a data source. The628empty string if no distro release file is used as a data source.629630* ``include_lsb`` (bool): The result of the ``include_lsb`` parameter.631This controls whether the lsb information will be loaded.632633* ``include_uname`` (bool): The result of the ``include_uname``634parameter. This controls whether the uname information will635be loaded.636637Raises:638639* :py:exc:`IOError`: Some I/O issue with an os-release file or distro640release file.641642* :py:exc:`subprocess.CalledProcessError`: The lsb_release command had643some issue (other than not being available in the program execution644path).645646* :py:exc:`UnicodeError`: A data source has unexpected characters or647uses an unexpected encoding.648"""649self.os_release_file = os_release_file or \650os.path.join(_UNIXCONFDIR, _OS_RELEASE_BASENAME)651self.distro_release_file = distro_release_file or '' # updated later652self.include_lsb = include_lsb653self.include_uname = include_uname654655def __repr__(self):656"""Return repr of all info657"""658return \659"LinuxDistribution(" \660"os_release_file={self.os_release_file!r}, " \661"distro_release_file={self.distro_release_file!r}, " \662"include_lsb={self.include_lsb!r}, " \663"include_uname={self.include_uname!r}, " \664"_os_release_info={self._os_release_info!r}, " \665"_lsb_release_info={self._lsb_release_info!r}, " \666"_distro_release_info={self._distro_release_info!r}, " \667"_uname_info={self._uname_info!r})".format(668self=self)669670def linux_distribution(self, full_distribution_name=True):671"""672Return information about the OS distribution that is compatible673with Python's :func:`platform.linux_distribution`, supporting a subset674of its parameters.675676For details, see :func:`distro.linux_distribution`.677"""678return (679self.name() if full_distribution_name else self.id(),680self.version(),681self.codename()682)683684def id(self):685"""Return the distro ID of the OS distribution, as a string.686687For details, see :func:`distro.id`.688"""689def normalize(distro_id, table):690distro_id = distro_id.lower().replace(' ', '_')691return table.get(distro_id, distro_id)692693distro_id = self.os_release_attr('id')694if distro_id:695return normalize(distro_id, NORMALIZED_OS_ID)696697distro_id = self.lsb_release_attr('distributor_id')698if distro_id:699return normalize(distro_id, NORMALIZED_LSB_ID)700701distro_id = self.distro_release_attr('id')702if distro_id:703return normalize(distro_id, NORMALIZED_DISTRO_ID)704705distro_id = self.uname_attr('id')706if distro_id:707return normalize(distro_id, NORMALIZED_DISTRO_ID)708709return ''710711def name(self, pretty=False):712"""713Return the name of the OS distribution, as a string.714715For details, see :func:`distro.name`.716"""717name = self.os_release_attr('name') \718or self.lsb_release_attr('distributor_id') \719or self.distro_release_attr('name') \720or self.uname_attr('name')721if pretty:722name = self.os_release_attr('pretty_name') \723or self.lsb_release_attr('description')724if not name:725name = self.distro_release_attr('name') \726or self.uname_attr('name')727version = self.version(pretty=True)728if version:729name = name + ' ' + version730return name or ''731732def version(self, pretty=False, best=False):733"""734Return the version of the OS distribution, as a string.735736For details, see :func:`distro.version`.737"""738versions = [739self.os_release_attr('version_id'),740self.lsb_release_attr('release'),741self.distro_release_attr('version_id'),742self._parse_distro_release_content(743self.os_release_attr('pretty_name')).get('version_id', ''),744self._parse_distro_release_content(745self.lsb_release_attr('description')).get('version_id', ''),746self.uname_attr('release')747]748version = ''749if best:750# This algorithm uses the last version in priority order that has751# the best precision. If the versions are not in conflict, that752# does not matter; otherwise, using the last one instead of the753# first one might be considered a surprise.754for v in versions:755if v.count(".") > version.count(".") or version == '':756version = v757else:758for v in versions:759if v != '':760version = v761break762if pretty and version and self.codename():763version = '{0} ({1})'.format(version, self.codename())764return version765766def version_parts(self, best=False):767"""768Return the version of the OS distribution, as a tuple of version769numbers.770771For details, see :func:`distro.version_parts`.772"""773version_str = self.version(best=best)774if version_str:775version_regex = re.compile(r'(\d+)\.?(\d+)?\.?(\d+)?')776matches = version_regex.match(version_str)777if matches:778major, minor, build_number = matches.groups()779return major, minor or '', build_number or ''780return '', '', ''781782def major_version(self, best=False):783"""784Return the major version number of the current distribution.785786For details, see :func:`distro.major_version`.787"""788return self.version_parts(best)[0]789790def minor_version(self, best=False):791"""792Return the minor version number of the current distribution.793794For details, see :func:`distro.minor_version`.795"""796return self.version_parts(best)[1]797798def build_number(self, best=False):799"""800Return the build number of the current distribution.801802For details, see :func:`distro.build_number`.803"""804return self.version_parts(best)[2]805806def like(self):807"""808Return the IDs of distributions that are like the OS distribution.809810For details, see :func:`distro.like`.811"""812return self.os_release_attr('id_like') or ''813814def codename(self):815"""816Return the codename of the OS distribution.817818For details, see :func:`distro.codename`.819"""820try:821# Handle os_release specially since distros might purposefully set822# this to empty string to have no codename823return self._os_release_info['codename']824except KeyError:825return self.lsb_release_attr('codename') \826or self.distro_release_attr('codename') \827or ''828829def info(self, pretty=False, best=False):830"""831Return certain machine-readable information about the OS832distribution.833834For details, see :func:`distro.info`.835"""836return dict(837id=self.id(),838version=self.version(pretty, best),839version_parts=dict(840major=self.major_version(best),841minor=self.minor_version(best),842build_number=self.build_number(best)843),844like=self.like(),845codename=self.codename(),846)847848def os_release_info(self):849"""850Return a dictionary containing key-value pairs for the information851items from the os-release file data source of the OS distribution.852853For details, see :func:`distro.os_release_info`.854"""855return self._os_release_info856857def lsb_release_info(self):858"""859Return a dictionary containing key-value pairs for the information860items from the lsb_release command data source of the OS861distribution.862863For details, see :func:`distro.lsb_release_info`.864"""865return self._lsb_release_info866867def distro_release_info(self):868"""869Return a dictionary containing key-value pairs for the information870items from the distro release file data source of the OS871distribution.872873For details, see :func:`distro.distro_release_info`.874"""875return self._distro_release_info876877def uname_info(self):878"""879Return a dictionary containing key-value pairs for the information880items from the uname command data source of the OS distribution.881882For details, see :func:`distro.uname_info`.883"""884return self._uname_info885886def os_release_attr(self, attribute):887"""888Return a single named information item from the os-release file data889source of the OS distribution.890891For details, see :func:`distro.os_release_attr`.892"""893return self._os_release_info.get(attribute, '')894895def lsb_release_attr(self, attribute):896"""897Return a single named information item from the lsb_release command898output data source of the OS distribution.899900For details, see :func:`distro.lsb_release_attr`.901"""902return self._lsb_release_info.get(attribute, '')903904def distro_release_attr(self, attribute):905"""906Return a single named information item from the distro release file907data source of the OS distribution.908909For details, see :func:`distro.distro_release_attr`.910"""911return self._distro_release_info.get(attribute, '')912913def uname_attr(self, attribute):914"""915Return a single named information item from the uname command916output data source of the OS distribution.917918For details, see :func:`distro.uname_release_attr`.919"""920return self._uname_info.get(attribute, '')921922@cached_property923def _os_release_info(self):924"""925Get the information items from the specified os-release file.926927Returns:928A dictionary containing all information items.929"""930if os.path.isfile(self.os_release_file):931with open(self.os_release_file) as release_file:932return self._parse_os_release_content(release_file)933return {}934935@staticmethod936def _parse_os_release_content(lines):937"""938Parse the lines of an os-release file.939940Parameters:941942* lines: Iterable through the lines in the os-release file.943Each line must be a unicode string or a UTF-8 encoded byte944string.945946Returns:947A dictionary containing all information items.948"""949props = {}950lexer = shlex.shlex(lines, posix=True)951lexer.whitespace_split = True952953# The shlex module defines its `wordchars` variable using literals,954# making it dependent on the encoding of the Python source file.955# In Python 2.6 and 2.7, the shlex source file is encoded in956# 'iso-8859-1', and the `wordchars` variable is defined as a byte957# string. This causes a UnicodeDecodeError to be raised when the958# parsed content is a unicode object. The following fix resolves that959# (... but it should be fixed in shlex...):960if sys.version_info[0] == 2 and isinstance(lexer.wordchars, bytes):961lexer.wordchars = lexer.wordchars.decode('iso-8859-1')962963tokens = list(lexer)964for token in tokens:965# At this point, all shell-like parsing has been done (i.e.966# comments processed, quotes and backslash escape sequences967# processed, multi-line values assembled, trailing newlines968# stripped, etc.), so the tokens are now either:969# * variable assignments: var=value970# * commands or their arguments (not allowed in os-release)971if '=' in token:972k, v = token.split('=', 1)973props[k.lower()] = v974else:975# Ignore any tokens that are not variable assignments976pass977978if 'version_codename' in props:979# os-release added a version_codename field. Use that in980# preference to anything else Note that some distros purposefully981# do not have code names. They should be setting982# version_codename=""983props['codename'] = props['version_codename']984elif 'ubuntu_codename' in props:985# Same as above but a non-standard field name used on older Ubuntus986props['codename'] = props['ubuntu_codename']987elif 'version' in props:988# If there is no version_codename, parse it from the version989codename = re.search(r'(\(\D+\))|,(\s+)?\D+', props['version'])990if codename:991codename = codename.group()992codename = codename.strip('()')993codename = codename.strip(',')994codename = codename.strip()995# codename appears within paranthese.996props['codename'] = codename997998return props9991000@cached_property1001def _lsb_release_info(self):1002"""1003Get the information items from the lsb_release command output.10041005Returns:1006A dictionary containing all information items.1007"""1008if not self.include_lsb:1009return {}1010with open(os.devnull, 'w') as devnull:1011try:1012cmd = ('lsb_release', '-a')1013stdout = subprocess.check_output(cmd, stderr=devnull)1014except OSError: # Command not found1015return {}1016content = self._to_str(stdout).splitlines()1017return self._parse_lsb_release_content(content)10181019@staticmethod1020def _parse_lsb_release_content(lines):1021"""1022Parse the output of the lsb_release command.10231024Parameters:10251026* lines: Iterable through the lines of the lsb_release output.1027Each line must be a unicode string or a UTF-8 encoded byte1028string.10291030Returns:1031A dictionary containing all information items.1032"""1033props = {}1034for line in lines:1035kv = line.strip('\n').split(':', 1)1036if len(kv) != 2:1037# Ignore lines without colon.1038continue1039k, v = kv1040props.update({k.replace(' ', '_').lower(): v.strip()})1041return props10421043@cached_property1044def _uname_info(self):1045with open(os.devnull, 'w') as devnull:1046try:1047cmd = ('uname', '-rs')1048stdout = subprocess.check_output(cmd, stderr=devnull)1049except OSError:1050return {}1051content = self._to_str(stdout).splitlines()1052return self._parse_uname_content(content)10531054@staticmethod1055def _parse_uname_content(lines):1056props = {}1057match = re.search(r'^([^\s]+)\s+([\d\.]+)', lines[0].strip())1058if match:1059name, version = match.groups()10601061# This is to prevent the Linux kernel version from1062# appearing as the 'best' version on otherwise1063# identifiable distributions.1064if name == 'Linux':1065return {}1066props['id'] = name.lower()1067props['name'] = name1068props['release'] = version1069return props10701071@staticmethod1072def _to_str(text):1073encoding = sys.getfilesystemencoding()1074encoding = 'utf-8' if encoding == 'ascii' else encoding10751076if sys.version_info[0] >= 3:1077if isinstance(text, bytes):1078return text.decode(encoding)1079else:1080if isinstance(text, unicode): # noqa1081return text.encode(encoding)10821083return text10841085@cached_property1086def _distro_release_info(self):1087"""1088Get the information items from the specified distro release file.10891090Returns:1091A dictionary containing all information items.1092"""1093if self.distro_release_file:1094# If it was specified, we use it and parse what we can, even if1095# its file name or content does not match the expected pattern.1096distro_info = self._parse_distro_release_file(1097self.distro_release_file)1098basename = os.path.basename(self.distro_release_file)1099# The file name pattern for user-specified distro release files1100# is somewhat more tolerant (compared to when searching for the1101# file), because we want to use what was specified as best as1102# possible.1103match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)1104if 'name' in distro_info \1105and 'cloudlinux' in distro_info['name'].lower():1106distro_info['id'] = 'cloudlinux'1107elif match:1108distro_info['id'] = match.group(1)1109return distro_info1110else:1111try:1112basenames = os.listdir(_UNIXCONFDIR)1113# We sort for repeatability in cases where there are multiple1114# distro specific files; e.g. CentOS, Oracle, Enterprise all1115# containing `redhat-release` on top of their own.1116basenames.sort()1117except OSError:1118# This may occur when /etc is not readable but we can't be1119# sure about the *-release files. Check common entries of1120# /etc for information. If they turn out to not be there the1121# error is handled in `_parse_distro_release_file()`.1122basenames = ['SuSE-release',1123'arch-release',1124'base-release',1125'centos-release',1126'fedora-release',1127'gentoo-release',1128'mageia-release',1129'mandrake-release',1130'mandriva-release',1131'mandrivalinux-release',1132'manjaro-release',1133'oracle-release',1134'redhat-release',1135'sl-release',1136'slackware-version']1137for basename in basenames:1138if basename in _DISTRO_RELEASE_IGNORE_BASENAMES:1139continue1140match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)1141if match:1142filepath = os.path.join(_UNIXCONFDIR, basename)1143distro_info = self._parse_distro_release_file(filepath)1144if 'name' in distro_info:1145# The name is always present if the pattern matches1146self.distro_release_file = filepath1147distro_info['id'] = match.group(1)1148if 'cloudlinux' in distro_info['name'].lower():1149distro_info['id'] = 'cloudlinux'1150return distro_info1151return {}11521153def _parse_distro_release_file(self, filepath):1154"""1155Parse a distro release file.11561157Parameters:11581159* filepath: Path name of the distro release file.11601161Returns:1162A dictionary containing all information items.1163"""1164try:1165with open(filepath) as fp:1166# Only parse the first line. For instance, on SLES there1167# are multiple lines. We don't want them...1168return self._parse_distro_release_content(fp.readline())1169except (OSError, IOError):1170# Ignore not being able to read a specific, seemingly version1171# related file.1172# See https://github.com/nir0s/distro/issues/1621173return {}11741175@staticmethod1176def _parse_distro_release_content(line):1177"""1178Parse a line from a distro release file.11791180Parameters:1181* line: Line from the distro release file. Must be a unicode string1182or a UTF-8 encoded byte string.11831184Returns:1185A dictionary containing all information items.1186"""1187matches = _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN.match(1188line.strip()[::-1])1189distro_info = {}1190if matches:1191# regexp ensures non-None1192distro_info['name'] = matches.group(3)[::-1]1193if matches.group(2):1194distro_info['version_id'] = matches.group(2)[::-1]1195if matches.group(1):1196distro_info['codename'] = matches.group(1)[::-1]1197elif line:1198distro_info['name'] = line.strip()1199return distro_info120012011202_distro = LinuxDistribution()120312041205def main():1206logger = logging.getLogger(__name__)1207logger.setLevel(logging.DEBUG)1208logger.addHandler(logging.StreamHandler(sys.stdout))12091210parser = argparse.ArgumentParser(description="OS distro info tool")1211parser.add_argument(1212'--json',1213'-j',1214help="Output in machine readable format",1215action="store_true")1216args = parser.parse_args()12171218if args.json:1219logger.info(json.dumps(info(), indent=4, sort_keys=True))1220else:1221logger.info('Name: %s', name(pretty=True))1222distribution_version = version(pretty=True)1223logger.info('Version: %s', distribution_version)1224distribution_codename = codename()1225logger.info('Codename: %s', distribution_codename)122612271228if __name__ == '__main__':1229main()123012311232