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,8 @@
"""ARDL Module CLI."""
from __future__ import annotations
from .cli import cli
if __name__ == "__main__":
cli()

View file

@ -14,8 +14,8 @@ import click
from eos_downloader import __version__
from eos_downloader.cli.debug import commands as debug_commands
from eos_downloader.cli.get import commands as get_commands
from eos_downloader.cli.info import commands as info_commands
from eos_downloader.cli.get import commands as get_commands
from eos_downloader.cli.utils import AliasedGroup
@ -29,10 +29,29 @@ from eos_downloader.cli.utils import AliasedGroup
default=None,
help="Arista Token from your customer account",
)
def ardl(ctx: click.Context, token: str) -> None:
@click.option(
"--log-level",
"--log",
help="Logging level of the command",
default="error",
type=click.Choice(
["debug", "info", "warning", "error", "critical"], case_sensitive=False
),
)
# Boolean triggers
@click.option(
"--debug-enabled",
"--debug",
is_flag=True,
help="Activate debug mode for ardl cli",
default=False,
)
def ardl(ctx: click.Context, token: str, log_level: str, debug_enabled: bool) -> None:
"""Arista Network Download CLI"""
ctx.ensure_object(dict)
ctx.obj["token"] = token
ctx.obj["log_level"] = log_level
ctx.obj["debug"] = debug_enabled
@ardl.group(cls=AliasedGroup, no_args_is_help=True)
@ -61,11 +80,19 @@ def debug(ctx: click.Context, cls: click.Group = AliasedGroup) -> None:
def cli() -> None:
"""Load ANTA CLI"""
# Load group commands
# Load group commands for get
get.add_command(get_commands.eos)
get.add_command(get_commands.cvp)
info.add_command(info_commands.eos_versions)
get.add_command(get_commands.path)
# Debug
debug.add_command(debug_commands.xml)
# Get info commands
info.add_command(info_commands.versions)
info.add_command(info_commands.latest)
info.add_command(info_commands.mapping)
# Load CLI
ardl(obj={}, auto_envvar_prefix="arista")

View file

