From b0554023303fe8656173d3dfbd0d2449145df38e Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Tue, 10 Oct 2023 11:54:17 +0200 Subject: Merging upstream version 0.9.0. Signed-off-by: Daniel Baumann --- eos_downloader/cli/cli.py | 48 ++++---- eos_downloader/cli/debug/commands.py | 41 +++++-- eos_downloader/cli/get/commands.py | 211 +++++++++++++++++++++++++++-------- eos_downloader/cli/info/commands.py | 86 ++++++++++---- eos_downloader/cli/utils.py | 38 +++++++ 5 files changed, 320 insertions(+), 104 deletions(-) create mode 100644 eos_downloader/cli/utils.py (limited to 'eos_downloader/cli') diff --git a/eos_downloader/cli/cli.py b/eos_downloader/cli/cli.py index ddd0dea..ad77f2b 100644 --- a/eos_downloader/cli/cli.py +++ b/eos_downloader/cli/cli.py @@ -11,49 +11,51 @@ ARDL CLI Baseline. """ import click -from rich.console import Console -import eos_downloader -from eos_downloader.cli.get import commands as get_commands + +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.utils import AliasedGroup + -@click.group() +@click.group(cls=AliasedGroup) +@click.version_option(__version__) @click.pass_context -@click.option('--token', show_envvar=True, default=None, help='Arista Token from your customer account') +@click.option( + "--token", + show_envvar=True, + default=None, + help="Arista Token from your customer account", +) def ardl(ctx: click.Context, token: str) -> None: """Arista Network Download CLI""" ctx.ensure_object(dict) - ctx.obj['token'] = token + ctx.obj["token"] = token -@click.command() -def version() -> None: - """Display version of ardl""" - console = Console() - console.print(f'ardl is running version {eos_downloader.__version__}') - - -@ardl.group(no_args_is_help=True) +@ardl.group(cls=AliasedGroup, no_args_is_help=True) @click.pass_context -def get(ctx: click.Context) -> None: +def get(ctx: click.Context, cls: click.Group = AliasedGroup) -> None: # pylint: disable=redefined-builtin """Download Arista from Arista website""" -@ardl.group(no_args_is_help=True) +@ardl.group(cls=AliasedGroup, no_args_is_help=True) @click.pass_context -def info(ctx: click.Context) -> None: +def info(ctx: click.Context, cls: click.Group = AliasedGroup) -> None: # pylint: disable=redefined-builtin """List information from Arista website""" -@ardl.group(no_args_is_help=True) +@ardl.group(cls=AliasedGroup, no_args_is_help=True) @click.pass_context -def debug(ctx: click.Context) -> None: +def debug(ctx: click.Context, cls: click.Group = AliasedGroup) -> None: # pylint: disable=redefined-builtin """Debug commands to work with ardl""" + # ANTA CLI Execution @@ -64,13 +66,9 @@ def cli() -> None: get.add_command(get_commands.cvp) info.add_command(info_commands.eos_versions) debug.add_command(debug_commands.xml) - ardl.add_command(version) # Load CLI - ardl( - obj={}, - auto_envvar_prefix='arista' - ) + ardl(obj={}, auto_envvar_prefix="arista") -if __name__ == '__main__': +if __name__ == "__main__": cli() diff --git a/eos_downloader/cli/debug/commands.py b/eos_downloader/cli/debug/commands.py index 107b8a0..5a0d7f8 100644 --- a/eos_downloader/cli/debug/commands.py +++ b/eos_downloader/cli/debug/commands.py @@ -22,32 +22,51 @@ import eos_downloader.eos @click.command() @click.pass_context -@click.option('--output', default=str('arista.xml'), help='Path to save XML file', type=click.Path(), show_default=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)) +@click.option( + "--output", + default=str("arista.xml"), + help="Path to save XML file", + type=click.Path(), + show_default=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 + ), +) 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 - token = ctx.obj['token'] + 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', + image="unset", + software="EOS", + version="unset", token=token, - hash_method='sha512sum') + hash_method="sha512sum", + ) my_download.authenticate() - xml_object: ET.ElementTree = my_download._get_folder_tree() # pylint: disable=protected-access + xml_object: ET.ElementTree = ( + my_download.get_folder_tree() + ) # pylint: disable=protected-access xml_content = xml_object.getroot() - xmlstr = minidom.parseString(ET.tostring(xml_content)).toprettyxml(indent=" ", newl='') - with open(output, "w", encoding='utf-8') as f: + xmlstr = minidom.parseString(ET.tostring(xml_content)).toprettyxml( + indent=" ", newl="" + ) + with open(output, "w", encoding="utf-8") as f: f.write(str(xmlstr)) - console.print(f'XML file saved in: { output }') + console.print(f"XML file saved in: { output }") diff --git a/eos_downloader/cli/get/commands.py b/eos_downloader/cli/get/commands.py index 13a8eec..b4525fe 100644 --- a/eos_downloader/cli/get/commands.py +++ b/eos_downloader/cli/get/commands.py @@ -21,68 +21,156 @@ from rich.console import Console 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'] +EOS_IMAGE_TYPE = [ + "64", + "INT", + "2GB-INT", + "cEOS", + "cEOS64", + "vEOS", + "vEOS-lab", + "EOS-2GB", + "default", +] +CVP_IMAGE_TYPE = ["ova", "rpm", "kvm", "upgrade"] + @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.option('--output', default=str(os.path.relpath(os.getcwd(), start=os.curdir)), help='Path to save image', type=click.Path(),show_default=True) +@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.option( + "--output", + default=str(os.path.relpath(os.getcwd(), start=os.curdir)), + help="Path to save image", + type=click.Path(), + show_default=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)) +@click.option( + "--log-level", + "--log", + help="Logging level of the command", + default=None, + type=click.Choice( + ["debug", "info", "warning", "error", "critical"], case_sensitive=False + ), +) # 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) -@click.option('--import-docker', is_flag=True, help='Import docker image (only available with --image_type cEOSlab)', default=False) +@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, +) +@click.option( + "--import-docker", + is_flag=True, + help="Import docker image (only available with --image_type cEOSlab)", + default=False, +) def eos( - ctx: click.Context, image_type: str, output: str, log_level: str, eve_ng: bool, disable_ztp: bool, - import_docker: bool, docker_name: str, version: Union[str, None] = None, release_type: str = RTYPE_FEATURE, - latest: bool = False, branch: Union[str,None] = None - ) -> int: + ctx: click.Context, + image_type: str, + output: str, + log_level: str, + eve_ng: bool, + disable_ztp: bool, + import_docker: bool, + docker_name: str, + version: Union[str, None] = None, + release_type: str = RTYPE_FEATURE, + latest: bool = False, + branch: Union[str, None] = None, +) -> int: """Download EOS image from Arista website""" console = Console() # Get from Context - token = ctx.obj['token'] - if token is None or token == '': - console.print('❗ Token is unset ! Please configure ARISTA_TOKEN or use --token option', style="bold red") + token = ctx.obj["token"] + 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...", ) - console.print(f' - Image Type: {image_type}') - console.print(f' - Version: {version}') - + console.print( + "🪐 [bold blue]eos-downloader[/bold blue] is starting...", + ) + 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', + software="EOS", version=version, token=token, - hash_method='sha512sum') + hash_method="sha512sum", + ) my_download.authenticate() elif latest: my_download = eos_downloader.eos.EOSDownloader( image=image_type, - software='EOS', - version='unset', + software="EOS", + version="unset", token=token, - hash_method='sha512sum') + 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') + console.print( + f"[red]Error[/red], cannot find any version in {branch} for {release_type} release type" + ) sys.exit(1) my_download.version = str(latest_version) @@ -92,46 +180,71 @@ def eos( my_download.download_local(file_path=output, checksum=True) if import_docker: - my_download.docker_import( - image_name=docker_name - ) - console.print('✅ processing done !') + my_download.docker_import(image_name=docker_name) + console.print("✅ processing done !") sys.exit(0) - @click.command(no_args_is_help=True) @click.pass_context -@click.option('--format', default='upgrade', help='CVP Image type', type=click.Choice(CVP_IMAGE_TYPE), required=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) -@click.option('--log-level', '--log', help='Logging level of the command', default=None, type=click.Choice(['debug', 'info', 'warning', 'error', 'critical'], case_sensitive=False)) -def cvp(ctx: click.Context, version: str, format: str, output: str, log_level: str) -> int: +@click.option( + "--format", + default="upgrade", + help="CVP Image type", + type=click.Choice(CVP_IMAGE_TYPE), + required=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, +) +@click.option( + "--log-level", + "--log", + help="Logging level of the command", + default=None, + type=click.Choice( + ["debug", "info", "warning", "error", "critical"], case_sensitive=False + ), +) +def cvp( + ctx: click.Context, version: str, format: str, output: str, log_level: str +) -> int: """Download CVP image from Arista website""" console = Console() # Get from Context - token = ctx.obj['token'] - if token is None or token == '': - console.print('❗ Token is unset ! Please configure ARISTA_TOKEN or use --token option', style="bold red") + token = ctx.obj["token"] + 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...", ) - console.print(f' - Image Type: {format}') - console.print(f' - Version: {version}') + console.print( + "🪐 [bold blue]eos-downloader[/bold blue] is starting...", + ) + console.print(f" - Image Type: {format}") + console.print(f" - Version: {version}") my_download = eos_downloader.eos.EOSDownloader( image=format, - software='CloudVision', + software="CloudVision", version=version, token=token, - hash_method='md5sum') + hash_method="md5sum", + ) my_download.authenticate() my_download.download_local(file_path=output, checksum=False) - console.print('✅ processing done !') + console.print("✅ processing done !") sys.exit(0) diff --git a/eos_downloader/cli/info/commands.py b/eos_downloader/cli/info/commands.py index b51003b..64097a1 100644 --- a/eos_downloader/cli/info/commands.py +++ b/eos_downloader/cli/info/commands.py @@ -24,12 +24,53 @@ from eos_downloader.models.version import BASE_VERSION_STR, RTYPE_FEATURE, RTYPE @click.command(no_args_is_help=True) @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(ctx: click.Context, log_level: str, branch: Union[str,None] = None, release_type: str = RTYPE_FEATURE, latest: bool = False, verbose: bool = False) -> None: +@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( + ctx: click.Context, + log_level: str, + branch: Union[str, None] = None, + release_type: str = RTYPE_FEATURE, + latest: bool = False, + verbose: bool = False, +) -> None: # pylint: disable = too-many-branches """ List Available EOS version on Arista.com website. @@ -42,22 +83,23 @@ def eos_versions(ctx: click.Context, log_level: str, branch: Union[str,None] = N """ console = Console() # Get from Context - token = ctx.obj['token'] + 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', + image="unset", + software="EOS", + version="unset", token=token, - hash_method='sha512sum') + hash_method="sha512sum", + ) auth = my_download.authenticate() if verbose and auth: - console.print('✅ Authenticated on arista.com') + console.print("✅ Authenticated on arista.com") if release_type is not None: release_type = release_type.upper() @@ -67,21 +109,27 @@ def eos_versions(ctx: click.Context, log_level: str, branch: Union[str,None] = N 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') + 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}') + 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}') + console.print(f"Latest release for {branch}: {latest_version}") else: - console.print(f'Latest EOS release: {latest_version}') + console.print(f"Latest EOS release: {latest_version}") else: - console.print(f'{ latest_version }') + 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"}') + 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(f" → {str(version)}") else: pprint([str(version) for version in versions]) diff --git a/eos_downloader/cli/utils.py b/eos_downloader/cli/utils.py new file mode 100644 index 0000000..4a14f53 --- /dev/null +++ b/eos_downloader/cli/utils.py @@ -0,0 +1,38 @@ +#!/usr/bin/python +# coding: utf-8 -*- +# pylint: disable=inconsistent-return-statements + + +""" +Extension for the python ``click`` module +to provide a group or command with aliases. +""" + + +from typing import Any +import click + + +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)] + if not matches: + return None + if len(matches) == 1: + return click.Group.get_command(self, ctx, matches[0]) + ctx.fail(f"Too many matches: {', '.join(sorted(matches))}") + + def resolve_command(self, ctx: click.Context, args: Any) -> Any: + """Documentation to build""" + # always return the full command name + _, cmd, args = super().resolve_command(ctx, args) + return cmd.name, cmd, args -- cgit v1.2.3