caddy lables

This commit is contained in:
2026-05-29 22:30:35 +03:00
parent 0c8718b7bf
commit 70a6229ca6
4 changed files with 208 additions and 58 deletions
@@ -32,8 +32,10 @@ public class ProjectConfig {
@Data
@NoArgsConstructor
@AllArgsConstructor
// Класс настроек (ServiceSettings.java)
public static class ServiceSettings {
private String subdomain; // Уникальный поддомен (например, lioshik-app-api)
private Integer port; // Внутренний порт контейнера (8080, 80 и т.д.)
private String yamlServiceName; // "web-test" или "logger-test"
private String subdomain; // "test-web"
private Integer port; // 80
}
}
@@ -95,17 +95,132 @@ public class ProjectService {
@Transactional
public void updateProjectConfig(UUID projectId, ProjectConfig newConfig) {
ProjectConfig existing = configRepository.findByProjectId(String.valueOf(projectId))
.orElseThrow(() -> new RuntimeException("Configuration not found"));
// 1. Поиск существующего проекта в Postgres
Project project = projectRepository.findById(projectId)
.orElseThrow(() -> new IllegalArgumentException("Проект не найден с ID: " + projectId));
// Валидация поддоменов перед сохранением
validateSubdomains(newConfig);
// Находим или инициализируем корневую конфигурацию в MongoDB (ключ — String)
ProjectConfig existing = configRepository.findByProjectId(projectId.toString())
.orElseGet(() -> {
ProjectConfig c = new ProjectConfig();
c.setProjectId(projectId.toString());
c.setServiceConfigs(new HashMap<>());
return c;
});
existing.setRawComposeContent(newConfig.getRawComposeContent());
existing.setServiceConfigs(newConfig.getServiceConfigs());
existing.setServiceNames(extractServiceNames(newConfig.getRawComposeContent()));
existing.setVersion(existing.getVersion() + 1);
// ПЕРВОПРИЧИНА ИСПРАВЛЕНА: Берем исходный YAML текст из прилетевшего конфига (newConfig)
String rawYaml = newConfig.getRawComposeContent();
// 2. Парсинг актуальных имен сервисов из docker-compose.yml
List<String> actualYamlNames = new ArrayList<>();
if (rawYaml != null && !rawYaml.isBlank()) {
try {
Yaml yaml = new Yaml();
Map<String, Object> obj = yaml.load(rawYaml);
if (obj != null && obj.get("services") instanceof Map) {
Map<?, ?> servicesMap = (Map<?, ?>) obj.get("services");
for (Object key : servicesMap.keySet()) {
actualYamlNames.add(String.valueOf(key));
}
}
} catch (Exception e) {
_logger.error("Ошибка парсинга docker-compose.yml для извлечения имен сервисов в проекте {}", projectId, e);
}
}
// 3. Умное сопоставление (маппинг) Service ID
Map<String, ProjectConfig.ServiceSettings> incomingConfigs = newConfig.getServiceConfigs();
Map<String, ProjectConfig.ServiceSettings> existingConfigs = existing.getServiceConfigs();
if (incomingConfigs == null) incomingConfigs = new HashMap<>();
if (existingConfigs == null) existingConfigs = new HashMap<>();
Map<String, ProjectConfig.ServiceSettings> alignedConfigs = new HashMap<>();
// Итерируемся по реальным сервисам из файла конфигурации
for (int i = 0; i < actualYamlNames.size(); i++) {
String yamlName = actualYamlNames.get(i);
final int currentIndex = i;
// Определяем стабильный системный ID (например, srv-0) на основе старой базы
String targetServiceId = existingConfigs.entrySet().stream()
.filter(e -> yamlName.equals(e.getValue().getYamlServiceName()))
.map(Map.Entry::getKey)
.findFirst()
.orElse(null);
if (targetServiceId == null) {
// Если в базе еще нет, проверяем, вдруг фронт привязал к srv-X
targetServiceId = incomingConfigs.entrySet().stream()
.filter(e -> yamlName.equals(e.getValue().getYamlServiceName()))
.map(Map.Entry::getKey)
.findFirst()
.orElse("srv-" + currentIndex);
}
// ИЩЕМ ИСТОЧНИК ДАННЫХ (Где лежат введенные пользователем порт и поддомен)
ProjectConfig.ServiceSettings userUiSettings = null;
// Стратегия А: Ищем по стабильному ID (srv-0)
ProjectConfig.ServiceSettings settingsBySubId = incomingConfigs.get(targetServiceId);
if (settingsBySubId != null && (settingsBySubId.getSubdomain() != null || settingsBySubId.getPort() != null)) {
userUiSettings = settingsBySubId;
}
// Стратегия Б: Ищем по полному YAML-имени как по ключу мапы (например, "web-test")
if (userUiSettings == null) {
ProjectConfig.ServiceSettings settingsByFullName = incomingConfigs.get(yamlName);
if (settingsByFullName != null && (settingsByFullName.getSubdomain() != null || settingsByFullName.getPort() != null)) {
userUiSettings = settingsByFullName;
}
}
// Стратегия В (Решение для image_bb4570): Ищем частичное совпадение ключей мапы (фронт прислал ключ "web" для сервиса "web-test")
if (userUiSettings == null) {
userUiSettings = incomingConfigs.entrySet().stream()
.filter(entry -> yamlName.startsWith(entry.getKey()) || entry.getKey().startsWith(yamlName))
.map(Map.Entry::getValue)
.filter(s -> s.getSubdomain() != null || s.getPort() != null)
.findFirst()
.orElse(null);
}
// Стратегия Г: Ищем по внутреннему полю yamlServiceName, если оно заполнено
if (userUiSettings == null) {
userUiSettings = incomingConfigs.values().stream()
.filter(s -> yamlName.equals(s.getYamlServiceName()))
.findFirst()
.orElse(null);
}
// Создаем или извлекаем целевой объект настроек для базы
ProjectConfig.ServiceSettings finalSettings = existingConfigs.get(targetServiceId);
if (finalSettings == null) {
finalSettings = new ProjectConfig.ServiceSettings();
}
// НАКАТЫВАЕМ ДАННЫЕ: Если нашли донора из UI, забираем его конфигурацию порта и домена
if (userUiSettings != null) {
finalSettings.setSubdomain(userUiSettings.getSubdomain());
finalSettings.setPort(userUiSettings.getPort());
}
// Жестко гарантируем железную привязку к реальному имени контейнера
finalSettings.setYamlServiceName(yamlName);
alignedConfigs.put(targetServiceId, finalSettings);
}
// Сохраняем выровненную конфигурацию и метаданные в документ Mongo
existing.setServiceConfigs(alignedConfigs);
existing.setRawComposeContent(rawYaml);
existing.setServiceNames(actualYamlNames);
// Валидируем поддомены перед записью в репозиторий
validateSubdomains(existing);
// 4. Сохранение обновленного монолита в MongoDB
configRepository.save(existing);
_logger.info("[Jambotron] Конфигурация шлюза для проекта {} успешно синхронизирована.", projectId);
}
private void validateSubdomains(ProjectConfig config) {
@@ -202,21 +317,39 @@ public class ProjectService {
}
// Перебираем конфигурации сервисов, сохраненные в MongoDB
config.getServiceConfigs().forEach((serviceName, serviceSetting) -> {
if (services.containsKey(serviceName) && serviceSetting.getSubdomain() != null && !serviceSetting.getSubdomain().isEmpty()) {
Map<String, Object> service = (Map<String, Object>) services.get(serviceName);
config.getServiceConfigs().forEach((serviceKey, serviceSetting) -> {
// Умный поиск: ищем ключ в docker-compose.yml
String actualComposeKey = null;
// 1. Проверяем, может serviceKey — это прямое имя из YAML (web-test)
if (services.containsKey(serviceKey)) {
actualComposeKey = serviceKey;
}
// 2. Или может это сохраненный yamlServiceName внутри настроек?
else if (serviceSetting.getYamlServiceName() != null && services.containsKey(serviceSetting.getYamlServiceName())) {
actualComposeKey = serviceSetting.getYamlServiceName();
}
// 3. Резервный вариант: если прилетело урезанное "web", ищем частичное совпадение в YAML ("web-test" стартует с "web")
else {
actualComposeKey = services.keySet().stream()
.filter(k -> k.startsWith(serviceKey) || serviceKey.startsWith(k))
.findFirst()
.orElse(null);
}
// Если нашли точку соприкосновения и задан поддомен — инжектим метки Caddy
if (actualComposeKey != null && serviceSetting.getSubdomain() != null && !serviceSetting.getSubdomain().isEmpty()) {
Map<String, Object> service = (Map<String, Object>) services.get(actualComposeKey);
// Получаем существующий блок labels или создаем новый, если его нет
Map<String, String> labels = (Map<String, String>) service.get("labels");
if (labels == null) {
labels = new HashMap<>();
}
String domain = serviceSetting.getSubdomain();
// Если внутренний порт не задан, по умолчанию шлем на 80
String domain = serviceSetting.getSubdomain() + ".jambotron.com";
String internalPort = serviceSetting.getPort() != null ? serviceSetting.getPort().toString() : "80";
// Прописываем директивы созидания для caddy-docker-proxy
labels.put("caddy", domain);
labels.put("caddy.reverse_proxy", "{{upstreams " + internalPort + "}}");
@@ -224,7 +357,6 @@ public class ProjectService {
}
});
// Настраиваем красивый вывод структуры YAML (BLOCK style)
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Yaml dumper = new Yaml(options);
@@ -233,7 +365,6 @@ public class ProjectService {
} catch (Exception e) {
_logger.error("Ошибка при модификации YAML структуры для Caddy", e);
// Если упал парсинг, возвращаем исходный текст, чтобы не сломать деплой целиком
return rawComposeContent;
}
}