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/discord/service/GcpBotService.java b/src/main/java/com/gcp/domain/discord/service/GcpBotService.java index 942cbe3..99085a6 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(); @@ -154,7 +166,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"); @@ -172,64 +190,75 @@ 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{ + event.deferReply().queue(); + 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 65068d4..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,45 +179,55 @@ public String getEstimatedCost() { return "πŸ’° μ˜ˆμƒ λΉ„μš©: " + response; } catch (Exception e) { log.error("❌ λΉ„μš© 쑰회 였λ₯˜", e); - return "❌ λΉ„μš© 쑰회 μ‹€νŒ¨!"; + throw new RuntimeException("CloudBilling API 호좜 도쀑 μ—λŸ¬ λ°œμƒ: ", e); } } @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); + throw new RuntimeException("Compute API (μΈμŠ€ν„΄μŠ€ λͺ©λ‘ 쑰회) 호좜 도쀑 μ—λŸ¬ λ°œμƒ: ", e); + } } 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); + throw new RuntimeException("CloudResourceManager API 호좜 도쀑 μ—λŸ¬ λ°œμƒ: ", e); } - return projectIds; } public List getActiveInstanceZones(String userId, String guildId) { @@ -261,7 +266,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); + throw new RuntimeException("Compute API (VM Zone 쑰회) 호좜 도쀑 μ—λŸ¬ λ°œμƒ: ", e); } } @@ -365,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) { @@ -410,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) { @@ -451,7 +457,7 @@ public String createFirewallRule(String userId, String guildId, int port, List