From 38462b8ad7e3d63e3f85700d87417c1529f8a06c Mon Sep 17 00:00:00 2001 From: jade Date: Wed, 2 Sep 2026 17:50:08 +0900 Subject: [PATCH] =?UTF-8?q?=EC=86=8C=EC=8A=A4=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dat/lib/manifest/ToolManifestService.java | 6 +- .../manifest/ToolServiceManifestResponse.java | 2 +- .../dat/lib/util/PodScaffolder.java | 74 +++++++++++++++++++ .../lib/manifest/ToolManifestServiceTest.java | 11 +++ .../dat/lib/util/PodScaffolderTest.java | 43 +++++++++++ 5 files changed, 132 insertions(+), 4 deletions(-) diff --git a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolManifestService.java b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolManifestService.java index c5fbf23fa..f7d586320 100644 --- a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolManifestService.java +++ b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolManifestService.java @@ -64,16 +64,16 @@ public class ToolManifestService { public ToolServiceManifestResponse currentToolServiceManifest() { McpProperties.Manifest manifest = properties.getManifest(); - String bundleId = manifest == null ? null : manifest.getBundleId(); + String serviceId = manifest == null ? null : manifest.getBundleId(); List configured = manifest == null ? List.of() : defaultRoutingList(manifest.getRoutingFunctions()); List> routingFunctions = configured.stream() .map(this::toRoutingFunctionEnvelope) .toList(); try { - String source = objectMapper.writeValueAsString(Map.of("bundleId", bundleId, + String source = objectMapper.writeValueAsString(Map.of("serviceId", serviceId, "routingFunctions", routingFunctions)); - return new ToolServiceManifestResponse(bundleId, sha256(source), routingFunctions); + return new ToolServiceManifestResponse(serviceId, sha256(source), routingFunctions); } catch (Exception exception) { throw new IllegalStateException("Failed to build Tool Service manifest", exception); } diff --git a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolServiceManifestResponse.java b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolServiceManifestResponse.java index 4857ecbd0..85f631dde 100644 --- a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolServiceManifestResponse.java +++ b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolServiceManifestResponse.java @@ -4,6 +4,6 @@ import java.util.List; import java.util.Map; /** Server-level routing metadata loaded from tool-service-manifest.yml. */ -public record ToolServiceManifestResponse(String bundleId, String revision, +public record ToolServiceManifestResponse(String serviceId, String revision, List> routingFunctions) { } diff --git a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/PodScaffolder.java b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/PodScaffolder.java index b016397da..97538ba7f 100644 --- a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/PodScaffolder.java +++ b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/PodScaffolder.java @@ -1,5 +1,7 @@ package io.shinhanlife.dat.lib.util; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -8,10 +10,19 @@ import java.nio.charset.StandardCharsets; import java.nio.file.StandardOpenOption; import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class PodScaffolder { + private static final ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory()); + public static void main(String[] args) throws IOException { Scanner scanner = new Scanner(System.in); @@ -369,6 +380,12 @@ public class PodScaffolder { } } + List updatedManifests = updateReciprocalConfusableServers(rootDir, moduleName, manifest); + if (!updatedManifests.isEmpty()) { + log.append("[추가] 기존 Pod confusable-servers 갱신: ") + .append(String.join(", ", updatedManifests)).append("\n"); + } + log.append("\n=========================================\n"); log.append(" Pod Scaffolding Complete! \n"); log.append("=========================================\n"); @@ -404,6 +421,63 @@ public class PodScaffolder { """.formatted(moduleName, key, key, moduleName, key, key, key, key); } + private static List updateReciprocalConfusableServers(Path rootDir, String moduleName, + String newManifest) throws IOException { + List updated = new ArrayList<>(); + for (String target : extractConfusableServers(newManifest)) { + if (target.equals(moduleName) || !target.matches("^dat-was-[a-z0-9-]+$")) continue; + Path path = rootDir.resolve(target).resolve("src/main/resources/tool-service-manifest.yml"); + if (!Files.isRegularFile(path)) continue; + String before = Files.readString(path, StandardCharsets.UTF_8); + String after = addConfusableServer(before, moduleName); + if (!before.equals(after)) { + writeUtf8(path, after); + updated.add(rootDir.relativize(path).toString().replace('\\', '/')); + } + } + return updated; + } + + @SuppressWarnings("unchecked") + private static List extractConfusableServers(String manifest) throws IOException { + Map root = YAML_MAPPER.readValue(manifest, Map.class); + Object mcpValue = root.get("mcp"); + if (!(mcpValue instanceof Map mcp)) return List.of(); + Object manifestValue = mcp.get("manifest"); + if (!(manifestValue instanceof Map manifestMap)) return List.of(); + Object functionsValue = manifestMap.get("routing-functions"); + if (!(functionsValue instanceof List functions) || functions.isEmpty() + || !(functions.getFirst() instanceof Map function)) return List.of(); + Object serversValue = function.get("confusable-servers"); + if (!(serversValue instanceof Collection servers)) return List.of(); + LinkedHashSet values = new LinkedHashSet<>(); + for (Object value : servers) { + String normalized = value == null ? "" : value.toString().trim(); + if (!normalized.isBlank()) values.add(normalized); + } + return List.copyOf(values); + } + + private static String addConfusableServer(String manifest, String moduleName) { + Pattern pattern = Pattern.compile("(?m)^(\\s*)confusable-servers:\\s*\\[([^]]*)]\\s*$"); + Matcher matcher = pattern.matcher(manifest); + if (matcher.find()) { + LinkedHashSet values = new LinkedHashSet<>(); + for (String value : matcher.group(2).split(",")) { + String normalized = value.trim(); + if (!normalized.isBlank()) values.add(normalized); + } + if (!values.add(moduleName)) return manifest; + String replacement = matcher.group(1) + "confusable-servers: [" + String.join(", ", values) + "]"; + return matcher.replaceFirst(Matcher.quoteReplacement(replacement)); + } + Matcher category = Pattern.compile("(?m)^(\\s*)category-key:[^\\r\\n]*$").matcher(manifest); + if (!category.find()) return manifest; + String replacement = category.group() + System.lineSeparator() + category.group(1) + + "confusable-servers: [" + moduleName + "]"; + return category.replaceFirst(Matcher.quoteReplacement(replacement)); + } + private static String capitalize(String str) { if (str == null || str.isEmpty()) return str; return str.substring(0, 1).toUpperCase() + str.substring(1); diff --git a/dat-was-lib/src/test/java/io/shinhanlife/dat/lib/manifest/ToolManifestServiceTest.java b/dat-was-lib/src/test/java/io/shinhanlife/dat/lib/manifest/ToolManifestServiceTest.java index 410e4f091..1d7e69e08 100644 --- a/dat-was-lib/src/test/java/io/shinhanlife/dat/lib/manifest/ToolManifestServiceTest.java +++ b/dat-was-lib/src/test/java/io/shinhanlife/dat/lib/manifest/ToolManifestServiceTest.java @@ -51,6 +51,17 @@ class ToolManifestServiceTest { assertTrue(!before.currentManifest().revision().equals(after.currentManifest().revision())); } + @Test + void serializesToolServiceManifestWithServiceIdInsteadOfBundleId() throws Exception { + McpProperties properties = manifestProperties("was-cus", "cus."); + ToolManifestService service = new ToolManifestService(() -> List.of(), objectMapper, properties); + + String json = objectMapper.writeValueAsString(service.currentToolServiceManifest()); + + assertTrue(json.contains("\"serviceId\":\"was-cus\"")); + assertTrue(!json.contains("\"bundleId\"")); + } + @Test void rejectsEntireManifestWhenToolNameDoesNotMatchConfiguredPrefix() { McpProperties properties = manifestProperties("insurance-processing", "processing."); diff --git a/dat-was-lib/src/test/java/io/shinhanlife/dat/lib/util/PodScaffolderTest.java b/dat-was-lib/src/test/java/io/shinhanlife/dat/lib/util/PodScaffolderTest.java index 62824cdd0..456ac00c0 100644 --- a/dat-was-lib/src/test/java/io/shinhanlife/dat/lib/util/PodScaffolderTest.java +++ b/dat-was-lib/src/test/java/io/shinhanlife/dat/lib/util/PodScaffolderTest.java @@ -66,4 +66,47 @@ class PodScaffolderTest { assertFalse(compose.contains("depends_on:\n - redis")); assertFalse(compose.contains("SPRING_REDIS_HOST=redis")); } + + @Test + void addsNewPodToReferencedExistingConfusableServerManifests() throws Exception { + Path proManifest = existingManifest("dat-was-pro", "dat-was-cus"); + Path cusManifest = existingManifest("dat-was-cus", "dat-was-pro"); + String newManifest = """ + mcp: + manifest: + routing-functions: + - name: route_to_dat-was-cla + server-id: dat-was-cla + category-key: cla + confusable-servers: + - dat-was-pro + - dat-was-cus + - dat-was-missing + - dat-was-cla + """; + + String result = PodScaffolder.scaffoldPod(root, "dat-was-cla", "8098", "cla", "tester", + "2026.09.02", newManifest); + + assertTrue(Files.readString(proManifest).contains("confusable-servers: [dat-was-cus, dat-was-cla]")); + assertTrue(Files.readString(cusManifest).contains("confusable-servers: [dat-was-pro, dat-was-cla]")); + assertFalse(Files.exists(root.resolve("dat-was-missing"))); + assertTrue(result.contains("dat-was-pro/src/main/resources/tool-service-manifest.yml"), result); + assertTrue(result.contains("dat-was-cus/src/main/resources/tool-service-manifest.yml"), result); + } + + private Path existingManifest(String moduleName, String confusableServer) throws Exception { + Path path = root.resolve(moduleName + "/src/main/resources/tool-service-manifest.yml"); + Files.createDirectories(path.getParent()); + Files.writeString(path, """ + mcp: + manifest: + routing-functions: + - name: route_to_%s + server-id: %s + category-key: %s + confusable-servers: [%s] + """.formatted(moduleName, moduleName, moduleName.replace("dat-was-", ""), confusableServer)); + return path; + } }