mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
950 lines
34 KiB
Python
950 lines
34 KiB
Python
"""
|
|
Set up the version of Salt
|
|
"""
|
|
|
|
import argparse
|
|
import operator
|
|
import os
|
|
import platform
|
|
import re
|
|
import sys
|
|
from collections import namedtuple
|
|
from functools import total_ordering
|
|
|
|
MAX_SIZE = sys.maxsize
|
|
VERSION_LIMIT = MAX_SIZE - 200
|
|
|
|
# ----- ATTENTION --------------------------------------------------------------------------------------------------->
|
|
#
|
|
# ALL major version bumps, new release codenames, MUST be defined in the SaltStackVersion.NAMES dictionary, i.e.:
|
|
#
|
|
# class SaltStackVersion:
|
|
#
|
|
# NAMES = {
|
|
# 'Hydrogen': (2014, 1), # <- This is the tuple to bump versions
|
|
# ( ... )
|
|
# }
|
|
#
|
|
#
|
|
# ONLY UPDATE CODENAMES AFTER BRANCHING
|
|
#
|
|
# As an example, The Helium codename must only be properly defined with "(2014, 7)" after Hydrogen, "(2014, 1)", has
|
|
# been branched out into its own branch.
|
|
#
|
|
# ALL OTHER VERSION INFORMATION IS EXTRACTED FROM THE GIT TAGS
|
|
#
|
|
# <---- ATTENTION ----------------------------------------------------------------------------------------------------
|
|
|
|
|
|
@total_ordering
|
|
class SaltVersion(namedtuple("SaltVersion", "name, info, released")):
|
|
__slots__ = ()
|
|
|
|
def __new__(cls, name, info, released=False):
|
|
if isinstance(info, int):
|
|
info = (info,)
|
|
return super().__new__(cls, name, info, released)
|
|
|
|
def __eq__(self, other):
|
|
return self.info == other.info
|
|
|
|
def __gt__(self, other):
|
|
return self.info > other.info
|
|
|
|
|
|
class SaltVersionsInfo(type):
|
|
|
|
_sorted_versions = ()
|
|
_current_release = None
|
|
_previous_release = None
|
|
_next_release = None
|
|
|
|
# pylint: disable=bad-whitespace
|
|
# ----- Please refrain from fixing whitespace ---------------------------------->
|
|
# The idea is to keep this readable.
|
|
# -------------------------------------------------------------------------------
|
|
# fmt: off
|
|
HYDROGEN = SaltVersion("Hydrogen" , info=(2014, 1), released=True)
|
|
HELIUM = SaltVersion("Helium" , info=(2014, 7), released=True)
|
|
LITHIUM = SaltVersion("Lithium" , info=(2015, 5), released=True)
|
|
BERYLLIUM = SaltVersion("Beryllium" , info=(2015, 8), released=True)
|
|
BORON = SaltVersion("Boron" , info=(2016, 3), released=True)
|
|
CARBON = SaltVersion("Carbon" , info=(2016, 11), released=True)
|
|
NITROGEN = SaltVersion("Nitrogen" , info=(2017, 7), released=True)
|
|
OXYGEN = SaltVersion("Oxygen" , info=(2018, 3), released=True)
|
|
FLUORINE = SaltVersion("Fluorine" , info=(2019, 2), released=True)
|
|
NEON = SaltVersion("Neon" , info=3000, released=True)
|
|
SODIUM = SaltVersion("Sodium" , info=3001, released=True)
|
|
MAGNESIUM = SaltVersion("Magnesium" , info=3002, released=True)
|
|
ALUMINIUM = SaltVersion("Aluminium" , info=3003, released=True)
|
|
SILICON = SaltVersion("Silicon" , info=3004, released=True)
|
|
PHOSPHORUS = SaltVersion("Phosphorus" , info=3005, released=True)
|
|
SULFUR = SaltVersion("Sulfur" , info=3006, released=True)
|
|
CHLORINE = SaltVersion("Chlorine" , info=3007)
|
|
ARGON = SaltVersion("Argon" , info=3008)
|
|
POTASSIUM = SaltVersion("Potassium" , info=3009)
|
|
CALCIUM = SaltVersion("Calcium" , info=3010)
|
|
SCANDIUM = SaltVersion("Scandium" , info=3011)
|
|
TITANIUM = SaltVersion("Titanium" , info=3012)
|
|
VANADIUM = SaltVersion("Vanadium" , info=3013)
|
|
CHROMIUM = SaltVersion("Chromium" , info=3014)
|
|
MANGANESE = SaltVersion("Manganese" , info=3015)
|
|
IRON = SaltVersion("Iron" , info=3016)
|
|
COBALT = SaltVersion("Cobalt" , info=3017)
|
|
NICKEL = SaltVersion("Nickel" , info=3018)
|
|
COPPER = SaltVersion("Copper" , info=3019)
|
|
ZINC = SaltVersion("Zinc" , info=3020)
|
|
GALLIUM = SaltVersion("Gallium" , info=3021)
|
|
GERMANIUM = SaltVersion("Germanium" , info=3022)
|
|
ARSENIC = SaltVersion("Arsenic" , info=3023)
|
|
SELENIUM = SaltVersion("Selenium" , info=3024)
|
|
BROMINE = SaltVersion("Bromine" , info=3025)
|
|
KRYPTON = SaltVersion("Krypton" , info=3026)
|
|
RUBIDIUM = SaltVersion("Rubidium" , info=3027)
|
|
STRONTIUM = SaltVersion("Strontium" , info=3028)
|
|
YTTRIUM = SaltVersion("Yttrium" , info=3029)
|
|
ZIRCONIUM = SaltVersion("Zirconium" , info=3030)
|
|
NIOBIUM = SaltVersion("Niobium" , info=3031)
|
|
MOLYBDENUM = SaltVersion("Molybdenum" , info=3032)
|
|
TECHNETIUM = SaltVersion("Technetium" , info=3033)
|
|
RUTHENIUM = SaltVersion("Ruthenium" , info=3034)
|
|
RHODIUM = SaltVersion("Rhodium" , info=3035)
|
|
PALLADIUM = SaltVersion("Palladium" , info=3036)
|
|
SILVER = SaltVersion("Silver" , info=3037)
|
|
CADMIUM = SaltVersion("Cadmium" , info=3038)
|
|
INDIUM = SaltVersion("Indium" , info=3039)
|
|
TIN = SaltVersion("Tin" , info=3040)
|
|
ANTIMONY = SaltVersion("Antimony" , info=3041)
|
|
TELLURIUM = SaltVersion("Tellurium" , info=3042)
|
|
IODINE = SaltVersion("Iodine" , info=3043)
|
|
XENON = SaltVersion("Xenon" , info=3044)
|
|
CESIUM = SaltVersion("Cesium" , info=3045)
|
|
BARIUM = SaltVersion("Barium" , info=3046)
|
|
LANTHANUM = SaltVersion("Lanthanum" , info=3047)
|
|
CERIUM = SaltVersion("Cerium" , info=3048)
|
|
PRASEODYMIUM = SaltVersion("Praseodymium" , info=3049)
|
|
NEODYMIUM = SaltVersion("Neodymium" , info=3050)
|
|
PROMETHIUM = SaltVersion("Promethium" , info=3051)
|
|
SAMARIUM = SaltVersion("Samarium" , info=3052)
|
|
EUROPIUM = SaltVersion("Europium" , info=3053)
|
|
GADOLINIUM = SaltVersion("Gadolinium" , info=3054)
|
|
TERBIUM = SaltVersion("Terbium" , info=3055)
|
|
DYSPROSIUM = SaltVersion("Dysprosium" , info=3056)
|
|
HOLMIUM = SaltVersion("Holmium" , info=3057)
|
|
ERBIUM = SaltVersion("Erbium" , info=3058)
|
|
THULIUM = SaltVersion("Thulium" , info=3059)
|
|
YTTERBIUM = SaltVersion("Ytterbium" , info=3060)
|
|
LUTETIUM = SaltVersion("Lutetium" , info=3061)
|
|
HAFNIUM = SaltVersion("Hafnium" , info=3062)
|
|
TANTALUM = SaltVersion("Tantalum" , info=3063)
|
|
TUNGSTEN = SaltVersion("Tungsten" , info=3064)
|
|
RHENIUM = SaltVersion("Rhenium" , info=3065)
|
|
OSMIUM = SaltVersion("Osmium" , info=3066)
|
|
IRIDIUM = SaltVersion("Iridium" , info=3067)
|
|
PLATINUM = SaltVersion("Platinum" , info=3068)
|
|
GOLD = SaltVersion("Gold" , info=3069)
|
|
MERCURY = SaltVersion("Mercury" , info=3070)
|
|
THALLIUM = SaltVersion("Thallium" , info=3071)
|
|
LEAD = SaltVersion("Lead" , info=3072)
|
|
BISMUTH = SaltVersion("Bismuth" , info=3073)
|
|
POLONIUM = SaltVersion("Polonium" , info=3074)
|
|
ASTATINE = SaltVersion("Astatine" , info=3075)
|
|
RADON = SaltVersion("Radon" , info=3076)
|
|
FRANCIUM = SaltVersion("Francium" , info=3077)
|
|
RADIUM = SaltVersion("Radium" , info=3078)
|
|
ACTINIUM = SaltVersion("Actinium" , info=3079)
|
|
THORIUM = SaltVersion("Thorium" , info=3080)
|
|
PROTACTINIUM = SaltVersion("Protactinium" , info=3081)
|
|
URANIUM = SaltVersion("Uranium" , info=3082)
|
|
NEPTUNIUM = SaltVersion("Neptunium" , info=3083)
|
|
PLUTONIUM = SaltVersion("Plutonium" , info=3084)
|
|
AMERICIUM = SaltVersion("Americium" , info=3085)
|
|
CURIUM = SaltVersion("Curium" , info=3086)
|
|
BERKELIUM = SaltVersion("Berkelium" , info=3087)
|
|
CALIFORNIUM = SaltVersion("Californium" , info=3088)
|
|
EINSTEINIUM = SaltVersion("Einsteinium" , info=3089)
|
|
FERMIUM = SaltVersion("Fermium" , info=3090)
|
|
MENDELEVIUM = SaltVersion("Mendelevium" , info=3091)
|
|
NOBELIUM = SaltVersion("Nobelium" , info=3092)
|
|
LAWRENCIUM = SaltVersion("Lawrencium" , info=3093)
|
|
RUTHERFORDIUM = SaltVersion("Rutherfordium", info=3094)
|
|
DUBNIUM = SaltVersion("Dubnium" , info=3095)
|
|
SEABORGIUM = SaltVersion("Seaborgium" , info=3096)
|
|
BOHRIUM = SaltVersion("Bohrium" , info=3097)
|
|
HASSIUM = SaltVersion("Hassium" , info=3098)
|
|
MEITNERIUM = SaltVersion("Meitnerium" , info=3099)
|
|
DARMSTADTIUM = SaltVersion("Darmstadtium" , info=3100)
|
|
ROENTGENIUM = SaltVersion("Roentgenium" , info=3101)
|
|
COPERNICIUM = SaltVersion("Copernicium" , info=3102)
|
|
NIHONIUM = SaltVersion("Nihonium" , info=3103)
|
|
FLEROVIUM = SaltVersion("Flerovium" , info=3104)
|
|
MOSCOVIUM = SaltVersion("Moscovium" , info=3105)
|
|
LIVERMORIUM = SaltVersion("Livermorium" , info=3106)
|
|
TENNESSINE = SaltVersion("Tennessine" , info=3107)
|
|
OGANESSON = SaltVersion("Oganesson" , info=3108)
|
|
# <---- Please refrain from fixing whitespace -----------------------------------
|
|
# The idea is to keep this readable.
|
|
# -------------------------------------------------------------------------------
|
|
# pylint: enable=bad-whitespace
|
|
# fmt: on
|
|
|
|
@classmethod
|
|
def versions(cls):
|
|
if not cls._sorted_versions:
|
|
cls._sorted_versions = sorted(
|
|
(getattr(cls, name) for name in dir(cls) if name.isupper()),
|
|
key=operator.attrgetter("info"),
|
|
)
|
|
return cls._sorted_versions
|
|
|
|
@classmethod
|
|
def current_release(cls):
|
|
if cls._current_release is None:
|
|
for version in cls.versions():
|
|
if version.released is False:
|
|
cls._current_release = version
|
|
break
|
|
return cls._current_release
|
|
|
|
@classmethod
|
|
def next_release(cls):
|
|
if cls._next_release is None:
|
|
next_release_ahead = False
|
|
for version in cls.versions():
|
|
if next_release_ahead:
|
|
cls._next_release = version
|
|
break
|
|
if version == cls.current_release():
|
|
next_release_ahead = True
|
|
return cls._next_release
|
|
|
|
@classmethod
|
|
def previous_release(cls):
|
|
if cls._previous_release is None:
|
|
previous = None
|
|
for version in cls.versions():
|
|
if version == cls.current_release():
|
|
break
|
|
previous = version
|
|
cls._previous_release = previous
|
|
return cls._previous_release
|
|
|
|
|
|
class SaltStackVersion:
|
|
"""
|
|
Handle SaltStack versions class.
|
|
|
|
Knows how to parse ``git describe`` output, knows about release candidates
|
|
and also supports version comparison.
|
|
"""
|
|
|
|
__slots__ = (
|
|
"name",
|
|
"major",
|
|
"minor",
|
|
"bugfix",
|
|
"mbugfix",
|
|
"pre_type",
|
|
"pre_num",
|
|
"noc",
|
|
"sha",
|
|
)
|
|
|
|
git_sha_regex = r"(?P<sha>g?[a-f0-9]{7,40})"
|
|
|
|
git_describe_regex = re.compile(
|
|
r"(?:[^\d]+)?(?P<major>[\d]{1,4})"
|
|
r"(?:\.(?P<minor>[\d]{1,2}))?"
|
|
r"(?:\.(?P<bugfix>[\d]{0,2}))?"
|
|
r"(?:\.(?P<mbugfix>[\d]{0,2}))?"
|
|
r"(?:(?P<pre_type>rc|a|b|alpha|beta|nb)(?P<pre_num>[\d]+))?"
|
|
r"(?:(?:.*)(?:\+|-)(?P<noc>(?:0na|[\d]+|n/a))(?:-|\.)" + git_sha_regex + r")?"
|
|
)
|
|
git_sha_regex = r"^" + git_sha_regex
|
|
|
|
git_sha_regex = re.compile(git_sha_regex)
|
|
|
|
NAMES = {v.name: v.info for v in SaltVersionsInfo.versions()}
|
|
LNAMES = {k.lower(): v for (k, v) in iter(NAMES.items())}
|
|
VNAMES = {v: k for (k, v) in iter(NAMES.items())}
|
|
RMATCH = {v[:2]: k for (k, v) in iter(NAMES.items())}
|
|
|
|
def __init__(
|
|
self, # pylint: disable=C0103
|
|
major,
|
|
minor=None,
|
|
bugfix=None,
|
|
mbugfix=0,
|
|
pre_type=None,
|
|
pre_num=None,
|
|
noc=0,
|
|
sha=None,
|
|
):
|
|
if isinstance(major, str):
|
|
major = int(major)
|
|
|
|
if isinstance(minor, str):
|
|
if not minor:
|
|
# Empty string
|
|
minor = None
|
|
else:
|
|
minor = int(minor)
|
|
if self.can_have_dot_zero(major):
|
|
minor = minor if minor else 0
|
|
|
|
if bugfix is None and not self.new_version(major=major):
|
|
bugfix = 0
|
|
elif isinstance(bugfix, str):
|
|
if not bugfix:
|
|
bugfix = None
|
|
else:
|
|
bugfix = int(bugfix)
|
|
|
|
if mbugfix is None:
|
|
mbugfix = 0
|
|
elif isinstance(mbugfix, str):
|
|
mbugfix = int(mbugfix)
|
|
|
|
if pre_type is None:
|
|
pre_type = ""
|
|
if pre_num is None:
|
|
pre_num = 0
|
|
elif isinstance(pre_num, str):
|
|
pre_num = int(pre_num)
|
|
|
|
if noc is None:
|
|
noc = 0
|
|
elif isinstance(noc, str) and noc in ("0na", "n/a"):
|
|
noc = -1
|
|
elif isinstance(noc, str):
|
|
noc = int(noc)
|
|
|
|
self.major = major
|
|
self.minor = minor
|
|
self.bugfix = bugfix
|
|
self.mbugfix = mbugfix
|
|
self.pre_type = pre_type
|
|
self.pre_num = pre_num
|
|
if self.new_version(major):
|
|
vnames_key = (major,)
|
|
else:
|
|
vnames_key = (major, minor)
|
|
self.name = self.VNAMES.get(vnames_key)
|
|
self.noc = noc
|
|
self.sha = sha
|
|
|
|
def new_version(self, major):
|
|
"""
|
|
determine if using new versioning scheme
|
|
"""
|
|
return bool(int(major) >= 3000 and int(major) < VERSION_LIMIT)
|
|
|
|
def can_have_dot_zero(self, major):
|
|
"""
|
|
determine if using new versioning scheme
|
|
"""
|
|
return bool(int(major) >= 3006 and int(major) < VERSION_LIMIT)
|
|
|
|
@classmethod
|
|
def parse(cls, version_string):
|
|
if version_string.lower() in cls.LNAMES:
|
|
return cls.from_name(version_string)
|
|
vstr = (
|
|
version_string.decode()
|
|
if isinstance(version_string, bytes)
|
|
else version_string
|
|
)
|
|
match = cls.git_describe_regex.match(vstr)
|
|
if not match:
|
|
raise ValueError(f"Unable to parse version string: '{version_string}'")
|
|
return cls(*match.groups())
|
|
|
|
@classmethod
|
|
def from_name(cls, name):
|
|
if name.lower() not in cls.LNAMES:
|
|
raise ValueError(f"Named version '{name}' is not known")
|
|
return cls(*cls.LNAMES[name.lower()])
|
|
|
|
@classmethod
|
|
def from_last_named_version(cls):
|
|
import salt.utils.versions
|
|
|
|
salt.utils.versions.warn_until(
|
|
SaltVersionsInfo.SULFUR,
|
|
"The use of SaltStackVersion.from_last_named_version() is "
|
|
"deprecated and set to be removed in {version}. Please use "
|
|
"SaltStackVersion.current_release() instead.",
|
|
)
|
|
return cls.current_release()
|
|
|
|
@classmethod
|
|
def current_release(cls):
|
|
return cls(*SaltVersionsInfo.current_release().info)
|
|
|
|
@classmethod
|
|
def next_release(cls):
|
|
return cls(*SaltVersionsInfo.next_release().info)
|
|
|
|
@property
|
|
def sse(self):
|
|
# Higher than 0.17, lower than first date based
|
|
return 0 < self.major < 2014
|
|
|
|
def min_info(self):
|
|
info = [self.major]
|
|
if self.new_version(self.major):
|
|
if self.minor:
|
|
info.append(self.minor)
|
|
elif self.can_have_dot_zero(self.major):
|
|
info.append(self.minor)
|
|
else:
|
|
info.extend([self.minor, self.bugfix, self.mbugfix])
|
|
return info
|
|
|
|
@property
|
|
def info(self):
|
|
return tuple(self.min_info())
|
|
|
|
@property
|
|
def pre_info(self):
|
|
info = self.min_info()
|
|
info.extend([self.pre_type, self.pre_num])
|
|
return tuple(info)
|
|
|
|
@property
|
|
def noc_info(self):
|
|
info = self.min_info()
|
|
info.extend([self.pre_type, self.pre_num, self.noc])
|
|
return tuple(info)
|
|
|
|
@property
|
|
def full_info(self):
|
|
info = self.min_info()
|
|
info.extend([self.pre_type, self.pre_num, self.noc, self.sha])
|
|
return tuple(info)
|
|
|
|
@property
|
|
def full_info_all_versions(self):
|
|
"""
|
|
Return the full info regardless
|
|
of which versioning scheme we
|
|
are using.
|
|
"""
|
|
info = [
|
|
self.major,
|
|
self.minor,
|
|
self.bugfix,
|
|
self.mbugfix,
|
|
self.pre_type,
|
|
self.pre_num,
|
|
self.noc,
|
|
self.sha,
|
|
]
|
|
return tuple(info)
|
|
|
|
@property
|
|
def string(self):
|
|
if self.new_version(self.major):
|
|
version_string = f"{self.major}"
|
|
if self.minor:
|
|
version_string = f"{self.major}.{self.minor}"
|
|
if not self.minor and self.can_have_dot_zero(self.major):
|
|
version_string = f"{self.major}.{self.minor}"
|
|
else:
|
|
version_string = f"{self.major}.{self.minor}.{self.bugfix}"
|
|
if self.mbugfix:
|
|
version_string += f".{self.mbugfix}"
|
|
if self.pre_type:
|
|
version_string += f"{self.pre_type}{self.pre_num}"
|
|
if self.noc and self.sha:
|
|
noc = self.noc
|
|
if noc < 0:
|
|
noc = "0na"
|
|
version_string += f"+{noc}.{self.sha}"
|
|
return version_string
|
|
|
|
@property
|
|
def formatted_version(self):
|
|
if self.name and self.major > 10000:
|
|
version_string = self.name
|
|
if self.sse:
|
|
version_string += " Enterprise"
|
|
version_string += " (Unreleased)"
|
|
return version_string
|
|
version_string = self.string
|
|
if self.sse:
|
|
version_string += " Enterprise"
|
|
if self.new_version(self.major):
|
|
rmatch_key = (self.major,)
|
|
else:
|
|
rmatch_key = (self.major, self.minor)
|
|
if rmatch_key in self.RMATCH:
|
|
version_string += f" ({self.RMATCH[rmatch_key]})"
|
|
return version_string
|
|
|
|
@property
|
|
def pre_index(self):
|
|
if self.new_version(self.major):
|
|
pre_type = 2
|
|
if not isinstance(self.minor, int):
|
|
pre_type = 1
|
|
else:
|
|
pre_type = 4
|
|
return pre_type
|
|
|
|
def __str__(self):
|
|
return self.string
|
|
|
|
def __compare__(self, other, method):
|
|
if not isinstance(other, SaltStackVersion):
|
|
if isinstance(other, str):
|
|
other = SaltStackVersion.parse(other)
|
|
elif isinstance(other, (list, tuple)):
|
|
other = SaltStackVersion(*other)
|
|
else:
|
|
raise ValueError(
|
|
f"Cannot instantiate Version from type '{type(other)}'"
|
|
)
|
|
pre_type = self.pre_index
|
|
other_pre_type = other.pre_index
|
|
other_noc_info = list(other.noc_info)
|
|
noc_info = list(self.noc_info)
|
|
|
|
if self.new_version(self.major):
|
|
if self.minor and not other.minor:
|
|
# We have minor information, the other side does not
|
|
if self.minor > 0:
|
|
other_noc_info[1] = 0
|
|
|
|
if not self.minor and other.minor:
|
|
# The other side has minor information, we don't
|
|
if other.minor > 0:
|
|
noc_info[1] = 0
|
|
|
|
if self.pre_type and not other.pre_type:
|
|
# We have pre-release information, the other side doesn't
|
|
other_noc_info[other_pre_type] = "zzzzz"
|
|
|
|
if not self.pre_type and other.pre_type:
|
|
# The other side has pre-release information, we don't
|
|
noc_info[pre_type] = "zzzzz"
|
|
|
|
return method(tuple(noc_info), tuple(other_noc_info))
|
|
|
|
def __lt__(self, other):
|
|
return self.__compare__(other, lambda _self, _other: _self < _other)
|
|
|
|
def __le__(self, other):
|
|
return self.__compare__(other, lambda _self, _other: _self <= _other)
|
|
|
|
def __eq__(self, other):
|
|
return self.__compare__(other, lambda _self, _other: _self == _other)
|
|
|
|
def __ne__(self, other):
|
|
return self.__compare__(other, lambda _self, _other: _self != _other)
|
|
|
|
def __ge__(self, other):
|
|
return self.__compare__(other, lambda _self, _other: _self >= _other)
|
|
|
|
def __gt__(self, other):
|
|
return self.__compare__(other, lambda _self, _other: _self > _other)
|
|
|
|
def __repr__(self):
|
|
parts = []
|
|
if self.name:
|
|
parts.append(f"name='{self.name}'")
|
|
parts.extend([f"major={self.major}", f"minor={self.minor}"])
|
|
|
|
if self.new_version(self.major):
|
|
if not self.can_have_dot_zero(self.major) and not self.minor:
|
|
parts.remove("".join([x for x in parts if re.search("^minor*", x)]))
|
|
else:
|
|
parts.extend([f"bugfix={self.bugfix}"])
|
|
|
|
if self.mbugfix:
|
|
parts.append(f"minor-bugfix={self.mbugfix}")
|
|
if self.pre_type:
|
|
parts.append(f"{self.pre_type}={self.pre_num}")
|
|
noc = self.noc
|
|
if noc == -1:
|
|
noc = "0na"
|
|
if noc and self.sha:
|
|
parts.extend([f"noc={noc}", f"sha={self.sha}"])
|
|
return "<{} {}>".format(self.__class__.__name__, " ".join(parts))
|
|
|
|
|
|
# ----- Hardcoded Salt Codename Version Information ----------------------------------------------------------------->
|
|
#
|
|
# There's no need to do anything here. The last released codename will be picked up
|
|
# --------------------------------------------------------------------------------------------------------------------
|
|
__saltstack_version__ = SaltStackVersion.current_release()
|
|
# <---- Hardcoded Salt Version Information ---------------------------------------------------------------------------
|
|
|
|
|
|
# ----- Dynamic/Runtime Salt Version Information -------------------------------------------------------------------->
|
|
def __discover_version(saltstack_version):
|
|
# This might be a 'python setup.py develop' installation type. Let's
|
|
# discover the version information at runtime.
|
|
import subprocess
|
|
|
|
if "SETUP_DIRNAME" in globals():
|
|
# This is from the exec() call in Salt's setup.py
|
|
cwd = SETUP_DIRNAME # pylint: disable=E0602
|
|
if not os.path.exists(os.path.join(cwd, ".git")):
|
|
# This is not a Salt git checkout!!! Don't even try to parse...
|
|
return saltstack_version
|
|
else:
|
|
cwd = os.path.abspath(os.path.dirname(__file__))
|
|
if not os.path.exists(os.path.join(os.path.dirname(cwd), ".git")):
|
|
# This is not a Salt git checkout!!! Don't even try to parse...
|
|
return saltstack_version
|
|
|
|
try:
|
|
kwargs = dict(stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
|
|
|
|
if not sys.platform.startswith("win"):
|
|
# Let's not import `salt.utils` for the above check
|
|
kwargs["close_fds"] = True
|
|
|
|
process = subprocess.Popen(
|
|
[
|
|
"git",
|
|
"describe",
|
|
"--tags",
|
|
"--long",
|
|
"--match",
|
|
"v[0-9]*",
|
|
"--always",
|
|
],
|
|
**kwargs,
|
|
)
|
|
|
|
out, err = process.communicate()
|
|
|
|
out = out.decode().strip()
|
|
err = err.decode().strip()
|
|
|
|
if not out or err:
|
|
return saltstack_version
|
|
|
|
if SaltStackVersion.git_sha_regex.match(out):
|
|
# We only define the parsed SHA and set NOC as ??? (unknown)
|
|
saltstack_version.sha = out.strip()
|
|
saltstack_version.noc = -1
|
|
return saltstack_version
|
|
|
|
return SaltStackVersion.parse(out)
|
|
|
|
except OSError as os_err:
|
|
if os_err.errno != 2:
|
|
# If the errno is not 2(The system cannot find the file
|
|
# specified), raise the exception so it can be catch by the
|
|
# developers
|
|
raise
|
|
return saltstack_version
|
|
|
|
|
|
def __get_version(saltstack_version):
|
|
"""
|
|
If we can get a version provided at installation time or from Git, use
|
|
that instead, otherwise we carry on.
|
|
"""
|
|
_hardcoded_version_file = os.path.join(
|
|
os.path.dirname(os.path.abspath(__file__)), "_version.txt"
|
|
)
|
|
if not os.path.exists(_hardcoded_version_file):
|
|
return __discover_version(saltstack_version)
|
|
with open( # pylint: disable=resource-leakage
|
|
_hardcoded_version_file, encoding="utf-8"
|
|
) as rfh:
|
|
return SaltStackVersion.parse(rfh.read().strip())
|
|
|
|
|
|
# Get additional version information if available
|
|
__saltstack_version__ = __get_version(__saltstack_version__)
|
|
if __saltstack_version__.name:
|
|
# Set SaltVersionsInfo._current_release to avoid lookups when finding previous and next releases
|
|
SaltVersionsInfo._current_release = getattr(
|
|
SaltVersionsInfo, __saltstack_version__.name.upper()
|
|
)
|
|
|
|
# This function has executed once, we're done with it. Delete it!
|
|
del __get_version
|
|
# <---- Dynamic/Runtime Salt Version Information ---------------------------------------------------------------------
|
|
|
|
|
|
# ----- Common version related attributes - NO NEED TO CHANGE ------------------------------------------------------->
|
|
__version_info__ = __saltstack_version__.info
|
|
__version__ = __saltstack_version__.string
|
|
# <---- Common version related attributes - NO NEED TO CHANGE --------------------------------------------------------
|
|
|
|
|
|
def salt_information():
|
|
"""
|
|
Report version of salt.
|
|
"""
|
|
yield "Salt", __version__
|
|
|
|
|
|
def dependency_information(include_salt_cloud=False):
|
|
"""
|
|
Report versions of library dependencies.
|
|
"""
|
|
libs = [
|
|
("Jinja2", "jinja2", "__version__"),
|
|
("M2Crypto", "M2Crypto", "version"),
|
|
("msgpack", "msgpack", "version"),
|
|
("msgpack-pure", "msgpack_pure", "version"),
|
|
("pycrypto", "Crypto", "__version__"),
|
|
("pycryptodome", "Cryptodome", "version_info"),
|
|
("cryptography", "cryptography", "__version__"),
|
|
("PyYAML", "yaml", "__version__"),
|
|
("PyZMQ", "zmq", "__version__"),
|
|
("ZMQ", "zmq", "zmq_version"),
|
|
("Mako", "mako", "__version__"),
|
|
("Tornado", "tornado", "version"),
|
|
("timelib", "timelib", "version"),
|
|
("dateutil", "dateutil", "__version__"),
|
|
("pygit2", "pygit2", "__version__"),
|
|
("libgit2", "pygit2", "LIBGIT2_VERSION"),
|
|
("smmap", "smmap", "__version__"),
|
|
("cffi", "cffi", "__version__"),
|
|
("pycparser", "pycparser", "__version__"),
|
|
("gitdb", "gitdb", "__version__"),
|
|
("gitpython", "git", "__version__"),
|
|
("python-gnupg", "gnupg", "__version__"),
|
|
("mysql-python", "MySQLdb", "__version__"),
|
|
("cherrypy", "cherrypy", "__version__"),
|
|
("docker-py", "docker", "__version__"),
|
|
("packaging", "packaging", "__version__"),
|
|
("looseversion", "looseversion", None),
|
|
("relenv", "relenv", "__version__"),
|
|
]
|
|
|
|
if include_salt_cloud:
|
|
libs.append(
|
|
("Apache Libcloud", "libcloud", "__version__"),
|
|
)
|
|
|
|
def _sort_by_lowercased_name(entry):
|
|
return entry[0].lower()
|
|
|
|
for name, imp, attr in sorted(libs, key=_sort_by_lowercased_name):
|
|
if imp is None:
|
|
yield name, attr
|
|
continue
|
|
try:
|
|
if attr is None:
|
|
# Late import to reduce the needed available modules and libs
|
|
# installed when running `python salt/version.py`
|
|
from salt._compat import importlib_metadata
|
|
|
|
version = importlib_metadata.version(imp)
|
|
yield name, version
|
|
continue
|
|
imp = __import__(imp)
|
|
version = getattr(imp, attr)
|
|
if callable(version):
|
|
version = version()
|
|
if isinstance(version, (tuple, list)):
|
|
version = ".".join(map(str, version))
|
|
yield name, version
|
|
except Exception: # pylint: disable=broad-except
|
|
yield name, None
|
|
|
|
|
|
def system_information():
|
|
"""
|
|
Report system versions.
|
|
"""
|
|
# Late import so that when getting called from setup.py does not break
|
|
from salt.utils.platform import linux_distribution
|
|
|
|
def system_version():
|
|
"""
|
|
Return host system version.
|
|
"""
|
|
|
|
lin_ver = linux_distribution()
|
|
mac_ver = platform.mac_ver()
|
|
win_ver = platform.win32_ver()
|
|
|
|
# linux_distribution() will return a
|
|
# distribution on OS X and Windows.
|
|
# Check mac_ver and win_ver first,
|
|
# then lin_ver.
|
|
if mac_ver[0]:
|
|
if isinstance(mac_ver[1], (tuple, list)) and "".join(mac_ver[1]):
|
|
return " ".join([mac_ver[0], ".".join(mac_ver[1]), mac_ver[2]])
|
|
else:
|
|
return " ".join([mac_ver[0], mac_ver[2]])
|
|
elif win_ver[0]:
|
|
return " ".join(win_ver)
|
|
elif lin_ver[0]:
|
|
return " ".join(lin_ver)
|
|
else:
|
|
return ""
|
|
|
|
if platform.win32_ver()[0]:
|
|
# Get the version and release info based on the Windows Operating
|
|
# System Product Name. As long as Microsoft maintains a similar format
|
|
# this should be future proof
|
|
import win32api # pylint: disable=3rd-party-module-not-gated
|
|
import win32con # pylint: disable=3rd-party-module-not-gated
|
|
|
|
# Get the product name from the registry
|
|
hkey = win32con.HKEY_LOCAL_MACHINE
|
|
key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"
|
|
value_name = "ProductName"
|
|
reg_handle = win32api.RegOpenKey(hkey, key)
|
|
|
|
# Returns a tuple of (product_name, value_type)
|
|
product_name, _ = win32api.RegQueryValueEx(reg_handle, value_name)
|
|
|
|
version = "Unknown"
|
|
release = ""
|
|
if "Server" in product_name:
|
|
for item in product_name.split(" "):
|
|
# If it's all digits, then it's version
|
|
if re.match(r"\d+", item):
|
|
version = item
|
|
# If it starts with R and then numbers, it's the release
|
|
# ie: R2
|
|
if re.match(r"^R\d+$", item):
|
|
release = item
|
|
release = f"{version}Server{release}"
|
|
else:
|
|
for item in product_name.split(" "):
|
|
# If it's a number, decimal number, Thin or Vista, then it's the
|
|
# version
|
|
if re.match(r"^(\d+(\.\d+)?)|Thin|Vista$", item):
|
|
version = item
|
|
release = version
|
|
|
|
_, ver, service_pack, extra = platform.win32_ver()
|
|
version = " ".join([release, ver, service_pack, extra])
|
|
else:
|
|
version = system_version()
|
|
release = platform.release()
|
|
|
|
system = [
|
|
("system", platform.system()),
|
|
("dist", " ".join(linux_distribution(full_distribution_name=False))),
|
|
("release", release),
|
|
("machine", platform.machine()),
|
|
("version", version),
|
|
("locale", __salt_system_encoding__),
|
|
]
|
|
|
|
for name, attr in system:
|
|
yield name, attr
|
|
continue
|
|
|
|
|
|
def extensions_information():
|
|
"""
|
|
Gather infomation about any installed salt extensions
|
|
"""
|
|
# Late import
|
|
import salt.utils.entrypoints
|
|
|
|
extensions = {}
|
|
for entry_point in salt.utils.entrypoints.iter_entry_points("salt.loader"):
|
|
dist_nv = salt.utils.entrypoints.name_and_version_from_entry_point(entry_point)
|
|
if not dist_nv:
|
|
continue
|
|
if dist_nv.name in extensions:
|
|
continue
|
|
extensions[dist_nv.name] = dist_nv.version
|
|
return extensions
|
|
|
|
|
|
def versions_information(include_salt_cloud=False, include_extensions=True):
|
|
"""
|
|
Report the versions of dependent software.
|
|
"""
|
|
py_info = [
|
|
("Python", sys.version.rsplit("\n", maxsplit=1)[0].strip()),
|
|
]
|
|
salt_info = list(salt_information())
|
|
lib_info = list(dependency_information(include_salt_cloud))
|
|
sys_info = list(system_information())
|
|
|
|
info = {
|
|
"Salt Version": dict(salt_info),
|
|
"Python Version": dict(py_info),
|
|
"Dependency Versions": dict(lib_info),
|
|
"System Versions": dict(sys_info),
|
|
}
|
|
if include_extensions:
|
|
extensions_info = extensions_information()
|
|
if extensions_info:
|
|
info["Salt Extensions"] = extensions_info
|
|
return info
|
|
|
|
|
|
def versions_report(include_salt_cloud=False, include_extensions=True):
|
|
"""
|
|
Yield each version properly formatted for console output.
|
|
"""
|
|
ver_info = versions_information(
|
|
include_salt_cloud=include_salt_cloud, include_extensions=include_extensions
|
|
)
|
|
not_installed = "Not Installed"
|
|
ns_pad = len(not_installed)
|
|
lib_pad = max(len(name) for name in ver_info["Dependency Versions"])
|
|
sys_pad = max(len(name) for name in ver_info["System Versions"])
|
|
if include_extensions and "Salt Extensions" in ver_info:
|
|
ext_pad = max(len(name) for name in ver_info["Salt Extensions"])
|
|
else:
|
|
ext_pad = 1
|
|
padding = max(lib_pad, sys_pad, ns_pad, ext_pad) + 1
|
|
|
|
fmt = "{0:>{pad}}: {1}"
|
|
info = []
|
|
for ver_type in (
|
|
"Salt Version",
|
|
"Python Version",
|
|
"Dependency Versions",
|
|
"Salt Extensions",
|
|
"System Versions",
|
|
):
|
|
if ver_type == "Salt Extensions" and ver_type not in ver_info:
|
|
# No salt Extensions to report
|
|
continue
|
|
info.append(f"{ver_type}:")
|
|
# List dependencies in alphabetical, case insensitive order
|
|
for name in sorted(ver_info[ver_type], key=lambda x: x.lower()):
|
|
ver = fmt.format(
|
|
name, ver_info[ver_type][name] or not_installed, pad=padding
|
|
)
|
|
info.append(ver)
|
|
info.append(" ")
|
|
|
|
yield from info
|
|
|
|
|
|
def _parser():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"--next-release", help="Return the next release", action="store_true"
|
|
)
|
|
parser.add_argument("--parse", help="Parse the passed string as a salt version")
|
|
# When pip installing we pass in other args to this script.
|
|
# This allows us to catch those args but not use them
|
|
parser.add_argument("unknown", nargs=argparse.REMAINDER)
|
|
return parser.parse_args()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
args = _parser()
|
|
if args.next_release:
|
|
print(__saltstack_version__.next_release())
|
|
elif args.parse:
|
|
try:
|
|
print(SaltStackVersion.parse(args.parse))
|
|
except Exception as exc: # pylint: disable=broad-except
|
|
print(f"Failed to parse '{args.parse}' as a salt version: {exc}")
|
|
sys.exit(1)
|
|
else:
|
|
print(__version__)
|