diff --git a/run.py b/run.py index 2269e05..db5a842 100644 --- a/run.py +++ b/run.py @@ -53,7 +53,7 @@ def main(): cli = JabberwockyCLI(stdin, stdout) cli.container_manager = ContainerManagerClient() - inp = " ".join(argv[1:]) + inp = argv[1:] cli.parse_cmd(inp) diff --git a/src/cli/cli.py b/src/cli/cli.py index 6678cb4..bf2cc06 100644 --- a/src/cli/cli.py +++ b/src/cli/cli.py @@ -4,6 +4,7 @@ import re from sys import stdin, stdout +from typing import List from src.containers.container_manager_client import ContainerManagerClient @@ -24,7 +25,7 @@ def __init__(self, in_stream=stdin, out_stream=stdout) -> None: self.in_stream = in_stream self.out_stream = out_stream - def parse_cmd(self, cmd: str) -> None: + def parse_cmd(self, cmd: List[str]) -> None: """ Parses the cmd sent from script """ @@ -49,22 +50,19 @@ def parse_cmd(self, cmd: str) -> None: "ssh-address": self.ssh_address, } - cmd = cmd.strip() - cmd_list = cmd.split(None, 1) - if len(cmd_list) == 0: + if len(cmd) == 0: command = "help" - rest = "" - elif len(cmd_list) == 1: - command = cmd_list[0] - rest = "" + rest = [] else: - command, rest = cmd_list + command = cmd[0] + rest = cmd[1:] + if command not in subcmd_dict: self.out_stream.write(f"Command of '{command}' is not valid\n") return subcmd_dict[command](rest) - def help(self, cmd: str) -> None: # pylint: disable=unused-argument + def help(self, cmd: List[str]) -> None: # pylint: disable=unused-argument """ Prints the basic help menu for the CLI @@ -112,82 +110,84 @@ def help(self, cmd: str) -> None: # pylint: disable=unused-argument """ self.out_stream.write(help_str) - def interact(self, cmd: str) -> None: + def interact(self, cmd: List[str]) -> None: """ Allows user to directly interact with shell :param cmd: The rest of the command sent """ + name = cmd[0] comp = re.compile(CONTAINER_NAME_REGEX) - if not comp.match(cmd.strip()): - self.out_stream.write(f"'{cmd.strip()}' is not a valid container name\n") + if not comp.match(name): + self.out_stream.write(f"'{name}' is not a valid container name\n") return - self.container_manager.run_shell(cmd.strip()) + self.container_manager.run_shell(name) - def start(self, cmd: str) -> None: + def start(self, cmd: List[str]) -> None: """ Starts a container :param cmd: The rest of the command sent """ + name = cmd[0] comp = re.compile(CONTAINER_NAME_REGEX) - if not comp.match(cmd.strip()): - self.out_stream.write(f"'{cmd.strip()}' is not a valid container name\n") + if not comp.match(name): + self.out_stream.write(f"'{name}' is not a valid container name\n") return - self.container_manager.start(cmd) + self.container_manager.start(name) - def stop(self, cmd: str) -> None: + def stop(self, cmd: List[str]) -> None: """ Stops a container :param cmd: The rest of the command sent """ + name = cmd[0] comp = re.compile(CONTAINER_NAME_REGEX) - if not comp.match(cmd.strip()): - self.out_stream.write(f"'{cmd.strip()}' is not a valid container name\n") + if not comp.match(name): + self.out_stream.write(f"'{name}' is not a valid container name\n") return - self.container_manager.stop(cmd) + self.container_manager.stop(name) - def kill(self, cmd: str) -> None: + def kill(self, cmd: List[str]) -> None: """ Kills a container :param cmd: The rest of the command sent """ + name = cmd[0] comp = re.compile(CONTAINER_NAME_REGEX) - if not comp.match(cmd.strip()): - self.out_stream.write(f"'{cmd.strip()}' is not a valid container name\n") + if not comp.match(name): + self.out_stream.write(f"'{name}' is not a valid container name\n") return - self.container_manager.kill(cmd) + self.container_manager.kill(name) - def run(self, cmd: str) -> None: + def run(self, cmd: List[str]) -> None: """ Runs a command in the container :param cmd: The rest of the command sent """ - cmd_list = cmd.split(None, 1) - if len(cmd_list) != 2: + if len(cmd) < 2: self.out_stream.write("Command requires two arguments\n") return - container_name, command = (*cmd_list,) + container_name, command = cmd[0], cmd[1:] comp = re.compile(CONTAINER_NAME_REGEX) if not comp.match(container_name): self.out_stream.write(f"'{container_name}' is not a valid container name\n") return - self.container_manager.run_command(container_name, [command]) + self.container_manager.run_command(container_name, command) - def send_file(self, cmd: str) -> None: + def send_file(self, cmd: List[str]) -> None: """ Sends a file to a container :param cmd: The rest of the command sent """ - cmd_list = cmd.split() - if len(cmd_list) != 3: + if len(cmd) < 3: self.out_stream.write("Command requires three arguments\n") return - container_name, local_file, remote_file = (*cmd_list,) + container_name, local_file, remote_file = cmd[0], cmd[1], cmd[2] comp = re.compile(CONTAINER_NAME_REGEX) if not comp.match(container_name): self.out_stream.write(f"'{container_name}' is not a valid container name\n") @@ -201,17 +201,16 @@ def send_file(self, cmd: str) -> None: return self.container_manager.put_file(container_name, local_file, remote_file) - def get_file(self, cmd: str) -> None: + def get_file(self, cmd: List[str]) -> None: """ Gets a file from a container :param cmd: The rest of the command sent """ - cmd_list = cmd.split() - if len(cmd_list) != 3: + if len(cmd) < 3: self.out_stream.write("Command requires three arguments\n") return - container_name, remote_file, local_file = (*cmd_list,) + container_name, remote_file, local_file = cmd[0], cmd[1], cmd[2] comp = re.compile(CONTAINER_NAME_REGEX) if not comp.match(container_name): self.out_stream.write(f"'{container_name}' is not a valid container name\n") @@ -225,37 +224,36 @@ def get_file(self, cmd: str) -> None: return self.container_manager.get_file(container_name, remote_file, local_file) - def install(self, cmd: str) -> None: + def install(self, cmd: List[str]) -> None: """ Installs a container from an archive :param cmd: The rest of the command sent """ - cmd_list = cmd.split() - if len(cmd_list) != 2: + if len(cmd) != 2: self.out_stream.write("Command requires two arguments\n") return - archive_path_str, container_name = (*cmd_list,) + archive_path_str, container_name = cmd[0], cmd[1] comp = re.compile(CONTAINER_NAME_REGEX) if not comp.match(container_name): self.out_stream.write(f"'{container_name}' is not a valid container name\n") return self.container_manager.install(archive_path_str, container_name) - def delete(self, cmd: str) -> None: + def delete(self, cmd: List[str]) -> None: """ Deletes a container from the file system :param cmd: The rest of the command sent """ - container_name = cmd.strip() + container_name = cmd[0] comp = re.compile(CONTAINER_NAME_REGEX) if not comp.match(container_name): self.out_stream.write(f"'{container_name}' is not a valid container name\n") return self.container_manager.delete(container_name) - def download(self, cmd: str) -> None: # pylint: disable=unused-argument + def download(self, cmd: List[str]) -> None: # pylint: disable=unused-argument """ Downloads a container from an archive @@ -263,7 +261,7 @@ def download(self, cmd: str) -> None: # pylint: disable=unused-argument """ self.out_stream.write("Command not yet supported") - def archive(self, cmd: str) -> None: # pylint: disable=unused-argument + def archive(self, cmd: List[str]) -> None: # pylint: disable=unused-argument """ Sends a container to an archive @@ -271,7 +269,7 @@ def archive(self, cmd: str) -> None: # pylint: disable=unused-argument """ self.out_stream.write("Command not yet supported") - def add_repo(self, cmd: str) -> None: # pylint: disable=unused-argument + def add_repo(self, cmd: List[str]) -> None: # pylint: disable=unused-argument """ Adds an archive to the system @@ -279,7 +277,7 @@ def add_repo(self, cmd: str) -> None: # pylint: disable=unused-argument """ self.out_stream.write("Command not yet supported") - def update_repo(self, cmd: str) -> None: # pylint: disable=unused-argument + def update_repo(self, cmd: List[str]) -> None: # pylint: disable=unused-argument """ Updates an archive @@ -287,7 +285,7 @@ def update_repo(self, cmd: str) -> None: # pylint: disable=unused-argument """ self.out_stream.write("Command not yet supported") - def create(self, cmd: str) -> None: # pylint: disable=unused-argument + def create(self, cmd: List[str]) -> None: # pylint: disable=unused-argument """ Runs the container creation wizard @@ -295,7 +293,7 @@ def create(self, cmd: str) -> None: # pylint: disable=unused-argument """ self.out_stream.write("Command not yet supported") - def server_halt(self, cmd: str) -> None: # pylint: disable=unused-argument + def server_halt(self, cmd: List[str]) -> None: # pylint: disable=unused-argument """ Tells the server to halt @@ -303,7 +301,7 @@ def server_halt(self, cmd: str) -> None: # pylint: disable=unused-argument """ self.container_manager.server_halt() - def ping(self, cmd: str) -> None: # pylint: disable=unused-argument + def ping(self, cmd: List[str]) -> None: # pylint: disable=unused-argument """ Pings the server @@ -311,7 +309,7 @@ def ping(self, cmd: str) -> None: # pylint: disable=unused-argument """ self.container_manager.ping() - def ssh_address(self, cmd: str) -> None: # pylint: disable=unused-argument + def ssh_address(self, cmd: List[str]) -> None: # pylint: disable=unused-argument """ Prints the information necessary to SSH into the container's shell diff --git a/src/containers/container_manager_client.py b/src/containers/container_manager_client.py index d0469da..e07b2a6 100644 --- a/src/containers/container_manager_client.py +++ b/src/containers/container_manager_client.py @@ -79,6 +79,7 @@ def start(self, container_name: str) -> None: sock.send(bytes(container_name, "utf-8")) self._recv_expect(sock, 1024, b"OK") sock.close() + self.run_command(container_name, ["cat /etc/motd"]) def stop(self, container_name: str) -> None: """ @@ -287,7 +288,13 @@ def _send_select(self) -> None: try: while not self.recv_closed: if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []): - self.sock.send(bytes(sys.stdin.readline(), "utf-8")) + buffer = bytes(sys.stdin.readline(), "utf-8") + while buffer: + msg = buffer[:255] + self.sock.send(bytes([len(msg)]) + msg) + buffer = buffer[255:] + else: + self.sock.send(b"\x00") time.sleep(0.1) except (ConnectionError, OSError): pass @@ -306,7 +313,11 @@ def _send_msvcrt(self) -> None: if char == "\r": print(end="\n") - self.sock.send(bytes(msg + "\n", "utf-8")) + buffer = bytes(msg + "\n", "utf-8") + while buffer: + msg = buffer[:255] + self.sock.send(bytes([len(msg)]) + msg) + buffer = buffer[255:] msg = "" elif char == "\b": diff --git a/src/containers/container_manager_server.py b/src/containers/container_manager_server.py index fd6c830..800b5a1 100644 --- a/src/containers/container_manager_server.py +++ b/src/containers/container_manager_server.py @@ -397,8 +397,11 @@ def send_and_recv(self): def _recv(self): try: - while msg := self.client_sock.recv(1024): - self.stdin.write(msg) + while msg := self.client_sock.recv(1 << 16): + while msg: + size = msg[0] + self.stdin.write(msg[1:size + 1]) + msg = msg[size + 1:] except (ConnectionError, OSError): pass @@ -411,6 +414,7 @@ def _send_stdout(self): finally: self.stdout_closed = True if self.stderr_closed: + time.sleep(0.25) self.client_sock.close() def _send_stderr(self): @@ -422,4 +426,5 @@ def _send_stderr(self): finally: self.stderr_closed = True if self.stdout_closed: + time.sleep(0.25) self.client_sock.close() diff --git a/src/containers/exceptions.py b/src/containers/exceptions.py index ca5aa0d..7a46dc2 100644 --- a/src/containers/exceptions.py +++ b/src/containers/exceptions.py @@ -76,3 +76,10 @@ class FailedToAuthorizeKeyError(RuntimeError): """ Raised during failure to authorize keys """ + + +class ContainerAlreadyExistsError(RuntimeError): + """ + Raised during container installation when a + container of the desired name already exists + """ diff --git a/src/system/syspath.py b/src/system/syspath.py index 1449235..38a4aa3 100644 --- a/src/system/syspath.py +++ b/src/system/syspath.py @@ -107,7 +107,7 @@ def install_container(archive_path: Path, container_name: str) -> None: :param archive_path: The path to the archive """ if not tarfile.is_tarfile(str(archive_path)): - raise FileNotFoundError(str(archive_path)) + raise TypeError(f"'{archive_path}' is not a tar archive") with tarfile.open(str(archive_path)) as tar: tar.extractall(path=get_container_dir(container_name)) # TODO Sanity check this extraction