1
0
Fork 0

Merging upstream version 0.12.0.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-10 06:39:52 +01:00
parent f45bc3d463
commit 8d2f70e3c7
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
77 changed files with 23610 additions and 2331 deletions

View file

@ -0,0 +1,140 @@
# coding: utf-8 -*-
"""This module defines data models and mappings for image types of CloudVision and EOS on Arista.com.
Classes
-------
ImageInfo:
A Pydantic model representing image information for a specific image type.
DataMapping:
A Pydantic model representing data mapping for image types of CloudVision and EOS on Arista.com.
Methods
-------
DataMapping.filename(software: AristaMapping, image_type: str, version: str) -> str:
Generates a filename based on the provided software, image type, and version.
Constants
-------
- RTYPE_FEATURE (ReleaseType): Represents a feature release type.
- RTYPE_MAINTENANCE (ReleaseType): Represents a maintenance release type.
- RTYPES (List[ReleaseType]): A list containing the feature and maintenance release types.
Variables
-------
- software_mapping (DataMapping): An instance of DataMapping containing the mappings for CloudVision and EOS image types.
"""
from typing import Dict, List
from pydantic import BaseModel
from eos_downloader.models.types import AristaMapping, ReleaseType
RTYPE_FEATURE: ReleaseType = "F"
RTYPE_MAINTENANCE: ReleaseType = "M"
RTYPES: List[ReleaseType] = [RTYPE_FEATURE, RTYPE_MAINTENANCE]
class ImageInfo(BaseModel):
"""Image information for a specific image type.
Attributes
----------
extension : str
The file extension for the image type.
prepend : str
The prefix to prepend to the filename.
"""
extension: str
prepend: str
class DataMapping(BaseModel):
"""Data mapping for image types of CloudVision and EOS on Arista.com.
Attributes
----------
CloudVision : Dict[str, ImageInfo]
Mapping of image types to their information for CloudVision.
EOS : Dict[str, ImageInfo]
Mapping of image types to their information for EOS.
Methods
-------
filename(software: AristaMapping, image_type: str, version: str) -> str
Generates a filename based on the provided software, image type, and version.
"""
CloudVision: Dict[str, ImageInfo]
EOS: Dict[str, ImageInfo]
def filename(self, software: AristaMapping, image_type: str, version: str) -> str:
"""Generates a filename based on the provided software, image type, and version.
Parameters
----------
software : AristaMapping
The name of the software for which the filename is being generated.
image_type : str
The type of image for which the filename is being generated.
version : str
The version of the software or image.
Returns
-------
str
The generated filename.
Raises
------
ValueError
If the software does not have a corresponding mapping.
ValueError
If no configuration is found for the given image type and no default configuration is available.
"""
if hasattr(self, software):
soft_mapping = getattr(self, software)
image_config = soft_mapping.get(image_type, None)
if image_config is None:
image_config = getattr(soft_mapping, "default", None)
if image_config is None:
raise ValueError(
f"No default configuration found for image type {image_type}"
)
if image_config is not None:
return f"{image_config.prepend}-{version}{image_config.extension}"
raise ValueError(f"No configuration found for image type {image_type}")
raise ValueError(f"Incorrect value for software {software}")
# Data mapping for image types of CloudVision and EOS on Arista.com.
software_mapping = DataMapping(
CloudVision={
"ova": {"extension": ".ova", "prepend": "cvp"},
"rpm": {"extension": "", "prepend": "cvp-rpm-installer"},
"kvm": {"extension": "-kvm.tgz", "prepend": "cvp"},
"upgrade": {"extension": ".tgz", "prepend": "cvp-upgrade"},
},
EOS={
"64": {"extension": ".swi", "prepend": "EOS64"},
"INT": {"extension": "-INT.swi", "prepend": "EOS"},
"2GB-INT": {"extension": "-INT.swi", "prepend": "EOS-2GB"},
"cEOS": {"extension": ".tar.xz", "prepend": "cEOS-lab"},
"cEOS64": {"extension": ".tar.xz", "prepend": "cEOS64-lab"},
"vEOS": {"extension": ".vmdk", "prepend": "vEOS"},
"vEOS-lab": {"extension": ".vmdk", "prepend": "vEOS-lab"},
"EOS-2GB": {"extension": ".swi", "prepend": "EOS-2GB"},
"RN": {"extension": "-", "prepend": "RN"},
"SOURCE": {"extension": "-source.tar", "prepend": "EOS"},
"default": {"extension": ".swi", "prepend": "EOS"},
},
)
# List of supported format for EOS software packages
eos_package_format = software_mapping.EOS.keys()
# List of supported format for CloudVision software packages
cvp_package_format = software_mapping.EOS.keys()