@ -10,14 +10,17 @@
Commands for ARDL CLI to get data.
"""
# Standard library imports
import xml.etree.ElementTree as ET
from xml.dom import minidom
# Third party imports
import click
from loguru import logger
from rich.console import Console
import eos_downloader.eos
# Local imports
import eos_downloader.defaults
import eos_downloader.logics.arista_server
from eos_downloader.cli.utils import cli_logging
@click.command()
@ -33,34 +36,46 @@ import eos_downloader.eos
"--log-level",
"--log",
help="Logging level of the command",
default=None,
default="INFO",
type=click.Choice(
["debug", "info", "warning", "error", "critical"], case_sensitive=False
),
)
def xml(ctx: click.Context, output: str, log_level: str) -> None:
# sourcery skip: remove-unnecessary-cast
"""Extract XML directory structure"""
console = Console()
# Get from Context
"""Downloads and saves XML data from Arista EOS server.
This function authenticates with an Arista server, retrieves XML data,
and saves it to a file in a prettified format.
Args:
ctx (click.Context): Click context object containing authentication token
output (str): File path where the XML output should be saved
log_level (str): Logging level to use for output messages
Raises:
Exception: If authentication with the server fails
Example:
>>> xml(ctx, "output.xml", "INFO")
INFO: connected to server aaa.bbb.ccc
INFO: XML file saved under output.xml
"""
log = cli_logging(log_level)
token = ctx.obj["token"]
logger.remove()
if log_level is not None:
logger.add("eos-downloader.log", rotation="10 MB", level=log_level.upper())
my_download = eos_downloader.eos.EOSDownloader(
image="unset",
software="EOS",
version="unset",
token=token,
hash_method="sha512sum",
server = eos_downloader.logics.arista_server.AristaServer(
token=token, session_server=eos_downloader.defaults.DEFAULT_SERVER_SESSION
)
my_download.authenticate()
xml_object: ET.ElementTree = (
my_download.get_folder_tree()
) # pylint: disable=protected-access
try:
server.authenticate()
except Exception as error: # pylint: disable=W0703
log.error(f"Cant connect to server: {error}")
log.info(f"connected to server {eos_downloader.defaults.DEFAULT_SERVER_SESSION}")
xml_data = server.get_xml_data()
if xml_data is None:
log.error("No XML data received")
return
xml_object: ET.ElementTree = xml_data # pylint: disable=protected-access
xml_content = xml_object.getroot()
xmlstr = minidom.parseString(ET.tostring(xml_content)).toprettyxml(
@ -68,5 +83,4 @@ def xml(ctx: click.Context, output: str, log_level: str) -> None:
)
with open(output, "w", encoding="utf-8") as f:
f.write(str(xmlstr))
console.print(f"XML file saved in: { output }")
log.info(f"XML file saved under {output}")

View file

@ -2,252 +2,385 @@
# coding: utf-8 -*-
# pylint: disable=no-value-for-parameter
# pylint: disable=too-many-arguments
# pylint: disable=too-many-positional-arguments
# pylint: disable=line-too-long
# pylint: disable=redefined-builtin
# pylint: disable=broad-exception-caught
# flake8: noqa E501
"""
Commands for ARDL CLI to get data.
"""
"""CLI commands for listing Arista package information."""
import os
import sys
from typing import Union
import click
from loguru import logger
from rich.console import Console
from eos_downloader.models.data import RTYPE_FEATURE
from eos_downloader.logics.download import SoftManager
from eos_downloader.logics.arista_server import AristaServer
from eos_downloader.logics.arista_xml_server import (
EosXmlObject,
AristaXmlQuerier,
CvpXmlObject,
)
import eos_downloader.eos
from eos_downloader.models.version import BASE_VERSION_STR, RTYPE_FEATURE, RTYPES
EOS_IMAGE_TYPE = [
"64",
"INT",
"2GB-INT",
"cEOS",
"cEOS64",
"vEOS",
"vEOS-lab",
"EOS-2GB",
"default",
]
CVP_IMAGE_TYPE = ["ova", "rpm", "kvm", "upgrade"]
from .utils import initialize, search_version, download_files, handle_docker_import
@click.command(no_args_is_help=True)
@click.pass_context
@click.option(
"--image-type",
default="default",
help="EOS Image type",
type=click.Choice(EOS_IMAGE_TYPE),
required=True,
)
@click.option("--version", default=None, help="EOS version", type=str, required=False)
@click.option(
"--latest",
"-l",
is_flag=True,
type=click.BOOL,
default=False,
help="Get latest version in given branch. If --branch is not use, get the latest branch with specific release type",
)
@click.option(
"--release-type",
"-rtype",
type=click.Choice(RTYPES, case_sensitive=False),
default=RTYPE_FEATURE,
help="EOS release type to search",
)
@click.option(
"--branch",
"-b",
type=click.STRING,
default=None,
help="EOS Branch to list releases",
)
@click.option(
"--docker-name",
default="arista/ceos",
help="Docker image name (default: arista/ceos)",
type=str,
show_default=True,
)
@click.command()
@click.option("--format", default="vmdk", help="Image format", show_default=True)
@click.option(
"--output",
default=str(os.path.relpath(os.getcwd(), start=os.curdir)),
help="Path to save image",
type=click.Path(),
show_default=True,
show_envvar=True,
)
# Debugging
@click.option(
"--log-level",
"--log",
help="Logging level of the command",
default=None,
type=click.Choice(
["debug", "info", "warning", "error", "critical"], case_sensitive=False
),
"--latest",
is_flag=True,
help="Get latest version. If --branch is not use, get the latest branch with specific release type",
default=False,
show_envvar=True,
)
# Boolean triggers
@click.option(
"--eve-ng",
is_flag=True,
help="Run EVE-NG vEOS provisioning (only if CLI runs on an EVE-NG server)",
default=False,
)
@click.option(
"--disable-ztp",
is_flag=True,
help="Disable ZTP process in vEOS image (only available with --eve-ng)",
default=False,
show_envvar=True,
)
@click.option(
"--import-docker",
is_flag=True,
help="Import docker image (only available with --image_type cEOSlab)",
help="Import docker image to local docker",
default=False,
show_envvar=True,
)
@click.option(
"--skip-download",
is_flag=True,
help="Skip download process - for debug only",
default=False,
)
@click.option(
"--docker-name",
default="arista/ceos",
help="Docker image name",
show_default=True,
show_envvar=True,
)
@click.option(
"--docker-tag",
default=None,
help="Docker image tag",
show_default=True,
show_envvar=True,
)
@click.option(
"--version",
default=None,
help="EOS version to download",
show_default=True,
show_envvar=True,
)
@click.option(
"--release-type",
default=RTYPE_FEATURE,
help="Release type (M for Maintenance, F for Feature)",
show_default=True,
show_envvar=True,
)
@click.option(
"--branch",
default=None,
help="Branch to download",
show_default=True,
show_envvar=True,
)
@click.option(
"--dry-run",
is_flag=True,
help="Enable dry-run mode: only run code without system changes",
default=False,
)
@click.pass_context
def eos(
ctx: click.Context,
image_type: str,
format: str,
output: str,
log_level: str,
eve_ng: bool,
disable_ztp: bool,
import_docker: bool,
skip_download: bool,
docker_name: str,
version: Union[str, None] = None,
release_type: str = RTYPE_FEATURE,
latest: bool = False,
branch: Union[str, None] = None,
docker_tag: str,
version: Union[str, None],
release_type: str,
latest: bool,
branch: Union[str, None],
dry_run: bool,
) -> int:
# pylint: disable=R0917
"""Download EOS image from Arista website"""
console = Console()
# Get from Context
token = ctx.obj["token"]
is_latest: bool = False
if token is None or token == "":
console.print(
"❗ Token is unset ! Please configure ARISTA_TOKEN or use --token option",
style="bold red",
)
sys.exit(1)
logger.remove()
if log_level is not None:
logger.add("eos-downloader.log", rotation="10 MB", level=log_level.upper())
console.print(
"🪐 [bold blue]eos-downloader[/bold blue] is starting...",
"""Download EOS image from Arista server."""
# pylint: disable=unused-variable
console, token, debug, log_level = initialize(ctx)
version = search_version(
console, token, version, latest, branch, format, release_type
)
console.print(f" - Image Type: {image_type}")
console.print(f" - Version: {version}")
if version is not None:
my_download = eos_downloader.eos.EOSDownloader(
image=image_type,
software="EOS",
version=version,
token=token,
hash_method="sha512sum",
if version is None:
raise ValueError("Version is not set correctly")
try:
eos_dl_obj = EosXmlObject(
searched_version=version, token=token, image_type=format
)
my_download.authenticate()
except Exception:
console.print_exception(show_locals=True)
return 1
elif latest:
is_latest = True
my_download = eos_downloader.eos.EOSDownloader(
image=image_type,
software="EOS",
version="unset",
token=token,
hash_method="sha512sum",
)
my_download.authenticate()
if branch is None:
branch = str(my_download.latest_branch(rtype=release_type).branch)
latest_version = my_download.latest_eos(branch, rtype=release_type)
if str(latest_version) == BASE_VERSION_STR:
console.print(
f"[red]Error[/red], cannot find any version in {branch} for {release_type} release type"
cli = SoftManager(dry_run=dry_run)
if not skip_download:
if not eve_ng:
download_files(
console, cli, eos_dl_obj, output, rich_interface=True, debug=debug
)
sys.exit(1)
my_download.version = str(latest_version)
if eve_ng:
my_download.provision_eve(noztp=disable_ztp, checksum=True)
else:
my_download.download_local(file_path=output, checksum=True)
else:
try:
cli.provision_eve(eos_dl_obj, noztp=True)
except Exception as e:
if debug:
console.print_exception(show_locals=True)
else:
console.print(f"\n[red]Exception raised: {e}[/red]")
return 1
if import_docker:
my_download.docker_import(image_name=docker_name, is_latest=is_latest)
console.print("✅ processing done !")
sys.exit(0)
return handle_docker_import(
console, cli, eos_dl_obj, output, docker_name, docker_tag, debug
)
return 0
@click.command(no_args_is_help=True)
@click.pass_context
@click.command()
@click.option(
"--format",
default="upgrade",
help="CVP Image type",
type=click.Choice(CVP_IMAGE_TYPE),
required=True,
default="ova",
help="Image format",
show_default=True,
show_envvar=True,
)
@click.option("--version", default=None, help="CVP version", type=str, required=True)
@click.option(
"--output",
default=str(os.path.relpath(os.getcwd(), start=os.curdir)),
help="Path to save image",
type=click.Path(),
show_default=True,
show_envvar=True,
)
@click.option(
"--log-level",
"--log",
help="Logging level of the command",
default=None,
type=click.Choice(
["debug", "info", "warning", "error", "critical"], case_sensitive=False
),
"--latest",
is_flag=True,
help="Get latest version. If --branch is not use, get the latest branch with specific release type",
default=False,
show_envvar=True,
)
@click.option(
"--version",
default=None,
help="EOS version to download",
show_default=True,
show_envvar=True,
)
@click.option(
"--branch",
default=None,
help="Branch to download",
show_default=True,
show_envvar=True,
)
@click.option(
"--dry-run",
is_flag=True,
help="Enable dry-run mode: only run code without system changes",
default=False,
)
@click.pass_context
def cvp(
ctx: click.Context, version: str, format: str, output: str, log_level: str
ctx: click.Context,
latest: bool,
format: str,
output: str,
version: Union[str, None],
branch: Union[str, None],
dry_run: bool = False,
) -> int:
"""Download CVP image from Arista website"""
console = Console()
# Get from Context
token = ctx.obj["token"]
if token is None or token == "":
"""Download CVP image from Arista server."""
# pylint: disable=unused-variable
console, token, debug, log_level = initialize(ctx)
if version is not None:
console.print(
"❗ Token is unset ! Please configure ARISTA_TOKEN or use --token option",
style="bold red",
f"Searching for EOS version [green]{version}[/green] for [blue]{format}[/blue] format..."
)
elif latest:
console.print(
f"Searching for [blue]latest[/blue] EOS version for [blue]{format}[/blue] format..."
)
elif branch is not None:
console.print(
f"Searching for EOS [b]latest[/b] version for [blue]{branch}[/blue] branch for [blue]{format}[/blue] format..."
)
sys.exit(1)
logger.remove()
if log_level is not None:
logger.add("eos-downloader.log", rotation="10 MB", level=log_level.upper())
if branch is not None or latest:
try:
querier = AristaXmlQuerier(token=token)
version_obj = querier.latest(package="cvp", branch=branch)
version = str(version_obj)
except Exception as e:
console.print(f"Token is set to: {token}")
console.print_exception(show_locals=True)
return 1
console.print(
"🪐 [bold blue]eos-downloader[/bold blue] is starting...",
)
console.print(f" - Image Type: {format}")
console.print(f" - Version: {version}")
console.print(f"version to download is {version}")
my_download = eos_downloader.eos.EOSDownloader(
image=format,
software="CloudVision",
version=version,
token=token,
hash_method="md5sum",
if version is None:
raise ValueError("Version is not set correctly")
try:
cvp_dl_obj = CvpXmlObject(
searched_version=version, token=token, image_type=format
)
except Exception as e:
if debug:
console.print_exception(show_locals=True)
else:
console.print(f"\n[red]Exception raised: {e}[/red]")
return 1
cli = SoftManager(dry_run=dry_run)
download_files(
console,
cli,
cvp_dl_obj,
output,
rich_interface=True,
debug=debug,
checksum_format="md5sum",
)
my_download.authenticate()
console.print(f"CVP file is saved under: {output}")
return 0
my_download.download_local(file_path=output, checksum=False)
console.print("✅ processing done !")
sys.exit(0)
@click.command()
@click.option(
"--source",
"-s",
help="Image path to download from Arista Website",
type=str,
show_default=False,
show_envvar=False,
)
@click.option(
"--output",
"-o",
default=str(os.path.relpath(os.getcwd(), start=os.curdir)),
help="Path to save downloaded package",
type=click.Path(),
show_default=True,
show_envvar=True,
)
@click.option(
"--import-docker",
is_flag=True,
help="Import docker image to local docker",
default=False,
show_envvar=True,
)
@click.option(
"--docker-name",
default="arista/ceos:raw",
help="Docker image name",
show_default=True,
show_envvar=True,
)
@click.option(
"--docker-tag",
default="dev",
help="Docker image tag",
show_default=True,
show_envvar=True,
)
@click.pass_context
# pylint: disable=too-many-branches
def path(
ctx: click.Context,
output: str,
source: str,
import_docker: bool,
docker_name: str,
docker_tag: str,
) -> int:
"""Download image from Arista server using direct path."""
console, token, debug, log_level = initialize(ctx)
if source is None:
console.print("[red]Source is not set correctly ![/red]")
return 1
filename = os.path.basename(source)
console.print(f"Downloading file {filename} from source: {source}")
console.print(f"Saving file to: {output}")
ar_server = AristaServer(token=token)
try:
file_url = ar_server.get_url(source)
if log_level == "debug":
console.print(f"URL to download file is: {file_url}")
except Exception as e:
if debug:
console.print_exception(show_locals=True)
else:
console.print(f"\n[red]Exception raised: {e}[/red]")
return 1
if file_url is None:
console.print("File URL is set to None when we expect a string")
return 1
cli = SoftManager(dry_run=False)
try:
cli.download_file(file_url, output, filename=filename)
except Exception as e:
if debug:
console.print_exception(show_locals=True)
else:
console.print(f"\n[red]Exception raised: {e}[/red]")
return 1
if import_docker:
console.print(
f"Importing docker image [green]{docker_name}:{docker_tag}[/green] from [blue]{os.path.join(output, filename)}[/blue]..."
)
try:
cli.import_docker(
local_file_path=os.path.join(output, filename),
docker_name=docker_name,
docker_tag=docker_tag,
)
except FileNotFoundError:
if debug:
console.print_exception(show_locals=True)
else:
console.print(
f"\n[red]File not found: {os.path.join(output, filename)}[/red]"
)
return 1
console.print(
f"Docker image imported successfully: [green]{docker_name}:{docker_tag}[/green]"
)
return 0

View file

@ -0,0 +1,182 @@
"""Generic functions for the CLI."""
# pylint: disable=too-many-arguments
# pylint: disable=too-many-positional-arguments
import os
from typing import cast, Optional, Union, Any
import subprocess
import click
from rich.console import Console
from eos_downloader.cli.utils import cli_logging, console_configuration
from eos_downloader.models.data import RTYPE_FEATURE, RTYPES
from eos_downloader.models.types import ReleaseType
from eos_downloader.logics.arista_xml_server import AristaXmlQuerier, AristaXmlObjects
def initialize(ctx: click.Context) -> tuple[Console, str, bool, str]:
"""Initializes the CLI context with necessary configurations.
Args:
ctx (click.Context): The Click context object containing command-line parameters.
Returns:
tuple: A tuple containing the console configuration, token, debug flag, and log level.
"""
console = console_configuration()
token = ctx.obj["token"]
debug = ctx.obj["debug"]
log_level = ctx.obj["log_level"]
cli_logging(log_level)
return console, token, debug, log_level
def search_version(
console: Console,
token: str,
version: Optional[str],
latest: bool,
branch: Optional[str],
file_format: str,
release_type: str,
) -> Union[str, None]:
"""Searches for the specified EOS version based on the provided parameters.
Args:
console (Console): The console object used for printing messages.
token (str): The authentication token for accessing the EOS API.
version (str or None): The specific version of EOS to search for. If None, other parameters are used.
latest (bool): If True, search for the latest EOS version.
branch (str or None): The branch of EOS to search for. If None, the default branch is used.
format (str): The format of the EOS version (e.g., 'tar', 'zip').
release_type (str): The type of release (e.g., 'feature', 'maintenance').
Returns:
str: The version of EOS found based on the search criteria.
"""
if version is not None:
console.print(
f"Searching for EOS version [green]{version}[/green] for [blue]{file_format}[/blue] format..."
)
elif latest:
console.print(
f"Searching for [blue]latest[/blue] EOS version for [blue]{file_format}[/blue] format..."
)
elif branch is not None:
console.print(
f"Searching for EOS [b]latest[/b] version for [blue]{branch}[/blue] branch for [blue]{file_format}[/blue] format..."
)
if branch is not None or latest:
querier = AristaXmlQuerier(token=token)
rtype: ReleaseType = cast(
ReleaseType, release_type if release_type in RTYPES else RTYPE_FEATURE
)
version_obj = querier.latest(package="eos", branch=branch, rtype=rtype)
version = str(version_obj)
return version
def download_files(
console: Console,
cli: Any,
arista_dl_obj: AristaXmlObjects,
output: str,
rich_interface: bool,
debug: bool,
checksum_format: str = "sha512sum",
) -> None:
"""Downloads EOS files and verifies their checksums.
Args:
console (Console): The console object for printing messages.
cli (CLI): The CLI object used to perform download and checksum operations.
arista_dl_obj (AristaPackage): The EOS download object containing version and filename information.
output (str): The output directory where the files will be saved.
rich_interface (bool): Flag to indicate if rich interface should be used.
debug (bool): Flag to indicate if debug information should be printed.
checksum_format (str): The checksum format to use for verification.
Raises:
Exception: If there is an error during the checksum verification.
"""
console.print(
f"Starting download for EOS version [green]{arista_dl_obj.version}[/green] for [blue]{arista_dl_obj.image_type}[/blue] format."
)
cli.downloads(arista_dl_obj, file_path=output, rich_interface=rich_interface)
try:
cli.checksum(checksum_format)
except subprocess.CalledProcessError:
if debug:
console.print_exception(show_locals=True)
else:
console.print(
f"[red]Checksum error for file {arista_dl_obj.filename}[/red]"
)
console.print(
f"Arista file [green]{arista_dl_obj.filename}[/green] downloaded in: [blue]{output}[/blue]"
)
def handle_docker_import(
console: Console,
cli: Any,
arista_dl_obj: AristaXmlObjects,
output: str,
docker_name: str,
docker_tag: Optional[str],
debug: bool,
) -> int:
"""Handles the import of a Docker image using the provided CLI tool.
Args:
console: The console object used for printing messages.
cli: The CLI tool object that provides the import_docker method.
arista_dl_obj: An object containing information about the EOS download, including version and filename.
output: The directory where the Docker image file is located.
docker_name: The name to assign to the Docker image.
docker_tag: The tag to assign to the Docker image. If None, the version from eos_dl_obj is used.
debug: A boolean indicating whether to print detailed exception information.
Returns:
int: 0 if the Docker image is imported successfully, 1 if a FileNotFoundError occurs.
"""
console.print("Importing docker image...")
if docker_tag is None:
docker_tag = arista_dl_obj.version
if arista_dl_obj.filename is None:
console.print("[red]Invalid filename[/red]")
return 1
console.print(
f"Importing docker image [green]{docker_name}:{docker_tag}[/green] from [blue]{os.path.join(output, arista_dl_obj.filename)}[/blue]..."
)
try:
cli.import_docker(
local_file_path=os.path.join(output, arista_dl_obj.filename),
docker_name=docker_name,
docker_tag=docker_tag,
)
except FileNotFoundError:
if debug:
console.print_exception(show_locals=True)
else:
console.print(
f"\n[red]File not found: {os.path.join(output, arista_dl_obj.filename)}[/red]"
)
return 1
console.print(
f"Docker image imported successfully: [green]{docker_name}:{docker_tag}[/green]"
)
return 0

View file

@ -6,130 +6,235 @@
# pylint: disable=redefined-builtin
# flake8: noqa E501
"""
Commands for ARDL CLI to list data.
"""CLI commands for listing Arista package information.
This module provides CLI commands to query and display version information for Arista packages (EOS and CVP).
It includes commands to:
- List all available versions with filtering options
- Get the latest version for a given package/branch
The commands use Click for CLI argument parsing and support both text and JSON output formats.
Authentication is handled via a token passed through Click context.
Commands:
versions: Lists all available versions with optional filtering
latest: Shows the latest version matching the filter criteria
Dependencies:
click: CLI framework
rich: For pretty JSON output
eos_downloader.logics.arista_server: Core logic for querying Arista servers
"""
import sys
from typing import Union
import json
import click
from loguru import logger
from rich.console import Console
from rich.pretty import pprint
from rich import print_json
from rich.panel import Panel
import eos_downloader.eos
from eos_downloader.models.version import BASE_VERSION_STR, RTYPE_FEATURE, RTYPES
from eos_downloader.models.data import software_mapping
from eos_downloader.models.types import AristaPackage, ReleaseType, AristaMapping
import eos_downloader.logics.arista_xml_server
from eos_downloader.cli.utils import console_configuration
from eos_downloader.cli.utils import cli_logging
# """
# Commands for ARDL CLI to list data.
# """
@click.command(no_args_is_help=True)
@click.command()
@click.option(
"--format",
type=click.Choice(["json", "text", "fancy"]),
default="fancy",
help="Output format",
)
@click.option(
"--package", type=click.Choice(["eos", "cvp"]), default="eos", required=False
)
@click.option("--branch", "-b", type=str, required=False)
@click.option("--release-type", type=str, required=False)
@click.pass_context
@click.option(
"--latest",
"-l",
is_flag=True,
type=click.BOOL,
default=False,
help="Get latest version in given branch. If --branch is not use, get the latest branch with specific release type",
)
@click.option(
"--release-type",
"-rtype",
type=click.Choice(RTYPES, case_sensitive=False),
default=RTYPE_FEATURE,
help="EOS release type to search",
)
@click.option(
"--branch",
"-b",
type=click.STRING,
default=None,
help="EOS Branch to list releases",
)
@click.option(
"--verbose",
"-v",
is_flag=True,
type=click.BOOL,
default=False,
help="Human readable output. Default is none to use output in script)",
)
@click.option(
"--log-level",
"--log",
help="Logging level of the command",
default="warning",
type=click.Choice(
["debug", "info", "warning", "error", "critical"], case_sensitive=False
),
)
def eos_versions(
def versions(
ctx: click.Context,
log_level: str,
branch: Union[str, None] = None,
release_type: str = RTYPE_FEATURE,
latest: bool = False,
verbose: bool = False,
package: AristaPackage,
branch: str,
release_type: ReleaseType,
format: str,
) -> None:
# pylint: disable = too-many-branches, R0917
"""
List Available EOS version on Arista.com website.
"""List available package versions from Arista server."""
Comes with some filters to get latest release (F or M) as well as branch filtering
- To get latest M release available (without any branch): ardl info eos-versions --latest -rtype m
- To get latest F release available: ardl info eos-versions --latest -rtype F
"""
console = Console()
# Get from Context
console = console_configuration()
token = ctx.obj["token"]
debug = ctx.obj["debug"]
log_level = ctx.obj["log_level"]
cli_logging(log_level)
logger.remove()
if log_level is not None:
logger.add("eos-downloader.log", rotation="10 MB", level=log_level.upper())
querier = eos_downloader.logics.arista_xml_server.AristaXmlQuerier(token=token)
my_download = eos_downloader.eos.EOSDownloader(
image="unset",
software="EOS",
version="unset",
token=token,
hash_method="sha512sum",
)
auth = my_download.authenticate()
if verbose and auth:
console.print("✅ Authenticated on arista.com")
if release_type is not None:
release_type = release_type.upper()
if latest:
if branch is None:
branch = str(my_download.latest_branch(rtype=release_type).branch)
latest_version = my_download.latest_eos(branch, rtype=release_type)
if str(latest_version) == BASE_VERSION_STR:
console.print(
f"[red]Error[/red], cannot find any version in {branch} for {release_type} release type"
)
sys.exit(1)
if verbose:
console.print(
f"Branch {branch} has been selected with release type {release_type}"
)
if branch is not None:
console.print(f"Latest release for {branch}: {latest_version}")
else:
console.print(f"Latest EOS release: {latest_version}")
received_versions = None
try:
received_versions = querier.available_public_versions(
package=package, branch=branch, rtype=release_type
)
except ValueError:
if debug:
console.print_exception(show_locals=True)
else:
console.print(f"{ latest_version }")
else:
versions = my_download.get_eos_versions(branch=branch, rtype=release_type)
if verbose:
console.print(
f'List of available versions for {branch if branch is not None else "all branches"}'
)
for version in versions:
console.print(f"{str(version)}")
console.print("[red]No versions found[/red]")
return
if format == "text":
console.print("Listing available versions")
if received_versions is None:
console.print("[red]No versions found[/red]")
return
for version in received_versions:
console.print(f" - version: [blue]{version}[/blue]")
elif format == "fancy":
lines_output = []
if received_versions is None:
console.print("[red]No versions found[/red]")
return
for version in received_versions:
lines_output.append(f" - version: [blue]{version}[/blue]")
console.print("")
console.print(
Panel("\n".join(lines_output), title="Available versions", padding=1)
)
elif format == "json":
response = []
if received_versions is None:
console.print("[red]No versions found[/red]")
return
for version in received_versions:
out = {}
out["version"] = str(version)
out["branch"] = str(version.branch)
response.append(out)
response = json.dumps(response) # type: ignore
print_json(response)
@click.command()
@click.option(
"--format",
type=click.Choice(["json", "text", "fancy"]),
default="fancy",
help="Output format",
)
@click.option(
"--package", type=click.Choice(["eos", "cvp"]), default="eos", required=False
)
@click.option("--branch", "-b", type=str, required=False)
@click.option("--release-type", type=str, required=False)
@click.pass_context
def latest(
ctx: click.Context,
package: AristaPackage,
branch: str,
release_type: ReleaseType,
format: str,
) -> None:
"""List available versions of Arista packages (eos or CVP) packages."""
console = console_configuration()
token = ctx.obj["token"]
debug = ctx.obj["debug"]
log_level = ctx.obj["log_level"]
cli_logging(log_level)
querier = eos_downloader.logics.arista_xml_server.AristaXmlQuerier(token=token)
received_version = None
try:
received_version = querier.latest(
package=package, branch=branch, rtype=release_type
)
except ValueError:
if debug:
console.print_exception(show_locals=True)
else:
pprint([str(version) for version in versions])
console.print("[red]No versions found[/red]")
if format in ["text", "fancy"]:
version_info = f"Latest version for [green]{package}[/green]: [blue]{received_version}[/blue]"
if branch:
version_info += f" for branch [blue]{branch}[/blue]"
if format == "text":
console.print("")
console.print(version_info)
else: # fancy format
console.print("")
console.print(Panel(version_info, title="Latest version", padding=1))
else: # json format
print_json(json.dumps({"version": str(received_version)}))
@click.command()
@click.option(
"--package", type=click.Choice(["eos", "cvp"]), default="eos", required=False
)
@click.option(
"--format",
type=click.Choice(["json", "text", "fancy"]),
default="fancy",
help="Output format",
)
@click.option(
"--details",
is_flag=True,
show_default=True,
default=False,
help="Show details for each flavor",
)
@click.pass_context
def mapping(
ctx: click.Context, package: AristaPackage, details: bool, format: str
) -> None:
"""List available flavors of Arista packages (eos or CVP) packages."""
mapping_pkg_name: AristaMapping = "EOS"
if package == "eos":
mapping_pkg_name = "EOS"
elif package == "cvp":
mapping_pkg_name = "CloudVision"
console = console_configuration()
log_level = ctx.obj["log_level"]
console.print(f"Log Level is: {log_level}")
cli_logging(log_level)
if mapping_pkg_name in software_mapping.model_fields:
mapping_entries = getattr(software_mapping, mapping_pkg_name, None)
if format == "text":
console.print(
f"Following flavors for [red]{package}/{mapping_pkg_name}[/red] have been found:"
)
if mapping_entries is None:
console.print("[red]No flavors found[/red]")
return
for mapping_entry in mapping_entries:
console.print(f" * Flavor: [blue]{mapping_entry}[/blue]")
if details:
console.print(
f" - Information: [black]{mapping_entries[mapping_entry]}[/black]"
)
console.print("\n")
elif format == "fancy":
lines_output = []
if mapping_entries is None:
lines_output.append("[red]No flavors found[/red]")
console.print("\n".join(lines_output))
return
for mapping_entry in mapping_entries:
lines_output.append(f" * Flavor: [blue]{mapping_entry}[/blue]")
if details:
lines_output.append(
f" - Information: [black]{mapping_entries[mapping_entry]}[/black]"
)
console.print("")
console.print(Panel("\n".join(lines_output), title="Flavors", padding=1))
console.print("\n")
elif format == "json":
mapping_json = software_mapping.model_dump()[package.upper()]
print_json(json.dumps(mapping_json))

View file

@ -8,23 +8,27 @@ Extension for the python ``click`` module
to provide a group or command with aliases.
"""
import logging
from typing import Any
import click
from rich import pretty
from rich.logging import RichHandler
from rich.console import Console
class AliasedGroup(click.Group):
"""
Implements a subclass of Group that accepts a prefix for a command.
If there were a command called push, it would accept pus as an alias (so long as it was unique)
"""
def get_command(self, ctx: click.Context, cmd_name: str) -> Any:
"""Documentation to build"""
rv = click.Group.get_command(self, ctx, cmd_name)
if rv is not None:
return rv
matches = [x for x in self.list_commands(ctx)
if x.startswith(cmd_name)]
matches = [x for x in self.list_commands(ctx) if x.startswith(cmd_name)]
if not matches:
return None
if len(matches) == 1:
@ -36,3 +40,45 @@ class AliasedGroup(click.Group):
# always return the full command name
_, cmd, args = super().resolve_command(ctx, args)
return cmd.name, cmd, args
def cli_logging(level: str = "error") -> logging.Logger:
"""
Configures and returns a logger with the specified logging level.
This function sets up the logging configuration using the RichHandler
to provide rich formatted log messages. The log messages will include
the time and can contain markup and rich tracebacks.
Args:
level (str): The logging level as a string (e.g., 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL').
Returns:
logging.Logger: A configured logger instance.
"""
FORMAT = "%(message)s"
logging.basicConfig(
level=level.upper(),
format=FORMAT,
datefmt="[%X]",
handlers=[
RichHandler(
show_path=True,
show_time=True,
show_level=True,
markup=True,
rich_tracebacks=True,
tracebacks_suppress=[click],
)
],
)
log = logging.getLogger("rich")
return log
def console_configuration() -> Console:
"""Configure Rich Terminal for the CLI."""
pretty.install()
console = Console()
return console