import uuid import shutil import json import socket import subprocess import petname import hashlib import platform import os from dotenv import load_dotenv from datetime import datetime # Archivo JSON para guardar los datos de los contenedores CONTAINER_DATA_FILE = os.getenv("CONTAINER_DATA_FILE") # Nombre y etiqueta para la imagen base BASE_IMAGE_TAG = os.getenv("BASE_IMAGE_TAG") #Lista de puertos internos PUERTOS_INTERNOS = [5173, 5432, 8786, 8787, 8080, 17000] # Numero de conteineres a crear NUM_CONTAINERS = 4 # ------------------------------- # Función: Comprobar si un puerto está libre con nmap # ------------------------------- def is_port_available(port): # Cargar datos existentes si el archivo existe y no está vacío if os.path.exists(CONTAINER_DATA_FILE) and os.path.getsize(CONTAINER_DATA_FILE) > 0: with open(CONTAINER_DATA_FILE, 'r') as file: try: data = json.load(file) # Cargar datos existentes # Verificar si el puerto ya está en uso en los datos existentes for container in data: for port_map in container['ports']: if port_map['host_port'] == port: return False # Puerto en uso except json.JSONDecodeError: pass # Si hay error al leer, continuar con la comprobación de nmap # Ejecuta nmap para comprobar si el puerto está abierto command = f"nmap -p {port} 127.0.0.1" print(f"[INFO] Ejecutando nmap para el puerto {port}...") result = subprocess.run(command, shell=True, capture_output=True, text=True) # Muestra el resultado del comando nmap print(f"[DEBUG] Resultado de nmap para el puerto {port}:\n{result.stdout}") # Analiza el resultado para ver si el puerto está cerrado if "closed" in result.stdout: return True # Puerto libre else: return False # Puerto ocupado # ------------------------------- # Función: Ejecutar contenedor y guardar datos # ------------------------------- def run_docker_compose(internal_ports=None): # Genera un UUID único unique_uuid = str(uuid.uuid4()) # Genera un nombre único para el contenedor uuid_palabras = petname.Generate(3, separator='-') # Nombres únicos para el servicio, contenedor y la red service_name = f"{uuid_palabras}" container_name = f"{uuid_palabras}" network_name = f"alpine_network_{unique_uuid}" # Nombre de la imagen local a usar image_tag = BASE_IMAGE_TAG # Usar la imagen local directamente # Nombre del archivo docker-compose nuevo new_docker_compose_file = f'docker-compose-{unique_uuid}.yml' try: # Configuración personalizada de puertos ports_mapping = [] custom_ports = [] host_port = 27000 # Puerto inicial print(f"[INFO] Iniciando la configuración de puertos a partir del puerto {host_port}") if internal_ports: for internal_port in internal_ports: print(f"[INFO] Configurando puerto interno: {internal_port}") # Buscar el siguiente puerto disponible usando nmap while host_port <= 65000: if is_port_available(host_port): print(f"[INFO] Asignando puerto {host_port} para el puerto interno {internal_port}...") custom_ports.append(f" - \"{host_port}:{internal_port}\"") ports_mapping.append({ "host_port": host_port, "internal_port": internal_port }) host_port += 1 # Incrementa el puerto para el siguiente uso break else: print(f"[WARNING] Puerto {host_port} ocupado, probando el siguiente...") host_port += 1 # Incrementa si el puerto está ocupado if host_port > 65000: raise Exception("Se superó el límite de puertos disponibles.") custom_ports_yaml = "\n".join(custom_ports) print(f"[INFO] Puertos personalizados configurados:\n{custom_ports_yaml}") else: custom_ports_yaml = "" # No agrega puertos si no se especifican print("[INFO] No se especificaron puertos internos.") # Genera el contenido del archivo docker-compose dinámicamente docker_compose_content = f""" version: '3.8' services: {service_name}: image: {image_tag} # Usar la imagen base directamente container_name: {container_name} networks: - {network_name} restart: always tty: true volumes: - ./share:/app/share - ./d_apps/{container_name}:/app/host_transfer ports: {custom_ports_yaml} command: tail -f /dev/null # Mantener activo el contenedor networks: {network_name}: driver: bridge """ # Guarda el archivo docker-compose generado with open(new_docker_compose_file, 'w') as file: file.write(docker_compose_content) # Muestra el contenido generado para depuración print(f"[DEBUG] Contenido del archivo {new_docker_compose_file}:\n") print(docker_compose_content) # Ejecuta docker-compose con el archivo nuevo y captura la salida print("[INFO] Ejecutando docker compose...") result = subprocess.run( f'docker compose -f {new_docker_compose_file} up -d --no-build', shell=True, capture_output=True, text=True ) # Muestra el resultado del comando if result.returncode != 0: print(f"[ERROR] Error al ejecutar docker compose:\n{result.stderr}") raise Exception("Error al ejecutar docker-compose") else: print(f"[INFO] Contenedor iniciado exitosamente:\n{result.stdout}") # Mostrar contenedores activos y detenidos print("[DEBUG] Contenedores activos y detenidos:") subprocess.run("docker ps -a", shell=True) # Obtener el ID del contenedor creado container_id = subprocess.getoutput(f"docker ps -qf name={container_name}") if not container_id: print(f"[ERROR] No se pudo obtener el ID del contenedor {container_name}.") raise Exception("No se pudo obtener el ID del contenedor") print(f"[INFO] ID del contenedor: {container_id}") # Mostrar logs del contenedor si está detenido print("[INFO] Mostrando logs del contenedor:") subprocess.run(f"docker logs {container_id}", shell=True) # Guardar los datos del contenedor en el archivo JSON save_container_data({ 'id': container_id, 'name': container_name, 'network': network_name, 'image': image_tag, 'compose_file': new_docker_compose_file, 'creation_timestamp': datetime.now().isoformat(), 'ports': ports_mapping # Agrega la información de los puertos mapeados }) except Exception as e: print(f"[ERROR] Error: {str(e)}") finally: # Eliminar el archivo docker-compose generado si existe if os.path.exists(new_docker_compose_file): print(f"[INFO] Eliminando archivo temporal: {new_docker_compose_file}") os.remove(new_docker_compose_file) # ------------------------------- # Función: Guardar datos del contenedor en JSON # ------------------------------- def save_container_data(container_data): # Cargar datos existentes si el archivo existe y no está vacío if os.path.exists(CONTAINER_DATA_FILE) and os.path.getsize(CONTAINER_DATA_FILE) > 0: with open(CONTAINER_DATA_FILE, 'r') as file: try: data = json.load(file) # Cargar datos existentes except json.JSONDecodeError: data = [] # Si hay error al leer, inicializar como lista vacía else: # Crear el archivo vacío y usar una lista vacía with open(CONTAINER_DATA_FILE, 'w') as file: json.dump([], file) data = [] # Inicializar como lista vacía # Agregar nuevo contenedor data.append(container_data) # Guardar datos actualizados with open(CONTAINER_DATA_FILE, 'w') as file: json.dump(data, file, indent=4) # ------------------------------- # Función: Limpiar imágenes huérfanas # ------------------------------- def clean_unused_images(): print("➤ Eliminando imágenes huérfanas...") os.system('docker image prune -f') # ------------------------------- # Función: Limpiar redes huérfanas # ------------------------------- def clean_unused_networks(): print("➤ Eliminando redes huérfanas...") os.system('docker network prune -f') ######################################### ------------------------------- # Ejecución del script ######################################### ------------------------------- if __name__ == "__main__": # Crear contenedores y guardarlos en el JSON for _ in range(NUM_CONTAINERS): run_docker_compose(internal_ports=PUERTOS_INTERNOS) print("➤ Contenedores creados y guardados en el JSON") # Limpiar imágenes huérfanas clean_unused_images() # Limpiar redes huérfanas clean_unused_networks()