View file

@ -0,0 +1,52 @@
#!/usr/bin/python
# coding: utf-8 -*-
"""
This module defines type aliases and literals used in the eos_downloader project.
Attributes
----------
AristaPackage : Literal
Literal type for Arista package types. Can be either "eos" or "cvp".
AristaMapping : Literal
Literal type for Arista mapping types. Can be either "CloudVision" or "EOS".
AristaVersions : Union
Union type for supported SemVer object types. Can be either EosVersion or CvpVersion.
ReleaseType : Literal
Literal type for release types. Can be either "M" (maintenance) or "F" (feature).
Examples
--------
# Example usage of AristaPackage
def get_package_type(package: AristaPackage):
if package == "eos":
return "Arista EOS package"
elif package == "cvp":
return "CloudVision Portal package"
# Example usage of AristaVersions
def print_version(version: AristaVersions):
print(f"Version: {version}")
# Example usage of ReleaseType
def is_feature_release(release: ReleaseType) -> bool:
return release == "F"
"""
from typing import Literal, Union
import eos_downloader.logics
# import eos_downloader.logics.arista_server
import eos_downloader.models.version
# Define the product type using Literal
AristaPackage = Literal["eos", "cvp"]
AristaMapping = Literal["CloudVision", "EOS"]
# Define list of support SemVer object type
AristaVersions = Union[
eos_downloader.models.version.EosVersion, eos_downloader.models.version.CvpVersion
]
# List of supported release codes
ReleaseType = Literal["M", "F"]

View file

