"Fossies" - the Fresh Open Source Software Archive

Member "salt-3002.2/salt/version.py" (18 Nov 2020, 27300 Bytes) of package /linux/misc/salt-3002.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "version.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3002.1_vs_3002.2.

    1 """
    2 Set up the version of Salt
    3 """
    4 
    5 import platform
    6 import re
    7 import sys
    8 
    9 MAX_SIZE = sys.maxsize
   10 VERSION_LIMIT = MAX_SIZE - 200
   11 
   12 # ----- ATTENTION --------------------------------------------------------------------------------------------------->
   13 #
   14 # ALL major version bumps, new release codenames, MUST be defined in the SaltStackVersion.NAMES dictionary, i.e.:
   15 #
   16 #    class SaltStackVersion(object):
   17 #
   18 #        NAMES = {
   19 #            'Hydrogen': (2014, 1),   # <- This is the tuple to bump versions
   20 #            ( ... )
   21 #        }
   22 #
   23 #
   24 # ONLY UPDATE CODENAMES AFTER BRANCHING
   25 #
   26 # As an example, The Helium codename must only be properly defined with "(2014, 7)" after Hydrogen, "(2014, 1)", has
   27 # been branched out into its own branch.
   28 #
   29 # ALL OTHER VERSION INFORMATION IS EXTRACTED FROM THE GIT TAGS
   30 #
   31 # <---- ATTENTION ----------------------------------------------------------------------------------------------------
   32 
   33 
   34 class SaltStackVersion:
   35     """
   36     Handle SaltStack versions class.
   37 
   38     Knows how to parse ``git describe`` output, knows about release candidates
   39     and also supports version comparison.
   40     """
   41 
   42     __slots__ = (
   43         "name",
   44         "major",
   45         "minor",
   46         "bugfix",
   47         "mbugfix",
   48         "pre_type",
   49         "pre_num",
   50         "noc",
   51         "sha",
   52     )
   53 
   54     git_sha_regex = r"(?P<sha>g?[a-f0-9]{7,40})"
   55 
   56     git_describe_regex = re.compile(
   57         r"(?:[^\d]+)?(?P<major>[\d]{1,4})"
   58         r"(?:\.(?P<minor>[\d]{1,2}))?"
   59         r"(?:\.(?P<bugfix>[\d]{0,2}))?"
   60         r"(?:\.(?P<mbugfix>[\d]{0,2}))?"
   61         r"(?:(?P<pre_type>rc|a|b|alpha|beta|nb)(?P<pre_num>[\d]{1}))?"
   62         r"(?:(?:.*)-(?P<noc>(?:[\d]+|n/a))-" + git_sha_regex + r")?"
   63     )
   64     git_sha_regex = r"^" + git_sha_regex
   65 
   66     git_sha_regex = re.compile(git_sha_regex)
   67 
   68     # Salt versions after 0.17.0 will be numbered like:
   69     #   <4-digit-year>.<month>.<bugfix>
   70     #
   71     # Since the actual version numbers will only be know on release dates, the
   72     # periodic table element names will be what's going to be used to name
   73     # versions and to be able to mention them.
   74 
   75     NAMES = {
   76         # Let's keep at least 3 version names uncommented counting from the
   77         # latest release so we can map deprecation warnings to versions.
   78         # pylint: disable=E8203
   79         # ----- Please refrain from fixing PEP-8 E203 and E265 ----->
   80         # The idea is to keep this readable.
   81         # -----------------------------------------------------------
   82         "Hydrogen": (2014, 1),
   83         "Helium": (2014, 7),
   84         "Lithium": (2015, 5),
   85         "Beryllium": (2015, 8),
   86         "Boron": (2016, 3),
   87         "Carbon": (2016, 11),
   88         "Nitrogen": (2017, 7),
   89         "Oxygen": (2018, 3),
   90         "Fluorine": (2019, 2),
   91         "Neon": (3000,),
   92         "Sodium": (3001,),
   93         "Magnesium": (3002,),
   94         "Aluminium": (MAX_SIZE - 96, 0),
   95         "Silicon": (MAX_SIZE - 95, 0),
   96         "Phosphorus": (MAX_SIZE - 94, 0),
   97         # pylint: disable=E8265
   98         #'Sulfur'       : (MAX_SIZE - 93, 0),
   99         #'Chlorine'     : (MAX_SIZE - 92, 0),
  100         #'Argon'        : (MAX_SIZE - 91, 0),
  101         #'Potassium'    : (MAX_SIZE - 90, 0),
  102         #'Calcium'      : (MAX_SIZE - 89, 0),
  103         #'Scandium'     : (MAX_SIZE - 88, 0),
  104         #'Titanium'     : (MAX_SIZE - 87, 0),
  105         #'Vanadium'     : (MAX_SIZE - 86, 0),
  106         #'Chromium'     : (MAX_SIZE - 85, 0),
  107         #'Manganese'    : (MAX_SIZE - 84, 0),
  108         #'Iron'         : (MAX_SIZE - 83, 0),
  109         #'Cobalt'       : (MAX_SIZE - 82, 0),
  110         #'Nickel'       : (MAX_SIZE - 81, 0),
  111         #'Copper'       : (MAX_SIZE - 80, 0),
  112         #'Zinc'         : (MAX_SIZE - 79, 0),
  113         #'Gallium'      : (MAX_SIZE - 78, 0),
  114         #'Germanium'    : (MAX_SIZE - 77, 0),
  115         #'Arsenic'      : (MAX_SIZE - 76, 0),
  116         #'Selenium'     : (MAX_SIZE - 75, 0),
  117         #'Bromine'      : (MAX_SIZE - 74, 0),
  118         #'Krypton'      : (MAX_SIZE - 73, 0),
  119         #'Rubidium'     : (MAX_SIZE - 72, 0),
  120         #'Strontium'    : (MAX_SIZE - 71, 0),
  121         #'Yttrium'      : (MAX_SIZE - 70, 0),
  122         #'Zirconium'    : (MAX_SIZE - 69, 0),
  123         #'Niobium'      : (MAX_SIZE - 68, 0),
  124         #'Molybdenum'   : (MAX_SIZE - 67, 0),
  125         #'Technetium'   : (MAX_SIZE - 66, 0),
  126         #'Ruthenium'    : (MAX_SIZE - 65, 0),
  127         #'Rhodium'      : (MAX_SIZE - 64, 0),
  128         #'Palladium'    : (MAX_SIZE - 63, 0),
  129         #'Silver'       : (MAX_SIZE - 62, 0),
  130         #'Cadmium'      : (MAX_SIZE - 61, 0),
  131         #'Indium'       : (MAX_SIZE - 60, 0),
  132         #'Tin'          : (MAX_SIZE - 59, 0),
  133         #'Antimony'     : (MAX_SIZE - 58, 0),
  134         #'Tellurium'    : (MAX_SIZE - 57, 0),
  135         #'Iodine'       : (MAX_SIZE - 56, 0),
  136         #'Xenon'        : (MAX_SIZE - 55, 0),
  137         #'Caesium'      : (MAX_SIZE - 54, 0),
  138         #'Barium'       : (MAX_SIZE - 53, 0),
  139         #'Lanthanum'    : (MAX_SIZE - 52, 0),
  140         #'Cerium'       : (MAX_SIZE - 51, 0),
  141         #'Praseodymium' : (MAX_SIZE - 50, 0),
  142         #'Neodymium'    : (MAX_SIZE - 49, 0),
  143         #'Promethium'   : (MAX_SIZE - 48, 0),
  144         #'Samarium'     : (MAX_SIZE - 47, 0),
  145         #'Europium'     : (MAX_SIZE - 46, 0),
  146         #'Gadolinium'   : (MAX_SIZE - 45, 0),
  147         #'Terbium'      : (MAX_SIZE - 44, 0),
  148         #'Dysprosium'   : (MAX_SIZE - 43, 0),
  149         #'Holmium'      : (MAX_SIZE - 42, 0),
  150         #'Erbium'       : (MAX_SIZE - 41, 0),
  151         #'Thulium'      : (MAX_SIZE - 40, 0),
  152         #'Ytterbium'    : (MAX_SIZE - 39, 0),
  153         #'Lutetium'     : (MAX_SIZE - 38, 0),
  154         #'Hafnium'      : (MAX_SIZE - 37, 0),
  155         #'Tantalum'     : (MAX_SIZE - 36, 0),
  156         #'Tungsten'     : (MAX_SIZE - 35, 0),
  157         #'Rhenium'      : (MAX_SIZE - 34, 0),
  158         #'Osmium'       : (MAX_SIZE - 33, 0),
  159         #'Iridium'      : (MAX_SIZE - 32, 0),
  160         #'Platinum'     : (MAX_SIZE - 31, 0),
  161         #'Gold'         : (MAX_SIZE - 30, 0),
  162         #'Mercury'      : (MAX_SIZE - 29, 0),
  163         #'Thallium'     : (MAX_SIZE - 28, 0),
  164         #'Lead'         : (MAX_SIZE - 27, 0),
  165         #'Bismuth'      : (MAX_SIZE - 26, 0),
  166         #'Polonium'     : (MAX_SIZE - 25, 0),
  167         #'Astatine'     : (MAX_SIZE - 24, 0),
  168         #'Radon'        : (MAX_SIZE - 23, 0),
  169         #'Francium'     : (MAX_SIZE - 22, 0),
  170         #'Radium'       : (MAX_SIZE - 21, 0),
  171         #'Actinium'     : (MAX_SIZE - 20, 0),
  172         #'Thorium'      : (MAX_SIZE - 19, 0),
  173         #'Protactinium' : (MAX_SIZE - 18, 0),
  174         #'Uranium'      : (MAX_SIZE - 17, 0),
  175         #'Neptunium'    : (MAX_SIZE - 16, 0),
  176         #'Plutonium'    : (MAX_SIZE - 15, 0),
  177         #'Americium'    : (MAX_SIZE - 14, 0),
  178         #'Curium'       : (MAX_SIZE - 13, 0),
  179         #'Berkelium'    : (MAX_SIZE - 12, 0),
  180         #'Californium'  : (MAX_SIZE - 11, 0),
  181         #'Einsteinium'  : (MAX_SIZE - 10, 0),
  182         #'Fermium'      : (MAX_SIZE - 9, 0),
  183         #'Mendelevium'  : (MAX_SIZE - 8, 0),
  184         #'Nobelium'     : (MAX_SIZE - 7, 0),
  185         #'Lawrencium'   : (MAX_SIZE - 6, 0),
  186         #'Rutherfordium': (MAX_SIZE - 5, 0),
  187         #'Dubnium'      : (MAX_SIZE - 4, 0),
  188         #'Seaborgium'   : (MAX_SIZE - 3, 0),
  189         #'Bohrium'      : (MAX_SIZE - 2, 0),
  190         #'Hassium'      : (MAX_SIZE - 1, 0),
  191         #'Meitnerium'   : (MAX_SIZE - 0, 0),
  192         # <---- Please refrain from fixing PEP-8 E203 and E265 ------
  193         # pylint: enable=E8203,E8265
  194     }
  195 
  196     LNAMES = {k.lower(): v for (k, v) in iter(NAMES.items())}
  197     VNAMES = {v: k for (k, v) in iter(NAMES.items())}
  198     RMATCH = {v[:2]: k for (k, v) in iter(NAMES.items())}
  199 
  200     def __init__(
  201         self,  # pylint: disable=C0103
  202         major,
  203         minor=None,
  204         bugfix=None,
  205         mbugfix=0,
  206         pre_type=None,
  207         pre_num=None,
  208         noc=0,
  209         sha=None,
  210     ):
  211 
  212         if isinstance(major, str):
  213             major = int(major)
  214 
  215         if isinstance(minor, str):
  216             if not minor:
  217                 # Empty string
  218                 minor = None
  219             else:
  220                 minor = int(minor)
  221 
  222         if bugfix is None and not self.new_version(major=major):
  223             bugfix = 0
  224         elif isinstance(bugfix, str):
  225             if not bugfix:
  226                 bugfix = None
  227             else:
  228                 bugfix = int(bugfix)
  229 
  230         if mbugfix is None:
  231             mbugfix = 0
  232         elif isinstance(mbugfix, str):
  233             mbugfix = int(mbugfix)
  234 
  235         if pre_type is None:
  236             pre_type = ""
  237         if pre_num is None:
  238             pre_num = 0
  239         elif isinstance(pre_num, str):
  240             pre_num = int(pre_num)
  241 
  242         if noc is None:
  243             noc = 0
  244         elif isinstance(noc, str) and noc == "n/a":
  245             noc = -1
  246         elif isinstance(noc, str):
  247             noc = int(noc)
  248 
  249         self.major = major
  250         self.minor = minor
  251         self.bugfix = bugfix
  252         self.mbugfix = mbugfix
  253         self.pre_type = pre_type
  254         self.pre_num = pre_num
  255         self.name = self.VNAMES.get((major, minor), None)
  256         if self.new_version(major):
  257             self.name = self.VNAMES.get((major,), None)
  258         self.noc = noc
  259         self.sha = sha
  260 
  261     def new_version(self, major):
  262         """
  263         determine if using new versioning scheme
  264         """
  265         return bool(int(major) >= 3000 and int(major) < VERSION_LIMIT)
  266 
  267     @classmethod
  268     def parse(cls, version_string):
  269         if version_string.lower() in cls.LNAMES:
  270             return cls.from_name(version_string)
  271         vstr = (
  272             version_string.decode()
  273             if isinstance(version_string, bytes)
  274             else version_string
  275         )
  276         match = cls.git_describe_regex.match(vstr)
  277         if not match:
  278             raise ValueError(
  279                 "Unable to parse version string: '{}'".format(version_string)
  280             )
  281         return cls(*match.groups())
  282 
  283     @classmethod
  284     def from_name(cls, name):
  285         if name.lower() not in cls.LNAMES:
  286             raise ValueError("Named version '{}' is not known".format(name))
  287         return cls(*cls.LNAMES[name.lower()])
  288 
  289     @classmethod
  290     def from_last_named_version(cls):
  291         return cls.from_name(
  292             cls.VNAMES[
  293                 max(
  294                     [
  295                         version_info
  296                         for version_info in cls.VNAMES
  297                         if version_info[0] < (VERSION_LIMIT)
  298                     ]
  299                 )
  300             ]
  301         )
  302 
  303     @classmethod
  304     def next_release(cls):
  305         return cls.from_name(
  306             cls.VNAMES[
  307                 min(
  308                     [
  309                         version_info
  310                         for version_info in cls.VNAMES
  311                         if version_info > cls.from_last_named_version().info
  312                     ]
  313                 )
  314             ]
  315         )
  316 
  317     @property
  318     def sse(self):
  319         # Higher than 0.17, lower than first date based
  320         return 0 < self.major < 2014
  321 
  322     def min_info(self):
  323         info = [self.major]
  324         if self.new_version(self.major):
  325             if self.minor:
  326                 info.append(self.minor)
  327         else:
  328             info.extend([self.minor, self.bugfix, self.mbugfix])
  329         return info
  330 
  331     @property
  332     def info(self):
  333         return tuple(self.min_info())
  334 
  335     @property
  336     def pre_info(self):
  337         info = self.min_info()
  338         info.extend([self.pre_type, self.pre_num])
  339         return tuple(info)
  340 
  341     @property
  342     def noc_info(self):
  343         info = self.min_info()
  344         info.extend([self.pre_type, self.pre_num, self.noc])
  345         return tuple(info)
  346 
  347     @property
  348     def full_info(self):
  349         info = self.min_info()
  350         info.extend([self.pre_type, self.pre_num, self.noc, self.sha])
  351         return tuple(info)
  352 
  353     @property
  354     def full_info_all_versions(self):
  355         """
  356         Return the full info regardless
  357         of which versioning scheme we
  358         are using.
  359         """
  360         info = [
  361             self.major,
  362             self.minor,
  363             self.bugfix,
  364             self.mbugfix,
  365             self.pre_type,
  366             self.pre_num,
  367             self.noc,
  368             self.sha,
  369         ]
  370         return tuple(info)
  371 
  372     @property
  373     def string(self):
  374         if self.new_version(self.major):
  375             version_string = "{}".format(self.major)
  376             if self.minor:
  377                 version_string = "{}.{}".format(self.major, self.minor)
  378         else:
  379             version_string = "{}.{}.{}".format(self.major, self.minor, self.bugfix)
  380         if self.mbugfix:
  381             version_string += ".{}".format(self.mbugfix)
  382         if self.pre_type:
  383             version_string += "{}{}".format(self.pre_type, self.pre_num)
  384         if self.noc and self.sha:
  385             noc = self.noc
  386             if noc < 0:
  387                 noc = "n/a"
  388             version_string += "-{}-{}".format(noc, self.sha)
  389         return version_string
  390 
  391     @property
  392     def formatted_version(self):
  393         if self.name and self.major > 10000:
  394             version_string = self.name
  395             if self.sse:
  396                 version_string += " Enterprise"
  397             version_string += " (Unreleased)"
  398             return version_string
  399         version_string = self.string
  400         if self.sse:
  401             version_string += " Enterprise"
  402         if (self.major, self.minor) in self.RMATCH:
  403             version_string += " ({})".format(self.RMATCH[(self.major, self.minor)])
  404         return version_string
  405 
  406     @property
  407     def pre_index(self):
  408         if self.new_version(self.major):
  409             pre_type = 2
  410             if not isinstance(self.minor, int):
  411                 pre_type = 1
  412         else:
  413             pre_type = 4
  414         return pre_type
  415 
  416     def __str__(self):
  417         return self.string
  418 
  419     def __compare__(self, other, method):
  420         if not isinstance(other, SaltStackVersion):
  421             if isinstance(other, str):
  422                 other = SaltStackVersion.parse(other)
  423             elif isinstance(other, (list, tuple)):
  424                 other = SaltStackVersion(*other)
  425             else:
  426                 raise ValueError(
  427                     "Cannot instantiate Version from type '{}'".format(type(other))
  428                 )
  429 
  430         pre_type = self.pre_index
  431         other_pre_type = other.pre_index
  432         other_noc_info = list(other.noc_info)
  433         noc_info = list(self.noc_info)
  434 
  435         if self.new_version(self.major):
  436             if self.minor and not other.minor:
  437                 # We have minor information, the other side does not
  438                 if self.minor > 0:
  439                     other_noc_info[1] = 0
  440 
  441             if not self.minor and other.minor:
  442                 # The other side has minor information, we don't
  443                 if other.minor > 0:
  444                     noc_info[1] = 0
  445 
  446         if self.pre_type and not other.pre_type:
  447             # We have pre-release information, the other side doesn't
  448             other_noc_info[other_pre_type] = "zzzzz"
  449 
  450         if not self.pre_type and other.pre_type:
  451             # The other side has pre-release information, we don't
  452             noc_info[pre_type] = "zzzzz"
  453 
  454         return method(tuple(noc_info), tuple(other_noc_info))
  455 
  456     def __lt__(self, other):
  457         return self.__compare__(other, lambda _self, _other: _self < _other)
  458 
  459     def __le__(self, other):
  460         return self.__compare__(other, lambda _self, _other: _self <= _other)
  461 
  462     def __eq__(self, other):
  463         return self.__compare__(other, lambda _self, _other: _self == _other)
  464 
  465     def __ne__(self, other):
  466         return self.__compare__(other, lambda _self, _other: _self != _other)
  467 
  468     def __ge__(self, other):
  469         return self.__compare__(other, lambda _self, _other: _self >= _other)
  470 
  471     def __gt__(self, other):
  472         return self.__compare__(other, lambda _self, _other: _self > _other)
  473 
  474     def __repr__(self):
  475         parts = []
  476         if self.name:
  477             parts.append("name='{}'".format(self.name))
  478         parts.extend(["major={}".format(self.major), "minor={}".format(self.minor)])
  479 
  480         if self.new_version(self.major):
  481             if not self.minor:
  482                 parts.remove("".join([x for x in parts if re.search("^minor*", x)]))
  483         else:
  484             parts.extend(["bugfix={}".format(self.bugfix)])
  485 
  486         if self.mbugfix:
  487             parts.append("minor-bugfix={}".format(self.mbugfix))
  488         if self.pre_type:
  489             parts.append("{}={}".format(self.pre_type, self.pre_num))
  490         noc = self.noc
  491         if noc == -1:
  492             noc = "n/a"
  493         if noc and self.sha:
  494             parts.extend(["noc={}".format(noc), "sha={}".format(self.sha)])
  495         return "<{} {}>".format(self.__class__.__name__, " ".join(parts))
  496 
  497 
  498 # ----- Hardcoded Salt Codename Version Information ----------------------------------------------------------------->
  499 #
  500 #   There's no need to do anything here. The last released codename will be picked up
  501 # --------------------------------------------------------------------------------------------------------------------
  502 __saltstack_version__ = SaltStackVersion.from_last_named_version()
  503 # <---- Hardcoded Salt Version Information ---------------------------------------------------------------------------
  504 
  505 
  506 # ----- Dynamic/Runtime Salt Version Information -------------------------------------------------------------------->
  507 def __discover_version(saltstack_version):
  508     # This might be a 'python setup.py develop' installation type. Let's
  509     # discover the version information at runtime.
  510     import os
  511     import subprocess
  512 
  513     if "SETUP_DIRNAME" in globals():
  514         # This is from the exec() call in Salt's setup.py
  515         cwd = SETUP_DIRNAME  # pylint: disable=E0602
  516         if not os.path.exists(os.path.join(cwd, ".git")):
  517             # This is not a Salt git checkout!!! Don't even try to parse...
  518             return saltstack_version
  519     else:
  520         cwd = os.path.abspath(os.path.dirname(__file__))
  521         if not os.path.exists(os.path.join(os.path.dirname(cwd), ".git")):
  522             # This is not a Salt git checkout!!! Don't even try to parse...
  523             return saltstack_version
  524 
  525     try:
  526         kwargs = dict(stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
  527 
  528         if not sys.platform.startswith("win"):
  529             # Let's not import `salt.utils` for the above check
  530             kwargs["close_fds"] = True
  531 
  532         process = subprocess.Popen(
  533             [
  534                 "git",
  535                 "describe",
  536                 "--tags",
  537                 "--first-parent",
  538                 "--match",
  539                 "v[0-9]*",
  540                 "--always",
  541             ],
  542             **kwargs
  543         )
  544 
  545         out, err = process.communicate()
  546 
  547         if process.returncode != 0:
  548             # The git version running this might not support --first-parent
  549             # Revert to old command
  550             process = subprocess.Popen(
  551                 ["git", "describe", "--tags", "--match", "v[0-9]*", "--always"],
  552                 **kwargs
  553             )
  554             out, err = process.communicate()
  555         out = out.decode().strip()
  556         err = err.decode().strip()
  557 
  558         if not out or err:
  559             return saltstack_version
  560 
  561         if SaltStackVersion.git_sha_regex.match(out):
  562             # We only define the parsed SHA and set NOC as ??? (unknown)
  563             saltstack_version.sha = out.strip()
  564             saltstack_version.noc = -1
  565             return saltstack_version
  566 
  567         return SaltStackVersion.parse(out)
  568 
  569     except OSError as os_err:
  570         if os_err.errno != 2:
  571             # If the errno is not 2(The system cannot find the file
  572             # specified), raise the exception so it can be catch by the
  573             # developers
  574             raise
  575     return saltstack_version
  576 
  577 
  578 def __get_version(saltstack_version):
  579     """
  580     If we can get a version provided at installation time or from Git, use
  581     that instead, otherwise we carry on.
  582     """
  583     try:
  584         # Try to import the version information provided at install time
  585         from salt._version import __saltstack_version__  # pylint: disable=E0611,F0401
  586 
  587         return __saltstack_version__
  588     except ImportError:
  589         return __discover_version(saltstack_version)
  590 
  591 
  592 # Get additional version information if available
  593 __saltstack_version__ = __get_version(__saltstack_version__)
  594 # This function has executed once, we're done with it. Delete it!
  595 del __get_version
  596 # <---- Dynamic/Runtime Salt Version Information ---------------------------------------------------------------------
  597 
  598 
  599 # ----- Common version related attributes - NO NEED TO CHANGE ------------------------------------------------------->
  600 __version_info__ = __saltstack_version__.info
  601 __version__ = __saltstack_version__.string
  602 # <---- Common version related attributes - NO NEED TO CHANGE --------------------------------------------------------
  603 
  604 
  605 def salt_information():
  606     """
  607     Report version of salt.
  608     """
  609     yield "Salt", __version__
  610 
  611 
  612 def dependency_information(include_salt_cloud=False):
  613     """
  614     Report versions of library dependencies.
  615     """
  616     libs = [
  617         ("Python", None, sys.version.rsplit("\n")[0].strip()),
  618         ("Jinja2", "jinja2", "__version__"),
  619         ("M2Crypto", "M2Crypto", "version"),
  620         ("msgpack", "msgpack", "version"),
  621         ("msgpack-pure", "msgpack_pure", "version"),
  622         ("pycrypto", "Crypto", "__version__"),
  623         ("pycryptodome", "Cryptodome", "version_info"),
  624         ("PyYAML", "yaml", "__version__"),
  625         ("PyZMQ", "zmq", "__version__"),
  626         ("ZMQ", "zmq", "zmq_version"),
  627         ("Mako", "mako", "__version__"),
  628         ("Tornado", "tornado", "version"),
  629         ("timelib", "timelib", "version"),
  630         ("dateutil", "dateutil", "__version__"),
  631         ("pygit2", "pygit2", "__version__"),
  632         ("libgit2", "pygit2", "LIBGIT2_VERSION"),
  633         ("smmap", "smmap", "__version__"),
  634         ("cffi", "cffi", "__version__"),
  635         ("pycparser", "pycparser", "__version__"),
  636         ("gitdb", "gitdb", "__version__"),
  637         ("gitpython", "git", "__version__"),
  638         ("python-gnupg", "gnupg", "__version__"),
  639         ("mysql-python", "MySQLdb", "__version__"),
  640         ("cherrypy", "cherrypy", "__version__"),
  641         ("docker-py", "docker", "__version__"),
  642     ]
  643 
  644     if include_salt_cloud:
  645         libs.append(("Apache Libcloud", "libcloud", "__version__"),)
  646 
  647     for name, imp, attr in libs:
  648         if imp is None:
  649             yield name, attr
  650             continue
  651         try:
  652             imp = __import__(imp)
  653             version = getattr(imp, attr)
  654             if callable(version):
  655                 version = version()
  656             if isinstance(version, (tuple, list)):
  657                 version = ".".join(map(str, version))
  658             yield name, version
  659         except Exception:  # pylint: disable=broad-except
  660             yield name, None
  661 
  662 
  663 def system_information():
  664     """
  665     Report system versions.
  666     """
  667     # Late import so that when getting called from setup.py does not break
  668     from distro import linux_distribution
  669 
  670     def system_version():
  671         """
  672         Return host system version.
  673         """
  674 
  675         lin_ver = linux_distribution()
  676         mac_ver = platform.mac_ver()
  677         win_ver = platform.win32_ver()
  678 
  679         # linux_distribution() will return a
  680         # distribution on OS X and Windows.
  681         # Check mac_ver and win_ver first,
  682         # then lin_ver.
  683         if mac_ver[0]:
  684             if isinstance(mac_ver[1], (tuple, list)) and "".join(mac_ver[1]):
  685                 return " ".join([mac_ver[0], ".".join(mac_ver[1]), mac_ver[2]])
  686             else:
  687                 return " ".join([mac_ver[0], mac_ver[2]])
  688         elif win_ver[0]:
  689             return " ".join(win_ver)
  690         elif lin_ver[0]:
  691             return " ".join(lin_ver)
  692         else:
  693             return ""
  694 
  695     if platform.win32_ver()[0]:
  696         # Get the version and release info based on the Windows Operating
  697         # System Product Name. As long as Microsoft maintains a similar format
  698         # this should be future proof
  699         import win32api  # pylint: disable=3rd-party-module-not-gated
  700         import win32con  # pylint: disable=3rd-party-module-not-gated
  701 
  702         # Get the product name from the registry
  703         hkey = win32con.HKEY_LOCAL_MACHINE
  704         key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"
  705         value_name = "ProductName"
  706         reg_handle = win32api.RegOpenKey(hkey, key)
  707 
  708         # Returns a tuple of (product_name, value_type)
  709         product_name, _ = win32api.RegQueryValueEx(reg_handle, value_name)
  710 
  711         version = "Unknown"
  712         release = ""
  713         if "Server" in product_name:
  714             for item in product_name.split(" "):
  715                 # If it's all digits, then it's version
  716                 if re.match(r"\d+", item):
  717                     version = item
  718                 # If it starts with R and then numbers, it's the release
  719                 # ie: R2
  720                 if re.match(r"^R\d+$", item):
  721                     release = item
  722             release = "{}Server{}".format(version, release)
  723         else:
  724             for item in product_name.split(" "):
  725                 # If it's a number, decimal number, Thin or Vista, then it's the
  726                 # version
  727                 if re.match(r"^(\d+(\.\d+)?)|Thin|Vista$", item):
  728                     version = item
  729             release = version
  730 
  731         _, ver, service_pack, extra = platform.win32_ver()
  732         version = " ".join([release, ver, service_pack, extra])
  733     else:
  734         version = system_version()
  735         release = platform.release()
  736 
  737     system = [
  738         ("system", platform.system()),
  739         ("dist", " ".join(linux_distribution(full_distribution_name=False))),
  740         ("release", release),
  741         ("machine", platform.machine()),
  742         ("version", version),
  743         ("locale", __salt_system_encoding__),
  744     ]
  745 
  746     for name, attr in system:
  747         yield name, attr
  748         continue
  749 
  750 
  751 def versions_information(include_salt_cloud=False):
  752     """
  753     Report the versions of dependent software.
  754     """
  755     salt_info = list(salt_information())
  756     lib_info = list(dependency_information(include_salt_cloud))
  757     sys_info = list(system_information())
  758 
  759     return {
  760         "Salt Version": dict(salt_info),
  761         "Dependency Versions": dict(lib_info),
  762         "System Versions": dict(sys_info),
  763     }
  764 
  765 
  766 def versions_report(include_salt_cloud=False):
  767     """
  768     Yield each version properly formatted for console output.
  769     """
  770     ver_info = versions_information(include_salt_cloud)
  771     not_installed = "Not Installed"
  772     ns_pad = len(not_installed)
  773     lib_pad = max(len(name) for name in ver_info["Dependency Versions"])
  774     sys_pad = max(len(name) for name in ver_info["System Versions"])
  775     padding = max(lib_pad, sys_pad, ns_pad) + 1
  776 
  777     fmt = "{0:>{pad}}: {1}"
  778     info = []
  779     for ver_type in ("Salt Version", "Dependency Versions", "System Versions"):
  780         info.append("{}:".format(ver_type))
  781         # List dependencies in alphabetical, case insensitive order
  782         for name in sorted(ver_info[ver_type], key=lambda x: x.lower()):
  783             ver = fmt.format(
  784                 name, ver_info[ver_type][name] or not_installed, pad=padding
  785             )
  786             info.append(ver)
  787         info.append(" ")
  788 
  789     yield from info
  790 
  791 
  792 if __name__ == "__main__":
  793     print(__version__)