|
|
@ -31,6 +31,8 @@ access to OS distribution information is needed. See `Python issue 1322
|
|
|
|
<https://bugs.python.org/issue1322>`_ for more information.
|
|
|
|
<https://bugs.python.org/issue1322>`_ for more information.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
|
|
|
import json
|
|
|
|
import logging
|
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import re
|
|
|
@ -136,56 +138,6 @@ _DISTRO_RELEASE_IGNORE_BASENAMES = (
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
|
|
|
# Python 2.6 does not have subprocess.check_output so replicate it here
|
|
|
|
|
|
|
|
#
|
|
|
|
|
|
|
|
def _my_check_output(*popenargs, **kwargs):
|
|
|
|
|
|
|
|
r"""Run command with arguments and return its output as a byte string.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
If the exit code was non-zero it raises a CalledProcessError. The
|
|
|
|
|
|
|
|
CalledProcessError object will have the return code in the returncode
|
|
|
|
|
|
|
|
attribute and output in the output attribute.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The arguments are the same as for the Popen constructor. Example:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>>> check_output(["ls", "-l", "/dev/null"])
|
|
|
|
|
|
|
|
'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The stdout argument is not allowed as it is used internally.
|
|
|
|
|
|
|
|
To capture standard error in the result, use stderr=STDOUT.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>>> check_output(["/bin/sh", "-c",
|
|
|
|
|
|
|
|
... "ls -l non_existent_file ; exit 0"],
|
|
|
|
|
|
|
|
... stderr=STDOUT)
|
|
|
|
|
|
|
|
'ls: non_existent_file: No such file or directory\n'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This is a backport of Python-2.7's check output to Python-2.6
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
if 'stdout' in kwargs:
|
|
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
|
|
'stdout argument not allowed, it will be overridden.'
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
process = subprocess.Popen(
|
|
|
|
|
|
|
|
stdout=subprocess.PIPE, *popenargs, **kwargs
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
output, unused_err = process.communicate()
|
|
|
|
|
|
|
|
retcode = process.poll()
|
|
|
|
|
|
|
|
if retcode:
|
|
|
|
|
|
|
|
cmd = kwargs.get("args")
|
|
|
|
|
|
|
|
if cmd is None:
|
|
|
|
|
|
|
|
cmd = popenargs[0]
|
|
|
|
|
|
|
|
# Deviation from Python-2.7: Python-2.6's CalledProcessError does not
|
|
|
|
|
|
|
|
# have an argument for the stdout so simply omit it.
|
|
|
|
|
|
|
|
raise subprocess.CalledProcessError(retcode, cmd)
|
|
|
|
|
|
|
|
return output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
_check_output = subprocess.check_output
|
|
|
|
|
|
|
|
except AttributeError:
|
|
|
|
|
|
|
|
_check_output = _my_check_output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def linux_distribution(full_distribution_name=True):
|
|
|
|
def linux_distribution(full_distribution_name=True):
|
|
|
|
# type: (bool) -> Tuple[str, str, str]
|
|
|
|
# type: (bool) -> Tuple[str, str, str]
|
|
|
|
"""
|
|
|
|
"""
|
|
|
@ -204,7 +156,8 @@ def linux_distribution(full_distribution_name=True):
|
|
|
|
|
|
|
|
|
|
|
|
* ``version``: The result of :func:`distro.version`.
|
|
|
|
* ``version``: The result of :func:`distro.version`.
|
|
|
|
|
|
|
|
|
|
|
|
* ``codename``: The result of :func:`distro.codename`.
|
|
|
|
* ``codename``: The extra item (usually in parentheses) after the
|
|
|
|
|
|
|
|
os-release version number, or the result of :func:`distro.codename`.
|
|
|
|
|
|
|
|
|
|
|
|
The interface of this function is compatible with the original
|
|
|
|
The interface of this function is compatible with the original
|
|
|
|
:py:func:`platform.linux_distribution` function, supporting a subset of
|
|
|
|
:py:func:`platform.linux_distribution` function, supporting a subset of
|
|
|
@ -251,8 +204,9 @@ def id():
|
|
|
|
"fedora" Fedora
|
|
|
|
"fedora" Fedora
|
|
|
|
"sles" SUSE Linux Enterprise Server
|
|
|
|
"sles" SUSE Linux Enterprise Server
|
|
|
|
"opensuse" openSUSE
|
|
|
|
"opensuse" openSUSE
|
|
|
|
"amazon" Amazon Linux
|
|
|
|
"amzn" Amazon Linux
|
|
|
|
"arch" Arch Linux
|
|
|
|
"arch" Arch Linux
|
|
|
|
|
|
|
|
"buildroot" Buildroot
|
|
|
|
"cloudlinux" CloudLinux OS
|
|
|
|
"cloudlinux" CloudLinux OS
|
|
|
|
"exherbo" Exherbo Linux
|
|
|
|
"exherbo" Exherbo Linux
|
|
|
|
"gentoo" GenToo Linux
|
|
|
|
"gentoo" GenToo Linux
|
|
|
@ -272,6 +226,8 @@ def id():
|
|
|
|
"netbsd" NetBSD
|
|
|
|
"netbsd" NetBSD
|
|
|
|
"freebsd" FreeBSD
|
|
|
|
"freebsd" FreeBSD
|
|
|
|
"midnightbsd" MidnightBSD
|
|
|
|
"midnightbsd" MidnightBSD
|
|
|
|
|
|
|
|
"rocky" Rocky Linux
|
|
|
|
|
|
|
|
"guix" Guix System
|
|
|
|
============== =========================================
|
|
|
|
============== =========================================
|
|
|
|
|
|
|
|
|
|
|
|
If you have a need to get distros for reliable IDs added into this set,
|
|
|
|
If you have a need to get distros for reliable IDs added into this set,
|
|
|
@ -366,6 +322,10 @@ def version(pretty=False, best=False):
|
|
|
|
sources in a fixed priority order does not always yield the most precise
|
|
|
|
sources in a fixed priority order does not always yield the most precise
|
|
|
|
version (e.g. for Debian 8.2, or CentOS 7.1).
|
|
|
|
version (e.g. for Debian 8.2, or CentOS 7.1).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Some other distributions may not provide this kind of information. In these
|
|
|
|
|
|
|
|
cases, an empty string would be returned. This behavior can be observed
|
|
|
|
|
|
|
|
with rolling releases distributions (e.g. Arch Linux).
|
|
|
|
|
|
|
|
|
|
|
|
The *best* parameter can be used to control the approach for the returned
|
|
|
|
The *best* parameter can be used to control the approach for the returned
|
|
|
|
version:
|
|
|
|
version:
|
|
|
|
|
|
|
|
|
|
|
@ -681,7 +641,7 @@ except ImportError:
|
|
|
|
|
|
|
|
|
|
|
|
def __get__(self, obj, owner):
|
|
|
|
def __get__(self, obj, owner):
|
|
|
|
# type: (Any, Type[Any]) -> Any
|
|
|
|
# type: (Any, Type[Any]) -> Any
|
|
|
|
assert obj is not None, "call {0} on an instance".format(self._fname)
|
|
|
|
assert obj is not None, "call {} on an instance".format(self._fname)
|
|
|
|
ret = obj.__dict__[self._fname] = self._f(obj)
|
|
|
|
ret = obj.__dict__[self._fname] = self._f(obj)
|
|
|
|
return ret
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
@ -776,10 +736,6 @@ class LinuxDistribution(object):
|
|
|
|
* :py:exc:`IOError`: Some I/O issue with an os-release file or distro
|
|
|
|
* :py:exc:`IOError`: Some I/O issue with an os-release file or distro
|
|
|
|
release file.
|
|
|
|
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
|
|
|
|
* :py:exc:`UnicodeError`: A data source has unexpected characters or
|
|
|
|
uses an unexpected encoding.
|
|
|
|
uses an unexpected encoding.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
@ -837,7 +793,7 @@ class LinuxDistribution(object):
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
self.name() if full_distribution_name else self.id(),
|
|
|
|
self.name() if full_distribution_name else self.id(),
|
|
|
|
self.version(),
|
|
|
|
self.version(),
|
|
|
|
self.codename(),
|
|
|
|
self._os_release_info.get("release_codename") or self.codename(),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def id(self):
|
|
|
|
def id(self):
|
|
|
@ -913,6 +869,9 @@ class LinuxDistribution(object):
|
|
|
|
).get("version_id", ""),
|
|
|
|
).get("version_id", ""),
|
|
|
|
self.uname_attr("release"),
|
|
|
|
self.uname_attr("release"),
|
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
if self.id() == "debian" or "debian" in self.like().split():
|
|
|
|
|
|
|
|
# On Debian-like, add debian_version file content to candidates list.
|
|
|
|
|
|
|
|
versions.append(self._debian_version)
|
|
|
|
version = ""
|
|
|
|
version = ""
|
|
|
|
if best:
|
|
|
|
if best:
|
|
|
|
# This algorithm uses the last version in priority order that has
|
|
|
|
# This algorithm uses the last version in priority order that has
|
|
|
@ -1155,12 +1114,17 @@ class LinuxDistribution(object):
|
|
|
|
# stripped, etc.), so the tokens are now either:
|
|
|
|
# stripped, etc.), so the tokens are now either:
|
|
|
|
# * variable assignments: var=value
|
|
|
|
# * variable assignments: var=value
|
|
|
|
# * commands or their arguments (not allowed in os-release)
|
|
|
|
# * commands or their arguments (not allowed in os-release)
|
|
|
|
|
|
|
|
# Ignore any tokens that are not variable assignments
|
|
|
|
if "=" in token:
|
|
|
|
if "=" in token:
|
|
|
|
k, v = token.split("=", 1)
|
|
|
|
k, v = token.split("=", 1)
|
|
|
|
props[k.lower()] = v
|
|
|
|
props[k.lower()] = v
|
|
|
|
else:
|
|
|
|
|
|
|
|
# Ignore any tokens that are not variable assignments
|
|
|
|
if "version" in props:
|
|
|
|
pass
|
|
|
|
# extract release codename (if any) from version attribute
|
|
|
|
|
|
|
|
match = re.search(r"\((\D+)\)|,\s*(\D+)", props["version"])
|
|
|
|
|
|
|
|
if match:
|
|
|
|
|
|
|
|
release_codename = match.group(1) or match.group(2)
|
|
|
|
|
|
|
|
props["codename"] = props["release_codename"] = release_codename
|
|
|
|
|
|
|
|
|
|
|
|
if "version_codename" in props:
|
|
|
|
if "version_codename" in props:
|
|
|
|
# os-release added a version_codename field. Use that in
|
|
|
|
# os-release added a version_codename field. Use that in
|
|
|
@ -1171,16 +1135,6 @@ class LinuxDistribution(object):
|
|
|
|
elif "ubuntu_codename" in props:
|
|
|
|
elif "ubuntu_codename" in props:
|
|
|
|
# Same as above but a non-standard field name used on older Ubuntus
|
|
|
|
# Same as above but a non-standard field name used on older Ubuntus
|
|
|
|
props["codename"] = props["ubuntu_codename"]
|
|
|
|
props["codename"] = props["ubuntu_codename"]
|
|
|
|
elif "version" in props:
|
|
|
|
|
|
|
|
# If there is no version_codename, parse it from the version
|
|
|
|
|
|
|
|
match = re.search(r"(\(\D+\))|,(\s+)?\D+", props["version"])
|
|
|
|
|
|
|
|
if match:
|
|
|
|
|
|
|
|
codename = match.group()
|
|
|
|
|
|
|
|
codename = codename.strip("()")
|
|
|
|
|
|
|
|
codename = codename.strip(",")
|
|
|
|
|
|
|
|
codename = codename.strip()
|
|
|
|
|
|
|
|
# codename appears within paranthese.
|
|
|
|
|
|
|
|
props["codename"] = codename
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return props
|
|
|
|
return props
|
|
|
|
|
|
|
|
|
|
|
@ -1198,7 +1152,7 @@ class LinuxDistribution(object):
|
|
|
|
with open(os.devnull, "wb") as devnull:
|
|
|
|
with open(os.devnull, "wb") as devnull:
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
cmd = ("lsb_release", "-a")
|
|
|
|
cmd = ("lsb_release", "-a")
|
|
|
|
stdout = _check_output(cmd, stderr=devnull)
|
|
|
|
stdout = subprocess.check_output(cmd, stderr=devnull)
|
|
|
|
# Command not found or lsb_release returned error
|
|
|
|
# Command not found or lsb_release returned error
|
|
|
|
except (OSError, subprocess.CalledProcessError):
|
|
|
|
except (OSError, subprocess.CalledProcessError):
|
|
|
|
return {}
|
|
|
|
return {}
|
|
|
@ -1233,18 +1187,31 @@ class LinuxDistribution(object):
|
|
|
|
@cached_property
|
|
|
|
@cached_property
|
|
|
|
def _uname_info(self):
|
|
|
|
def _uname_info(self):
|
|
|
|
# type: () -> Dict[str, str]
|
|
|
|
# type: () -> Dict[str, str]
|
|
|
|
|
|
|
|
if not self.include_uname:
|
|
|
|
|
|
|
|
return {}
|
|
|
|
with open(os.devnull, "wb") as devnull:
|
|
|
|
with open(os.devnull, "wb") as devnull:
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
cmd = ("uname", "-rs")
|
|
|
|
cmd = ("uname", "-rs")
|
|
|
|
stdout = _check_output(cmd, stderr=devnull)
|
|
|
|
stdout = subprocess.check_output(cmd, stderr=devnull)
|
|
|
|
except OSError:
|
|
|
|
except OSError:
|
|
|
|
return {}
|
|
|
|
return {}
|
|
|
|
content = self._to_str(stdout).splitlines()
|
|
|
|
content = self._to_str(stdout).splitlines()
|
|
|
|
return self._parse_uname_content(content)
|
|
|
|
return self._parse_uname_content(content)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
|
|
|
|
def _debian_version(self):
|
|
|
|
|
|
|
|
# type: () -> str
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
with open(os.path.join(self.etc_dir, "debian_version")) as fp:
|
|
|
|
|
|
|
|
return fp.readline().rstrip()
|
|
|
|
|
|
|
|
except (OSError, IOError):
|
|
|
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
@staticmethod
|
|
|
|
def _parse_uname_content(lines):
|
|
|
|
def _parse_uname_content(lines):
|
|
|
|
# type: (Sequence[str]) -> Dict[str, str]
|
|
|
|
# type: (Sequence[str]) -> Dict[str, str]
|
|
|
|
|
|
|
|
if not lines:
|
|
|
|
|
|
|
|
return {}
|
|
|
|
props = {}
|
|
|
|
props = {}
|
|
|
|
match = re.search(r"^([^\s]+)\s+([\d\.]+)", lines[0].strip())
|
|
|
|
match = re.search(r"^([^\s]+)\s+([\d\.]+)", lines[0].strip())
|
|
|
|
if match:
|
|
|
|
if match:
|
|
|
@ -1270,7 +1237,7 @@ class LinuxDistribution(object):
|
|
|
|
if isinstance(text, bytes):
|
|
|
|
if isinstance(text, bytes):
|
|
|
|
return text.decode(encoding)
|
|
|
|
return text.decode(encoding)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
if isinstance(text, unicode): # noqa pylint: disable=undefined-variable
|
|
|
|
if isinstance(text, unicode): # noqa
|
|
|
|
return text.encode(encoding)
|
|
|
|
return text.encode(encoding)
|
|
|
|
|
|
|
|
|
|
|
|
return text
|
|
|
|
return text
|
|
|
@ -1325,6 +1292,7 @@ class LinuxDistribution(object):
|
|
|
|
"manjaro-release",
|
|
|
|
"manjaro-release",
|
|
|
|
"oracle-release",
|
|
|
|
"oracle-release",
|
|
|
|
"redhat-release",
|
|
|
|
"redhat-release",
|
|
|
|
|
|
|
|
"rocky-release",
|
|
|
|
"sl-release",
|
|
|
|
"sl-release",
|
|
|
|
"slackware-version",
|
|
|
|
"slackware-version",
|
|
|
|
]
|
|
|
|
]
|
|
|
@ -1403,8 +1371,31 @@ def main():
|
|
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
logger.addHandler(logging.StreamHandler(sys.stdout))
|
|
|
|
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"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
|
|
|
"--root-dir",
|
|
|
|
|
|
|
|
"-r",
|
|
|
|
|
|
|
|
type=str,
|
|
|
|
|
|
|
|
dest="root_dir",
|
|
|
|
|
|
|
|
help="Path to the root filesystem directory (defaults to /)",
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if args.root_dir:
|
|
|
|
|
|
|
|
dist = LinuxDistribution(
|
|
|
|
|
|
|
|
include_lsb=False, include_uname=False, root_dir=args.root_dir
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
else:
|
|
|
|
dist = _distro
|
|
|
|
dist = _distro
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if args.json:
|
|
|
|
|
|
|
|
logger.info(json.dumps(dist.info(), indent=4, sort_keys=True))
|
|
|
|
|
|
|
|
else:
|
|
|
|
logger.info("Name: %s", dist.name(pretty=True))
|
|
|
|
logger.info("Name: %s", dist.name(pretty=True))
|
|
|
|
distribution_version = dist.version(pretty=True)
|
|
|
|
distribution_version = dist.version(pretty=True)
|
|
|
|
logger.info("Version: %s", distribution_version)
|
|
|
|
logger.info("Version: %s", distribution_version)
|
|
|
|