@ -1,13 +1,67 @@
#!/usr/bin/python
# coding: utf-8 -*-
"""The module implements version management following semantic versioning principles with custom adaptations for
Arista EOS and CloudVision Portal (CVP) software versioning schemes.
"""Module for EOS version management"""
Classes
-------
SemVer:
Base class implementing semantic versioning with comparison and matching capabilities.
EosVersion:
Specialized version handling for Arista EOS software releases.
CvpVersion:
Specialized version handling for CloudVision Portal releases.
Attributes
----------
major : int
Major version number.
minor : int
Minor version number.
patch : int
Patch version number.
rtype : Optional[str]
Release type (e.g., 'M' for maintenance, 'F' for feature).
other : Any
Additional version information.
regex_version : ClassVar[Pattern[str]]
Regular expression to extract version information.
regex_branch : ClassVar[Pattern[str]]
Regular expression to extract branch information.
description : str
A basic description of this class.
Examples
--------
# Basic SemVer usage:
>>> version = SemVer(major=4, minor=23, patch=3)
'4.23.3'
# EOS version handling:
>>> eos = EosVersion.from_str('4.23.3M')
>>> eos.branch
'4.23'
# CVP version handling:
>>> cvp = CvpVersion.from_str('2024.1.0')
>>> str(cvp)
The module enforces version format validation through regular expressions and provides
comprehensive comparison operations (==, !=, <, <=, >, >=) between versions.
Note:
--------
- EOS versions follow the format: <major>.<minor>.<patch>[M|F]
- CVP versions follow the format: <year>.<minor>.<patch>
"""
from __future__ import annotations
import re
import typing
from typing import Any, Optional
import logging
from typing import Any, Optional, Pattern, ClassVar
from loguru import logger
from pydantic import BaseModel
@ -16,163 +70,188 @@ from eos_downloader.tools import exc_to_str
# logger = logging.getLogger(__name__)
BASE_VERSION_STR = "4.0.0F"
BASE_BRANCH_STR = "4.0"
RTYPE_FEATURE = "F"
RTYPE_MAINTENANCE = "M"
RTYPES = [RTYPE_FEATURE, RTYPE_MAINTENANCE]
class SemVer(BaseModel):
"""A class to represent a Semantic Version (SemVer) based on pydanntic.
# Regular Expression to capture multiple EOS version format
# 4.24
# 4.23.0
# 4.21.1M
# 4.28.10.F
# 4.28.6.1M
REGEX_EOS_VERSION = re.compile(
r"^.*(?P<major>4)\.(?P<minor>\d{1,2})\.(?P<patch>\d{1,2})(?P<other>\.\d*)*(?P<rtype>[M,F])*$"
)
REGEX_EOS_BRANCH = re.compile(
r"^.*(?P<major>4)\.(?P<minor>\d{1,2})(\.?P<patch>\d)*(\.\d)*(?P<rtype>[M,F])*$"
)
This class provides methods to parse, compare, and manipulate semantic versions.
It supports standard semantic versioning with optional release type and additional version information.
Examples
--------
>>> version = SemVer(major=4, minor=23, patch=3, rtype="M")
>>> str(version)
'4.23.3M'
class EosVersion(BaseModel):
"""
EosVersion object to play with version management in code
>>> version2 = SemVer.from_str('4.24.1F')
>>> version2.branch
'4.24'
Since EOS is not using strictly semver approach, this class mimic some functions from semver lib for Arista EOS versions
It is based on Pydantic and provides helpers for comparison:
>>> version < version2
True
Examples:
>>> eos_version_str = '4.23.2F'
>>> eos_version = EosVersion.from_str(eos_version_str)
>>> print(f'str representation is: {str(eos_version)}')
str representation is: 4.23.2F
>>> version.match("<=4.24.0")
True
>>> other_version = EosVersion.from_str(other_version_str)
>>> print(f'eos_version < other_version: {eos_version < other_version}')
eos_version < other_version: True
>>> version.is_in_branch("4.23")
True
>>> print(f'Is eos_version match("<=4.23.3M"): {eos_version.match("<=4.23.3M")}')
Is eos_version match("<=4.23.3M"): True
>>> print(f'Is eos_version in branch 4.23: {eos_version.is_in_branch("4.23.0")}')
Is eos_version in branch 4.23: True
Args:
BaseModel (Pydantic): Pydantic Base Model
Attributes
----------
major : int
Major version number.
minor : int
Minor version number.
patch : int
Patch version number.
rtype : Optional[str]
Release type (e.g., 'M' for maintenance, 'F' for feature).
other : Any
Additional version information.
regex_version : ClassVar[Pattern[str]]
Regular expression to extract version information.
regex_branch : ClassVar[Pattern[str]]
Regular expression to extract branch information.
description : str
A basic description of this class.
"""
major: int = 4
major: int = 0
minor: int = 0
patch: int = 0
rtype: Optional[str] = "F"
rtype: Optional[str] = None
other: Any = None
# Regular Expression to extract version information.
regex_version: ClassVar[Pattern[str]] = re.compile(
r"^.*(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d{1,2})(?P<other>\.\d*)*(?P<rtype>[M,F])*$"
)
regex_branch: ClassVar[Pattern[str]] = re.compile(
r"^.*(?P<major>\d+)\.(?P<minor>\d+)(\.?P<patch>\d)*(\.\d)*(?P<rtype>[M,F])*$"
)
# A Basic description of this class
description: str = "A Generic SemVer implementation"
@classmethod
def from_str(cls, eos_version: str) -> EosVersion:
def from_str(cls, semver: str) -> SemVer:
"""Parse a string into a SemVer object.
This method parses a semantic version string or branch name into a SemVer object.
It supports both standard semver format (x.y.z) and branch format.
Parameters
----------
semver : str
The version string to parse. Can be either a semantic version
string (e.g., "1.2.3") or a branch format.
Returns
-------
SemVer
A SemVer object representing the parsed version.
Returns an empty SemVer object if parsing fails.
Examples
--------
>>> SemVer.from_str("1.2.3")
SemVer(major=1, minor=2, patch=3)
>>> SemVer.from_str("branch-1.2.3")
SemVer(major=1, minor=2, patch=3)
"""
Class constructor from a string representing EOS version
Use regular expresion to extract fields from string.
It supports following formats:
- 4.24
- 4.23.0
- 4.21.1M
- 4.28.10.F
- 4.28.6.1M
logging.debug(f"Creating SemVer object from string: {semver}")
Args:
eos_version (str): EOS version in str format
Returns:
EosVersion object
"""
logger.debug(f"receiving version: {eos_version}")
if REGEX_EOS_VERSION.match(eos_version):
matches = REGEX_EOS_VERSION.match(eos_version)
if cls.regex_version.match(semver):
matches = cls.regex_version.match(semver)
# assert matches is not None
assert matches is not None
logging.debug(f"Matches version: {matches}")
return cls(**matches.groupdict())
if REGEX_EOS_BRANCH.match(eos_version):
matches = REGEX_EOS_BRANCH.match(eos_version)
if cls.regex_branch.match(semver):
matches = cls.regex_branch.match(semver)
# assert matches is not None
assert matches is not None
logging.debug(f"Matches branch: {matches}")
return cls(**matches.groupdict())
logger.error(f"Error occured with {eos_version}")
return EosVersion()
logging.error(f"Error occured with {semver}")
return SemVer()
@property
def branch(self) -> str:
"""
Extract branch of version
Extract branch of version.
Returns:
str: branch from version
Returns
-------
str
Branch from version.
"""
return f"{self.major}.{self.minor}"
def __str__(self) -> str:
"""
Standard str representation
Standard str representation.
Return string for EOS version like 4.23.3M
Return string for EOS version like 4.23.3M.
Returns:
str: A standard EOS version string representing <MAJOR>.<MINOR>.<PATCH><RTYPE>
Returns
-------
str
A standard EOS version string representing <MAJOR>.<MINOR>.<PATCH><RTYPE>.
"""
if self.other is None:
return f"{self.major}.{self.minor}.{self.patch}{self.rtype}"
return f"{self.major}.{self.minor}.{self.patch}{self.other}{self.rtype}"
return f"{self.major}.{self.minor}.{self.patch}{self.other if self.other is not None else ''}{self.rtype if self.rtype is not None else ''}"
def _compare(self, other: EosVersion) -> float:
def _compare(self, other: SemVer) -> float:
"""
An internal comparison function to compare 2 EosVersion objects
An internal comparison function to compare 2 EosVersion objects.
Do a deep comparison from Major to Release Type
The return value is
Do a deep comparison from Major to Release Type.
The return value is:
- negative if ver1 < ver2,
- zero if ver1 == ver2
- strictly positive if ver1 > ver2
- zero if ver1 == ver2,
- strictly positive if ver1 > ver2.
Args:
other (EosVersion): An EosVersion to compare with this object
Parameters
----------
other : SemVer
An EosVersion to compare with this object.
Raises:
ValueError: Raise ValueError if input is incorrect type
Raises
------
ValueError
Raise ValueError if input is incorrect type.
Returns:
float: -1 if ver1 < ver2, 0 if ver1 == ver2, 1 if ver1 > ver2
Returns
-------
float
-1 if ver1 < ver2, 0 if ver1 == ver2, 1 if ver1 > ver2.
"""
if not isinstance(other, EosVersion):
if not isinstance(other, SemVer):
raise ValueError(
f"could not compare {other} as it is not an EosVersion object"
)
comparison_flag: float = 0
logger.warning(
f"current version {self.__str__()} - other {str(other)}" # pylint: disable = unnecessary-dunder-call
)
for key, _ in self.dict().items():
if (
comparison_flag == 0
and self.dict()[key] is None
or other.dict()[key] is None
and self.model_dump()[key] is None
or other.model_dump()[key] is None
):
logger.debug(f"{key}: local None - remote None")
logger.debug(f"{key}: local {self.dict()} - remote {other.dict()}")
return comparison_flag
logger.debug(
f"{key}: local {self.dict()[key]} - remote {other.dict()[key]}"
)
if comparison_flag == 0 and self.dict()[key] < other.dict()[key]:
if (
comparison_flag == 0
and self.model_dump()[key] < other.model_dump()[key]
):
comparison_flag = -1
if comparison_flag == 0 and self.dict()[key] > other.dict()[key]:
if (
comparison_flag == 0
and self.model_dump()[key] > other.model_dump()[key]
):
comparison_flag = 1
if comparison_flag != 0:
logger.info(f"comparison result is {comparison_flag}")
logging.debug(
f"Comparison flag {self.model_dump()[key]} with {other.model_dump()[key]}: {comparison_flag}"
)
return comparison_flag
logger.info(f"comparison result is {comparison_flag}")
return comparison_flag
@typing.no_type_check
@ -214,26 +293,33 @@ class EosVersion(BaseModel):
"""
Compare self to match a match expression.
Example:
Parameters
----------
match_expr : str
Optional operator and version; valid operators are:
``<`` smaller than
``>`` greater than
``>=`` greater or equal than
``<=`` smaller or equal than
``==`` equal
``!=`` not equal.
Raises
------
ValueError
If input has no match_expr nor match_ver.
Returns
-------
bool
True if the expression matches the version, otherwise False.
Examples
--------
>>> eos_version.match("<=4.23.3M")
True
>>> eos_version.match("==4.23.3M")
False
Args:
match_expr (str): optional operator and version; valid operators are
``<`` smaller than
``>`` greater than
``>=`` greator or equal than
``<=`` smaller or equal than
``==`` equal
``!=`` not equal
Raises:
ValueError: If input has no match_expr nor match_ver
Returns:
bool: True if the expression matches the version, otherwise False
"""
prefix = match_expr[:2]
if prefix in (">=", "<=", "==", "!="):
@ -251,7 +337,6 @@ class EosVersion(BaseModel):
"['<', '>', '==', '<=', '>=', '!=']. "
f"You provided: {match_expr}"
)
logger.debug(f"work on comparison {prefix} with base release {match_version}")
possibilities_dict = {
">": (1,),
"<": (-1,),
@ -261,27 +346,133 @@ class EosVersion(BaseModel):
"<=": (-1, 0),
}
possibilities = possibilities_dict[prefix]
cmp_res = self._compare(EosVersion.from_str(match_version))
cmp_res = self._compare(SemVer.from_str(match_version))
return cmp_res in possibilities
def is_in_branch(self, branch_str: str) -> bool:
"""
Check if current version is part of a branch version
Check if current version is part of a branch version.
Comparison is done across MAJOR and MINOR
Comparison is done across MAJOR and MINOR.
Args:
branch_str (str): a string for EOS branch. It supports following formats 4.23 or 4.23.0
Parameters
----------
branch_str : str
A string for EOS branch. It supports following formats 4.23 or 4.23.0.
Returns:
bool: True if current version is in provided branch, otherwise False
Returns
-------
bool
True if current version is in provided branch, otherwise False.
"""
logging.info(f"Checking if {self} is in branch {branch_str}")
try:
logger.debug(f"reading branch str:{branch_str}")
branch = EosVersion.from_str(branch_str)
branch = SemVer.from_str(branch_str)
except Exception as error: # pylint: disable = broad-exception-caught
logger.error(exc_to_str(error))
else:
return self.major == branch.major and self.minor == branch.minor
return False
class EosVersion(SemVer):
"""EosVersion object to play with version management in code.
Since EOS is not using strictly semver approach, this class mimics some functions from the semver library for Arista EOS versions.
It is based on Pydantic and provides helpers for comparison.
Examples
--------
>>> version = EosVersion(major=4, minor=21, patch=1, rtype="M")
>>> print(version)
EosVersion(major=4, minor=21, patch=1, rtype='M', other=None)
>>> version = EosVersion.from_str('4.32.1F')
>>> print(version)
EosVersion(major=4, minor=32, patch=1, rtype='F', other=None)
Attributes
----------
major : int
Major version number, default is 4.
minor : int
Minor version number, default is 0.
patch : int
Patch version number, default is 0.
rtype : Optional[str]
Release type, default is "F".
other : Any
Any other version information.
regex_version : ClassVar[Pattern[str]]
Regular expression to extract version information.
regex_branch : ClassVar[Pattern[str]]
Regular expression to extract branch information.
description : str
A basic description of this class, default is "A Generic SemVer implementation".
"""
major: int = 4
minor: int = 0
patch: int = 0
rtype: Optional[str] = "F"
other: Any = None
# Regular Expression to extract version information.
regex_version: ClassVar[Pattern[str]] = re.compile(
r"^.*(?P<major>4)\.(?P<minor>\d{1,2})\.(?P<patch>\d{1,2})(?P<other>\.\d*)*(?P<rtype>[M,F])*$"
)
regex_branch: ClassVar[Pattern[str]] = re.compile(
r"^.*(?P<major>4)\.(?P<minor>\d{1,2})(\.?P<patch>\d)*(\.\d)*(?P<rtype>[M,F])*$"
)
# A Basic description of this class
description: str = "A SemVer implementation for EOS"
class CvpVersion(SemVer):
"""A CloudVision Portal Version class that inherits from SemVer.
This class implements version management for CloudVision Portal (CVP) versions
following a modified semantic versioning pattern where:
- major version represents the year (e.g. 2024)
- minor version represents feature releases
- patch version represents bug fixes
Examples
--------
>>> version = CvpVersion(2024, 1, 0)
>>> str(version)
'2024.1.0'
Attributes
----------
major : int
The year component of the version (e.g. 2024).
minor : int
The minor version number.
patch : int
The patch version number.
rtype : Optional[str]
Release type if any.
other : Any
Additional version information if any.
regex_version : ClassVar[Pattern[str]]
Regular expression to parse version strings.
regex_branch : ClassVar[Pattern[str]]
Regular expression to parse branch version strings.
description : str
Brief description of the class purpose.
"""
major: int = 2024
minor: int = 0
patch: int = 0
rtype: Optional[str] = None
other: Any = None
# Regular Expression to extract version information.
regex_version: ClassVar[Pattern[str]] = re.compile(
r"^.*(?P<major>\d{4})\.(?P<minor>\d{1,2})\.(?P<patch>\d{1,2})(?P<other>\.\d*)*$"
)
regex_branch: ClassVar[Pattern[str]] = re.compile(
r"^.*(?P<major>\d{4})\.(?P<minor>\d{1,2})\.(?P<patch>\d{1,2})(?P<other>\.\d*)*$"
)
# A Basic description of this class
description: str = "A SemVer implementation for CloudVision"