From f810a37247497864d560d857301bbb971edd2737 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 21 May 2024 11:19:23 +0200 Subject: [PATCH] add cli ui --- xrpld_netgen/cli.py | 103 +++++++++++++++++++++-- xrpld_netgen/deploykit/prerequisites.sh | 14 ++++ xrpld_netgen/main.py | 44 ++++++++-- xrpld_netgen/network.py | 24 ++++-- xrpld_netgen/utils/deploy_kit.py | 15 +++- xrpld_netgen/utils/misc.py | 105 +++++++++++++++++++++--- 6 files changed, 267 insertions(+), 38 deletions(-) create mode 100755 xrpld_netgen/deploykit/prerequisites.sh diff --git a/xrpld_netgen/cli.py b/xrpld_netgen/cli.py index d51e84c..6cb618a 100644 --- a/xrpld_netgen/cli.py +++ b/xrpld_netgen/cli.py @@ -47,8 +47,11 @@ enable_node_amendment, ) from xrpld_netgen.utils.misc import ( - run_file, remove_directory, + bcolors, + check_deps, + run_start, + run_stop, ) basedir = os.path.abspath(os.path.dirname(__file__)) @@ -58,6 +61,16 @@ def main(): + print("") + print(" _ __ ____ ____ __ ____ _ __ __ ______ ") + print(" | |/ // __ \/ __ \/ / / __ \ / | / /__ / /_/ ____/__ ____ ") + print(" | // /_/ / /_/ / / / / / / / |/ / _ \/ __/ / __/ _ \/ __ \ ") + print(" / |/ _, _/ ____/ /___/ /_/ / / /| / __/ /_/ /_/ / __/ / / / ") + print("/_/|_/_/ |_/_/ /_____/_____/ /_/ |_/\___/\__/\____/\___/_/ /_/ ") + print("") + + check_deps([f"{basedir}/deploykit/prerequisites.sh"]) + parser = argparse.ArgumentParser( description="A python cli to build xrpld networks and standalone ledgers." ) @@ -312,12 +325,23 @@ def main(): NETWORK_TYPE = args.network_type NETWORK_ID = args.network_id + print( + f"{bcolors.BLUE}Starting Local Network with the following parameters:{bcolors.END}" + ) + print(f" - Log Level: {LOG_LEVEL}") + print(f" - Public Key: {PUBLIC_KEY}") + print(f" - Import Key: {IMPORT_KEY}") + print(f" - Protocol: {PROTOCOL}") + print(f" - Network Type: {NETWORK_TYPE}") + print(f" - Network ID: {NETWORK_ID}") + start_local( LOG_LEVEL, PUBLIC_KEY, IMPORT_KEY, PROTOCOL, NETWORK_TYPE, NETWORK_ID ) if args.command == "stop:local": - run_file("./stop.sh") + print(f"{bcolors.BLUE}Stopping Local Network{bcolors.END}") + run_stop(["./stop.sh"]) # CREATE NETWORK if args.command == "create:network": @@ -344,6 +368,19 @@ def main(): if not QUORUM: QUORUM = NUM_VALIDATORS - 1 + print( + f"{bcolors.BLUE}Creating Network with the following parameters:{bcolors.END}" + ) + print(f" - Log Level: {LOG_LEVEL}") + print(f" - Protocol: {PROTOCOL}") + print(f" - Number of Validators: {NUM_VALIDATORS}") + print(f" - Number of Peers: {NUM_PEERS}") + print(f" - Network ID: {NETWORK_ID}") + print(f" - Build Server: {BUILD_SERVER}") + print(f" - Build Version: {BUILD_VERSION}") + print(f" - Genesis: {GENESIS}") + print(f" - Quorum: {QUORUM}") + create_network( LOG_LEVEL, import_vl_key, @@ -363,6 +400,14 @@ def main(): NODE_VERSION = args.node_version BUILD_SERVER = args.build_server BUILD_VERSION = args.build_version + print( + f"{bcolors.BLUE}Updating Node Version with the following parameters:{bcolors.END}" + ) + print(f" - Network Name: {NAME}") + print(f" - Node ID: {NODE_ID}") + print(f" - Node Type: {NODE_VERSION}") + print(f" - Build Server: {BUILD_SERVER}") + print(f" - Build Version: {BUILD_VERSION}") update_node_binary(NAME, NODE_ID, NODE_VERSION, BUILD_SERVER, BUILD_VERSION) if args.command == "enable:amendment": @@ -370,19 +415,34 @@ def main(): AMENDMENT_NAME = args.amendment_name NODE_ID = args.node_id NODE_VERSION = args.node_version + print( + f"{bcolors.BLUE}Enabling Amendment with the following parameters:{bcolors.END}" + ) + print(f" - Network Name: {NAME}") + print(f" - Amendment Name: {AMENDMENT_NAME}") + print(f" - Node ID: {NODE_ID}") + print(f" - Node Type: {NODE_VERSION}") enable_node_amendment(NAME, AMENDMENT_NAME, NODE_ID, NODE_VERSION) # MANAGE NETWORK/STANDALONE if args.command == "start": NAME = args.name - run_file(f"{basedir}/{NAME}/start.sh") + print(f"{bcolors.BLUE}Starting Network: {NAME}{bcolors.END}") + run_start( + [f"{basedir}/{NAME}/start.sh"], + PROTOCOL, + BUILD_VERSION, + "network", + ) if args.command == "stop": NAME = args.name - run_file(f"{basedir}/{NAME}/stop.sh") + print(f"{bcolors.BLUE}Stopping Network: {NAME}{bcolors.END}") + run_stop(f"{basedir}/{NAME}/stop.sh") if args.command == "remove": NAME = args.name + print(f"{bcolors.BLUE}Removing Network: {NAME}{bcolors.END}") remove_directory(f"{basedir}/{NAME}") # UP STANDALONE @@ -413,6 +473,20 @@ def main(): BUILD_SERVER: str = "rippleci" BUILD_TYPE: str = "image" + print( + f"{bcolors.BLUE}Setting Up Standalone Network with the following parameters:{bcolors.END}" + ) + print(f" - Log Level: {LOG_LEVEL}") + print(f" - Build Type: {BUILD_TYPE}") + print(f" - Public Key: {PUBLIC_KEY}") + print(f" - Import Key: {IMPORT_KEY}") + print(f" - Protocol: {PROTOCOL}") + print(f" - Network Type: {NETWORK_TYPE}") + print(f" - Network ID: {NETWORK_ID}") + print(f" - Build Server: {BUILD_SERVER}") + print(f" - Build Version: {BUILD_VERSION}") + print(f" - IPFS Server: {IPFS_SERVER}") + if BUILD_TYPE == "image": create_standalone_image( LOG_LEVEL, @@ -438,14 +512,23 @@ def main(): IPFS_SERVER, ) - run_file(f"{basedir}/{PROTOCOL}-{BUILD_VERSION}/start.sh") - print(f"Run with: xrpld-netgen start --name {PROTOCOL}-{BUILD_VERSION}") + run_start( + [f"{basedir}/{PROTOCOL}-{BUILD_VERSION}/start.sh"], + PROTOCOL, + BUILD_VERSION, + "standalone", + ) # DOWN STANDALONE if args.command == "down:standalone": NAME = args.name + print( + f"{bcolors.BLUE}Taking Down Standalone Network with the following parameters:{bcolors.END}" + ) + if NAME: - run_file(f"{basedir}/{NAME}/stop.sh") + print(f" - Network Name: {NAME}") + run_stop([f"{basedir}/{NAME}/stop.sh"]) remove_directory(f"{basedir}/{NAME}") return @@ -458,8 +541,10 @@ def main(): if PROTOCOL == "xrpl" and not BUILD_VERSION: BUILD_VERSION: str = XRPL_RELEASE - run_file(f"{basedir}/{PROTOCOL}-{BUILD_VERSION}/stop.sh") - # remove_directory(f"{basedir}/{PROTOCOL}-{BUILD_VERSION}") + print(f" - Network Name: {NAME}") + print(f" - Protocol: {PROTOCOL}") + print(f" - Build Version: {BUILD_VERSION}") + run_stop([f"{basedir}/{PROTOCOL}-{BUILD_VERSION}/stop.sh"]) if __name__ == "__main__": diff --git a/xrpld_netgen/deploykit/prerequisites.sh b/xrpld_netgen/deploykit/prerequisites.sh new file mode 100755 index 0000000..8a2da0f --- /dev/null +++ b/xrpld_netgen/deploykit/prerequisites.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +errors=0 + +which_docker=$(which docker) +[[ "$which_docker" = "" ]] && echo -e "❌ docker not installed" && errors=1 || echo "✅ docker installed" + +has_internet=$(curl --connect-timeout 2 --silent https://build.xahau.tech/ | grep Index| wc -l| xargs) +[[ "$has_internet" -lt 2 ]] && echo -e "❌ cannot reach xahau build server" && errors=1 || echo "✅ can reach xahau build server" + +can_run_docker=$(docker run --platform linux/amd64 --rm ubuntur uname -a 2>&1|grep x86_64|wc -l|xargs) +[[ "$can_run_docker" -gt 0 ]] && echo -e "❌ can run linux/amd64 in docker" && errors=1 || echo "✅ can run linux/amd64 in docker" + +exit $errors \ No newline at end of file diff --git a/xrpld_netgen/main.py b/xrpld_netgen/main.py index 860ca10..d3ccae3 100644 --- a/xrpld_netgen/main.py +++ b/xrpld_netgen/main.py @@ -13,11 +13,7 @@ get_commit_hash_from_server_version, download_file_at_commit, ) -from xrpld_netgen.utils.misc import ( - generate_ports, - save_local_config, - run_file, -) +from xrpld_netgen.utils.misc import generate_ports, save_local_config, bcolors from xrpl_helpers.common.utils import write_file, read_json from xrpl_helpers.rippled.utils import ( update_amendments, @@ -132,6 +128,7 @@ def create_standalone_folder( ) os.makedirs(f"{basedir}/{protocol}-{name}/config", exist_ok=True) save_local_config(cfg_path, configs[0].data, configs[1].data) + print(f"✅ {bcolors.CYAN}Creating config") lines: List[str] = get_feature_lines_from_content(feature_content) features_json: Dict[str, Any] = parse_rippled_amendments(lines) @@ -140,6 +137,8 @@ def create_standalone_folder( f"{basedir}/{protocol}-{name}/genesis.json", json.dumps(genesis_json, indent=4, sort_keys=True), ) + print(f"✅ {bcolors.CYAN}Updating features") + dockerfile: str = create_dockerfile( binary, name, @@ -160,6 +159,7 @@ def create_standalone_folder( f"{basedir}/deploykit/{protocol}.entrypoint", f"{basedir}/{protocol}-{name}/entrypoint", ) + print(f"✅ {bcolors.CYAN}Building docker container...") pwd_str: str = "${PWD}" services[f"{protocol}"] = { "build": { @@ -318,6 +318,7 @@ def create_binary_folder( ) os.makedirs(f"{basedir}/{protocol}-{name}/config", exist_ok=True) save_local_config(cfg_path, configs[0].data, configs[1].data) + print(f"✅ {bcolors.CYAN}Creating config") lines: List[str] = get_feature_lines_from_content(feature_content) features_json: Dict[str, Any] = parse_rippled_amendments(lines) @@ -326,6 +327,10 @@ def create_binary_folder( f"{basedir}/{protocol}-{name}/genesis.json", json.dumps(genesis_json, indent=4, sort_keys=True), ) + print(f"✅ {bcolors.CYAN}Updating features") + # for k, v in features_json.items(): + # print(f"{bcolors.GREEN}feature: {bcolors.BLUE}{k}") + dockerfile: str = create_dockerfile( binary, name, @@ -346,6 +351,7 @@ def create_binary_folder( f"{basedir}/deploykit/{protocol}.entrypoint", f"{basedir}/{protocol}-{name}/entrypoint", ) + print(f"✅ {bcolors.CYAN}Building docker container...") pwd_str: str = "${PWD}" services[f"{protocol}"] = { "build": { @@ -502,6 +508,8 @@ def create_local_folder( ) os.makedirs(cfg_path, exist_ok=True) save_local_config(cfg_path, configs[0].data, configs[1].data) + print(f"✅ {bcolors.CYAN}Creating config") + content: str = get_feature_lines_from_path( "../src/ripple/protocol/impl/Feature.cpp" ) @@ -511,6 +519,7 @@ def create_local_folder( f"{cfg_path}/genesis.json", json.dumps(genesis_json, indent=4, sort_keys=True), ) + print(f"✅ {bcolors.CYAN}Updating features") def start_local( @@ -564,4 +573,27 @@ def start_local( stop_sh_content: str = update_stop_sh(protocol, name, 0, 0, False, True) write_file("stop.sh", stop_sh_content) os.chmod("stop.sh", 0o755) - run_file("./start.sh") + import sys + import subprocess + + try: + result = subprocess.run( + ["./start.sh"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + if result.returncode == 0: + print( + f"{bcolors.CYAN}{protocol.capitalize()} local running at: {bcolors.PURPLE}6006 {bcolors.END}" + ) + print(f"{bcolors.CYAN}Explorer running / starting container{bcolors.END}") + print(f"Listening at: {bcolors.PURPLE}http://localhost:4000{bcolors.END}") + else: + print(f"{bcolors.RED}ERROR{bcolors.END}", file=sys.stderr) + sys.exit(1) + except subprocess.CalledProcessError: + print( + f"{bcolors.RED}❌ Cannot connect to the Docker daemon at docker.sock. Is the docker daemon running?{bcolors.END}" + ) + sys.exit(1) diff --git a/xrpld_netgen/network.py b/xrpld_netgen/network.py index fbec4cc..b1ec3c7 100644 --- a/xrpld_netgen/network.py +++ b/xrpld_netgen/network.py @@ -28,8 +28,9 @@ save_local_config, get_node_port, sha512_half, - run_file, + run_stop, remove_directory, + bcolors, ) from xrpl_helpers.common.utils import write_file, read_json @@ -102,6 +103,8 @@ def create_node_folders( validators.append(keys["public_key"]) tokens.append(token) + print(f"✅ {bcolors.CYAN}Created validator keys") + for i in range(1, num_validators + 1): ips_dir = ips[i - 1] if ansible else f"vnode{i}" node_dir = f"vnode{i}" @@ -141,6 +144,8 @@ def create_node_folders( os.makedirs(f"{basedir}/{name}-cluster/{node_dir}/config", exist_ok=True) save_local_config(cfg_path, configs[0].data, configs[1].data) + print(f"✅ {bcolors.CYAN}Created validator: {i} config") + # default features features_json: Any = read_json(f"{basedir}/default.{protocol}.features.json") @@ -166,6 +171,8 @@ def create_node_folders( json.dumps(features_json, indent=4, sort_keys=True), ) + print(f"✅ {bcolors.CYAN}Updated validator: {i} features") + if protocol == "xahau": shutil.copyfile( f"{basedir}/{name}-cluster/rippled.{name}", @@ -194,6 +201,8 @@ def create_node_folders( f"{basedir}/{name}-cluster/{node_dir}/entrypoint", ) + print(f"✅ {bcolors.CYAN}Built validator: {i} docker container...") + pwd_str: str = basedir services[f"vnode{i}"] = { "build": { @@ -250,6 +259,8 @@ def create_node_folders( os.makedirs(f"{basedir}/{name}-cluster/{node_dir}/config", exist_ok=True) save_local_config(cfg_path, configs[0].data, configs[1].data) + print(f"✅ {bcolors.CYAN}Created peer: {i} config") + # default features features_json: Any = read_json(f"{basedir}/default.xahau.features.json") @@ -275,6 +286,8 @@ def create_node_folders( json.dumps(features_json, indent=4, sort_keys=True), ) + print(f"✅ {bcolors.CYAN}Updated peer: {i} features") + if protocol == "xahau": shutil.copyfile( f"{basedir}/{name}-cluster/rippled.{name}", @@ -302,6 +315,9 @@ def create_node_folders( f"{basedir}/deploykit/{protocol}.entrypoint", f"{basedir}/{name}-cluster/{node_dir}/entrypoint", ) + + print(f"✅ {bcolors.CYAN}Built peer: {i} docker container...") + pwd_str: str = basedir services[f"pnode{i}"] = { "build": { @@ -716,15 +732,11 @@ def create_ansible( write_file(f"{basedir}/{name}-cluster/ansible/hosts.txt", hosts_content) -def start_network(name: str): - run_file(f"{basedir}/{name}/start.sh") - - def stop_network(name: str, remove: bool = False): cmd: List[str] = [f"{basedir}/{name}/stop.sh"] if remove: cmd.append("--remove") - run_file(cmd) + run_stop(cmd) def remove_network(name: str): diff --git a/xrpld_netgen/utils/deploy_kit.py b/xrpld_netgen/utils/deploy_kit.py index b2b6abb..35e8fdd 100644 --- a/xrpld_netgen/utils/deploy_kit.py +++ b/xrpld_netgen/utils/deploy_kit.py @@ -6,6 +6,8 @@ import os import yaml +from .misc import bcolors + class DockerVars: def __init__( @@ -101,12 +103,18 @@ def create_dockerfile( def download_binary(url: str, save_path: str) -> None: - # Check if the file already exists + version: str = url.split("/")[-1] + print(f"{bcolors.END}Fetching versions of xahaud..") if os.path.exists(save_path): - print(f"The file {save_path} already exists.") + print( + f"{bcolors.GREEN}version: {bcolors.BLUE}{version} {bcolors.END}already exists..." + ) return try: + print( + f"{bcolors.GREEN}Found latest version: {bcolors.BLUE}{version}, downloading..." + ) # Send a GET request to the URL response = requests.get(url, stream=True) @@ -120,9 +128,8 @@ def download_binary(url: str, save_path: str) -> None: # Set the file permissions to be readable and executable by the owner os.chmod(save_path, 0o755) - print(f"Download complete. File saved as {save_path}") except requests.exceptions.RequestException as e: - print(f"An error occurred: {e}") + print(f"{bcolors.GREEN}An error occurred: {e}") def update_dockerfile(build_version: str, save_path: str) -> None: diff --git a/xrpld_netgen/utils/misc.py b/xrpld_netgen/utils/misc.py index 31550ea..10c5460 100644 --- a/xrpld_netgen/utils/misc.py +++ b/xrpld_netgen/utils/misc.py @@ -8,34 +8,113 @@ import subprocess import shlex import hashlib +import sys -from typing import Dict, Any, Tuple +from typing import Dict, Any, Tuple, List + + +class bcolors: + RED = "\033[31m" + GREEN = "\033[32m" + BLUE = "\033[34m" + PURPLE = "\033[35m" + CYAN = "\033[36m" + END = "\033[0m" def remove_directory(directory_path: str) -> None: try: + name: str = directory_path.split("/")[-1] shutil.rmtree(directory_path) - print(f"Directory '{directory_path}' has been removed successfully.") + print( + f"{bcolors.CYAN}Directory {name} has been removed successfully. {bcolors.END}" + ) except FileNotFoundError: - print(f"The directory '{directory_path}' does not exist.") + print( + f"{bcolors.RED}❌ The file {directory_path} does not exist or cannot be found.{bcolors.END}" + ) except PermissionError: - print(f"Permission denied: Unable to remove '{directory_path}'.") + print( + f"{bcolors.RED}❌ Permission denied: Unable to remove '{directory_path}'." + ) except Exception as e: - print(f"An error occurred: {e}") + print(f"{bcolors.RED}❌ An OS error occurred: {e}.{bcolors.END}") + + +def run_start(cmd: List[str], protocol: str, version: str, type: str): + try: + result = subprocess.run( + cmd, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + if result.returncode == 0: + print( + f"{bcolors.CYAN}{protocol.capitalize()} {bcolors.GREEN}{version} {type} running at: {bcolors.PURPLE}6006 {bcolors.END}" + ) + print(f"{bcolors.CYAN}Explorer running / starting container{bcolors.END}") + print(f"Listening at: {bcolors.PURPLE}http://localhost:4000{bcolors.END}") + else: + print(f"{bcolors.RED}ERROR{bcolors.END}", file=sys.stderr) + sys.exit(1) + except subprocess.CalledProcessError: + print( + f"{bcolors.RED}❌ Cannot connect to the Docker daemon at docker.sock. Is the docker daemon running?{bcolors.END}" + ) + sys.exit(1) + except FileNotFoundError: + print(f"{bcolors.RED}❌ The file {cmd[0]} does not exist or cannot be found.") + sys.exit(1) + except OSError as e: + print(f"{bcolors.RED}❌ An OS error occurred: {e}") + sys.exit(1) + + +def run_stop(cmd: List[str]): + try: + result = subprocess.run( + cmd, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + if result.returncode == 0: + print(f"{bcolors.CYAN}shut down docker container {bcolors.END}") + else: + print(f"{bcolors.RED}ERROR{bcolors.END}", file=sys.stderr) + sys.exit(1) + except subprocess.CalledProcessError: + print( + f"{bcolors.RED}❌ Cannot connect to the Docker daemon at docker.sock. Is the docker daemon running?{bcolors.END}" + ) + sys.exit(1) + except FileNotFoundError: + print(f"{bcolors.RED}❌ The file {cmd[0]} does not exist or cannot be found.") + sys.exit(1) + except OSError as e: + print(f"{bcolors.RED}❌ An OS error occurred: {e}") + sys.exit(1) -def run_file(file_path: str) -> None: +def check_deps(cmd: List[str]) -> None: try: - # Run the file as a subprocess - result = subprocess.run(file_path, check=True) - print(result) - print(f"File {file_path} executed successfully.") + print(bcolors.BLUE + "Checking dependencies: \n") + result = subprocess.run(cmd, check=True) + if result.returncode == 0: + print(f"{bcolors.GREEN}Dependencies OK{bcolors.END}") + else: + print(f"{bcolors.RED}Dependency ERROR{bcolors.END}") + sys.exit(1) except subprocess.CalledProcessError as e: - print(f"An error occurred while trying to run the file: {e}") + print(f"{bcolors.RED}Dependency ERROR{bcolors.END}") + sys.exit(1) except FileNotFoundError: - print(f"The file {file_path} does not exist or cannot be found.") + print(f"{bcolors.RED}Dependency ERROR{bcolors.END}") + sys.exit(1) except OSError as e: - print(f"An error occurred while trying to run the file: {e}") + print(f"{bcolors.RED}Dependency ERROR{bcolors.END}") + sys.exit(1) def run_command(dir: str, command: str) -> None: