caddy lables
This commit is contained in:
+2
-2
@@ -61,14 +61,14 @@
|
|||||||
<div class="flex flex-col gap-0.5">
|
<div class="flex flex-col gap-0.5">
|
||||||
<span class="text-[10px] uppercase tracking-wider font-bold text-gray-500">Маршрутизация сети:</span>
|
<span class="text-[10px] uppercase tracking-wider font-bold text-gray-500">Маршрутизация сети:</span>
|
||||||
|
|
||||||
<ng-container *ngIf="getContainerDomain(container.name) as domain; else noDomain">
|
<ng-container *ngIf="getContainerDomain(container) as domain; else noDomain">
|
||||||
<div class="flex items-center gap-2 mt-1">
|
<div class="flex items-center gap-2 mt-1">
|
||||||
<a [href]="'https://' + domain" target="_blank" class="text-xs font-semibold text-blue-400 hover:text-blue-300 hover:underline flex items-center gap-1 font-mono">
|
<a [href]="'https://' + domain" target="_blank" class="text-xs font-semibold text-blue-400 hover:text-blue-300 hover:underline flex items-center gap-1 font-mono">
|
||||||
<mat-icon class="!w-3.5 !h-3.5 !text-sm text-blue-400 shrink-0">open_in_new</mat-icon>
|
<mat-icon class="!w-3.5 !h-3.5 !text-sm text-blue-400 shrink-0">open_in_new</mat-icon>
|
||||||
<span class="truncate max-w-[200px]">{{ domain }}</span>
|
<span class="truncate max-w-[200px]">{{ domain }}</span>
|
||||||
</a>
|
</a>
|
||||||
<span class="text-[10px] text-gray-400 font-mono bg-gray-950 px-2 py-0.5 rounded border border-gray-850 shrink-0">
|
<span class="text-[10px] text-gray-400 font-mono bg-gray-950 px-2 py-0.5 rounded border border-gray-850 shrink-0">
|
||||||
порт {{ getContainerPort(container.name) }}
|
порт {{ getContainerPort(container) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
+49
-32
@@ -264,64 +264,81 @@ export class ProjectDetailsComponent implements OnInit, OnDestroy, AfterViewChec
|
|||||||
* Безопасно извлекает имя сервиса (например, 'web') из имени Docker-контейнера.
|
* Безопасно извлекает имя сервиса (например, 'web') из имени Docker-контейнера.
|
||||||
* Проверяет префиксы как по ID, так и по Name проекта.
|
* Проверяет префиксы как по ID, так и по Name проекта.
|
||||||
*/
|
*/
|
||||||
getServiceName(containerName: string): string | null {
|
getServiceName(container: any): string | null {
|
||||||
if (!containerName || !this.project) return null;
|
if (!container) return null;
|
||||||
|
|
||||||
const prefixId = `jambotron-${this.project.id}-`;
|
// 1. Идеальный путь созидания: берем стабильный ID из меток Docker
|
||||||
const prefixName = `jambotron-${this.project.name}-`;
|
if (container.labels && container.labels['jambotron.service.id']) {
|
||||||
|
return container.labels['jambotron.service.id'];
|
||||||
|
}
|
||||||
|
|
||||||
let servicePart = '';
|
// 2. Если меток нет, переходим к парсингу имени контейнера
|
||||||
|
const containerName = container.name || '';
|
||||||
|
|
||||||
if (containerName.startsWith(prefixId)) {
|
// Проверяем базовый маркер нашей системы
|
||||||
servicePart = containerName.substring(prefixId.length);
|
if (!containerName.startsWith('jambotron-')) {
|
||||||
} else if (containerName.startsWith(prefixName)) {
|
console.warn(`[Jambotron] Контейнер не принадлежит системе Jambotron: ${containerName}`);
|
||||||
servicePart = containerName.substring(prefixName.length);
|
|
||||||
} else {
|
|
||||||
// Резервный разбор по токенам (jambotron-проект-сервис)
|
|
||||||
const parts = containerName.split('-');
|
|
||||||
if (parts.length >= 3 && parts[0] === 'jambotron') {
|
|
||||||
servicePart = parts.slice(2).join('-');
|
|
||||||
} else {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Отрезаем индекс реплики (-1, -2) и префикс "jambotron-"
|
||||||
|
let cleanName = containerName.replace(/-\d+$/, '').replace(/^jambotron-/, '');
|
||||||
|
|
||||||
|
// 3. Проверяем массив известных имен сервисов из YAML
|
||||||
|
const validServices: string[] = this.config?.serviceNames || [];
|
||||||
|
if (validServices.length > 0) {
|
||||||
|
const match = validServices.find((service: string) => cleanName.endsWith(service) || cleanName.includes(service));
|
||||||
|
if (match) return match;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отрезаем индекс реплики Docker Compose (например, "-1")
|
// 4. Финальный пуленепробиваемый фоллбэк для старых контейнеров (вырезаем имя проекта)
|
||||||
if (servicePart.endsWith('-1')) {
|
// Для "test-web" -> cleanName = "test-web" -> parts = ['test', 'web'] -> возвращает "web"
|
||||||
servicePart = servicePart.substring(0, servicePart.length - 2);
|
const parts = cleanName.split('-');
|
||||||
|
if (parts.length >= 2) {
|
||||||
|
return parts.slice(1).join('-');
|
||||||
}
|
}
|
||||||
return servicePart;
|
|
||||||
|
// Только если вообще ничего не получилось извлечь — выводим реальное предупреждение
|
||||||
|
if (!cleanName) {
|
||||||
|
console.warn(`[Jambotron] Не удалось определить имя сервиса для: ${containerName}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Возвращает текущий сформированный домен для вывода в UI
|
* Возвращает текущий сформированный домен для вывода в UI
|
||||||
*/
|
*/
|
||||||
getContainerDomain(containerName: string): string | null {
|
getContainerDomain(container: any): string | null {
|
||||||
const serviceName = this.getServiceName(containerName);
|
const serviceId = this.getServiceName(container);
|
||||||
if (!serviceName || !this.config?.serviceConfigs) return null;
|
if (!serviceId || !this.config?.serviceConfigs) return null;
|
||||||
|
|
||||||
const serviceConfig = this.config.serviceConfigs[serviceName];
|
// Ищем настройки в мапе по системному ID (например, srv-0)
|
||||||
|
const serviceConfig = this.config.serviceConfigs[serviceId];
|
||||||
return serviceConfig && serviceConfig.subdomain ? `${serviceConfig.subdomain}.jambotron.com` : null;
|
return serviceConfig && serviceConfig.subdomain ? `${serviceConfig.subdomain}.jambotron.com` : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Возвращает порт сервиса из конфигурации MongoDB
|
* Возвращает порт сервиса из конфигурации MongoDB
|
||||||
*/
|
*/
|
||||||
getContainerPort(containerName: string): number {
|
getContainerPort(container: any): number {
|
||||||
const serviceName = this.getServiceName(containerName);
|
const serviceId = this.getServiceName(container);
|
||||||
if (!serviceName || !this.config?.serviceConfigs) return 80;
|
if (!serviceId || !this.config?.serviceConfigs) return 80;
|
||||||
|
|
||||||
const serviceConfig = this.config.serviceConfigs[serviceName];
|
// Ищем настройки в мапе по системному ID (например, srv-0)
|
||||||
|
const serviceConfig = this.config.serviceConfigs[serviceId];
|
||||||
return serviceConfig && serviceConfig.port ? serviceConfig.port : 80;
|
return serviceConfig && serviceConfig.port ? serviceConfig.port : 80;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Активация режима редактирования. Теперь полностью безопасна.
|
* Активация режима редактирования.
|
||||||
*/
|
*/
|
||||||
startEditDomain(container: any) {
|
startEditDomain(container: any) {
|
||||||
const serviceName = this.getServiceName(container.name);
|
// ПЕРЕДАЕМ КОНТЕЙНЕР ЦЕЛИКОМ, а не container.name
|
||||||
|
const serviceName = this.getServiceName(container);
|
||||||
if (!serviceName) {
|
if (!serviceName) {
|
||||||
console.warn(`[Jambotron] Не удалось определить имя сервиса для: ${container.name}`);
|
console.warn(`[Jambotron] Не удалось определить имя сервиса для:`, container);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,7 +355,8 @@ export class ProjectDetailsComponent implements OnInit, OnDestroy, AfterViewChec
|
|||||||
* Сохранение изменённого поддомена и порта в MongoDB базы данных
|
* Сохранение изменённого поддомена и порта в MongoDB базы данных
|
||||||
*/
|
*/
|
||||||
saveDomainConfig(container: any) {
|
saveDomainConfig(container: any) {
|
||||||
const serviceName = this.getServiceName(container.name);
|
// ПЕРЕДАЕМ КОНТЕЙНЕР ЦЕЛИКОМ, а не container.name
|
||||||
|
const serviceName = this.getServiceName(container);
|
||||||
if (!serviceName || !this.project?.id || !this.config) return;
|
if (!serviceName || !this.project?.id || !this.config) return;
|
||||||
|
|
||||||
if (!this.config.serviceConfigs) {
|
if (!this.config.serviceConfigs) {
|
||||||
@@ -360,7 +378,6 @@ export class ProjectDetailsComponent implements OnInit, OnDestroy, AfterViewChec
|
|||||||
// Отправка монолитного конфига на бэкенд
|
// Отправка монолитного конфига на бэкенд
|
||||||
this.projectService.updateProjectConfig(this.project.id, this.config).subscribe({
|
this.projectService.updateProjectConfig(this.project.id, this.config).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
// Бэкенд возвращает void (200 OK). Нам НЕ нужно затирать config!
|
|
||||||
this.editingDomains[container.id] = false;
|
this.editingDomains[container.id] = false;
|
||||||
console.log(`[Caddy] Маршрут для сервиса ${serviceName} успешно сохранён.`);
|
console.log(`[Caddy] Маршрут для сервиса ${serviceName} успешно сохранён.`);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,8 +32,10 @@ public class ProjectConfig {
|
|||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
|
// Класс настроек (ServiceSettings.java)
|
||||||
public static class ServiceSettings {
|
public static class ServiceSettings {
|
||||||
private String subdomain; // Уникальный поддомен (например, lioshik-app-api)
|
private String yamlServiceName; // "web-test" или "logger-test"
|
||||||
private Integer port; // Внутренний порт контейнера (8080, 80 и т.д.)
|
private String subdomain; // "test-web"
|
||||||
|
private Integer port; // 80
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,17 +95,132 @@ public class ProjectService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void updateProjectConfig(UUID projectId, ProjectConfig newConfig) {
|
public void updateProjectConfig(UUID projectId, ProjectConfig newConfig) {
|
||||||
ProjectConfig existing = configRepository.findByProjectId(String.valueOf(projectId))
|
// 1. Поиск существующего проекта в Postgres
|
||||||
.orElseThrow(() -> new RuntimeException("Configuration not found"));
|
Project project = projectRepository.findById(projectId)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Проект не найден с ID: " + projectId));
|
||||||
|
|
||||||
// Валидация поддоменов перед сохранением
|
// Находим или инициализируем корневую конфигурацию в MongoDB (ключ — String)
|
||||||
validateSubdomains(newConfig);
|
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());
|
// ПЕРВОПРИЧИНА ИСПРАВЛЕНА: Берем исходный YAML текст из прилетевшего конфига (newConfig)
|
||||||
existing.setServiceConfigs(newConfig.getServiceConfigs());
|
String rawYaml = newConfig.getRawComposeContent();
|
||||||
existing.setServiceNames(extractServiceNames(newConfig.getRawComposeContent()));
|
|
||||||
existing.setVersion(existing.getVersion() + 1);
|
// 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);
|
configRepository.save(existing);
|
||||||
|
_logger.info("[Jambotron] Конфигурация шлюза для проекта {} успешно синхронизирована.", projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateSubdomains(ProjectConfig config) {
|
private void validateSubdomains(ProjectConfig config) {
|
||||||
@@ -202,21 +317,39 @@ public class ProjectService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Перебираем конфигурации сервисов, сохраненные в MongoDB
|
// Перебираем конфигурации сервисов, сохраненные в MongoDB
|
||||||
config.getServiceConfigs().forEach((serviceName, serviceSetting) -> {
|
config.getServiceConfigs().forEach((serviceKey, serviceSetting) -> {
|
||||||
if (services.containsKey(serviceName) && serviceSetting.getSubdomain() != null && !serviceSetting.getSubdomain().isEmpty()) {
|
|
||||||
Map<String, Object> service = (Map<String, Object>) services.get(serviceName);
|
// Умный поиск: ищем ключ в 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");
|
Map<String, String> labels = (Map<String, String>) service.get("labels");
|
||||||
if (labels == null) {
|
if (labels == null) {
|
||||||
labels = new HashMap<>();
|
labels = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
String domain = serviceSetting.getSubdomain();
|
String domain = serviceSetting.getSubdomain() + ".jambotron.com";
|
||||||
// Если внутренний порт не задан, по умолчанию шлем на 80
|
|
||||||
String internalPort = serviceSetting.getPort() != null ? serviceSetting.getPort().toString() : "80";
|
String internalPort = serviceSetting.getPort() != null ? serviceSetting.getPort().toString() : "80";
|
||||||
|
|
||||||
// Прописываем директивы созидания для caddy-docker-proxy
|
|
||||||
labels.put("caddy", domain);
|
labels.put("caddy", domain);
|
||||||
labels.put("caddy.reverse_proxy", "{{upstreams " + internalPort + "}}");
|
labels.put("caddy.reverse_proxy", "{{upstreams " + internalPort + "}}");
|
||||||
|
|
||||||
@@ -224,7 +357,6 @@ public class ProjectService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Настраиваем красивый вывод структуры YAML (BLOCK style)
|
|
||||||
DumperOptions options = new DumperOptions();
|
DumperOptions options = new DumperOptions();
|
||||||
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||||
Yaml dumper = new Yaml(options);
|
Yaml dumper = new Yaml(options);
|
||||||
@@ -233,7 +365,6 @@ public class ProjectService {
|
|||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
_logger.error("Ошибка при модификации YAML структуры для Caddy", e);
|
_logger.error("Ошибка при модификации YAML структуры для Caddy", e);
|
||||||
// Если упал парсинг, возвращаем исходный текст, чтобы не сломать деплой целиком
|
|
||||||
return rawComposeContent;
|
return rawComposeContent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user