Spaces:
Runtime error
Runtime error
| # Copyright 2015,2016,2017 Nir Cohen | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """ | |
| The ``distro`` package (``distro`` stands for Linux Distribution) provides | |
| information about the Linux distribution it runs on, such as a reliable | |
| machine-readable distro ID, or version information. | |
| It is the recommended replacement for Python's original | |
| :py:func:`platform.linux_distribution` function, but it provides much more | |
| functionality. An alternative implementation became necessary because Python | |
| 3.5 deprecated this function, and Python 3.8 will remove it altogether. | |
| Its predecessor function :py:func:`platform.dist` was already | |
| deprecated since Python 2.6 and will also be removed in Python 3.8. | |
| Still, there are many cases in which access to OS distribution information | |
| is needed. See `Python issue 1322 <https://bugs.python.org/issue1322>`_ for | |
| more information. | |
| """ | |
| import os | |
| import re | |
| import sys | |
| import json | |
| import shlex | |
| import logging | |
| import argparse | |
| import subprocess | |
| _UNIXCONFDIR = os.environ.get('UNIXCONFDIR', '/etc') | |
| _OS_RELEASE_BASENAME = 'os-release' | |
| #: Translation table for normalizing the "ID" attribute defined in os-release | |
| #: files, for use by the :func:`distro.id` method. | |
| #: | |
| #: * Key: Value as defined in the os-release file, translated to lower case, | |
| #: with blanks translated to underscores. | |
| #: | |
| #: * Value: Normalized value. | |
| NORMALIZED_OS_ID = { | |
| 'ol': 'oracle', # Oracle Linux | |
| } | |
| #: Translation table for normalizing the "Distributor ID" attribute returned by | |
| #: the lsb_release command, for use by the :func:`distro.id` method. | |
| #: | |
| #: * Key: Value as returned by the lsb_release command, translated to lower | |
| #: case, with blanks translated to underscores. | |
| #: | |
| #: * Value: Normalized value. | |
| NORMALIZED_LSB_ID = { | |
| 'enterpriseenterpriseas': 'oracle', # Oracle Enterprise Linux 4 | |
| 'enterpriseenterpriseserver': 'oracle', # Oracle Linux 5 | |
| 'redhatenterpriseworkstation': 'rhel', # RHEL 6, 7 Workstation | |
| 'redhatenterpriseserver': 'rhel', # RHEL 6, 7 Server | |
| 'redhatenterprisecomputenode': 'rhel', # RHEL 6 ComputeNode | |
| } | |
| #: Translation table for normalizing the distro ID derived from the file name | |
| #: of distro release files, for use by the :func:`distro.id` method. | |
| #: | |
| #: * Key: Value as derived from the file name of a distro release file, | |
| #: translated to lower case, with blanks translated to underscores. | |
| #: | |
| #: * Value: Normalized value. | |
| NORMALIZED_DISTRO_ID = { | |
| 'redhat': 'rhel', # RHEL 6.x, 7.x | |
| } | |
| # Pattern for content of distro release file (reversed) | |
| _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN = re.compile( | |
| r'(?:[^)]*\)(.*)\()? *(?:STL )?([\d.+\-a-z]*\d) *(?:esaeler *)?(.+)') | |
| # Pattern for base file name of distro release file | |
| _DISTRO_RELEASE_BASENAME_PATTERN = re.compile( | |
| r'(\w+)[-_](release|version)$') | |
| # Base file names to be ignored when searching for distro release file | |
| _DISTRO_RELEASE_IGNORE_BASENAMES = ( | |
| 'debian_version', | |
| 'lsb-release', | |
| 'oem-release', | |
| _OS_RELEASE_BASENAME, | |
| 'system-release', | |
| 'plesk-release', | |
| ) | |
| def linux_distribution(full_distribution_name=True): | |
| """ | |
| Return information about the current OS distribution as a tuple | |
| ``(id_name, version, codename)`` with items as follows: | |
| * ``id_name``: If *full_distribution_name* is false, the result of | |
| :func:`distro.id`. Otherwise, the result of :func:`distro.name`. | |
| * ``version``: The result of :func:`distro.version`. | |
| * ``codename``: The result of :func:`distro.codename`. | |
| The interface of this function is compatible with the original | |
| :py:func:`platform.linux_distribution` function, supporting a subset of | |
| its parameters. | |
| The data it returns may not exactly be the same, because it uses more data | |
| sources than the original function, and that may lead to different data if | |
| the OS distribution is not consistent across multiple data sources it | |
| provides (there are indeed such distributions ...). | |
| Another reason for differences is the fact that the :func:`distro.id` | |
| method normalizes the distro ID string to a reliable machine-readable value | |
| for a number of popular OS distributions. | |
| """ | |
| return _distro.linux_distribution(full_distribution_name) | |
| def id(): | |
| """ | |
| Return the distro ID of the current distribution, as a | |
| machine-readable string. | |
| For a number of OS distributions, the returned distro ID value is | |
| *reliable*, in the sense that it is documented and that it does not change | |
| across releases of the distribution. | |
| This package maintains the following reliable distro ID values: | |
| ============== ========================================= | |
| Distro ID Distribution | |
| ============== ========================================= | |
| "ubuntu" Ubuntu | |
| "debian" Debian | |
| "rhel" RedHat Enterprise Linux | |
| "centos" CentOS | |
| "fedora" Fedora | |
| "sles" SUSE Linux Enterprise Server | |
| "opensuse" openSUSE | |
| "amazon" Amazon Linux | |
| "arch" Arch Linux | |
| "cloudlinux" CloudLinux OS | |
| "exherbo" Exherbo Linux | |
| "gentoo" GenToo Linux | |
| "ibm_powerkvm" IBM PowerKVM | |
| "kvmibm" KVM for IBM z Systems | |
| "linuxmint" Linux Mint | |
| "mageia" Mageia | |
| "mandriva" Mandriva Linux | |
| "parallels" Parallels | |
| "pidora" Pidora | |
| "raspbian" Raspbian | |
| "oracle" Oracle Linux (and Oracle Enterprise Linux) | |
| "scientific" Scientific Linux | |
| "slackware" Slackware | |
| "xenserver" XenServer | |
| "openbsd" OpenBSD | |
| "netbsd" NetBSD | |
| "freebsd" FreeBSD | |
| "midnightbsd" MidnightBSD | |
| ============== ========================================= | |
| If you have a need to get distros for reliable IDs added into this set, | |
| or if you find that the :func:`distro.id` function returns a different | |
| distro ID for one of the listed distros, please create an issue in the | |
| `distro issue tracker`_. | |
| **Lookup hierarchy and transformations:** | |
| First, the ID is obtained from the following sources, in the specified | |
| order. The first available and non-empty value is used: | |
| * the value of the "ID" attribute of the os-release file, | |
| * the value of the "Distributor ID" attribute returned by the lsb_release | |
| command, | |
| * the first part of the file name of the distro release file, | |
| The so determined ID value then passes the following transformations, | |
| before it is returned by this method: | |
| * it is translated to lower case, | |
| * blanks (which should not be there anyway) are translated to underscores, | |
| * a normalization of the ID is performed, based upon | |
| `normalization tables`_. The purpose of this normalization is to ensure | |
| that the ID is as reliable as possible, even across incompatible changes | |
| in the OS distributions. A common reason for an incompatible change is | |
| the addition of an os-release file, or the addition of the lsb_release | |
| command, with ID values that differ from what was previously determined | |
| from the distro release file name. | |
| """ | |
| return _distro.id() | |
| def name(pretty=False): | |
| """ | |
| Return the name of the current OS distribution, as a human-readable | |
| string. | |
| If *pretty* is false, the name is returned without version or codename. | |
| (e.g. "CentOS Linux") | |
| If *pretty* is true, the version and codename are appended. | |
| (e.g. "CentOS Linux 7.1.1503 (Core)") | |
| **Lookup hierarchy:** | |
| The name is obtained from the following sources, in the specified order. | |
| The first available and non-empty value is used: | |
| * If *pretty* is false: | |
| - the value of the "NAME" attribute of the os-release file, | |
| - the value of the "Distributor ID" attribute returned by the lsb_release | |
| command, | |
| - the value of the "<name>" field of the distro release file. | |
| * If *pretty* is true: | |
| - the value of the "PRETTY_NAME" attribute of the os-release file, | |
| - the value of the "Description" attribute returned by the lsb_release | |
| command, | |
| - the value of the "<name>" field of the distro release file, appended | |
| with the value of the pretty version ("<version_id>" and "<codename>" | |
| fields) of the distro release file, if available. | |
| """ | |
| return _distro.name(pretty) | |
| def version(pretty=False, best=False): | |
| """ | |
| Return the version of the current OS distribution, as a human-readable | |
| string. | |
| If *pretty* is false, the version is returned without codename (e.g. | |
| "7.0"). | |
| If *pretty* is true, the codename in parenthesis is appended, if the | |
| codename is non-empty (e.g. "7.0 (Maipo)"). | |
| Some distributions provide version numbers with different precisions in | |
| the different sources of distribution information. Examining the different | |
| sources in a fixed priority order does not always yield the most precise | |
| version (e.g. for Debian 8.2, or CentOS 7.1). | |
| The *best* parameter can be used to control the approach for the returned | |
| version: | |
| If *best* is false, the first non-empty version number in priority order of | |
| the examined sources is returned. | |
| If *best* is true, the most precise version number out of all examined | |
| sources is returned. | |
| **Lookup hierarchy:** | |
| In all cases, the version number is obtained from the following sources. | |
| If *best* is false, this order represents the priority order: | |
| * the value of the "VERSION_ID" attribute of the os-release file, | |
| * the value of the "Release" attribute returned by the lsb_release | |
| command, | |
| * the version number parsed from the "<version_id>" field of the first line | |
| of the distro release file, | |
| * the version number parsed from the "PRETTY_NAME" attribute of the | |
| os-release file, if it follows the format of the distro release files. | |
| * the version number parsed from the "Description" attribute returned by | |
| the lsb_release command, if it follows the format of the distro release | |
| files. | |
| """ | |
| return _distro.version(pretty, best) | |
| def version_parts(best=False): | |
| """ | |
| Return the version of the current OS distribution as a tuple | |
| ``(major, minor, build_number)`` with items as follows: | |
| * ``major``: The result of :func:`distro.major_version`. | |
| * ``minor``: The result of :func:`distro.minor_version`. | |
| * ``build_number``: The result of :func:`distro.build_number`. | |
| For a description of the *best* parameter, see the :func:`distro.version` | |
| method. | |
| """ | |
| return _distro.version_parts(best) | |
| def major_version(best=False): | |
| """ | |
| Return the major version of the current OS distribution, as a string, | |
| if provided. | |
| Otherwise, the empty string is returned. The major version is the first | |
| part of the dot-separated version string. | |
| For a description of the *best* parameter, see the :func:`distro.version` | |
| method. | |
| """ | |
| return _distro.major_version(best) | |
| def minor_version(best=False): | |
| """ | |
| Return the minor version of the current OS distribution, as a string, | |
| if provided. | |
| Otherwise, the empty string is returned. The minor version is the second | |
| part of the dot-separated version string. | |
| For a description of the *best* parameter, see the :func:`distro.version` | |
| method. | |
| """ | |
| return _distro.minor_version(best) | |
| def build_number(best=False): | |
| """ | |
| Return the build number of the current OS distribution, as a string, | |
| if provided. | |
| Otherwise, the empty string is returned. The build number is the third part | |
| of the dot-separated version string. | |
| For a description of the *best* parameter, see the :func:`distro.version` | |
| method. | |
| """ | |
| return _distro.build_number(best) | |
| def like(): | |
| """ | |
| Return a space-separated list of distro IDs of distributions that are | |
| closely related to the current OS distribution in regards to packaging | |
| and programming interfaces, for example distributions the current | |
| distribution is a derivative from. | |
| **Lookup hierarchy:** | |
| This information item is only provided by the os-release file. | |
| For details, see the description of the "ID_LIKE" attribute in the | |
| `os-release man page | |
| <http://www.freedesktop.org/software/systemd/man/os-release.html>`_. | |
| """ | |
| return _distro.like() | |
| def codename(): | |
| """ | |
| Return the codename for the release of the current OS distribution, | |
| as a string. | |
| If the distribution does not have a codename, an empty string is returned. | |
| Note that the returned codename is not always really a codename. For | |
| example, openSUSE returns "x86_64". This function does not handle such | |
| cases in any special way and just returns the string it finds, if any. | |
| **Lookup hierarchy:** | |
| * the codename within the "VERSION" attribute of the os-release file, if | |
| provided, | |
| * the value of the "Codename" attribute returned by the lsb_release | |
| command, | |
| * the value of the "<codename>" field of the distro release file. | |
| """ | |
| return _distro.codename() | |
| def info(pretty=False, best=False): | |
| """ | |
| Return certain machine-readable information items about the current OS | |
| distribution in a dictionary, as shown in the following example: | |
| .. sourcecode:: python | |
| { | |
| 'id': 'rhel', | |
| 'version': '7.0', | |
| 'version_parts': { | |
| 'major': '7', | |
| 'minor': '0', | |
| 'build_number': '' | |
| }, | |
| 'like': 'fedora', | |
| 'codename': 'Maipo' | |
| } | |
| The dictionary structure and keys are always the same, regardless of which | |
| information items are available in the underlying data sources. The values | |
| for the various keys are as follows: | |
| * ``id``: The result of :func:`distro.id`. | |
| * ``version``: The result of :func:`distro.version`. | |
| * ``version_parts -> major``: The result of :func:`distro.major_version`. | |
| * ``version_parts -> minor``: The result of :func:`distro.minor_version`. | |
| * ``version_parts -> build_number``: The result of | |
| :func:`distro.build_number`. | |
| * ``like``: The result of :func:`distro.like`. | |
| * ``codename``: The result of :func:`distro.codename`. | |
| For a description of the *pretty* and *best* parameters, see the | |
| :func:`distro.version` method. | |
| """ | |
| return _distro.info(pretty, best) | |
| def os_release_info(): | |
| """ | |
| Return a dictionary containing key-value pairs for the information items | |
| from the os-release file data source of the current OS distribution. | |
| See `os-release file`_ for details about these information items. | |
| """ | |
| return _distro.os_release_info() | |
| def lsb_release_info(): | |
| """ | |
| Return a dictionary containing key-value pairs for the information items | |
| from the lsb_release command data source of the current OS distribution. | |
| See `lsb_release command output`_ for details about these information | |
| items. | |
| """ | |
| return _distro.lsb_release_info() | |
| def distro_release_info(): | |
| """ | |
| Return a dictionary containing key-value pairs for the information items | |
| from the distro release file data source of the current OS distribution. | |
| See `distro release file`_ for details about these information items. | |
| """ | |
| return _distro.distro_release_info() | |
| def uname_info(): | |
| """ | |
| Return a dictionary containing key-value pairs for the information items | |
| from the distro release file data source of the current OS distribution. | |
| """ | |
| return _distro.uname_info() | |
| def os_release_attr(attribute): | |
| """ | |
| Return a single named information item from the os-release file data source | |
| of the current OS distribution. | |
| Parameters: | |
| * ``attribute`` (string): Key of the information item. | |
| Returns: | |
| * (string): Value of the information item, if the item exists. | |
| The empty string, if the item does not exist. | |
| See `os-release file`_ for details about these information items. | |
| """ | |
| return _distro.os_release_attr(attribute) | |
| def lsb_release_attr(attribute): | |
| """ | |
| Return a single named information item from the lsb_release command output | |
| data source of the current OS distribution. | |
| Parameters: | |
| * ``attribute`` (string): Key of the information item. | |
| Returns: | |
| * (string): Value of the information item, if the item exists. | |
| The empty string, if the item does not exist. | |
| See `lsb_release command output`_ for details about these information | |
| items. | |
| """ | |
| return _distro.lsb_release_attr(attribute) | |
| def distro_release_attr(attribute): | |
| """ | |
| Return a single named information item from the distro release file | |
| data source of the current OS distribution. | |
| Parameters: | |
| * ``attribute`` (string): Key of the information item. | |
| Returns: | |
| * (string): Value of the information item, if the item exists. | |
| The empty string, if the item does not exist. | |
| See `distro release file`_ for details about these information items. | |
| """ | |
| return _distro.distro_release_attr(attribute) | |
| def uname_attr(attribute): | |
| """ | |
| Return a single named information item from the distro release file | |
| data source of the current OS distribution. | |
| Parameters: | |
| * ``attribute`` (string): Key of the information item. | |
| Returns: | |
| * (string): Value of the information item, if the item exists. | |
| The empty string, if the item does not exist. | |
| """ | |
| return _distro.uname_attr(attribute) | |
| class cached_property(object): | |
| """A version of @property which caches the value. On access, it calls the | |
| underlying function and sets the value in `__dict__` so future accesses | |
| will not re-call the property. | |
| """ | |
| def __init__(self, f): | |
| self._fname = f.__name__ | |
| self._f = f | |
| def __get__(self, obj, owner): | |
| assert obj is not None, 'call {} on an instance'.format(self._fname) | |
| ret = obj.__dict__[self._fname] = self._f(obj) | |
| return ret | |
| class LinuxDistribution(object): | |
| """ | |
| Provides information about a OS distribution. | |
| This package creates a private module-global instance of this class with | |
| default initialization arguments, that is used by the | |
| `consolidated accessor functions`_ and `single source accessor functions`_. | |
| By using default initialization arguments, that module-global instance | |
| returns data about the current OS distribution (i.e. the distro this | |
| package runs on). | |
| Normally, it is not necessary to create additional instances of this class. | |
| However, in situations where control is needed over the exact data sources | |
| that are used, instances of this class can be created with a specific | |
| distro release file, or a specific os-release file, or without invoking the | |
| lsb_release command. | |
| """ | |
| def __init__(self, | |
| include_lsb=True, | |
| os_release_file='', | |
| distro_release_file='', | |
| include_uname=True): | |
| """ | |
| The initialization method of this class gathers information from the | |
| available data sources, and stores that in private instance attributes. | |
| Subsequent access to the information items uses these private instance | |
| attributes, so that the data sources are read only once. | |
| Parameters: | |
| * ``include_lsb`` (bool): Controls whether the | |
| `lsb_release command output`_ is included as a data source. | |
| If the lsb_release command is not available in the program execution | |
| path, the data source for the lsb_release command will be empty. | |
| * ``os_release_file`` (string): The path name of the | |
| `os-release file`_ that is to be used as a data source. | |
| An empty string (the default) will cause the default path name to | |
| be used (see `os-release file`_ for details). | |
| If the specified or defaulted os-release file does not exist, the | |
| data source for the os-release file will be empty. | |
| * ``distro_release_file`` (string): The path name of the | |
| `distro release file`_ that is to be used as a data source. | |
| An empty string (the default) will cause a default search algorithm | |
| to be used (see `distro release file`_ for details). | |
| If the specified distro release file does not exist, or if no default | |
| distro release file can be found, the data source for the distro | |
| release file will be empty. | |
| * ``include_uname`` (bool): Controls whether uname command output is | |
| included as a data source. If the uname command is not available in | |
| the program execution path the data source for the uname command will | |
| be empty. | |
| Public instance attributes: | |
| * ``os_release_file`` (string): The path name of the | |
| `os-release file`_ that is actually used as a data source. The | |
| empty string if no distro release file is used as a data source. | |
| * ``distro_release_file`` (string): The path name of the | |
| `distro release file`_ that is actually used as a data source. The | |
| empty string if no distro release file is used as a data source. | |
| * ``include_lsb`` (bool): The result of the ``include_lsb`` parameter. | |
| This controls whether the lsb information will be loaded. | |
| * ``include_uname`` (bool): The result of the ``include_uname`` | |
| parameter. This controls whether the uname information will | |
| be loaded. | |
| Raises: | |
| * :py:exc:`IOError`: Some I/O issue with an os-release file or distro | |
| release file. | |
| * :py:exc:`subprocess.CalledProcessError`: The lsb_release command had | |
| some issue (other than not being available in the program execution | |
| path). | |
| * :py:exc:`UnicodeError`: A data source has unexpected characters or | |
| uses an unexpected encoding. | |
| """ | |
| self.os_release_file = os_release_file or \ | |
| os.path.join(_UNIXCONFDIR, _OS_RELEASE_BASENAME) | |
| self.distro_release_file = distro_release_file or '' # updated later | |
| self.include_lsb = include_lsb | |
| self.include_uname = include_uname | |
| def __repr__(self): | |
| """Return repr of all info | |
| """ | |
| return \ | |
| "LinuxDistribution(" \ | |
| "os_release_file={self.os_release_file!r}, " \ | |
| "distro_release_file={self.distro_release_file!r}, " \ | |
| "include_lsb={self.include_lsb!r}, " \ | |
| "include_uname={self.include_uname!r}, " \ | |
| "_os_release_info={self._os_release_info!r}, " \ | |
| "_lsb_release_info={self._lsb_release_info!r}, " \ | |
| "_distro_release_info={self._distro_release_info!r}, " \ | |
| "_uname_info={self._uname_info!r})".format( | |
| self=self) | |
| def linux_distribution(self, full_distribution_name=True): | |
| """ | |
| Return information about the OS distribution that is compatible | |
| with Python's :func:`platform.linux_distribution`, supporting a subset | |
| of its parameters. | |
| For details, see :func:`distro.linux_distribution`. | |
| """ | |
| return ( | |
| self.name() if full_distribution_name else self.id(), | |
| self.version(), | |
| self.codename() | |
| ) | |
| def id(self): | |
| """Return the distro ID of the OS distribution, as a string. | |
| For details, see :func:`distro.id`. | |
| """ | |
| def normalize(distro_id, table): | |
| distro_id = distro_id.lower().replace(' ', '_') | |
| return table.get(distro_id, distro_id) | |
| distro_id = self.os_release_attr('id') | |
| if distro_id: | |
| return normalize(distro_id, NORMALIZED_OS_ID) | |
| distro_id = self.lsb_release_attr('distributor_id') | |
| if distro_id: | |
| return normalize(distro_id, NORMALIZED_LSB_ID) | |
| distro_id = self.distro_release_attr('id') | |
| if distro_id: | |
| return normalize(distro_id, NORMALIZED_DISTRO_ID) | |
| distro_id = self.uname_attr('id') | |
| if distro_id: | |
| return normalize(distro_id, NORMALIZED_DISTRO_ID) | |
| return '' | |
| def name(self, pretty=False): | |
| """ | |
| Return the name of the OS distribution, as a string. | |
| For details, see :func:`distro.name`. | |
| """ | |
| name = self.os_release_attr('name') \ | |
| or self.lsb_release_attr('distributor_id') \ | |
| or self.distro_release_attr('name') \ | |
| or self.uname_attr('name') | |
| if pretty: | |
| name = self.os_release_attr('pretty_name') \ | |
| or self.lsb_release_attr('description') | |
| if not name: | |
| name = self.distro_release_attr('name') \ | |
| or self.uname_attr('name') | |
| version = self.version(pretty=True) | |
| if version: | |
| name = name + ' ' + version | |
| return name or '' | |
| def version(self, pretty=False, best=False): | |
| """ | |
| Return the version of the OS distribution, as a string. | |
| For details, see :func:`distro.version`. | |
| """ | |
| versions = [ | |
| self.os_release_attr('version_id'), | |
| self.lsb_release_attr('release'), | |
| self.distro_release_attr('version_id'), | |
| self._parse_distro_release_content( | |
| self.os_release_attr('pretty_name')).get('version_id', ''), | |
| self._parse_distro_release_content( | |
| self.lsb_release_attr('description')).get('version_id', ''), | |
| self.uname_attr('release') | |
| ] | |
| version = '' | |
| if best: | |
| # This algorithm uses the last version in priority order that has | |
| # the best precision. If the versions are not in conflict, that | |
| # does not matter; otherwise, using the last one instead of the | |
| # first one might be considered a surprise. | |
| for v in versions: | |
| if v.count(".") > version.count(".") or version == '': | |
| version = v | |
| else: | |
| for v in versions: | |
| if v != '': | |
| version = v | |
| break | |
| if pretty and version and self.codename(): | |
| version = '{0} ({1})'.format(version, self.codename()) | |
| return version | |
| def version_parts(self, best=False): | |
| """ | |
| Return the version of the OS distribution, as a tuple of version | |
| numbers. | |
| For details, see :func:`distro.version_parts`. | |
| """ | |
| version_str = self.version(best=best) | |
| if version_str: | |
| version_regex = re.compile(r'(\d+)\.?(\d+)?\.?(\d+)?') | |
| matches = version_regex.match(version_str) | |
| if matches: | |
| major, minor, build_number = matches.groups() | |
| return major, minor or '', build_number or '' | |
| return '', '', '' | |
| def major_version(self, best=False): | |
| """ | |
| Return the major version number of the current distribution. | |
| For details, see :func:`distro.major_version`. | |
| """ | |
| return self.version_parts(best)[0] | |
| def minor_version(self, best=False): | |
| """ | |
| Return the minor version number of the current distribution. | |
| For details, see :func:`distro.minor_version`. | |
| """ | |
| return self.version_parts(best)[1] | |
| def build_number(self, best=False): | |
| """ | |
| Return the build number of the current distribution. | |
| For details, see :func:`distro.build_number`. | |
| """ | |
| return self.version_parts(best)[2] | |
| def like(self): | |
| """ | |
| Return the IDs of distributions that are like the OS distribution. | |
| For details, see :func:`distro.like`. | |
| """ | |
| return self.os_release_attr('id_like') or '' | |
| def codename(self): | |
| """ | |
| Return the codename of the OS distribution. | |
| For details, see :func:`distro.codename`. | |
| """ | |
| try: | |
| # Handle os_release specially since distros might purposefully set | |
| # this to empty string to have no codename | |
| return self._os_release_info['codename'] | |
| except KeyError: | |
| return self.lsb_release_attr('codename') \ | |
| or self.distro_release_attr('codename') \ | |
| or '' | |
| def info(self, pretty=False, best=False): | |
| """ | |
| Return certain machine-readable information about the OS | |
| distribution. | |
| For details, see :func:`distro.info`. | |
| """ | |
| return dict( | |
| id=self.id(), | |
| version=self.version(pretty, best), | |
| version_parts=dict( | |
| major=self.major_version(best), | |
| minor=self.minor_version(best), | |
| build_number=self.build_number(best) | |
| ), | |
| like=self.like(), | |
| codename=self.codename(), | |
| ) | |
| def os_release_info(self): | |
| """ | |
| Return a dictionary containing key-value pairs for the information | |
| items from the os-release file data source of the OS distribution. | |
| For details, see :func:`distro.os_release_info`. | |
| """ | |
| return self._os_release_info | |
| def lsb_release_info(self): | |
| """ | |
| Return a dictionary containing key-value pairs for the information | |
| items from the lsb_release command data source of the OS | |
| distribution. | |
| For details, see :func:`distro.lsb_release_info`. | |
| """ | |
| return self._lsb_release_info | |
| def distro_release_info(self): | |
| """ | |
| Return a dictionary containing key-value pairs for the information | |
| items from the distro release file data source of the OS | |
| distribution. | |
| For details, see :func:`distro.distro_release_info`. | |
| """ | |
| return self._distro_release_info | |
| def uname_info(self): | |
| """ | |
| Return a dictionary containing key-value pairs for the information | |
| items from the uname command data source of the OS distribution. | |
| For details, see :func:`distro.uname_info`. | |
| """ | |
| return self._uname_info | |
| def os_release_attr(self, attribute): | |
| """ | |
| Return a single named information item from the os-release file data | |
| source of the OS distribution. | |
| For details, see :func:`distro.os_release_attr`. | |
| """ | |
| return self._os_release_info.get(attribute, '') | |
| def lsb_release_attr(self, attribute): | |
| """ | |
| Return a single named information item from the lsb_release command | |
| output data source of the OS distribution. | |
| For details, see :func:`distro.lsb_release_attr`. | |
| """ | |
| return self._lsb_release_info.get(attribute, '') | |
| def distro_release_attr(self, attribute): | |
| """ | |
| Return a single named information item from the distro release file | |
| data source of the OS distribution. | |
| For details, see :func:`distro.distro_release_attr`. | |
| """ | |
| return self._distro_release_info.get(attribute, '') | |
| def uname_attr(self, attribute): | |
| """ | |
| Return a single named information item from the uname command | |
| output data source of the OS distribution. | |
| For details, see :func:`distro.uname_release_attr`. | |
| """ | |
| return self._uname_info.get(attribute, '') | |
| def _os_release_info(self): | |
| """ | |
| Get the information items from the specified os-release file. | |
| Returns: | |
| A dictionary containing all information items. | |
| """ | |
| if os.path.isfile(self.os_release_file): | |
| with open(self.os_release_file) as release_file: | |
| return self._parse_os_release_content(release_file) | |
| return {} | |
| def _parse_os_release_content(lines): | |
| """ | |
| Parse the lines of an os-release file. | |
| Parameters: | |
| * lines: Iterable through the lines in the os-release file. | |
| Each line must be a unicode string or a UTF-8 encoded byte | |
| string. | |
| Returns: | |
| A dictionary containing all information items. | |
| """ | |
| props = {} | |
| lexer = shlex.shlex(lines, posix=True) | |
| lexer.whitespace_split = True | |
| # The shlex module defines its `wordchars` variable using literals, | |
| # making it dependent on the encoding of the Python source file. | |
| # In Python 2.6 and 2.7, the shlex source file is encoded in | |
| # 'iso-8859-1', and the `wordchars` variable is defined as a byte | |
| # string. This causes a UnicodeDecodeError to be raised when the | |
| # parsed content is a unicode object. The following fix resolves that | |
| # (... but it should be fixed in shlex...): | |
| if sys.version_info[0] == 2 and isinstance(lexer.wordchars, bytes): | |
| lexer.wordchars = lexer.wordchars.decode('iso-8859-1') | |
| tokens = list(lexer) | |
| for token in tokens: | |
| # At this point, all shell-like parsing has been done (i.e. | |
| # comments processed, quotes and backslash escape sequences | |
| # processed, multi-line values assembled, trailing newlines | |
| # stripped, etc.), so the tokens are now either: | |
| # * variable assignments: var=value | |
| # * commands or their arguments (not allowed in os-release) | |
| if '=' in token: | |
| k, v = token.split('=', 1) | |
| props[k.lower()] = v | |
| else: | |
| # Ignore any tokens that are not variable assignments | |
| pass | |
| if 'version_codename' in props: | |
| # os-release added a version_codename field. Use that in | |
| # preference to anything else Note that some distros purposefully | |
| # do not have code names. They should be setting | |
| # version_codename="" | |
| props['codename'] = props['version_codename'] | |
| elif 'ubuntu_codename' in props: | |
| # Same as above but a non-standard field name used on older Ubuntus | |
| props['codename'] = props['ubuntu_codename'] | |
| elif 'version' in props: | |
| # If there is no version_codename, parse it from the version | |
| codename = re.search(r'(\(\D+\))|,(\s+)?\D+', props['version']) | |
| if codename: | |
| codename = codename.group() | |
| codename = codename.strip('()') | |
| codename = codename.strip(',') | |
| codename = codename.strip() | |
| # codename appears within paranthese. | |
| props['codename'] = codename | |
| return props | |
| def _lsb_release_info(self): | |
| """ | |
| Get the information items from the lsb_release command output. | |
| Returns: | |
| A dictionary containing all information items. | |
| """ | |
| if not self.include_lsb: | |
| return {} | |
| with open(os.devnull, 'w') as devnull: | |
| try: | |
| cmd = ('lsb_release', '-a') | |
| stdout = subprocess.check_output(cmd, stderr=devnull) | |
| except OSError: # Command not found | |
| return {} | |
| content = self._to_str(stdout).splitlines() | |
| return self._parse_lsb_release_content(content) | |
| def _parse_lsb_release_content(lines): | |
| """ | |
| Parse the output of the lsb_release command. | |
| Parameters: | |
| * lines: Iterable through the lines of the lsb_release output. | |
| Each line must be a unicode string or a UTF-8 encoded byte | |
| string. | |
| Returns: | |
| A dictionary containing all information items. | |
| """ | |
| props = {} | |
| for line in lines: | |
| kv = line.strip('\n').split(':', 1) | |
| if len(kv) != 2: | |
| # Ignore lines without colon. | |
| continue | |
| k, v = kv | |
| props.update({k.replace(' ', '_').lower(): v.strip()}) | |
| return props | |
| def _uname_info(self): | |
| with open(os.devnull, 'w') as devnull: | |
| try: | |
| cmd = ('uname', '-rs') | |
| stdout = subprocess.check_output(cmd, stderr=devnull) | |
| except OSError: | |
| return {} | |
| content = self._to_str(stdout).splitlines() | |
| return self._parse_uname_content(content) | |
| def _parse_uname_content(lines): | |
| props = {} | |
| match = re.search(r'^([^\s]+)\s+([\d\.]+)', lines[0].strip()) | |
| if match: | |
| name, version = match.groups() | |
| # This is to prevent the Linux kernel version from | |
| # appearing as the 'best' version on otherwise | |
| # identifiable distributions. | |
| if name == 'Linux': | |
| return {} | |
| props['id'] = name.lower() | |
| props['name'] = name | |
| props['release'] = version | |
| return props | |
| def _to_str(text): | |
| encoding = sys.getfilesystemencoding() | |
| encoding = 'utf-8' if encoding == 'ascii' else encoding | |
| if sys.version_info[0] >= 3: | |
| if isinstance(text, bytes): | |
| return text.decode(encoding) | |
| else: | |
| if isinstance(text, unicode): # noqa | |
| return text.encode(encoding) | |
| return text | |
| def _distro_release_info(self): | |
| """ | |
| Get the information items from the specified distro release file. | |
| Returns: | |
| A dictionary containing all information items. | |
| """ | |
| if self.distro_release_file: | |
| # If it was specified, we use it and parse what we can, even if | |
| # its file name or content does not match the expected pattern. | |
| distro_info = self._parse_distro_release_file( | |
| self.distro_release_file) | |
| basename = os.path.basename(self.distro_release_file) | |
| # The file name pattern for user-specified distro release files | |
| # is somewhat more tolerant (compared to when searching for the | |
| # file), because we want to use what was specified as best as | |
| # possible. | |
| match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename) | |
| if 'name' in distro_info \ | |
| and 'cloudlinux' in distro_info['name'].lower(): | |
| distro_info['id'] = 'cloudlinux' | |
| elif match: | |
| distro_info['id'] = match.group(1) | |
| return distro_info | |
| else: | |
| try: | |
| basenames = os.listdir(_UNIXCONFDIR) | |
| # We sort for repeatability in cases where there are multiple | |
| # distro specific files; e.g. CentOS, Oracle, Enterprise all | |
| # containing `redhat-release` on top of their own. | |
| basenames.sort() | |
| except OSError: | |
| # This may occur when /etc is not readable but we can't be | |
| # sure about the *-release files. Check common entries of | |
| # /etc for information. If they turn out to not be there the | |
| # error is handled in `_parse_distro_release_file()`. | |
| basenames = ['SuSE-release', | |
| 'arch-release', | |
| 'base-release', | |
| 'centos-release', | |
| 'fedora-release', | |
| 'gentoo-release', | |
| 'mageia-release', | |
| 'mandrake-release', | |
| 'mandriva-release', | |
| 'mandrivalinux-release', | |
| 'manjaro-release', | |
| 'oracle-release', | |
| 'redhat-release', | |
| 'sl-release', | |
| 'slackware-version'] | |
| for basename in basenames: | |
| if basename in _DISTRO_RELEASE_IGNORE_BASENAMES: | |
| continue | |
| match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename) | |
| if match: | |
| filepath = os.path.join(_UNIXCONFDIR, basename) | |
| distro_info = self._parse_distro_release_file(filepath) | |
| if 'name' in distro_info: | |
| # The name is always present if the pattern matches | |
| self.distro_release_file = filepath | |
| distro_info['id'] = match.group(1) | |
| if 'cloudlinux' in distro_info['name'].lower(): | |
| distro_info['id'] = 'cloudlinux' | |
| return distro_info | |
| return {} | |
| def _parse_distro_release_file(self, filepath): | |
| """ | |
| Parse a distro release file. | |
| Parameters: | |
| * filepath: Path name of the distro release file. | |
| Returns: | |
| A dictionary containing all information items. | |
| """ | |
| try: | |
| with open(filepath) as fp: | |
| # Only parse the first line. For instance, on SLES there | |
| # are multiple lines. We don't want them... | |
| return self._parse_distro_release_content(fp.readline()) | |
| except (OSError, IOError): | |
| # Ignore not being able to read a specific, seemingly version | |
| # related file. | |
| # See https://github.com/nir0s/distro/issues/162 | |
| return {} | |
| def _parse_distro_release_content(line): | |
| """ | |
| Parse a line from a distro release file. | |
| Parameters: | |
| * line: Line from the distro release file. Must be a unicode string | |
| or a UTF-8 encoded byte string. | |
| Returns: | |
| A dictionary containing all information items. | |
| """ | |
| matches = _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN.match( | |
| line.strip()[::-1]) | |
| distro_info = {} | |
| if matches: | |
| # regexp ensures non-None | |
| distro_info['name'] = matches.group(3)[::-1] | |
| if matches.group(2): | |
| distro_info['version_id'] = matches.group(2)[::-1] | |
| if matches.group(1): | |
| distro_info['codename'] = matches.group(1)[::-1] | |
| elif line: | |
| distro_info['name'] = line.strip() | |
| return distro_info | |
| _distro = LinuxDistribution() | |
| def main(): | |
| logger = logging.getLogger(__name__) | |
| logger.setLevel(logging.DEBUG) | |
| logger.addHandler(logging.StreamHandler(sys.stdout)) | |
| parser = argparse.ArgumentParser(description="OS distro info tool") | |
| parser.add_argument( | |
| '--json', | |
| '-j', | |
| help="Output in machine readable format", | |
| action="store_true") | |
| args = parser.parse_args() | |
| if args.json: | |
| logger.info(json.dumps(info(), indent=4, sort_keys=True)) | |
| else: | |
| logger.info('Name: %s', name(pretty=True)) | |
| distribution_version = version(pretty=True) | |
| logger.info('Version: %s', distribution_version) | |
| distribution_codename = codename() | |
| logger.info('Codename: %s', distribution_codename) | |
| if __name__ == '__main__': | |
| main() | |