From c870b9cc5f619490e359c63e0daec18fed53b74c Mon Sep 17 00:00:00 2001 From: leeeunda Date: Tue, 26 Aug 2025 17:16:14 +0900 Subject: [PATCH 1/4] feat: Destroy infra by project id --- .../controller/TerraformController.java | 27 +++++++- .../blockcloud/service/TerraformService.java | 61 +++++++++++++------ 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/blockcloud/controller/TerraformController.java b/src/main/java/com/blockcloud/controller/TerraformController.java index e7edf0c..b079428 100644 --- a/src/main/java/com/blockcloud/controller/TerraformController.java +++ b/src/main/java/com/blockcloud/controller/TerraformController.java @@ -89,17 +89,38 @@ public ResponseDto applyTerraform( return ResponseDto.ok(terraformService.applyTerraform(projectId, requestDto, userDetails.getUsername())); } + /** + * 특정 배포의 인프라를 삭제합니다. + * + * @param projectId 프로젝트 ID + * @param deploymentId 배포 ID + * @param authentication 인증 정보 + * @return 삭제 결과가 담긴 응답 객체 + */ + @Operation( + summary = "특정 배포 인프라 삭제", + description = "특정 배포의 인프라를 삭제합니다. 원본 배포의 Terraform 코드를 사용하여 삭제를 수행합니다." + ) + @DeleteMapping("/deployments/{deploymentId}/destroy") + public ResponseDto destroyTerraformByDeployment( + @Parameter(description = "프로젝트 ID", required = true) @PathVariable Long projectId, + @Parameter(description = "배포 ID", required = true) @PathVariable Long deploymentId, + Authentication authentication) { + CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); + return ResponseDto.ok(terraformService.destroyTerraformByDeployment(projectId, deploymentId, userDetails.getUsername())); + } + /** * Terraform 코드를 실행하여 인프라를 삭제합니다. * * @param projectId 프로젝트 ID * @param requestDto Terraform 삭제 요청 * @param authentication 인증 정보 - * @return 삭제 시작 정보가 담긴 응답 객체 + * @return 삭제 결과가 담긴 응답 객체 */ @Operation( - summary = "Terraform 인프라 삭제", - description = "Terraform 코드를 실행하여 생성된 인프라를 삭제합니다. 배포는 비동기로 실행되며, 삭제 ID를 반환합니다." + summary = "Terraform 인프라 삭제 (코드 기반)", + description = "Terraform 코드를 실행하여 생성된 인프라를 삭제합니다. 배포는 동기로 실행되며, 삭제 결과를 즉시 반환합니다." ) @PostMapping("/destroy") public ResponseDto destroyTerraform( diff --git a/src/main/java/com/blockcloud/service/TerraformService.java b/src/main/java/com/blockcloud/service/TerraformService.java index 5f75b88..2170335 100644 --- a/src/main/java/com/blockcloud/service/TerraformService.java +++ b/src/main/java/com/blockcloud/service/TerraformService.java @@ -37,9 +37,6 @@ public class TerraformService { private final DeploymentRepository deploymentRepository; private final TerraformExecutor terraformExecutor; - /** - * Terraform 코드를 검증합니다. - */ public TerraformValidateResponseDto validateTerraform(Long projectId, TerraformValidateRequestDto requestDto) { Project project = projectRepository.findById(projectId) .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); @@ -53,9 +50,6 @@ public TerraformValidateResponseDto validateTerraform(Long projectId, TerraformV .build(); } - /** - * Terraform 코드의 변경 사항을 미리 확인합니다. - */ public TerraformPlanResponseDto planTerraform(Long projectId, TerraformPlanRequestDto requestDto) { Project project = projectRepository.findById(projectId) .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); @@ -72,9 +66,6 @@ public TerraformPlanResponseDto planTerraform(Long projectId, TerraformPlanReque .build(); } - /** - * Terraform 코드를 적용하여 배포를 시작합니다. - */ @Transactional public TerraformApplyResponseDto applyTerraform(Long projectId, TerraformApplyRequestDto requestDto, String username) { Project project = projectRepository.findById(projectId) @@ -109,9 +100,48 @@ public TerraformApplyResponseDto applyTerraform(Long projectId, TerraformApplyRe .build(); } - /** - * Terraform 코드를 실행하여 인프라를 삭제합니다. - */ + @Transactional + public TerraformDestroyResponseDto destroyTerraformByDeployment(Long projectId, Long deploymentId, String username) { + Project project = projectRepository.findById(projectId) + .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); + + Deployment deployment = deploymentRepository.findById(deploymentId) + .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_DEPLOYMENT)); + + // 프로젝트에 속한 배포인지 확인 + if (!deployment.getProject().getId().equals(projectId)) { + throw new CommonException(ErrorCode.ACCESS_DENIED); + } + + // 삭제용 배포 이력 생성 + Deployment destroyDeployment = Deployment.builder() + .project(project) + .status(DeploymentStatus.PENDING) + .message("인프라 삭제 대기 중") + .terraformCode(deployment.getTerraformCode()) + .startedAt(LocalDateTime.now()) + .build(); + + Deployment savedDestroyDeployment = deploymentRepository.save(destroyDeployment); + + // 비동기로 삭제 실행 + CompletableFuture.runAsync(() -> { + try { + executeTerraformDestroy(savedDestroyDeployment.getId(), projectId, deployment.getTerraformCode()); + } catch (Exception e) { + log.error("Terraform destroy failed for deployment {}: {}", savedDestroyDeployment.getId(), e.getMessage()); + updateDeploymentStatus(savedDestroyDeployment.getId(), DeploymentStatus.FAILED, "삭제 실패: " + e.getMessage()); + } + }); + + return TerraformDestroyResponseDto.builder() + .deploymentId(savedDestroyDeployment.getId()) + .status("PENDING") + .message("인프라 삭제가 시작되었습니다.") + .startedAt(savedDestroyDeployment.getStartedAt()) + .build(); + } + @Transactional public TerraformDestroyResponseDto destroyTerraform(Long projectId, TerraformDestroyRequestDto requestDto, String username) { Project project = projectRepository.findById(projectId) @@ -146,9 +176,6 @@ public TerraformDestroyResponseDto destroyTerraform(Long projectId, TerraformDes .build(); } - /** - * 배포 상태를 조회합니다. - */ public DeploymentStatusResponseDto getDeploymentStatus(Long projectId, Long deploymentId) { Project project = projectRepository.findById(projectId) .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); @@ -171,9 +198,6 @@ public DeploymentStatusResponseDto getDeploymentStatus(Long projectId, Long depl .build(); } - /** - * 프로젝트의 배포 이력을 조회합니다. - */ public DeploymentListResponseDto getDeploymentHistory(Long projectId, Long lastId, int size) { Project project = projectRepository.findById(projectId) .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); @@ -203,7 +227,6 @@ public DeploymentListResponseDto getDeploymentHistory(Long projectId, Long lastI .build(); } - // Private helper methods private void executeTerraformApply(Long deploymentId, Long projectId, String terraformCode) { try { From 87bc21bdc54bb73ad3a30a078c02f93ab721a231 Mon Sep 17 00:00:00 2001 From: leeeunda Date: Tue, 26 Aug 2025 17:32:42 +0900 Subject: [PATCH 2/4] fix: resolve merge conflicts and clean up validate API --- .../blockcloud/controller/TerraformController.java | 4 +--- .../com/blockcloud/service/TerraformService.java | 12 +++++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/blockcloud/controller/TerraformController.java b/src/main/java/com/blockcloud/controller/TerraformController.java index b079428..49647d3 100644 --- a/src/main/java/com/blockcloud/controller/TerraformController.java +++ b/src/main/java/com/blockcloud/controller/TerraformController.java @@ -35,7 +35,6 @@ public class TerraformController { /** * Terraform 코드를 검증합니다. * - * @param projectId 프로젝트 ID * @param requestDto Terraform 코드 검증 요청 * @return 검증 결과가 담긴 응답 객체 */ @@ -45,9 +44,8 @@ public class TerraformController { ) @PostMapping("/validate") public ResponseDto validateTerraform( - @Parameter(description = "프로젝트 ID", required = true) @PathVariable Long projectId, @Valid @RequestBody TerraformValidateRequestDto requestDto) { - return ResponseDto.ok(terraformService.validateTerraform(projectId, requestDto)); + return ResponseDto.ok(terraformService.validateTerraform(requestDto)); } /** diff --git a/src/main/java/com/blockcloud/service/TerraformService.java b/src/main/java/com/blockcloud/service/TerraformService.java index 2170335..443ccfa 100644 --- a/src/main/java/com/blockcloud/service/TerraformService.java +++ b/src/main/java/com/blockcloud/service/TerraformService.java @@ -37,10 +37,10 @@ public class TerraformService { private final DeploymentRepository deploymentRepository; private final TerraformExecutor terraformExecutor; - public TerraformValidateResponseDto validateTerraform(Long projectId, TerraformValidateRequestDto requestDto) { - Project project = projectRepository.findById(projectId) - .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); - + /** + * Terraform 코드를 검증합니다. + */ + public TerraformValidateResponseDto validateTerraform(TerraformValidateRequestDto requestDto) { TerraformExecutor.TerraformExecutionResult result = terraformExecutor.validate(requestDto.getTerraformCode()); return TerraformValidateResponseDto.builder() @@ -50,6 +50,9 @@ public TerraformValidateResponseDto validateTerraform(Long projectId, TerraformV .build(); } + /** + * Terraform 코드의 변경 사항을 미리 확인합니다. + */ public TerraformPlanResponseDto planTerraform(Long projectId, TerraformPlanRequestDto requestDto) { Project project = projectRepository.findById(projectId) .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); @@ -227,7 +230,6 @@ public DeploymentListResponseDto getDeploymentHistory(Long projectId, Long lastI .build(); } - private void executeTerraformApply(Long deploymentId, Long projectId, String terraformCode) { try { // 상태를 RUNNING으로 업데이트 From e2b68aef03ffa7a302a621987e965abdd67bc7ac Mon Sep 17 00:00:00 2001 From: leeeunda Date: Tue, 26 Aug 2025 17:36:16 +0900 Subject: [PATCH 3/4] refactor: remove unused destroyTerraform method and API endpoint --- .../controller/TerraformController.java | 23 +---------- .../blockcloud/service/TerraformService.java | 40 +------------------ 2 files changed, 3 insertions(+), 60 deletions(-) diff --git a/src/main/java/com/blockcloud/controller/TerraformController.java b/src/main/java/com/blockcloud/controller/TerraformController.java index 49647d3..d950da2 100644 --- a/src/main/java/com/blockcloud/controller/TerraformController.java +++ b/src/main/java/com/blockcloud/controller/TerraformController.java @@ -1,7 +1,7 @@ package com.blockcloud.controller; import com.blockcloud.dto.RequestDto.TerraformApplyRequestDto; -import com.blockcloud.dto.RequestDto.TerraformDestroyRequestDto; + import com.blockcloud.dto.RequestDto.TerraformPlanRequestDto; import com.blockcloud.dto.RequestDto.TerraformValidateRequestDto; import com.blockcloud.dto.ResponseDto.DeploymentListResponseDto; @@ -108,26 +108,7 @@ public ResponseDto destroyTerraformByDeployment( return ResponseDto.ok(terraformService.destroyTerraformByDeployment(projectId, deploymentId, userDetails.getUsername())); } - /** - * Terraform 코드를 실행하여 인프라를 삭제합니다. - * - * @param projectId 프로젝트 ID - * @param requestDto Terraform 삭제 요청 - * @param authentication 인증 정보 - * @return 삭제 결과가 담긴 응답 객체 - */ - @Operation( - summary = "Terraform 인프라 삭제 (코드 기반)", - description = "Terraform 코드를 실행하여 생성된 인프라를 삭제합니다. 배포는 동기로 실행되며, 삭제 결과를 즉시 반환합니다." - ) - @PostMapping("/destroy") - public ResponseDto destroyTerraform( - @Parameter(description = "프로젝트 ID", required = true) @PathVariable Long projectId, - @Valid @RequestBody TerraformDestroyRequestDto requestDto, - Authentication authentication) { - CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); - return ResponseDto.ok(terraformService.destroyTerraform(projectId, requestDto, userDetails.getUsername())); - } + /** * 특정 배포의 상태를 조회합니다. diff --git a/src/main/java/com/blockcloud/service/TerraformService.java b/src/main/java/com/blockcloud/service/TerraformService.java index 443ccfa..586f946 100644 --- a/src/main/java/com/blockcloud/service/TerraformService.java +++ b/src/main/java/com/blockcloud/service/TerraformService.java @@ -6,7 +6,7 @@ import com.blockcloud.domain.project.Project; import com.blockcloud.domain.project.ProjectRepository; import com.blockcloud.dto.RequestDto.TerraformApplyRequestDto; -import com.blockcloud.dto.RequestDto.TerraformDestroyRequestDto; + import com.blockcloud.dto.RequestDto.TerraformPlanRequestDto; import com.blockcloud.dto.RequestDto.TerraformValidateRequestDto; import com.blockcloud.dto.ResponseDto.DeploymentListResponseDto; @@ -37,9 +37,6 @@ public class TerraformService { private final DeploymentRepository deploymentRepository; private final TerraformExecutor terraformExecutor; - /** - * Terraform 코드를 검증합니다. - */ public TerraformValidateResponseDto validateTerraform(TerraformValidateRequestDto requestDto) { TerraformExecutor.TerraformExecutionResult result = terraformExecutor.validate(requestDto.getTerraformCode()); @@ -50,9 +47,6 @@ public TerraformValidateResponseDto validateTerraform(TerraformValidateRequestDt .build(); } - /** - * Terraform 코드의 변경 사항을 미리 확인합니다. - */ public TerraformPlanResponseDto planTerraform(Long projectId, TerraformPlanRequestDto requestDto) { Project project = projectRepository.findById(projectId) .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); @@ -145,39 +139,7 @@ public TerraformDestroyResponseDto destroyTerraformByDeployment(Long projectId, .build(); } - @Transactional - public TerraformDestroyResponseDto destroyTerraform(Long projectId, TerraformDestroyRequestDto requestDto, String username) { - Project project = projectRepository.findById(projectId) - .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); - - // 배포 이력 생성 (삭제용) - Deployment deployment = Deployment.builder() - .project(project) - .status(DeploymentStatus.PENDING) - .message("인프라 삭제 대기 중") - .terraformCode(requestDto.getTerraformCode()) - .startedAt(LocalDateTime.now()) - .build(); - - Deployment savedDeployment = deploymentRepository.save(deployment); - - // 비동기로 삭제 실행 - CompletableFuture.runAsync(() -> { - try { - executeTerraformDestroy(savedDeployment.getId(), projectId, requestDto.getTerraformCode()); - } catch (Exception e) { - log.error("Terraform destroy failed for deployment {}: {}", savedDeployment.getId(), e.getMessage()); - updateDeploymentStatus(savedDeployment.getId(), DeploymentStatus.FAILED, "삭제 실패: " + e.getMessage()); - } - }); - return TerraformDestroyResponseDto.builder() - .deploymentId(savedDeployment.getId()) - .status("PENDING") - .message("인프라 삭제가 시작되었습니다.") - .startedAt(savedDeployment.getStartedAt()) - .build(); - } public DeploymentStatusResponseDto getDeploymentStatus(Long projectId, Long deploymentId) { Project project = projectRepository.findById(projectId) From 7df357d87705ea81859347c553dbc9a607835deb Mon Sep 17 00:00:00 2001 From: leeeunda Date: Tue, 26 Aug 2025 17:38:05 +0900 Subject: [PATCH 4/4] fix: resolve merge conflicts and add method documentation --- .../java/com/blockcloud/service/TerraformService.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/blockcloud/service/TerraformService.java b/src/main/java/com/blockcloud/service/TerraformService.java index 586f946..3a0a757 100644 --- a/src/main/java/com/blockcloud/service/TerraformService.java +++ b/src/main/java/com/blockcloud/service/TerraformService.java @@ -37,6 +37,9 @@ public class TerraformService { private final DeploymentRepository deploymentRepository; private final TerraformExecutor terraformExecutor; + /** + * Terraform 코드를 검증합니다. + */ public TerraformValidateResponseDto validateTerraform(TerraformValidateRequestDto requestDto) { TerraformExecutor.TerraformExecutionResult result = terraformExecutor.validate(requestDto.getTerraformCode()); @@ -47,6 +50,9 @@ public TerraformValidateResponseDto validateTerraform(TerraformValidateRequestDt .build(); } + /** + * Terraform 코드의 변경 사항을 미리 확인합니다. + */ public TerraformPlanResponseDto planTerraform(Long projectId, TerraformPlanRequestDto requestDto) { Project project = projectRepository.findById(projectId) .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); @@ -139,8 +145,6 @@ public TerraformDestroyResponseDto destroyTerraformByDeployment(Long projectId, .build(); } - - public DeploymentStatusResponseDto getDeploymentStatus(Long projectId, Long deploymentId) { Project project = projectRepository.findById(projectId) .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT));