diff --git a/.docker/Dockerfile.das-toolbox b/.docker/Dockerfile.das-toolbox index bf3af2f..7f26a6e 100644 --- a/.docker/Dockerfile.das-toolbox +++ b/.docker/Dockerfile.das-toolbox @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 AS builder +FROM ubuntu:22.04 AS builder ENV TZ=America/Sao_Paulo \ DEBIAN_FRONTEND=noninteractive diff --git a/src/common/command.py b/src/common/command.py index 805e3e4..a682188 100644 --- a/src/common/command.py +++ b/src/common/command.py @@ -2,6 +2,7 @@ from typing import Any import click +from fabric import Connection from common.logger import logger @@ -34,6 +35,34 @@ class Command: help = "" short_help = "" params = [] + # TODO: Add more ways to connect, example: ssh-key file, etc. Look at the fabric docs. + remote_params = [ + CommandOption( + ["--remote"], + type=bool, + default=False, + is_flag=True, + help="whether to run the command on a remote server", + ), + CommandOption( + ["--host", "-H"], + type=str, + help="the login user for the remote connection", + required=False, + ), + CommandOption( + ["--user", "-H"], + type=str, + help="the login user for the remote connection", + required=False, + ), + CommandOption( + ["--port", "-H"], + type=int, + help="the remote port", + required=False, + ), + ] def __init__(self) -> None: self.command = click.Command( @@ -41,12 +70,64 @@ def __init__(self) -> None: callback=self.safe_run, help=self.help, short_help=self.short_help, - params=self.params, + params=self.params + self.remote_params, ) - def safe_run(self, **kwarg): + def _get_remote_kwargs(self, kwargs) -> tuple[bool, dict, dict]: + """ + Gets remote kwargs from kwargs. + Params: + Returns (bool, kwargs, remote_kwargs): + First value is whether the command should be run on a remote server or not. + """ + if not kwargs: + return (False, kwargs, {}) + remote_kwargs = { + "user": kwargs.pop("user") or "", + "port": kwargs.pop("port") or 22, + "host": kwargs.pop("host") or "", + } + remote = kwargs.pop("remote") or False + + return (remote, kwargs, remote_kwargs) + + def _dict_to_command_line_args(self, d: dict) -> str: + """ + Convert dict to command line args + + Params: + d (dict): the dict to be converted convert + + """ + args = [] + for key, value in d.items(): + arg_key = str(key).replace("_", "-") + + if isinstance(value, bool): + if value: + args.append(f"--{arg_key}") + else: + if value: + arg_value = str(value).lower() + arg = f"--{arg_key} {arg_value}" + args.append(arg) + + return " ".join(args) + + def _remote_run(self, kwargs, remote_kwargs): + ctx = click.get_current_context() + prefix = "das-cli" + command_path = " ".join(ctx.command_path.split(" ")[1:]) + extra_args = self._dict_to_command_line_args(kwargs) + command = f"{prefix} {command_path} {extra_args}" + Connection(**remote_kwargs).run(command) + + def safe_run(self, **kwargs): + remote, kwargs, remote_kwargs = self._get_remote_kwargs(kwargs) try: - return self.run(**kwarg) + if remote: + return self._remote_run(kwargs, remote_kwargs) + return self.run(**kwargs) except Exception as e: error_type = e.__class__.__name__ error_message = str(e) diff --git a/src/requirements.txt b/src/requirements.txt index e88d3a9..19ff45a 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,4 +1,4 @@ -Click==7.0 +Click==8.1.6 docker==7.1.0 distro==1.9.0 click-man==0.4.1 diff --git a/tests/integration/test_remote.bats b/tests/integration/test_remote.bats new file mode 100644 index 0000000..a65f9f3 --- /dev/null +++ b/tests/integration/test_remote.bats @@ -0,0 +1,55 @@ +#!/usr/local/bin/bats + +load 'libs/bats-support/load' +load 'libs/bats-assert/load' +load 'libs/utils' +load 'libs/docker' + +setup() { + if [ -z "${REMOTE_HOST+x}" ]; then + skip "Environment variable REMOTE_HOST is not set. Skipping all tests in this file." + fi + + libs=(hyperon-das hyperon-das-atomdb) + + pip3 install hyperon-das==0.7.13 +} + +teardown() { + pip3 uninstall -y hyperon-das + pip3 uninstall -y hyperon-das-atomdb +} + +@test "Listing all python library versions" { + local available_versions_regex="^.*\savailable\sversions:" + local available_versions_match_count + + run python3 src/das_cli.py python-library list --remote --host $REMOTE_HOST + + available_versions_match_count=$(echo "$output" | grep -cE "$available_versions_regex") + + assert_output --regexp "$available_versions_regex" + assert [ "$available_versions_match_count" -eq "${#libs[@]}" ] +} + +@test "Show available python library versions" { + local available_versions_regex="^.*\savailable\sversions:" + + for lib in "${libs[@]}"; do + local available_versions_match_count + + run python3 src/das_cli.py python-library list --library "$lib" --remote --host $REMOTE_HOST + available_versions_match_count=$(echo "$output" | grep -cE "$available_versions_regex") + + assert_output --regexp "$available_versions_regex" + assert [ "$available_versions_match_count" -eq 1 ] + done +} + +@test "Trying to show version to an invalid python library" { + local invalid_lib="invalid-python-library" + + run python3 src/das_cli.py python-library list --library $invalid_lib --remote --host $REMOTE_HOST + + assert_failure +}