diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 0000000..aebeb0f --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,8 @@ +version = 1 + +[[analyzers]] +name = "python" +enabled = true + + [analyzers.meta] + runtime_version = "3.x.x" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 93ba2e6..832b01b 100644 --- a/.gitignore +++ b/.gitignore @@ -145,5 +145,5 @@ cython_debug/ # APX - servers.json +fb8396d910253f82aa01c909b3705493782c4ab2.json diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apx.py b/apx.py index 4be6ed7..e286f31 100644 --- a/apx.py +++ b/apx.py @@ -1,32 +1,58 @@ -from os.path import isfile -from json import load -from os import getcwd -from sys import path, argv - -path.append(getcwd()) -from args import parser -from commands.commands import SHELL_COMMANDS, CommandFailedException - -SERVER_DATA = [] - -if not isfile("servers.json"): - raise FileNotFoundError("Server config not existing") - -with open("servers.json", "r") as read_file: - SERVER_DATA = load(read_file) - -parsed_args = parser.parse_args() -if parsed_args.cmd is None: - raise Exception("No cmd given") - -for server in parsed_args.server: - config = parsed_args.config - env = {"server_data": SERVER_DATA, "server": None, "server_config": None} - env["server"] = server - if config is not None: - env["server_config"] = config - if parsed_args.cmd not in SHELL_COMMANDS: - raise Exception(f"command {parsed_args.cmd} not found") - result = SHELL_COMMANDS[parsed_args.cmd](env, parsed_args.args) - if not result: - raise Exception(f"Command failed: {argv}") +import logging +from os import path +from sys import argv + + +if __package__ is None or __package__ == '': + from args import parser + from helpers import get_servers_data + from commands.router import SHELL_COMMANDS +else: + from cli.args import parser + from cli.helpers import get_servers_data + from cli.commands.router import SHELL_COMMANDS + + +ROOT_PATH = path.dirname(path.realpath(__file__)) + +logging.basicConfig(filename=path.join(ROOT_PATH, "cli.log"), + encoding='utf-8', level=logging.DEBUG, format='%(asctime)s %(message)s') + + +def get_args(): + parsed_args = parser.parse_args() + if parsed_args.cmd is None: + logging.error("No cmd given") + raise Exception("No cmd given") + return parsed_args + + +if __name__ == "__main__": + + server_data = get_servers_data() + parsed_args = get_args() + + env_tpl = {"server_data": server_data, + "server": None, "server_config": None} + + for server in parsed_args.server: + config = parsed_args.config + + env = env_tpl.copy() + env["server"] = server + + if config is not None: + env["server_config"] = config + + if parsed_args.cmd not in SHELL_COMMANDS: + raise Exception(f"command {parsed_args.cmd} not found") + + cli_command = SHELL_COMMANDS[parsed_args.cmd] + + logging.info( + f'Running command "{parsed_args.cmd}" env={env} args={parsed_args.args}') + result = cli_command(env, parsed_args.args) + + if not result: + raise Exception( + f"Command failed: {parsed_args.cmd} {argv} result: {result}") diff --git a/commands/__init__.py b/commands/__init__.py index c3a12b0..e69de29 100644 --- a/commands/__init__.py +++ b/commands/__init__.py @@ -1,29 +0,0 @@ -from typing import Union -from requests import post - - -def http_api_helper(env: dict, route: str, data: dict, method=post) -> Union[bool, str]: - if "server_data" not in env or env["server"] not in env["server_data"]: - raise Exception("Server.json invalid") - result_ok = False - result_text = None - secret = env["server_data"][env["server"]]["secret"] - url = env["server_data"][env["server"]]["url"] - try: - got = method( - url + f"/{route}", - headers={ - "authorization": secret, - "content-type": "application/x-www-form-urlencoded", - }, - data=data, - ) - - result_ok = got.status_code == 200 - result_text = got.text - except Exception as err: - print(result_text) - result_ok = False - result_text = str(err) - - return result_ok, result_text diff --git a/commands/build.py b/commands/build.py index 5477e9e..e850d09 100644 --- a/commands/build.py +++ b/commands/build.py @@ -1,13 +1,16 @@ from requests import post, get from os.path import exists, join -from os import listdir, mkdir, unlink -from json import load, loads +from os import listdir +from json import load import re -from shutil import copyfile, rmtree import tarfile -from commands import http_api_helper import io import zipfile +from .util import http_api_helper +import logging + +logger = logging.getLogger(__name__) + team_pattern = r"(?P.+)\s?#(?P.+)\:(?P\d+)" @@ -51,7 +54,7 @@ def get_final_filename(needle: str, short_name: str, number: str) -> str: def build_track_command(env, *args, **kwargs): if not env["server"]: - print("no server set") + logger.info("no server set") else: server_key = env["server"] server_data = env["server_data"][server_key] @@ -96,7 +99,7 @@ def build_track_command(env, *args, **kwargs): def build_skin_command(env, *args, **kwargs): if not env["server"]: - print("no server set") + logger.info("no server set") else: server_key = env["server"] server_data = env["server_data"][server_key] @@ -104,7 +107,7 @@ def build_skin_command(env, *args, **kwargs): secret = server_data["secret"] file_name = args[0][0] if not exists(file_name): - print("file not existing") + logger.info(f"file not exists: {file_name}") else: # read templates @@ -117,6 +120,7 @@ def build_skin_command(env, *args, **kwargs): if file.endswith(".veh"): with open(join(templates_path, file)) as file_handle: templates[file.replace(".veh", "")] = file_handle.read() + logger.info(f"Opening file: {file_name}") with open(file_name, "r") as file: data = load(file) veh_mods = data["cars"] @@ -140,12 +144,12 @@ def build_skin_command(env, *args, **kwargs): tar.add( join(build_path, mod_name, raw_file), raw_file ) - print(f"Adding {raw_file} to archive") + logger.info(f"Adding {raw_file} to archive") if ".png" in raw_file: tar.add( join(build_path, mod_name, raw_file), raw_file ) - print(f"Adding {raw_file} to archive") + logger.info(f"Adding {raw_file} to archive") for entry in entries: match = re.match(team_pattern, entry) name = match.group("name").strip() @@ -164,7 +168,7 @@ def build_skin_command(env, *args, **kwargs): else None ) if overwrites: - print( + logger.info( "Found overwrites for VEH template for entry {}".format( number ) @@ -188,7 +192,7 @@ def build_skin_command(env, *args, **kwargs): if not use_quotes else '"{}"'.format(value), ) - print( + logger.info( "Using value {} (in quotes: {}) for key {} of entry {}".format( value, use_quotes, @@ -230,10 +234,10 @@ def build_skin_command(env, *args, **kwargs): final_name = skin_file had_custom_file = True tar.add(path, final_name) - print(f"Adding {final_name} to archive") + logger.info(f"Adding {final_name} to archive") if had_custom_file and mod_name in templates: - print(f"Adding generated {info.name} to archive") + logger.info(f"Adding generated {info.name} to archive") tar.addfile(info, io.BytesIO(tar_template)) got = post( @@ -247,13 +251,13 @@ def build_skin_command(env, *args, **kwargs): def query_config(env, *args, **kwargs): got, text = http_api_helper(env, "config", {}, get) - print(text) + logger.info(text) return got def get_config_command(env, *args, **kwargs) -> bool: got = query_config(env, args, kwargs) - print(got) + logger.info(got) return True @@ -266,5 +270,5 @@ def get_ports_command(env, *args, **kwargs) -> bool: "TCP": [simulation_port, http_port, reciever_port], "UDP": [simulation_port, http_port + 1, http_port + 2], } - print(ports) + logger.info(ports) return True diff --git a/commands/chat.py b/commands/chat.py index 2dbbb82..f73d03d 100644 --- a/commands/chat.py +++ b/commands/chat.py @@ -1,11 +1,14 @@ -from commands import http_api_helper +from .util import http_api_helper +import logging + +logger = logging.getLogger(__name__) def chat_command(env, *args, **kwargs) -> bool: got, text = http_api_helper(env, "chat", { "message": ' '.join(args[0]) }) - print(text) + logger.info(text) return got @@ -16,7 +19,7 @@ def kick_command(env, *args, **kwargs) -> bool: got, text = http_api_helper(env, "kick", { "driver": driver }) - print(text) + logger.info(text) return got @@ -27,14 +30,14 @@ def rejoin_driver_command(env, *args, **kwargs) -> bool: got_undq, undq_text = http_api_helper(env, "chat", { "message": "/undq " + ' '.join(args[0]) }) - print(dq_text) - print(undq_text) + logger.info(dq_text) + logger.info(undq_text) return got_dq and got_undq def action_helper(env, action: str): got, text = http_api_helper(env, "/action/" + action, {}) - print(text) + logger.info(text) return got diff --git a/commands/deployment.py b/commands/deployment.py index f13e6d1..d7e87e7 100644 --- a/commands/deployment.py +++ b/commands/deployment.py @@ -1,7 +1,10 @@ from requests import post, get -from os.path import exists, basename +from os.path import basename from json import loads, dumps -from commands import http_api_helper +from .util import http_api_helper, validate_file_path +import logging + +logger = logging.getLogger(__name__) def deploy_command(env, *args, **kwargs) -> bool: @@ -15,10 +18,15 @@ def deploy_command(env, *args, **kwargs) -> bool: raise Exception("Status check failed") status_json = loads(running_text) - if status_json and "not_running" not in status_json: + if status_json["running"] is True: raise Exception("Server is running, deploy failed") + file_name = args[0][0] + validate_file_path(file_name) + rfm_filename = args[0][1] + validate_file_path(rfm_filename) + result = False upload_files = {} with open(file_name, "r") as file: @@ -52,17 +60,20 @@ def weather_update_command(env, *args, **kwargs) -> bool: raise Exception("Status check failed") status_json = loads(running_text) - if status_json and "not_running" not in status_json: + if status_json["running"] is True: raise Exception("Server is running, deploy failed") + file_name = args[0][0] + validate_file_path(file_name) result = False - upload_files = {} + # upload_files = {} with open(file_name, "r") as file: data = file.read() # add grip, if possible - json_data = loads(data) + # json_data = loads(data) + # TODO: why not json_data? got = post( url + "/weather", headers={"authorization": secret}, @@ -80,23 +91,25 @@ def install_command(env, *args, **kwargs) -> bool: raise Exception("Status check failed") status_json = loads(running_text) - if "not_running" not in status_json: + if status_json["running"] is True: raise Exception("Server is running, install failed") + got, text = http_api_helper(env, "install", {}, get) - print(text) + logger.info(text) return got def unlock_command(env, *args, **kwargs): if not env["server"]: - print("no server set") + logger.info("no server set") else: server_key = env["server"] server_data = env["server_data"][server_key] url = server_data["url"] secret = server_data["secret"] file = args[0][0] - got = post( + validate_file_path(file) + post( url + "/unlock", headers={"authorization": secret}, files={"unlock": open(file, "rb")}, @@ -106,26 +119,26 @@ def unlock_command(env, *args, **kwargs): def install_plugins_command(env, *args, **kwargs): if not env["server"]: - print("no server set") + logger.info("no server set") else: server_key = env["server"] server_data = env["server_data"][server_key] url = server_data["url"] secret = server_data["secret"] - file = args[0][0] + # file = args[0][0] files = {} paths = {} for index, arg in enumerate(args[0]): if "|" in arg: # the target path is provided - parts = arg.split("|") + parts = arg.split("|") base_name = basename(parts[0]) paths[base_name] = parts[1] files[base_name] = open(parts[0], "rb") - else: + else: base_name = basename(arg) files[base_name] = open(arg, "rb") - got = post( + post( url + "/plugins", data={"paths": dumps(paths)}, headers={"authorization": secret, "enctype": "multipart/form-data"}, @@ -136,14 +149,17 @@ def install_plugins_command(env, *args, **kwargs): def get_lockfile_command(env, *args, **kwargs): if not env["server"]: - print("no server set") + logger.info("no server set") else: server_key = env["server"] server_data = env["server_data"][server_key] url = server_data["url"] secret = server_data["secret"] + target_file = args[0][0] + got = get(url + "/lockfile", headers={"authorization": secret}) + with open(target_file, "wb") as f: f.write(got.content) return True @@ -151,13 +167,14 @@ def get_lockfile_command(env, *args, **kwargs): def get_thumbs_command(env, *args, **kwargs): if not env["server"]: - print("no server set") + logger.info("no server set") else: server_key = env["server"] server_data = env["server_data"][server_key] url = server_data["url"] secret = server_data["secret"] target_file = args[0][0] + got = get(url + "/thumbs", headers={"authorization": secret}) with open(target_file, "wb") as f: f.write(got.content) @@ -166,7 +183,7 @@ def get_thumbs_command(env, *args, **kwargs): def get_log_command(env, *args, **kwargs): if not env["server"]: - print("no server set") + logger.info("no server set") else: server_key = env["server"] server_data = env["server_data"][server_key] @@ -176,4 +193,4 @@ def get_log_command(env, *args, **kwargs): got = get(url + "/log", headers={"authorization": secret}) with open(target_file, "wb") as f: f.write(got.content) - return True \ No newline at end of file + return True diff --git a/commands/env.py b/commands/env.py index 666c42c..32f8b33 100644 --- a/commands/env.py +++ b/commands/env.py @@ -1,6 +1,9 @@ +import logging from requests import get -from commands import http_api_helper from json import loads +from .util import http_api_helper + +logger = logging.getLogger(__name__) def oneclick_start_command(env, *args, **kwargs) -> bool: @@ -8,11 +11,11 @@ def oneclick_start_command(env, *args, **kwargs) -> bool: if not is_running_command: raise Exception("Status check failed") status_json = loads(running_text) - if status_json and "not_running" not in status_json: + if status_json["running"] is True: raise Exception("Server already running") got, text = http_api_helper(env, "oneclick_start_server", {}, get) - print(text) + logger.info(text) return got @@ -21,11 +24,11 @@ def start_command(env, *args, **kwargs) -> bool: if not is_running_command: raise Exception("Status check failed") status_json = loads(running_text) - if "not_running" not in status_json: + if status_json["running"] is True: raise Exception("Server already running") got, text = http_api_helper(env, "start", {}, get) - print(got) + logger.info(got) return got @@ -34,10 +37,10 @@ def stop_command(env, *args, **kwargs) -> bool: if not is_running_command: raise Exception("Status check failed") status_json = loads(running_text) - if "not_running" in status_json: + if status_json["running"] is False: raise Exception("Server is not running") got, text = http_api_helper(env, "stop", {}, get) - print(got) + logger.info(got) return got @@ -47,7 +50,7 @@ def list_command(env, *args, **kwargs) -> bool: servers = env["server_data"] for key, value in servers.items(): url = value["url"] - print(f"{key} => {url}") + logger.info(f"{key} => {url}") return True @@ -56,9 +59,9 @@ def update_command(env, *args, **kwargs) -> bool: if not is_running_command: raise Exception("Status check failed") status_json = loads(running_text) - if status_json is not None and "not_running" not in status_json: + if status_json["running"] is True: raise Exception("Server is running") got, text = http_api_helper(env, "update", {}, get) - print(got, text) - return got \ No newline at end of file + logger.info(got) + return got diff --git a/commands/router.py b/commands/router.py new file mode 100644 index 0000000..18872cf --- /dev/null +++ b/commands/router.py @@ -0,0 +1,76 @@ +from requests import post + +from .env import ( + stop_command, + list_command, + oneclick_start_command, + update_command, +) +from .status import get_status_command, get_drivers_command, get_states_command +from .chat import ( + chat_command, + kick_command, + add_bot_command, + restart_weekend_command, + restart_race_command, + restart_race_command, + next_session_command, + rejoin_driver_command, +) +from .deployment import ( + deploy_command, + install_command, + unlock_command, + get_lockfile_command, + get_log_command, + install_plugins_command, + get_thumbs_command, + weather_update_command, +) +from .build import ( + build_skin_command, + build_track_command, + get_config_command, + get_ports_command, +) +from .util import ( + get_rfcmp_info_command, + get_components_in_directory_command, + check_config_command, +) + +SHELL_COMMANDS = { + "status": get_status_command, + "states": get_states_command, + "chat": chat_command, + "rejoin": rejoin_driver_command, + "kick": kick_command, + "addbot": add_bot_command, + "new_weekend": restart_weekend_command, + "restart": restart_race_command, + "advance": next_session_command, + "deploy": deploy_command, + "update": update_command, + "start": oneclick_start_command, + "stop": stop_command, + "list": list_command, + "drivers": get_drivers_command, + "build_skins": build_skin_command, + "build_track": build_track_command, + "config": get_config_command, + "ports": get_ports_command, + "install": install_command, + "unlock": unlock_command, + "lockfile": get_lockfile_command, + "log": get_log_command, + "rfcmpinfo": get_rfcmp_info_command, + "rfcmpdir": get_components_in_directory_command, + "checkconfig": check_config_command, + "plugins": install_plugins_command, + "thumbnails": get_thumbs_command, + "weatherupdate": weather_update_command, +} + + +class CommandFailedException(Exception): + pass diff --git a/commands/status.py b/commands/status.py index 09c86ef..309aa25 100644 --- a/commands/status.py +++ b/commands/status.py @@ -1,12 +1,14 @@ from requests import get -from json import loads +import logging + +logger = logging.getLogger(__name__) def get_status_command(env, *args, **kwargs): if not env["server"]: - print("no server set") + logger.info("no server set") else: - print(background_status(env)) + logger.info(background_status(env)) return True @@ -27,7 +29,7 @@ def background_status(env): def get_drivers_command(env, *args, **kwargs): if not env["server"]: - print("no server set") + logger.info("no server set") else: got = background_status(env) if got: @@ -47,7 +49,7 @@ def get_drivers_command(env, *args, **kwargs): vehicles, ) ) - print(mapped_vehicles) + logger.info(mapped_vehicles) else: return False return True @@ -55,7 +57,7 @@ def get_drivers_command(env, *args, **kwargs): def get_states_command(env, *args, **kwargs): if not env["server"]: - print("no server set") + logger.info("no server set") else: got = background_status(env) - print(got["states"]) + logger.info(got["states"]) diff --git a/commands/util.py b/commands/util.py index e448f23..ede8a44 100644 --- a/commands/util.py +++ b/commands/util.py @@ -1,8 +1,51 @@ +from os.path import exists as file_exists from subprocess import check_output, Popen, PIPE from re import match import glob -from os import listdir from json import loads +from typing import Union +from requests import post +from urllib.parse import urljoin +import logging + +logger = logging.getLogger(__name__) + + +def validate_file_path(path): + if not file_exists(path): + msg = f'File not found: "{path}"' + logger.error(msg) + raise Exception(msg) + return True + + +def http_api_helper(env: dict, route: str, data: dict, method=post) -> Union[bool, str]: + if "server_data" not in env or env["server"] not in env["server_data"]: + raise Exception("Server.json invalid") + result_ok = False + result_text = None + secret = env["server_data"][env["server"]]["secret"] + url = env["server_data"][env["server"]]["url"] + endpoint = urljoin(url, route) + logger.info(f'Requesting {endpoint}') + try: + got = method( + endpoint, + headers={ + "authorization": secret, + "content-type": "application/x-www-form-urlencoded", + }, + data=data, + ) + + result_ok = got.status_code == 200 + result_text = got.text + except Exception as err: + logger.error(result_text) + result_ok = False + result_text = str(err) + + return result_ok, result_text def get_rfcmp_info_command(env, *args, **kwargs): @@ -24,9 +67,9 @@ def get_rfcmp_info_command(env, *args, **kwargs): value = got.groups(1)[1] results[file][name] = value except: - print("File read error") + logger.error("File read error") return False - print(results) + logger.info(results) return True @@ -37,7 +80,7 @@ def get_components_in_directory_command(env, *args, **kwargs): for file in files: component_in_file = get_rfcmp_info_command(env, [file], kwargs) components.append(component_in_file) - print(components) + logger.info(components) return True @@ -45,4 +88,4 @@ def check_config_command(env, *args, **kwargs): config = args[0][0] with open(config, "r") as file: data = loads(file.read()) - print(data) + logger.info(data) diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..8174127 --- /dev/null +++ b/helpers.py @@ -0,0 +1,56 @@ +import logging +from os import path +from os.path import isfile +from json import load + +if __package__ is None or __package__ == "": + from commands.router import SHELL_COMMANDS +else: + from cli.commands.router import SHELL_COMMANDS + + +logger = logging.getLogger(__name__) + +ROOT_PATH = path.dirname(path.realpath(__file__)) +SERVERS_JSON_PATH = path.join(ROOT_PATH, "servers.json") + + +def get_servers_data(path=SERVERS_JSON_PATH): + if not isfile(path): + logging.error(f"Servers file not found: {path}") + raise FileNotFoundError("Server config not existing") + with open(path, "r") as read_file: + logging.debug(f"Reading file: {path}") + return load(read_file) + + +def run_apx_package_command(server_hash=None, command=None, args=None): + + if args is not None: + + if not isinstance(args, list) or len(args) == 0: + msg = f"APX package command args need to be not emty list, got args={args}" + logging.error(msg) + raise Exception(msg) + + server_data = get_servers_data() + + env = {"server_data": server_data, "server": server_hash, "server_config": None} + + if command not in SHELL_COMMANDS: + msg = f"APX package command not found {command}" + logger.error(msg) + raise Exception(msg) + + cli_command = SHELL_COMMANDS[command] + + logger.info( + f"APX package command running {command} server={server_hash} env={env} args={args}" + ) + + result = cli_command(env, args) + + if not result: + msg = f"APX package command failed {command} server={server_hash} env={env} args={args} result={result}" + logger.error(msg) + raise Exception(msg)