From ef779233b9d6688864768df4e0f96cfeb314ebb6 Mon Sep 17 00:00:00 2001 From: clore Date: Mon, 1 Apr 2024 01:00:32 +0000 Subject: [PATCH] use wrapped cli to create containers --- lib/config.py | 5 +- lib/docker_cli_wrapper.py | 112 ++++++++++++++++++++++++++++++++++++++ lib/docker_deploy.py | 21 ++++--- 3 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 lib/docker_cli_wrapper.py diff --git a/lib/config.py b/lib/config.py index 9d25fb8..7a67080 100644 --- a/lib/config.py +++ b/lib/config.py @@ -14,7 +14,7 @@ hard_config = { "run_iptables_with_sudo":True, "clore_iptables_rules":[ "-A INPUT -s -j DROP", - "-I DOCKER-USER -i -d -j DROP" + "-I FORWARD -i -d -j DROP" ], "clore_br_first_allowed_octet":"172", "ws_peers_recheck_interval": 300, @@ -31,7 +31,8 @@ hard_config = { "max_container_log_size": 262144, # Characters "container_log_streaming_interval": 2, # Seconds "maximum_service_loop_time": 900, # Seconds, failsafe variable - if service is stuck processing longer than this timeframe it will lead into restarting the app - "maximum_pull_service_loop_time": 14400 # Exception for image pulling + "maximum_pull_service_loop_time": 14400, # Exception for image pulling + "creation_engine": "wrapper" # "wrapper" or "sdk" | Wrapper - wrapped docker cli, SDK - docker sdk } parser = argparse.ArgumentParser(description='Example argparse usage') diff --git a/lib/docker_cli_wrapper.py b/lib/docker_cli_wrapper.py new file mode 100644 index 0000000..f225f52 --- /dev/null +++ b/lib/docker_cli_wrapper.py @@ -0,0 +1,112 @@ +from lib import logging as logging_lib +from lib import config as config_module + +import subprocess +import re, os +import signal +import docker + +config = config_module.config +log = logging_lib.log + +def create_container(container_options, ip=None, docker_gpus=False, timeout=30): + # Sanitize and validate input + container_options = sanitize_input(container_options) + + command = ["docker", "run", "--detach", "--tty"] + + if "name" in container_options: + command.extend(["--name", container_options["name"]]) + + if "network_mode" in container_options: + command.extend(["--network", container_options["network_mode"]]) + + if "cap_add" in container_options: + for cap in container_options["cap_add"]: + command.extend(["--cap-add", cap]) + + if "volumes" in container_options: + for volume_host, volume_container in container_options["volumes"].items(): + bind = f"{volume_host}:{volume_container['bind']}" + if "mode" in volume_container: + bind += f":{volume_container['mode']}" + command.extend(["--volume", bind]) + + if "ports" in container_options: + for port_container, port_host in container_options["ports"].items(): + command.extend(["-p", f"{port_host}:{port_container}"]) + + if "environment" in container_options: + for env_var, env_value in container_options["environment"].items(): + command.extend(["-e", f"{env_var}={env_value}"]) + + if "log_config" in container_options: + log_config = container_options["log_config"] + if isinstance(log_config, dict): + for key, value in log_config["Config"].items(): + log_option = (f"{key}={value}") + command.extend(["--log-opt", log_option]) + command.extend(["--log-driver", log_config["Type"]]) + else: + log.debug("Invalid log_config format. Skipping log configuration.") + + if "runtime" in container_options: + command.extend(["--runtime", container_options["runtime"]]) + if docker_gpus: + if type(docker_gpus)==list: + command.extend(['--gpus', '"device=' + ','.join(str(gpu_id) for gpu_id in docker_gpus) + '"']) + else: + command.extend(["--gpus", "all"]) + + if "storage_opt" in container_options: + for storage_opt, value in container_options["storage_opt"].items(): + command.extend(["--storage-opt", f"{storage_opt}={value}"]) + + if "entrypoint" in container_options: + command.extend(["--entrypoint", container_options["entrypoint"]]) + + if ip: + command.extend(["--ip", ip]) + + command.append(container_options["image"]) + + try: + process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + try: + output, error = process.communicate(timeout=timeout) + if process.returncode == 0: + container_id = output.strip() + return container_id + else: + print(f"Error creating container: {error}") + return None + except subprocess.TimeoutExpired: + process.send_signal(signal.SIGTERM) + process.kill() + print("Timeout exceeded while creating container.") + return None + except subprocess.CalledProcessError as e: + print(f"Error creating container: {e.output}") + return None + +def sanitize_input(container_options): + sanitized_options = {} + for key, value in container_options.items(): + if isinstance(value, str): + # Remove any potential shell injection or escapes + sanitized_value = re.sub(r'[`$\\\'\"]', '', value) + sanitized_options[key] = sanitized_value + elif isinstance(value, dict): + sanitized_options[key] = sanitize_input(value) + elif isinstance(value, list): + sanitized_list = [] + for item in value: + if isinstance(item, str): + sanitized_item = re.sub(r'[`$\\\'\"]', '', item) + sanitized_list.append(sanitized_item) + elif isinstance(item, dict): + sanitized_list.append(sanitize_input(item)) + sanitized_options[key] = sanitized_list + else: + sanitized_options[key] = value + return sanitized_options \ No newline at end of file diff --git a/lib/docker_deploy.py b/lib/docker_deploy.py index d2bfa5c..aa07cb8 100644 --- a/lib/docker_deploy.py +++ b/lib/docker_deploy.py @@ -1,5 +1,6 @@ from lib import config as config_module from lib import logging as logging_lib +from lib import docker_cli_wrapper from lib import docker_interface from lib import get_specs import docker @@ -44,6 +45,7 @@ def deploy(validated_containers): try: image_ready = False + docker_gpus = None for local_image in local_images: if local_image.replace(':latest','')==validated_container["image"].replace(':latest',''): image_ready = True @@ -76,14 +78,16 @@ def deploy(validated_containers): if "network" in validated_container: container_options["network_mode"]=validated_container["network"] - if "ip" in validated_container: + if "ip" in validated_container and config.creation_engine=="sdk": del container_options["network_mode"] if "gpus" in validated_container and type(validated_container["gpus"])==bool: container_options["runtime"]="nvidia" + docker_gpus=True container_options["device_requests"].append(docker.types.DeviceRequest(count=-1, capabilities=[['gpu']])) elif "gpus" in validated_container and type(validated_container["gpus"])==list: container_options["runtime"]="nvidia" + docker_gpus=validated_container["gpus"] container_options["device_requests"].append(docker.types.DeviceRequest( count=-1, capabilities=[['gpu']], @@ -118,12 +122,15 @@ def deploy(validated_containers): container_options["entrypoint"]=validated_container["entrypoint_command"] if not validated_container["name"] in created_container_names and image_ready: - container = client.containers.create(**container_options) - if "ip" in validated_container: - client.networks.get(validated_container["network"] if "network" in validated_container else "clore-br0").connect(container, ipv4_address=validated_container["ip"]) - client.networks.get("bridge").disconnect(container) - if not "paused" in validated_container: - container.start() + if config.creation_engine == "wrapper": + docker_cli_wrapper.create_container(container_options, ip=(validated_container["ip"] if "ip" in validated_container else None), docker_gpus=docker_gpus) + else: + container = client.containers.create(**container_options) + if "ip" in validated_container: + client.networks.get(validated_container["network"] if "network" in validated_container else "clore-br0").connect(container, ipv4_address=validated_container["ip"]) + client.networks.get("bridge").disconnect(container) + if not "paused" in validated_container: + container.start() except Exception as e: log.debug(f"Container creation issue | {e}") pass