From aaef62d6f402934c95a1f1c1153fab08e5c48889 Mon Sep 17 00:00:00 2001 From: Jinyoung Date: Sat, 9 Aug 2025 16:20:10 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20try-catch=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 2 +- .../gcp/domain/gcp/service/GcpService.java | 57 +++++++++++-------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1d81a01..6aebd1b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,7 +2,7 @@ name: CI / CD on: push: - branches: [main] + branches: [fix/#16-exception-handling-fix] jobs: CI: diff --git a/src/main/java/com/gcp/domain/gcp/service/GcpService.java b/src/main/java/com/gcp/domain/gcp/service/GcpService.java index 65068d4..2f5c128 100644 --- a/src/main/java/com/gcp/domain/gcp/service/GcpService.java +++ b/src/main/java/com/gcp/domain/gcp/service/GcpService.java @@ -190,39 +190,49 @@ public String getEstimatedCost() { @SneakyThrows public List> getVmList(String userId, String guildId) { - String url = String.format("https://compute.googleapis.com/compute/v1/projects/%s/zones/%s/instances", - PROJECT_ID, ZONE); + try { + String url = String.format("https://compute.googleapis.com/compute/v1/projects/%s/zones/%s/instances", + PROJECT_ID, ZONE); - String accessToken = discordUserRepository.findAccessTokenByUserIdAndGuildId(userId, guildId).orElseThrow(); - HttpHeaders headers = new HttpHeaders(); - headers.set("Authorization", "Bearer " + accessToken); - headers.setContentType(MediaType.APPLICATION_JSON); + String accessToken = discordUserRepository.findAccessTokenByUserIdAndGuildId(userId, guildId).orElseThrow(); + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "Bearer " + accessToken); + headers.setContentType(MediaType.APPLICATION_JSON); - HttpEntity entity = new HttpEntity<>(null, headers); - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); + HttpEntity entity = new HttpEntity<>(null, headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); - return parseVmResponse(response.getBody()); + return parseVmResponse(response.getBody()); + } catch (Exception e){ + log.error("❌ VM 목록 조회 실패", e); + return null; + } } public List getProjectIds(String userId, String guildId) { - String url = "https://cloudresourcemanager.googleapis.com/v1/projects"; - String accessToken = discordUserRepository.findAccessTokenByUserIdAndGuildId(userId, guildId).orElseThrow(); + try { + String url = "https://cloudresourcemanager.googleapis.com/v1/projects"; + String accessToken = discordUserRepository.findAccessTokenByUserIdAndGuildId(userId, guildId).orElseThrow(); - HttpHeaders headers = new HttpHeaders(); - headers.setBearerAuth(accessToken); - headers.setContentType(MediaType.APPLICATION_JSON); + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + headers.setContentType(MediaType.APPLICATION_JSON); - HttpEntity entity = new HttpEntity<>(null, headers); - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); + HttpEntity entity = new HttpEntity<>(null, headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); - JSONObject json = new JSONObject(response.getBody()); - JSONArray projects = json.getJSONArray("projects"); + JSONObject json = new JSONObject(response.getBody()); + JSONArray projects = json.getJSONArray("projects"); - List projectIds = new ArrayList<>(); - for (int i = 0; i < projects.length(); i++) { - projectIds.add(projects.getJSONObject(i).getString("projectId")); + List projectIds = new ArrayList<>(); + for (int i = 0; i < projects.length(); i++) { + projectIds.add(projects.getJSONObject(i).getString("projectId")); + } + return projectIds; + } catch (Exception e) { + log.error("❌ 프로젝트 ID 조회 중 에러 발생", e); + return null; } - return projectIds; } public List getActiveInstanceZones(String userId, String guildId) { @@ -261,7 +271,8 @@ public List getActiveInstanceZones(String userId, String guildId activeZones.add(dto); } catch (Exception e) { - log.warn("프로젝트 Zone 조회 실패 {}: {}", projectId, e.getMessage()); + log.warn("❌ 프로젝트 Zone 조회 실패 {}", projectId, e); + return null; } } From 992fd6383141edff2c63b6f30a90722052ce6a61 Mon Sep 17 00:00:00 2001 From: Jinyoung Date: Sat, 9 Aug 2025 16:29:22 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20=EC=97=90=EB=9F=AC=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/gcp/domain/discord/service/GcpBotService.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/gcp/domain/discord/service/GcpBotService.java b/src/main/java/com/gcp/domain/discord/service/GcpBotService.java index 942cbe3..9f123b2 100644 --- a/src/main/java/com/gcp/domain/discord/service/GcpBotService.java +++ b/src/main/java/com/gcp/domain/discord/service/GcpBotService.java @@ -154,7 +154,13 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { gcpService.enableVmNotifications(); event.reply("✅ GCP VM 상태 변경 시 알림을 받을 수 있습니다!").queue(); } - case "list" -> event.reply(gcpService.getVmList(userId, guildId).toString()).queue(); + case "list" -> { + try { + event.reply(gcpService.getVmList(userId, guildId).toString()).queue(); + } catch (Exception e){ + event.reply("❌ 인스턴스 조회 중 오류가 발생했습니다.").queue(); + } + } case "create" -> { try { String vmName = getRequiredOption(event, "vm_name"); From bd8c845168d4cda9a95f12d7a3cdd23b3cca780d Mon Sep 17 00:00:00 2001 From: Jinyoung Date: Sat, 9 Aug 2025 16:34:27 +0900 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20=EC=97=90=EB=9F=AC=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/gcp/domain/discord/service/GcpBotService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/gcp/domain/discord/service/GcpBotService.java b/src/main/java/com/gcp/domain/discord/service/GcpBotService.java index 9f123b2..20d2753 100644 --- a/src/main/java/com/gcp/domain/discord/service/GcpBotService.java +++ b/src/main/java/com/gcp/domain/discord/service/GcpBotService.java @@ -158,7 +158,7 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { try { event.reply(gcpService.getVmList(userId, guildId).toString()).queue(); } catch (Exception e){ - event.reply("❌ 인스턴스 조회 중 오류가 발생했습니다.").queue(); + event.reply("❌ 인스턴스 조회 중 오류가 발생했습니다. " + e.getMessage()).queue(); } } case "create" -> { From b911aee655a01b948a89af5e2ddd2c93797870c1 Mon Sep 17 00:00:00 2001 From: Jinyoung Date: Sat, 9 Aug 2025 21:02:37 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20GcpService=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20=EC=95=8A=EA=B3=A0=20=EC=98=88=EC=99=B8=EB=A7=8C=20?= =?UTF-8?q?=EB=8D=98=EC=A7=80=EA=B3=A0,=20GcpBotService=EC=97=90=EC=84=9C?= =?UTF-8?q?=EB=8A=94=20=EC=98=88=EC=99=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80?= =?UTF-8?q?=EB=A5=BC=20=EC=B1=84=EB=84=90=EB=A1=9C=20=EC=A0=84=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/discord/service/GcpBotService.java | 127 +++++++++++------- .../gcp/domain/gcp/service/GcpService.java | 39 +++--- 2 files changed, 93 insertions(+), 73 deletions(-) diff --git a/src/main/java/com/gcp/domain/discord/service/GcpBotService.java b/src/main/java/com/gcp/domain/discord/service/GcpBotService.java index 20d2753..b8bc832 100644 --- a/src/main/java/com/gcp/domain/discord/service/GcpBotService.java +++ b/src/main/java/com/gcp/domain/discord/service/GcpBotService.java @@ -127,26 +127,38 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { } case "start" -> { - String vmName = getRequiredOption(event, "vm_name"); - event.reply(gcpService.startVM(userId, guildId, vmName)).queue(); + try{ + String vmName = getRequiredOption(event, "vm_name"); + event.reply(gcpService.startVM(userId, guildId, vmName)).queue(); + } catch (RuntimeException e){ + event.reply("❌ " + e.getMessage()).queue();; + } } case "stop" -> { - String vmName = getRequiredOption(event, "vm_name"); - event.reply(gcpService.stopVM(userId, guildId, vmName)).queue(); + try { + String vmName = getRequiredOption(event, "vm_name"); + event.reply(gcpService.stopVM(userId, guildId, vmName)).queue(); + } catch (RuntimeException e){ + event.reply("❌ " + e.getMessage()).queue(); + } } case "logs" -> { - String vmName = getRequiredOption(event, "vm_name"); - event.deferReply().queue(); + try{ + String vmName = getRequiredOption(event, "vm_name"); + event.deferReply().queue(); - List logs = gcpService.getVmLogs(userId, guildId, vmName); + List logs = gcpService.getVmLogs(userId, guildId, vmName); - if (logs.isEmpty()) { - event.getHook().sendMessage("📭 로그가 없습니다.").queue(); - return; - } + if (logs.isEmpty()) { + event.getHook().sendMessage("📭 로그가 없습니다.").queue(); + return; + } - for (String log : logs) { - event.getHook().sendMessage("```bash\n" + log + "\n```").queue(); + for (String log : logs) { + event.getHook().sendMessage("```bash\n" + log + "\n```").queue(); + } + } catch (RuntimeException e){ + event.reply("❌ " + e.getMessage()).queue(); } } case "cost" -> event.reply(gcpService.getEstimatedCost()).queue(); @@ -158,7 +170,7 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { try { event.reply(gcpService.getVmList(userId, guildId).toString()).queue(); } catch (Exception e){ - event.reply("❌ 인스턴스 조회 중 오류가 발생했습니다. " + e.getMessage()).queue(); + event.reply("보유 중인 인스턴스가 없습니다.").queue(); } } case "create" -> { @@ -178,64 +190,77 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { String result = gcpService.createVM(userId, guildId, vmName, machineType, osImage, bootDiskGb, allowHttp, allowHttps); event.reply(result).queue(); } catch (Exception e) { - event.reply("❌ VM 생성 중 오류 발생: " + e.getMessage()).queue(); + event.reply("❌ " + e.getMessage()).queue(); } } case "firewall-list" -> { event.deferReply().queue(); + try{ + List> rules = gcpService.getFirewallRules(userId, guildId); - List> rules = gcpService.getFirewallRules(userId, guildId); + if (rules.isEmpty()) { + event.getHook().sendMessage("📭 조회된 방화벽 규칙이 없습니다.").queue(); + return; + } - if (rules.isEmpty()) { - event.getHook().sendMessage("📭 조회된 방화벽 규칙이 없습니다.").queue(); - return; - } + StringBuilder sb = new StringBuilder("📌 현재 방화벽 규칙 목록 (TCP 기준):\n"); - StringBuilder sb = new StringBuilder("📌 현재 방화벽 규칙 목록 (TCP 기준):\n"); + for (Map rule : rules) { + String name = (String) rule.get("name"); + List ports = (List) rule.get("tcpPorts"); + JsonNode sourceRanges = (JsonNode) rule.get("sourceRanges"); - for (Map rule : rules) { - String name = (String) rule.get("name"); - List ports = (List) rule.get("tcpPorts"); - JsonNode sourceRanges = (JsonNode) rule.get("sourceRanges"); + sb.append("• `").append(name).append("` - 포트: ") + .append(ports.isEmpty() ? "없음" : String.join(", ", ports)) + .append(", IP 범위: ").append(sourceRanges.toString()).append("\n"); + } - sb.append("• `").append(name).append("` - 포트: ") - .append(ports.isEmpty() ? "없음" : String.join(", ", ports)) - .append(", IP 범위: ").append(sourceRanges.toString()).append("\n"); + event.getHook().sendMessage(sb.toString()).queue(); + } catch (RuntimeException e){ + event.reply("❌ " + e.getMessage()).queue(); } - event.getHook().sendMessage(sb.toString()).queue(); + } case "firewall-create" -> { - int port = Optional.ofNullable(event.getOption("port")) - .map(OptionMapping::getAsInt) - .orElseThrow(() -> new IllegalArgumentException("포트가 필요합니다.")); + try{ + int port = Optional.ofNullable(event.getOption("port")) + .map(OptionMapping::getAsInt) + .orElseThrow(() -> new IllegalArgumentException("포트가 필요합니다.")); - if (port < 1 || port > 65535) { - event.reply("❌ 유효하지 않은 포트 번호입니다. 1 ~ 65535 사이여야 합니다.").setEphemeral(true).queue(); - return; - } + if (port < 1 || port > 65535) { + event.reply("❌ 유효하지 않은 포트 번호입니다. 1 ~ 65535 사이여야 합니다.").setEphemeral(true).queue(); + return; + } - String ipRangeRaw = Optional.ofNullable(event.getOption("source_ranges")) - .map(OptionMapping::getAsString) - .orElse("0.0.0.0/0"); + String ipRangeRaw = Optional.ofNullable(event.getOption("source_ranges")) + .map(OptionMapping::getAsString) + .orElse("0.0.0.0/0"); - List sourceRanges = List.of(ipRangeRaw.split("\\s*,\\s*")); + List sourceRanges = List.of(ipRangeRaw.split("\\s*,\\s*")); - String result = gcpService.createFirewallRule(userId, guildId, port, sourceRanges); - event.reply(result).queue(); + String result = gcpService.createFirewallRule(userId, guildId, port, sourceRanges); + event.reply(result).queue(); + } catch (RuntimeException e){ + event.reply("❌ " + e.getMessage()).queue(); + } } case "firewall-delete" -> { - int port = Optional.ofNullable(event.getOption("port")) - .map(OptionMapping::getAsInt) - .orElseThrow(() -> new IllegalArgumentException("포트가 필요합니다.")); + try{ + int port = Optional.ofNullable(event.getOption("port")) + .map(OptionMapping::getAsInt) + .orElseThrow(() -> new IllegalArgumentException("포트가 필요합니다.")); - if (port < 1 || port > 65535) { - event.reply("❌ 유효하지 않은 포트 번호입니다. 1 ~ 65535 사이여야 합니다.").setEphemeral(true).queue(); - return; - } + if (port < 1 || port > 65535) { + event.reply("❌ 유효하지 않은 포트 번호입니다. 1 ~ 65535 사이여야 합니다.").setEphemeral(true).queue(); + return; + } - String result = gcpService.deleteFirewallRule(userId, guildId, port); - event.reply(result).queue(); + String result = gcpService.deleteFirewallRule(userId, guildId, port); + event.reply(result).queue(); + } catch (RuntimeException e){ + event.reply("❌ " + e.getMessage()).queue(); + } } default -> event.reply("❌ 지원하지 않는 명령어입니다.").queue(); } diff --git a/src/main/java/com/gcp/domain/gcp/service/GcpService.java b/src/main/java/com/gcp/domain/gcp/service/GcpService.java index 2f5c128..9344ee8 100644 --- a/src/main/java/com/gcp/domain/gcp/service/GcpService.java +++ b/src/main/java/com/gcp/domain/gcp/service/GcpService.java @@ -6,12 +6,11 @@ import com.gcp.domain.discord.repository.DiscordUserRepository; import com.gcp.domain.gcp.dto.ProjectZoneDto; import com.gcp.domain.gcp.repository.GcpProjectRepository; -import com.google.auth.oauth2.GoogleCredentials; -import com.google.cloud.compute.v1.Project; + import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import org.checkerframework.checker.units.qual.A; + import org.json.JSONArray; import org.json.JSONObject; import org.springframework.http.*; @@ -19,9 +18,8 @@ import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; -import java.io.ByteArrayInputStream; + import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.*; @Service @@ -54,8 +52,8 @@ public String startVM(String userId, String guildId, String vmName) { return "🚀 `" + vmName + "` VM을 실행했습니다!"; } catch (Exception e) { - log.error("VM 실행 오류", e); - return "❌ `" + vmName + "` VM 실행 실패!"; + log.error("❌ VM 시작 오류", e); + throw new RuntimeException("Compute API (start) 호출 도중 에러 발생: ", e); } } @@ -77,7 +75,7 @@ public String stopVM(String userId, String guildId, String vmName) { return "🛑 `" + vmName + "` VM을 중지했습니다!"; } catch (Exception e) { log.error("❌ VM 중지 오류", e); - return "❌ `" + vmName + "` VM 중지 실패!"; + throw new RuntimeException("Compute API (stop) 호출 도중 에러 발생: ", e); } } @@ -103,7 +101,7 @@ public String getInstanceId(String userId, String guildId, String vmName, String } catch (Exception e) { log.error("❌ instance_id 조회 실패", e); - return null; + throw new RuntimeException("Compute API (인스턴스 ID 조회) 호출 도중 에러 발생: ", e); } } @@ -118,7 +116,7 @@ public List getVmLogs(String userId, String guildId, String vmName) { String vmId = getInstanceId(userId, guildId, vmName, ZONE); if (vmId == null){ - return List.of("❌ VM 인스턴스를 찾을 수 없습니다!"); + throw new RuntimeException("현재 보유 중인 VM이 없습니다."); } String filter = String.format( @@ -170,10 +168,7 @@ public List getVmLogs(String userId, String guildId, String vmName) { } catch (Exception e) { log.error("❌ 로그 조회 오류", e); - List errorMessage = new ArrayList<>(); - errorMessage.add("❌ 로그 조회 실패!"); - - return errorMessage; + throw new RuntimeException("Logging API 호출 도중 에러 발생: ", e); } } @@ -184,7 +179,7 @@ public String getEstimatedCost() { return "💰 예상 비용: " + response; } catch (Exception e) { log.error("❌ 비용 조회 오류", e); - return "❌ 비용 조회 실패!"; + throw new RuntimeException("CloudBilling API 호출 도중 에러 발생: ", e); } } @@ -205,7 +200,7 @@ public List> getVmList(String userId, String guildId) { return parseVmResponse(response.getBody()); } catch (Exception e){ log.error("❌ VM 목록 조회 실패", e); - return null; + throw new RuntimeException("Compute API (인스턴스 목록 조회) 호출 도중 에러 발생: ", e); } } @@ -231,7 +226,7 @@ public List getProjectIds(String userId, String guildId) { return projectIds; } catch (Exception e) { log.error("❌ 프로젝트 ID 조회 중 에러 발생", e); - return null; + throw new RuntimeException("CloudResourceManager API 호출 도중 에러 발생: ", e); } } @@ -272,7 +267,7 @@ public List getActiveInstanceZones(String userId, String guildId } catch (Exception e) { log.warn("❌ 프로젝트 Zone 조회 실패 {}", projectId, e); - return null; + throw new RuntimeException("Compute API (VM Zone 조회) 호출 도중 에러 발생: ", e); } } @@ -376,7 +371,7 @@ public String createVM(String userId, String guildId, String vmName, String mach ); } catch (Exception e) { log.error("❌ VM 생성 오류", e); - return "❌ `" + vmName + "` VM 생성 실패!"; + throw new RuntimeException("Compute API (인스턴스 생성) 호출 도중 에러 발생: ", e); } } public List> getFirewallRules(String userId, String guildId) { @@ -421,7 +416,7 @@ public List> getFirewallRules(String userId, String guildId) } catch (Exception e) { log.error("❌ 방화벽 규칙 조회 오류", e); - return List.of(Map.of("error", "방화벽 규칙 조회 실패")); + throw new RuntimeException("Compute API (방화벽 규칙 조회) 호출 도중 에러 발생: ", e); } } public String createFirewallRule(String userId, String guildId, int port, List sourceRanges) { @@ -462,7 +457,7 @@ public String createFirewallRule(String userId, String guildId, int port, List Date: Sat, 9 Aug 2025 23:51:21 +0900 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20defer=20try=EC=97=90=20=ED=8F=AC?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/gcp/domain/discord/service/GcpBotService.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/gcp/domain/discord/service/GcpBotService.java b/src/main/java/com/gcp/domain/discord/service/GcpBotService.java index b8bc832..99085a6 100644 --- a/src/main/java/com/gcp/domain/discord/service/GcpBotService.java +++ b/src/main/java/com/gcp/domain/discord/service/GcpBotService.java @@ -194,8 +194,8 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { } } case "firewall-list" -> { - event.deferReply().queue(); try{ + event.deferReply().queue(); List> rules = gcpService.getFirewallRules(userId, guildId); if (rules.isEmpty()) { @@ -219,8 +219,6 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { } catch (RuntimeException e){ event.reply("❌ " + e.getMessage()).queue(); } - - } case "firewall-create" -> { try{