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

@ -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