-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgen_docker_compose.py
More file actions
130 lines (107 loc) · 6.13 KB
/
gen_docker_compose.py
File metadata and controls
130 lines (107 loc) · 6.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/env python
import os
import re
from pathlib import Path
import yaml
DOCKER_COMPOSE_PATH = Path("docker-compose.yml")
def get_project_name() -> str:
# We are ensuring run from project root in main() func, so it is safe to assume that we are in project root
return Path(os.getcwd()).name
def get_relative_path(path: Path) -> str:
# Пытаемся вычислить относительный путь, если не получается — используем абсолютный путь
try:
rel_path = path.relative_to(".")
except ValueError:
# Если не удаётся вычислить относительный путь, выводим предупреждение и используем абсолютный
print(f"⚠️ Не удалось вычислить относительный путь для {path}")
rel_path = (
path.absolute() # Используем абсолютный путь, если относительный не удалось вычислить
)
# Заменяем '/' на '\\' в пути для Windows
return str(rel_path).replace("\\", "/")
def get_worker_name(path: Path) -> str:
# Извлекаем имя рабочего файла без расширения, заменяя "_" на "-"
match = re.search(r"([^/\\]+)(?=\.py$)", str(path))
return match.group().replace("_", "-") if match else ""
def get_service_name(path: Path) -> str:
# Получаем имя родительской папки и имя рабочего файла
parent_name = path.parent.parent.parent.name
if parent_name.strip() == "":
parent_name = get_project_name()
worker_name = get_worker_name(path)
return f"{parent_name}-{worker_name}"
def generate_service_block(service_name: str, path: Path, additional_args: list[str] | None = None) -> dict:
rel_path = get_relative_path(path)
# Формируем блок настроек для нового сервиса в Docker Compose
return {
"image": get_project_name(), # Имя образа Docker
"build": {"context": ".", "dockerfile": "Dockerfile"}, # Параметры сборки
"container_name": service_name, # Имя контейнера
"command": f"bash -c 'PYTHONPATH=/application poetry run python -u {rel_path}{" " + (" ".join(additional_args)) if additional_args is not None else " "}'", # Команда для запуска
"restart": "always", # Настройка перезапуска контейнера
"volumes": ["./src:/application/src"], # Монтируем директорию с исходным кодом
"env_file": [".env"], # Файл с переменными окружения
"logging": {"options": {"max-size": "10m"}}, # Настройки логирования
"networks": ["net"], # Сетевые настройки
}
def get_worker_files() -> list[Path]:
workers = []
# Ищем все Python файлы в папке src/workers
for path in Path("src/workers").rglob("*.py"):
try:
text = path.read_text(encoding="utf-8")
# Если файл содержит конструкцию 'if __name__ == "__main__"', добавляем его в список
if '__name__ == "__main__"' in text:
workers.append(path)
except UnicodeDecodeError:
# Если не удаётся прочитать файл, продолжаем обработку
continue
return workers
def main():
# Проверяем, что скрипт запускается из корневой директории проекта
if not Path("pyproject.toml").exists():
print("Ошибка: Скрипт должен быть запущен из корня проекта!")
exit(1)
# Пытаемся загрузить существующий файл docker-compose.yml
try:
compose = (
yaml.safe_load(DOCKER_COMPOSE_PATH.read_text())
if DOCKER_COMPOSE_PATH.exists() and DOCKER_COMPOSE_PATH.read_text() != ""
else {}
)
except yaml.YAMLError:
# Если файл YAML некорректен, выводим предупреждение и создаём пустой объект
print("⚠️ Некорректный YAML, перезаписываем...")
compose = {}
# Устанавливаем значения по умолчанию для сервисов и сетей, если их нет
compose.setdefault("services", {})
compose.setdefault("networks", {"net": {"name": "net", "external": True}})
# Получаем список всех рабочих файлов
worker_files = get_worker_files()
print(f"Найдено {len(worker_files)} файлов воркеров")
added = 0
api_service_name = f"{get_project_name()}-api"
if api_service_name not in compose["services"] and os.path.exists("src/api"):
compose["services"][api_service_name] = generate_service_block(
service_name=api_service_name,
path=Path("src/__main__.py"),
additional_args=["--run=api"]
)
print(f"✅ Добавлен сервис: {api_service_name}")
added += 1
# Для каждого рабочего файла генерируем и добавляем сервис в Docker Compose
for path in worker_files:
service_name = get_service_name(path)
if service_name not in compose["services"]:
compose["services"][service_name] = generate_service_block(
service_name, path
)
print(f"✅ Добавлен сервис: {service_name}")
added += 1
# Записываем обновлённый файл docker-compose.yml
DOCKER_COMPOSE_PATH.write_text(yaml.dump(compose, sort_keys=False))
print(
f"✅ Обновлён файл docker-compose.yml (добавлено {added} новых сервисов, всего: {len(compose['services'])})"
)
if __name__ == "__main__":
main()