From aaefe5d3f6a70cae71fd3da00c4b5c3977479141 Mon Sep 17 00:00:00 2001 From: leeeunda Date: Thu, 14 Aug 2025 19:50:00 +0900 Subject: [PATCH 01/12] build: To resolve the timeout issue --- .github/workflows/deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index eefe77a..0cf63c4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -75,6 +75,8 @@ jobs: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} + timeout: 60s + command_timeout: 15m script: | set -euo pipefail From 5ce73e67de242b6a5b0cc7e80f916304b8d68cd2 Mon Sep 17 00:00:00 2001 From: leeeunda Date: Fri, 15 Aug 2025 19:52:59 +0900 Subject: [PATCH 02/12] feat: add swagger url to Security Config --- src/main/java/com/blockcloud/config/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/blockcloud/config/SecurityConfig.java b/src/main/java/com/blockcloud/config/SecurityConfig.java index 2e71b3e..b533d9e 100644 --- a/src/main/java/com/blockcloud/config/SecurityConfig.java +++ b/src/main/java/com/blockcloud/config/SecurityConfig.java @@ -52,6 +52,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/signup/success", "/error", "/login/oauth2/code/google", + "/swagger-ui", "/swagger-ui/**", "/v3/api-docs/**", "/actuator/**" From b452e46e0558126747ffae8711e8ba1f5d405ee1 Mon Sep 17 00:00:00 2001 From: leeeunda Date: Fri, 15 Aug 2025 19:54:00 +0900 Subject: [PATCH 03/12] feat: terraform validate & apply --- .../controller/TerraformController.java | 109 ++++++++++++++++++ .../domain/deployment/Deployment.java | 55 +++++++++ .../deployment/DeploymentRepository.java | 18 +++ .../domain/deployment/DeploymentStatus.java | 8 ++ .../RequestDto/TerraformApplyRequestDto.java | 17 +++ .../TerraformValidateRequestDto.java | 17 +++ .../DeploymentListResponseDto.java | 14 +++ .../DeploymentStatusResponseDto.java | 18 +++ .../TerraformApplyResponseDto.java | 16 +++ .../TerraformValidateResponseDto.java | 15 +++ 10 files changed, 287 insertions(+) create mode 100644 src/main/java/com/blockcloud/controller/TerraformController.java create mode 100644 src/main/java/com/blockcloud/domain/deployment/Deployment.java create mode 100644 src/main/java/com/blockcloud/domain/deployment/DeploymentRepository.java create mode 100644 src/main/java/com/blockcloud/domain/deployment/DeploymentStatus.java create mode 100644 src/main/java/com/blockcloud/dto/RequestDto/TerraformApplyRequestDto.java create mode 100644 src/main/java/com/blockcloud/dto/RequestDto/TerraformValidateRequestDto.java create mode 100644 src/main/java/com/blockcloud/dto/ResponseDto/DeploymentListResponseDto.java create mode 100644 src/main/java/com/blockcloud/dto/ResponseDto/DeploymentStatusResponseDto.java create mode 100644 src/main/java/com/blockcloud/dto/ResponseDto/TerraformApplyResponseDto.java create mode 100644 src/main/java/com/blockcloud/dto/ResponseDto/TerraformValidateResponseDto.java diff --git a/src/main/java/com/blockcloud/controller/TerraformController.java b/src/main/java/com/blockcloud/controller/TerraformController.java new file mode 100644 index 0000000..62aada6 --- /dev/null +++ b/src/main/java/com/blockcloud/controller/TerraformController.java @@ -0,0 +1,109 @@ +package com.blockcloud.controller; + +import com.blockcloud.dto.RequestDto.TerraformApplyRequestDto; +import com.blockcloud.dto.RequestDto.TerraformValidateRequestDto; +import com.blockcloud.dto.ResponseDto.DeploymentListResponseDto; +import com.blockcloud.dto.ResponseDto.DeploymentStatusResponseDto; +import com.blockcloud.dto.ResponseDto.TerraformApplyResponseDto; +import com.blockcloud.dto.ResponseDto.TerraformValidateResponseDto; +import com.blockcloud.dto.common.ResponseDto; +import com.blockcloud.dto.oauth.CustomUserDetails; +import com.blockcloud.service.TerraformService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +/** + * Terraform 관련 API 요청을 처리하는 컨트롤러입니다. Terraform 코드 검증, 배포, 배포 상태 조회 기능을 제공합니다. + */ +@Tag(name = "Terraform API", description = "Terraform 코드 검증, 배포, 배포 상태 조회 관련 API") +@RestController +@RequestMapping("/api/projects/{projectId}/terraform") +@RequiredArgsConstructor +public class TerraformController { + + private final TerraformService terraformService; + + /** + * Terraform 코드를 검증합니다. + * + * @param projectId 프로젝트 ID + * @param requestDto Terraform 코드 검증 요청 + * @return 검증 결과가 담긴 응답 객체 + */ + @Operation( + summary = "Terraform 코드 검증", + description = "Terraform 코드의 문법과 구성을 검증합니다. 유효성 검사 결과와 에러/경고 메시지를 반환합니다." + ) + @PostMapping("/validate") + public ResponseDto validateTerraform( + @Parameter(description = "프로젝트 ID", required = true) @PathVariable Long projectId, + @Valid @RequestBody TerraformValidateRequestDto requestDto) { + return ResponseDto.ok(terraformService.validateTerraform(projectId, requestDto)); + } + + /** + * Terraform 코드를 적용하여 배포를 시작합니다. + * + * @param projectId 프로젝트 ID + * @param requestDto Terraform 배포 요청 + * @param authentication 인증 정보 + * @return 배포 시작 정보가 담긴 응답 객체 + */ + @Operation( + summary = "Terraform 코드 배포", + description = "Terraform 코드를 실제 클라우드 환경에 적용하여 인프라를 배포합니다. 배포는 비동기로 실행되며, 배포 ID를 반환합니다." + ) + @PostMapping("/apply") + public ResponseDto applyTerraform( + @Parameter(description = "프로젝트 ID", required = true) @PathVariable Long projectId, + @Valid @RequestBody TerraformApplyRequestDto requestDto, + Authentication authentication) { + CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); + return ResponseDto.ok(terraformService.applyTerraform(projectId, requestDto, userDetails.getUsername())); + } + + /** + * 특정 배포의 상태를 조회합니다. + * + * @param projectId 프로젝트 ID + * @param deploymentId 배포 ID + * @return 배포 상태 정보가 담긴 응답 객체 + */ + @Operation( + summary = "배포 상태 조회", + description = "특정 배포의 현재 상태를 조회합니다. PENDING, RUNNING, SUCCESS, FAILED 상태와 상세 정보를 반환합니다." + ) + @GetMapping("/deployments/{deploymentId}") + public ResponseDto getDeploymentStatus( + @Parameter(description = "프로젝트 ID", required = true) @PathVariable Long projectId, + @Parameter(description = "배포 ID", required = true) @PathVariable Long deploymentId) { + return ResponseDto.ok(terraformService.getDeploymentStatus(projectId, deploymentId)); + } + + /** + * 프로젝트의 배포 이력을 조회합니다. + * + * @param projectId 프로젝트 ID + * @param lastId 마지막으로 조회된 배포 ID (첫 조회 시 null) + * @param size 한 번에 조회할 배포 수 (기본값: 10) + * @return 배포 이력 목록이 담긴 응답 객체 + */ + @Operation( + summary = "배포 이력 조회", + description = "프로젝트의 배포 이력을 무한스크롤 방식으로 조회합니다. `lastId`를 넘기면 해당 ID보다 작은 배포부터 조회하며, `size`로 가져올 개수를 지정할 수 있습니다." + ) + @GetMapping("/deployments") + public ResponseDto getDeploymentHistory( + @Parameter(description = "프로젝트 ID", required = true) @PathVariable Long projectId, + @Parameter(description = "마지막으로 조회한 배포 ID (첫 호출 시 생략 가능)") + @RequestParam(required = false) Long lastId, + @Parameter(description = "가져올 데이터 개수 (기본값 10)") + @RequestParam(defaultValue = "10") int size) { + return ResponseDto.ok(terraformService.getDeploymentHistory(projectId, lastId, size)); + } +} diff --git a/src/main/java/com/blockcloud/domain/deployment/Deployment.java b/src/main/java/com/blockcloud/domain/deployment/Deployment.java new file mode 100644 index 0000000..13f1f5a --- /dev/null +++ b/src/main/java/com/blockcloud/domain/deployment/Deployment.java @@ -0,0 +1,55 @@ +package com.blockcloud.domain.deployment; + +import com.blockcloud.domain.global.BaseTimeEntity; +import com.blockcloud.domain.project.Project; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Deployment extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "project_id", nullable = false) + private Project project; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private DeploymentStatus status; + + @Column(columnDefinition = "TEXT") + private String message; + + @Column(columnDefinition = "LONGTEXT") + private String terraformCode; + + @Column(columnDefinition = "LONGTEXT") + private String output; + + @Column(name = "started_at") + private LocalDateTime startedAt; + + @Column(name = "completed_at") + private LocalDateTime completedAt; + + public void updateStatus(DeploymentStatus status, String message) { + this.status = status; + this.message = message; + if (status == DeploymentStatus.SUCCESS || status == DeploymentStatus.FAILED) { + this.completedAt = LocalDateTime.now(); + } + } + + public void setOutput(String output) { + this.output = output; + } +} diff --git a/src/main/java/com/blockcloud/domain/deployment/DeploymentRepository.java b/src/main/java/com/blockcloud/domain/deployment/DeploymentRepository.java new file mode 100644 index 0000000..ecc968f --- /dev/null +++ b/src/main/java/com/blockcloud/domain/deployment/DeploymentRepository.java @@ -0,0 +1,18 @@ +package com.blockcloud.domain.deployment; + +import com.blockcloud.domain.project.Project; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface DeploymentRepository extends JpaRepository { + + @Query("SELECT d FROM Deployment d WHERE d.project = :project ORDER BY d.createdAt DESC") + List findByProjectOrderByCreatedAtDesc(@Param("project") Project project, Pageable pageable); + + @Query("SELECT COUNT(d) > 0 FROM Deployment d WHERE d.project = :project AND d.createdAt > :lastId") + boolean existsByProjectAndCreatedAtAfter(@Param("project") Project project, @Param("lastId") Long lastId); +} diff --git a/src/main/java/com/blockcloud/domain/deployment/DeploymentStatus.java b/src/main/java/com/blockcloud/domain/deployment/DeploymentStatus.java new file mode 100644 index 0000000..4f5497a --- /dev/null +++ b/src/main/java/com/blockcloud/domain/deployment/DeploymentStatus.java @@ -0,0 +1,8 @@ +package com.blockcloud.domain.deployment; + +public enum DeploymentStatus { + PENDING, // 대기 중 + RUNNING, // 실행 중 + SUCCESS, // 성공 + FAILED // 실패 +} diff --git a/src/main/java/com/blockcloud/dto/RequestDto/TerraformApplyRequestDto.java b/src/main/java/com/blockcloud/dto/RequestDto/TerraformApplyRequestDto.java new file mode 100644 index 0000000..6926a02 --- /dev/null +++ b/src/main/java/com/blockcloud/dto/RequestDto/TerraformApplyRequestDto.java @@ -0,0 +1,17 @@ +package com.blockcloud.dto.RequestDto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TerraformApplyRequestDto { + + @NotBlank(message = "Terraform 코드는 필수입니다.") + private String terraformCode; +} diff --git a/src/main/java/com/blockcloud/dto/RequestDto/TerraformValidateRequestDto.java b/src/main/java/com/blockcloud/dto/RequestDto/TerraformValidateRequestDto.java new file mode 100644 index 0000000..77f7a61 --- /dev/null +++ b/src/main/java/com/blockcloud/dto/RequestDto/TerraformValidateRequestDto.java @@ -0,0 +1,17 @@ +package com.blockcloud.dto.RequestDto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TerraformValidateRequestDto { + + @NotBlank(message = "Terraform 코드는 필수입니다.") + private String terraformCode; +} diff --git a/src/main/java/com/blockcloud/dto/ResponseDto/DeploymentListResponseDto.java b/src/main/java/com/blockcloud/dto/ResponseDto/DeploymentListResponseDto.java new file mode 100644 index 0000000..7f9f246 --- /dev/null +++ b/src/main/java/com/blockcloud/dto/ResponseDto/DeploymentListResponseDto.java @@ -0,0 +1,14 @@ +package com.blockcloud.dto.ResponseDto; + +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +@Builder +public class DeploymentListResponseDto { + + private List deployments; + private boolean hasNext; +} diff --git a/src/main/java/com/blockcloud/dto/ResponseDto/DeploymentStatusResponseDto.java b/src/main/java/com/blockcloud/dto/ResponseDto/DeploymentStatusResponseDto.java new file mode 100644 index 0000000..d04b747 --- /dev/null +++ b/src/main/java/com/blockcloud/dto/ResponseDto/DeploymentStatusResponseDto.java @@ -0,0 +1,18 @@ +package com.blockcloud.dto.ResponseDto; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +@Builder +public class DeploymentStatusResponseDto { + + private Long deploymentId; + private String status; // PENDING, RUNNING, SUCCESS, FAILED + private String message; + private LocalDateTime startedAt; + private LocalDateTime completedAt; + private String output; +} diff --git a/src/main/java/com/blockcloud/dto/ResponseDto/TerraformApplyResponseDto.java b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformApplyResponseDto.java new file mode 100644 index 0000000..4117122 --- /dev/null +++ b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformApplyResponseDto.java @@ -0,0 +1,16 @@ +package com.blockcloud.dto.ResponseDto; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +@Builder +public class TerraformApplyResponseDto { + + private Long deploymentId; + private String status; + private String message; + private LocalDateTime startedAt; +} diff --git a/src/main/java/com/blockcloud/dto/ResponseDto/TerraformValidateResponseDto.java b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformValidateResponseDto.java new file mode 100644 index 0000000..95404a0 --- /dev/null +++ b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformValidateResponseDto.java @@ -0,0 +1,15 @@ +package com.blockcloud.dto.ResponseDto; + +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +@Builder +public class TerraformValidateResponseDto { + + private boolean isValid; + private List errors; + private List warnings; +} From 225b0f7841cd1c8d8541d8eba26209734a0c663d Mon Sep 17 00:00:00 2001 From: leeeunda Date: Fri, 15 Aug 2025 19:54:13 +0900 Subject: [PATCH 04/12] feat: add Deploymets Errors --- .../java/com/blockcloud/exception/error/ErrorCode.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/blockcloud/exception/error/ErrorCode.java b/src/main/java/com/blockcloud/exception/error/ErrorCode.java index 365dae5..a14bd8c 100644 --- a/src/main/java/com/blockcloud/exception/error/ErrorCode.java +++ b/src/main/java/com/blockcloud/exception/error/ErrorCode.java @@ -52,7 +52,13 @@ public enum ErrorCode { EXTERNAL_SERVER_ERROR(50200, HttpStatus.BAD_GATEWAY, "서버 외부 에러입니다."), // Project Errors - NOT_FOUND_PROJECT(40401, HttpStatus.NOT_FOUND, "존재하지 않는 프로젝트입니다."); + NOT_FOUND_PROJECT(40401, HttpStatus.NOT_FOUND, "존재하지 않는 프로젝트입니다."), + + // Deployment Errors + NOT_FOUND_DEPLOYMENT(40402, HttpStatus.NOT_FOUND, "존재하지 않는 배포입니다."), + DEPLOYMENT_ALREADY_RUNNING(40901, HttpStatus.CONFLICT, "이미 실행 중인 배포가 있습니다."), + TERRAFORM_VALIDATION_FAILED(40010, HttpStatus.BAD_REQUEST, "Terraform 코드 검증에 실패했습니다."), + TERRAFORM_APPLY_FAILED(50002, HttpStatus.INTERNAL_SERVER_ERROR, "Terraform 배포에 실패했습니다."); private final Integer code; private final HttpStatus httpStatus; From 47d3edbaa62a5c983dd15c39feff24b50be72ebb Mon Sep 17 00:00:00 2001 From: leeeunda Date: Fri, 15 Aug 2025 19:54:32 +0900 Subject: [PATCH 05/12] feat: add Terraform Service --- .../blockcloud/service/TerraformService.java | 281 ++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 src/main/java/com/blockcloud/service/TerraformService.java diff --git a/src/main/java/com/blockcloud/service/TerraformService.java b/src/main/java/com/blockcloud/service/TerraformService.java new file mode 100644 index 0000000..8dae96d --- /dev/null +++ b/src/main/java/com/blockcloud/service/TerraformService.java @@ -0,0 +1,281 @@ +package com.blockcloud.service; + +import com.blockcloud.domain.deployment.Deployment; +import com.blockcloud.domain.deployment.DeploymentRepository; +import com.blockcloud.domain.deployment.DeploymentStatus; +import com.blockcloud.domain.project.Project; +import com.blockcloud.domain.project.ProjectRepository; +import com.blockcloud.dto.RequestDto.TerraformApplyRequestDto; +import com.blockcloud.dto.RequestDto.TerraformValidateRequestDto; +import com.blockcloud.dto.ResponseDto.DeploymentListResponseDto; +import com.blockcloud.dto.ResponseDto.DeploymentStatusResponseDto; +import com.blockcloud.dto.ResponseDto.TerraformApplyResponseDto; +import com.blockcloud.dto.ResponseDto.TerraformValidateResponseDto; +import com.blockcloud.exception.CommonException; +import com.blockcloud.exception.error.ErrorCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class TerraformService { + + private final ProjectRepository projectRepository; + private final DeploymentRepository deploymentRepository; + + /** + * Terraform 코드를 검증합니다. + */ + public TerraformValidateResponseDto validateTerraform(Long projectId, TerraformValidateRequestDto requestDto) { + Project project = projectRepository.findById(projectId) + .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); + + try { + // 임시 디렉토리 생성 + String tempDir = createTempDirectory(projectId); + String terraformFile = tempDir + "/main.tf"; + + // Terraform 파일 생성 + writeTerraformFile(terraformFile, requestDto.getTerraformCode()); + + // Terraform validate 실행 + ProcessBuilder processBuilder = new ProcessBuilder("terraform", "validate"); + processBuilder.directory(new File(tempDir)); + + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + + List errors = new ArrayList<>(); + List warnings = new ArrayList<>(); + + if (exitCode != 0) { + // 에러 출력 읽기 + String errorOutput = new String(process.getErrorStream().readAllBytes()); + errors.add(errorOutput); + } + + // 임시 디렉토리 정리 + cleanupTempDirectory(tempDir); + + return TerraformValidateResponseDto.builder() + .isValid(exitCode == 0) + .errors(errors) + .warnings(warnings) + .build(); + + } catch (Exception e) { + log.error("Terraform validation failed for project {}: {}", projectId, e.getMessage()); + return TerraformValidateResponseDto.builder() + .isValid(false) + .errors(List.of("Terraform 검증 중 오류가 발생했습니다: " + e.getMessage())) + .warnings(new ArrayList<>()) + .build(); + } + } + + /** + * Terraform 코드를 적용하여 배포를 시작합니다. + */ + @Transactional + public TerraformApplyResponseDto applyTerraform(Long projectId, TerraformApplyRequestDto 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 { + executeTerraformApply(savedDeployment.getId(), projectId, requestDto.getTerraformCode()); + } catch (Exception e) { + log.error("Terraform apply failed for deployment {}: {}", savedDeployment.getId(), e.getMessage()); + updateDeploymentStatus(savedDeployment.getId(), DeploymentStatus.FAILED, "배포 실패: " + e.getMessage()); + } + }); + + return TerraformApplyResponseDto.builder() + .deploymentId(savedDeployment.getId()) + .status("PENDING") + .message("배포가 시작되었습니다.") + .startedAt(savedDeployment.getStartedAt()) + .build(); + } + + /** + * 배포 상태를 조회합니다. + */ + public DeploymentStatusResponseDto getDeploymentStatus(Long projectId, Long deploymentId) { + 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); + } + + return DeploymentStatusResponseDto.builder() + .deploymentId(deployment.getId()) + .status(deployment.getStatus().name()) + .message(deployment.getMessage()) + .startedAt(deployment.getStartedAt()) + .completedAt(deployment.getCompletedAt()) + .output(deployment.getOutput()) + .build(); + } + + /** + * 프로젝트의 배포 이력을 조회합니다. + */ + public DeploymentListResponseDto getDeploymentHistory(Long projectId, Long lastId, int size) { + Project project = projectRepository.findById(projectId) + .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); + + PageRequest pageRequest = PageRequest.of(0, size + 1); + List deployments = deploymentRepository.findByProjectOrderByCreatedAtDesc(project, pageRequest); + + boolean hasNext = deployments.size() > size; + if (hasNext) { + deployments = deployments.subList(0, size); + } + + List deploymentDtos = deployments.stream() + .map(deployment -> DeploymentStatusResponseDto.builder() + .deploymentId(deployment.getId()) + .status(deployment.getStatus().name()) + .message(deployment.getMessage()) + .startedAt(deployment.getStartedAt()) + .completedAt(deployment.getCompletedAt()) + .output(deployment.getOutput()) + .build()) + .toList(); + + return DeploymentListResponseDto.builder() + .deployments(deploymentDtos) + .hasNext(hasNext) + .build(); + } + + // Private helper methods + + private String createTempDirectory(Long projectId) throws IOException { + String tempDir = System.getProperty("java.io.tmpdir") + "/terraform-" + projectId + "-" + UUID.randomUUID(); + Files.createDirectories(Paths.get(tempDir)); + return tempDir; + } + + private void writeTerraformFile(String filePath, String terraformCode) throws IOException { + try (FileWriter writer = new FileWriter(filePath)) { + writer.write(terraformCode); + } + } + + private void cleanupTempDirectory(String tempDir) { + try { + Path path = Paths.get(tempDir); + Files.walk(path) + .sorted((a, b) -> b.compareTo(a)) + .forEach(p -> { + try { + Files.delete(p); + } catch (IOException e) { + log.warn("Failed to delete temp file: {}", p); + } + }); + } catch (IOException e) { + log.warn("Failed to cleanup temp directory: {}", tempDir); + } + } + + private void executeTerraformApply(Long deploymentId, Long projectId, String terraformCode) { + try { + // 상태를 RUNNING으로 업데이트 + updateDeploymentStatus(deploymentId, DeploymentStatus.RUNNING, "배포 실행 중"); + + // 임시 디렉토리 생성 + String tempDir = createTempDirectory(projectId); + String terraformFile = tempDir + "/main.tf"; + + // Terraform 파일 생성 + writeTerraformFile(terraformFile, terraformCode); + + // Terraform init 실행 + ProcessBuilder initBuilder = new ProcessBuilder("terraform", "init"); + initBuilder.directory(new File(tempDir)); + Process initProcess = initBuilder.start(); + int initExitCode = initProcess.waitFor(); + + if (initExitCode != 0) { + String errorOutput = new String(initProcess.getErrorStream().readAllBytes()); + updateDeploymentStatus(deploymentId, DeploymentStatus.FAILED, "Terraform 초기화 실패: " + errorOutput); + cleanupTempDirectory(tempDir); + return; + } + + // Terraform apply 실행 + ProcessBuilder applyBuilder = new ProcessBuilder("terraform", "apply", "-auto-approve"); + applyBuilder.directory(new File(tempDir)); + Process applyProcess = applyBuilder.start(); + int applyExitCode = applyProcess.waitFor(); + + String output = new String(applyProcess.getInputStream().readAllBytes()); + String errorOutput = new String(applyProcess.getErrorStream().readAllBytes()); + + if (applyExitCode == 0) { + updateDeploymentStatus(deploymentId, DeploymentStatus.SUCCESS, "배포 성공"); + updateDeploymentOutput(deploymentId, output); + } else { + updateDeploymentStatus(deploymentId, DeploymentStatus.FAILED, "배포 실패: " + errorOutput); + updateDeploymentOutput(deploymentId, errorOutput); + } + + // 임시 디렉토리 정리 + cleanupTempDirectory(tempDir); + + } catch (Exception e) { + updateDeploymentStatus(deploymentId, DeploymentStatus.FAILED, "배포 중 오류 발생: " + e.getMessage()); + } + } + + @Transactional + protected void updateDeploymentStatus(Long deploymentId, DeploymentStatus status, String message) { + Deployment deployment = deploymentRepository.findById(deploymentId) + .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_DEPLOYMENT)); + deployment.updateStatus(status, message); + } + + @Transactional + protected void updateDeploymentOutput(Long deploymentId, String output) { + Deployment deployment = deploymentRepository.findById(deploymentId) + .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_DEPLOYMENT)); + deployment.setOutput(output); + } +} From 88be1c0d1828945b05c11baa177a36227d9ea24f Mon Sep 17 00:00:00 2001 From: leeeunda Date: Sat, 16 Aug 2025 23:36:45 +0900 Subject: [PATCH 06/12] feat: add Terraform CLI Utility --- .../controller/TerraformController.java | 20 ++ .../TerraformApplyResponseDto.java | 2 + .../TerraformValidateResponseDto.java | 4 +- .../blockcloud/service/TerraformExecutor.java | 181 ++++++++++++++++++ .../blockcloud/service/TerraformService.java | 146 ++++---------- src/main/resources/application.yml | 2 +- 6 files changed, 240 insertions(+), 115 deletions(-) create mode 100644 src/main/java/com/blockcloud/service/TerraformExecutor.java diff --git a/src/main/java/com/blockcloud/controller/TerraformController.java b/src/main/java/com/blockcloud/controller/TerraformController.java index 62aada6..7fb3321 100644 --- a/src/main/java/com/blockcloud/controller/TerraformController.java +++ b/src/main/java/com/blockcloud/controller/TerraformController.java @@ -1,10 +1,12 @@ package com.blockcloud.controller; import com.blockcloud.dto.RequestDto.TerraformApplyRequestDto; +import com.blockcloud.dto.RequestDto.TerraformPlanRequestDto; import com.blockcloud.dto.RequestDto.TerraformValidateRequestDto; import com.blockcloud.dto.ResponseDto.DeploymentListResponseDto; import com.blockcloud.dto.ResponseDto.DeploymentStatusResponseDto; import com.blockcloud.dto.ResponseDto.TerraformApplyResponseDto; +import com.blockcloud.dto.ResponseDto.TerraformPlanResponseDto; import com.blockcloud.dto.ResponseDto.TerraformValidateResponseDto; import com.blockcloud.dto.common.ResponseDto; import com.blockcloud.dto.oauth.CustomUserDetails; @@ -46,6 +48,24 @@ public ResponseDto validateTerraform( return ResponseDto.ok(terraformService.validateTerraform(projectId, requestDto)); } + /** + * Terraform 코드의 변경 사항을 미리 확인합니다. + * + * @param projectId 프로젝트 ID + * @param requestDto Terraform plan 요청 + * @return plan 결과가 담긴 응답 객체 + */ + @Operation( + summary = "Terraform 코드 Plan", + description = "Terraform 코드를 실행했을 때 어떤 변경 사항이 발생할지 미리 확인합니다. 실제 배포는 하지 않습니다." + ) + @PostMapping("/plan") + public ResponseDto planTerraform( + @Parameter(description = "프로젝트 ID", required = true) @PathVariable Long projectId, + @Valid @RequestBody TerraformPlanRequestDto requestDto) { + return ResponseDto.ok(terraformService.planTerraform(projectId, requestDto)); + } + /** * Terraform 코드를 적용하여 배포를 시작합니다. * diff --git a/src/main/java/com/blockcloud/dto/ResponseDto/TerraformApplyResponseDto.java b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformApplyResponseDto.java index 4117122..568832e 100644 --- a/src/main/java/com/blockcloud/dto/ResponseDto/TerraformApplyResponseDto.java +++ b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformApplyResponseDto.java @@ -13,4 +13,6 @@ public class TerraformApplyResponseDto { private String status; private String message; private LocalDateTime startedAt; + private String output; + private String errorMessage; } diff --git a/src/main/java/com/blockcloud/dto/ResponseDto/TerraformValidateResponseDto.java b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformValidateResponseDto.java index 95404a0..7d9a0e1 100644 --- a/src/main/java/com/blockcloud/dto/ResponseDto/TerraformValidateResponseDto.java +++ b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformValidateResponseDto.java @@ -10,6 +10,6 @@ public class TerraformValidateResponseDto { private boolean isValid; - private List errors; - private List warnings; + private String output; + private String errorMessage; } diff --git a/src/main/java/com/blockcloud/service/TerraformExecutor.java b/src/main/java/com/blockcloud/service/TerraformExecutor.java new file mode 100644 index 0000000..7e5758a --- /dev/null +++ b/src/main/java/com/blockcloud/service/TerraformExecutor.java @@ -0,0 +1,181 @@ +package com.blockcloud.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Slf4j +@Component +public class TerraformExecutor { + + private static final String BASE_DIR = "/tmp/terraform/"; + + /** + * Terraform 명령어를 실행합니다. + * + * @param terraformCode Terraform 코드 + * @param command 실행할 명령어 (init, validate, plan, apply 등) + * @return 실행 결과 + */ + public TerraformExecutionResult executeCommand(String terraformCode, String command) { + String projectId = UUID.randomUUID().toString(); + Path workDir = Paths.get(BASE_DIR, projectId); + + try { + // 작업 디렉토리 생성 + Files.createDirectories(workDir); + + // Terraform 파일 생성 + Path terraformFile = workDir.resolve("main.tf"); + Files.writeString(terraformFile, terraformCode); + + // Terraform init 실행 + ProcessBuilder initBuilder = new ProcessBuilder("terraform", "init", "-input=false") + .directory(workDir.toFile()); + Process initProcess = initBuilder.start(); + int initExitCode = initProcess.waitFor(); + + if (initExitCode != 0) { + String initError = new String(initProcess.getErrorStream().readAllBytes()); + return TerraformExecutionResult.builder() + .success(false) + .exitCode(initExitCode) + .output("") + .error("Terraform 초기화 실패: " + initError) + .build(); + } + + // 요청된 명령어 실행 + List cmd = new ArrayList<>(List.of("terraform")); + cmd.addAll(List.of(command.split(" "))); + + ProcessBuilder builder = new ProcessBuilder(cmd) + .directory(workDir.toFile()); + Process process = builder.start(); + + // 출력과 에러 스트림 읽기 + String output = new String(process.getInputStream().readAllBytes()); + String error = new String(process.getErrorStream().readAllBytes()); + int exitCode = process.waitFor(); + + // 작업 디렉토리 정리 + cleanupDirectory(workDir); + + return TerraformExecutionResult.builder() + .success(exitCode == 0) + .exitCode(exitCode) + .output(output) + .error(error) + .build(); + + } catch (Exception e) { + log.error("Terraform 명령어 실행 중 오류 발생: {}", e.getMessage(), e); + + // 작업 디렉토리 정리 + try { + cleanupDirectory(workDir); + } catch (Exception cleanupException) { + log.warn("작업 디렉토리 정리 중 오류: {}", cleanupException.getMessage()); + } + + return TerraformExecutionResult.builder() + .success(false) + .exitCode(-1) + .output("") + .error("Terraform 실행 중 오류 발생: " + e.getMessage()) + .build(); + } + } + + /** + * Terraform plan을 실행하여 변경 사항을 미리 확인합니다. + * + * @param terraformCode Terraform 코드 + * @return plan 실행 결과 + */ + public TerraformExecutionResult plan(String terraformCode) { + return executeCommand(terraformCode, "plan -detailed-exitcode"); + } + + /** + * Terraform validate를 실행하여 코드를 검증합니다. + * + * @param terraformCode Terraform 코드 + * @return validate 실행 결과 + */ + public TerraformExecutionResult validate(String terraformCode) { + return executeCommand(terraformCode, "validate"); + } + + /** + * Terraform apply를 실행하여 인프라를 배포합니다. + * + * @param terraformCode Terraform 코드 + * @return apply 실행 결과 + */ + public TerraformExecutionResult apply(String terraformCode) { + return executeCommand(terraformCode, "apply -auto-approve"); + } + + /** + * 작업 디렉토리를 정리합니다. + */ + private void cleanupDirectory(Path directory) { + try { + if (Files.exists(directory)) { + Files.walk(directory) + .sorted((a, b) -> b.compareTo(a)) // 하위 디렉토리부터 삭제 + .forEach(path -> { + try { + Files.delete(path); + } catch (IOException e) { + log.warn("파일 삭제 실패: {}", path); + } + }); + } + } catch (IOException e) { + log.warn("디렉토리 정리 중 오류: {}", e.getMessage()); + } + } + + /** + * Terraform 실행 결과를 담는 내부 클래스 + */ + public static class TerraformExecutionResult { + private final boolean success; + private final int exitCode; + private final String output; + private final String error; + + @lombok.Builder + public TerraformExecutionResult(boolean success, int exitCode, String output, String error) { + this.success = success; + this.exitCode = exitCode; + this.output = output; + this.error = error; + } + + public boolean isSuccess() { + return success; + } + + public int getExitCode() { + return exitCode; + } + + public String getOutput() { + return output; + } + + public String getError() { + return error; + } + } +} diff --git a/src/main/java/com/blockcloud/service/TerraformService.java b/src/main/java/com/blockcloud/service/TerraformService.java index 8dae96d..78dde67 100644 --- a/src/main/java/com/blockcloud/service/TerraformService.java +++ b/src/main/java/com/blockcloud/service/TerraformService.java @@ -6,10 +6,12 @@ import com.blockcloud.domain.project.Project; import com.blockcloud.domain.project.ProjectRepository; import com.blockcloud.dto.RequestDto.TerraformApplyRequestDto; +import com.blockcloud.dto.RequestDto.TerraformPlanRequestDto; import com.blockcloud.dto.RequestDto.TerraformValidateRequestDto; import com.blockcloud.dto.ResponseDto.DeploymentListResponseDto; import com.blockcloud.dto.ResponseDto.DeploymentStatusResponseDto; import com.blockcloud.dto.ResponseDto.TerraformApplyResponseDto; +import com.blockcloud.dto.ResponseDto.TerraformPlanResponseDto; import com.blockcloud.dto.ResponseDto.TerraformValidateResponseDto; import com.blockcloud.exception.CommonException; import com.blockcloud.exception.error.ErrorCode; @@ -19,16 +21,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.time.LocalDateTime; -import java.util.ArrayList; import java.util.List; -import java.util.UUID; import java.util.concurrent.CompletableFuture; @Slf4j @@ -39,6 +33,7 @@ public class TerraformService { private final ProjectRepository projectRepository; private final DeploymentRepository deploymentRepository; + private final TerraformExecutor terraformExecutor; /** * Terraform 코드를 검증합니다. @@ -47,47 +42,32 @@ public TerraformValidateResponseDto validateTerraform(Long projectId, TerraformV Project project = projectRepository.findById(projectId) .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); - try { - // 임시 디렉토리 생성 - String tempDir = createTempDirectory(projectId); - String terraformFile = tempDir + "/main.tf"; - - // Terraform 파일 생성 - writeTerraformFile(terraformFile, requestDto.getTerraformCode()); - - // Terraform validate 실행 - ProcessBuilder processBuilder = new ProcessBuilder("terraform", "validate"); - processBuilder.directory(new File(tempDir)); - - Process process = processBuilder.start(); - int exitCode = process.waitFor(); - - List errors = new ArrayList<>(); - List warnings = new ArrayList<>(); - - if (exitCode != 0) { - // 에러 출력 읽기 - String errorOutput = new String(process.getErrorStream().readAllBytes()); - errors.add(errorOutput); - } - - // 임시 디렉토리 정리 - cleanupTempDirectory(tempDir); - - return TerraformValidateResponseDto.builder() - .isValid(exitCode == 0) - .errors(errors) - .warnings(warnings) - .build(); - - } catch (Exception e) { - log.error("Terraform validation failed for project {}: {}", projectId, e.getMessage()); - return TerraformValidateResponseDto.builder() - .isValid(false) - .errors(List.of("Terraform 검증 중 오류가 발생했습니다: " + e.getMessage())) - .warnings(new ArrayList<>()) - .build(); - } + TerraformExecutor.TerraformExecutionResult result = terraformExecutor.validate(requestDto.getTerraformCode()); + + return TerraformValidateResponseDto.builder() + .isValid(result.isSuccess()) + .output(result.getOutput()) + .errorMessage(result.getError()) + .build(); + } + + /** + * Terraform 코드의 변경 사항을 미리 확인합니다. + */ + public TerraformPlanResponseDto planTerraform(Long projectId, TerraformPlanRequestDto requestDto) { + Project project = projectRepository.findById(projectId) + .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_PROJECT)); + + TerraformExecutor.TerraformExecutionResult result = terraformExecutor.plan(requestDto.getTerraformCode()); + + // Terraform plan의 exit code: 0=성공, 1=에러, 2=변경사항 있음 + boolean hasChanges = result.getExitCode() == 2; + + return TerraformPlanResponseDto.builder() + .hasChanges(hasChanges) + .planOutput(result.getOutput()) + .errorMessage(result.getError()) + .build(); } /** @@ -186,80 +166,22 @@ public DeploymentListResponseDto getDeploymentHistory(Long projectId, Long lastI // Private helper methods - private String createTempDirectory(Long projectId) throws IOException { - String tempDir = System.getProperty("java.io.tmpdir") + "/terraform-" + projectId + "-" + UUID.randomUUID(); - Files.createDirectories(Paths.get(tempDir)); - return tempDir; - } - - private void writeTerraformFile(String filePath, String terraformCode) throws IOException { - try (FileWriter writer = new FileWriter(filePath)) { - writer.write(terraformCode); - } - } - - private void cleanupTempDirectory(String tempDir) { - try { - Path path = Paths.get(tempDir); - Files.walk(path) - .sorted((a, b) -> b.compareTo(a)) - .forEach(p -> { - try { - Files.delete(p); - } catch (IOException e) { - log.warn("Failed to delete temp file: {}", p); - } - }); - } catch (IOException e) { - log.warn("Failed to cleanup temp directory: {}", tempDir); - } - } - private void executeTerraformApply(Long deploymentId, Long projectId, String terraformCode) { try { // 상태를 RUNNING으로 업데이트 updateDeploymentStatus(deploymentId, DeploymentStatus.RUNNING, "배포 실행 중"); - // 임시 디렉토리 생성 - String tempDir = createTempDirectory(projectId); - String terraformFile = tempDir + "/main.tf"; - - // Terraform 파일 생성 - writeTerraformFile(terraformFile, terraformCode); - - // Terraform init 실행 - ProcessBuilder initBuilder = new ProcessBuilder("terraform", "init"); - initBuilder.directory(new File(tempDir)); - Process initProcess = initBuilder.start(); - int initExitCode = initProcess.waitFor(); - - if (initExitCode != 0) { - String errorOutput = new String(initProcess.getErrorStream().readAllBytes()); - updateDeploymentStatus(deploymentId, DeploymentStatus.FAILED, "Terraform 초기화 실패: " + errorOutput); - cleanupTempDirectory(tempDir); - return; - } - // Terraform apply 실행 - ProcessBuilder applyBuilder = new ProcessBuilder("terraform", "apply", "-auto-approve"); - applyBuilder.directory(new File(tempDir)); - Process applyProcess = applyBuilder.start(); - int applyExitCode = applyProcess.waitFor(); + TerraformExecutor.TerraformExecutionResult result = terraformExecutor.apply(terraformCode); - String output = new String(applyProcess.getInputStream().readAllBytes()); - String errorOutput = new String(applyProcess.getErrorStream().readAllBytes()); - - if (applyExitCode == 0) { + if (result.isSuccess()) { updateDeploymentStatus(deploymentId, DeploymentStatus.SUCCESS, "배포 성공"); - updateDeploymentOutput(deploymentId, output); + updateDeploymentOutput(deploymentId, result.getOutput()); } else { - updateDeploymentStatus(deploymentId, DeploymentStatus.FAILED, "배포 실패: " + errorOutput); - updateDeploymentOutput(deploymentId, errorOutput); + updateDeploymentStatus(deploymentId, DeploymentStatus.FAILED, "배포 실패: " + result.getError()); + updateDeploymentOutput(deploymentId, result.getError()); } - // 임시 디렉토리 정리 - cleanupTempDirectory(tempDir); - } catch (Exception e) { updateDeploymentStatus(deploymentId, DeploymentStatus.FAILED, "배포 중 오류 발생: " + e.getMessage()); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2eeec79..d5e9fc5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,5 +2,5 @@ spring: application: name: "BlockCloud Main Server API" profiles: - active: prod + active: dev include: secret From 49c78add777279ee60556a349ad071c76781cfba Mon Sep 17 00:00:00 2001 From: leeeunda Date: Sat, 16 Aug 2025 23:37:00 +0900 Subject: [PATCH 07/12] feat: add terraform plan API --- .../dto/RequestDto/TerraformPlanRequestDto.java | 17 +++++++++++++++++ .../ResponseDto/TerraformPlanResponseDto.java | 13 +++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 src/main/java/com/blockcloud/dto/RequestDto/TerraformPlanRequestDto.java create mode 100644 src/main/java/com/blockcloud/dto/ResponseDto/TerraformPlanResponseDto.java diff --git a/src/main/java/com/blockcloud/dto/RequestDto/TerraformPlanRequestDto.java b/src/main/java/com/blockcloud/dto/RequestDto/TerraformPlanRequestDto.java new file mode 100644 index 0000000..9f694b9 --- /dev/null +++ b/src/main/java/com/blockcloud/dto/RequestDto/TerraformPlanRequestDto.java @@ -0,0 +1,17 @@ +package com.blockcloud.dto.RequestDto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TerraformPlanRequestDto { + + @NotBlank(message = "Terraform 코드는 필수입니다.") + private String terraformCode; +} diff --git a/src/main/java/com/blockcloud/dto/ResponseDto/TerraformPlanResponseDto.java b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformPlanResponseDto.java new file mode 100644 index 0000000..4e5d3c8 --- /dev/null +++ b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformPlanResponseDto.java @@ -0,0 +1,13 @@ +package com.blockcloud.dto.ResponseDto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class TerraformPlanResponseDto { + + private boolean hasChanges; + private String planOutput; + private String errorMessage; +} From aab2e7c93b971864ad202a55193b83bc1f73cd7d Mon Sep 17 00:00:00 2001 From: leeeunda Date: Sun, 17 Aug 2025 16:55:55 +0900 Subject: [PATCH 08/12] feat: add terraform test code --- aws-test.tf | 128 ++++++++++++++++++++++++++ test-terraform-api.sh | 207 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 335 insertions(+) create mode 100644 aws-test.tf create mode 100755 test-terraform-api.sh diff --git a/aws-test.tf b/aws-test.tf new file mode 100644 index 0000000..7083e08 --- /dev/null +++ b/aws-test.tf @@ -0,0 +1,128 @@ +# AWS VPC와 EC2 인스턴스 생성 테스트 +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +# AWS Provider 설정 +provider "aws" { + region = "ap-northeast-2" # 서울 리전 +} + +# VPC 생성 +resource "aws_vpc" "test_vpc" { + cidr_block = "10.0.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = "test-vpc" + } +} + +# 인터넷 게이트웨이 +resource "aws_internet_gateway" "test_igw" { + vpc_id = aws_vpc.test_vpc.id + + tags = { + Name = "test-igw" + } +} + +# 서브넷 +resource "aws_subnet" "test_subnet" { + vpc_id = aws_vpc.test_vpc.id + cidr_block = "10.0.1.0/24" + availability_zone = "ap-northeast-2a" + map_public_ip_on_launch = true + + tags = { + Name = "test-subnet" + } +} + +# 라우팅 테이블 +resource "aws_route_table" "test_rt" { + vpc_id = aws_vpc.test_vpc.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.test_igw.id + } + + tags = { + Name = "test-rt" + } +} + +# 라우팅 테이블 연결 +resource "aws_route_table_association" "test_rta" { + subnet_id = aws_subnet.test_subnet.id + route_table_id = aws_route_table.test_rt.id +} + +# 보안 그룹 +resource "aws_security_group" "test_sg" { + name = "test-security-group" + description = "Test security group for EC2" + vpc_id = aws_vpc.test_vpc.id + + # SSH 접속 허용 + ingress { + description = "SSH" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # HTTP 접속 허용 + ingress { + description = "HTTP" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # 모든 아웃바운드 트래픽 허용 + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "test-sg" + } +} + +# EC2 인스턴스 +resource "aws_instance" "test_instance" { + ami = "ami-0c9c942bd7bf113a2" # Amazon Linux 2023 AMI (서울 리전) + instance_type = "t2.micro" + subnet_id = aws_subnet.test_subnet.id + vpc_security_group_ids = [aws_security_group.test_sg.id] + + tags = { + Name = "test-instance" + } +} + +# 출력 +output "vpc_id" { + value = aws_vpc.test_vpc.id +} + +output "instance_id" { + value = aws_instance.test_instance.id +} + +output "instance_public_ip" { + value = aws_instance.test_instance.public_ip +} diff --git a/test-terraform-api.sh b/test-terraform-api.sh new file mode 100755 index 0000000..317e15e --- /dev/null +++ b/test-terraform-api.sh @@ -0,0 +1,207 @@ +#!/bin/bash + +# Terraform API 테스트 스크립트 +# 사용법: ./test-terraform-api.sh [JWT_TOKEN] + +# 색상 정의 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 기본 설정 +API_BASE="http://localhost:8080" +PROJECT_ID=1 + +# JWT 토큰 설정 +if [ -z "$1" ]; then + echo -e "${RED}JWT 토큰이 필요합니다.${NC}" + echo "사용법: $0 " + exit 1 +fi + +JWT_TOKEN=$1 + +# AWS VPC와 EC2를 생성하는 Terraform 코드 +TERRAFORM_CODE='terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = "ap-northeast-2" +} + +resource "aws_vpc" "test_vpc" { + cidr_block = "10.0.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = "test-vpc" + } +} + +resource "aws_internet_gateway" "test_igw" { + vpc_id = aws_vpc.test_vpc.id + + tags = { + Name = "test-igw" + } +} + +resource "aws_subnet" "test_subnet" { + vpc_id = aws_vpc.test_vpc.id + cidr_block = "10.0.1.0/24" + availability_zone = "ap-northeast-2a" + map_public_ip_on_launch = true + + tags = { + Name = "test-subnet" + } +} + +resource "aws_route_table" "test_rt" { + vpc_id = aws_vpc.test_vpc.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.test_igw.id + } + + tags = { + Name = "test-rt" + } +} + +resource "aws_route_table_association" "test_rta" { + subnet_id = aws_subnet.test_subnet.id + route_table_id = aws_route_table.test_rt.id +} + +resource "aws_security_group" "test_sg" { + name = "test-security-group" + description = "Test security group for EC2" + vpc_id = aws_vpc.test_vpc.id + + ingress { + description = "SSH" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "test-sg" + } +} + +resource "aws_instance" "test_instance" { + ami = "ami-0c9c942bd7bf113a2" + instance_type = "t2.micro" + subnet_id = aws_subnet.test_subnet.id + vpc_security_group_ids = [aws_security_group.test_sg.id] + + tags = { + Name = "test-instance" + } +} + +output "vpc_id" { + value = aws_vpc.test_vpc.id +} + +output "instance_id" { + value = aws_instance.test_instance.id +}' + +echo -e "${BLUE}=== Terraform API 테스트 시작 ===${NC}" +echo "API Base: $API_BASE" +echo "Project ID: $PROJECT_ID" +echo "" + +# 1. Validate 테스트 +echo -e "${YELLOW}1. Terraform Validate 테스트${NC}" +VALIDATE_RESPONSE=$(curl -s -X POST "$API_BASE/api/projects/$PROJECT_ID/terraform/validate" \ + -H "Authorization: Bearer $JWT_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"terraformCode\": \"$TERRAFORM_CODE\"}") + +echo "Response: $VALIDATE_RESPONSE" +echo "" + +# 2. Plan 테스트 +echo -e "${YELLOW}2. Terraform Plan 테스트${NC}" +PLAN_RESPONSE=$(curl -s -X POST "$API_BASE/api/projects/$PROJECT_ID/terraform/plan" \ + -H "Authorization: Bearer $JWT_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"terraformCode\": \"$TERRAFORM_CODE\"}") + +echo "Response: $PLAN_RESPONSE" +echo "" + +# 3. Apply 테스트 (실제 AWS 리소스 생성) +echo -e "${YELLOW}3. Terraform Apply 테스트${NC}" +echo -e "${RED}⚠️ 주의: 실제 AWS 리소스가 생성됩니다!${NC}" +read -p "계속하시겠습니까? (y/N): " -n 1 -r +echo + +if [[ $REPLY =~ ^[Yy]$ ]]; then + APPLY_RESPONSE=$(curl -s -X POST "$API_BASE/api/projects/$PROJECT_ID/terraform/apply" \ + -H "Authorization: Bearer $JWT_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"terraformCode\": \"$TERRAFORM_CODE\"}") + + echo "Response: $APPLY_RESPONSE" + echo "" + + # 배포 ID 추출 + DEPLOYMENT_ID=$(echo $APPLY_RESPONSE | grep -o '"deploymentId":[0-9]*' | cut -d':' -f2) + + if [ ! -z "$DEPLOYMENT_ID" ]; then + echo -e "${GREEN}배포 ID: $DEPLOYMENT_ID${NC}" + echo "" + + # 4. 배포 상태 확인 + echo -e "${YELLOW}4. 배포 상태 확인${NC}" + for i in {1..10}; do + STATUS_RESPONSE=$(curl -s -X GET "$API_BASE/api/projects/$PROJECT_ID/terraform/deployments/$DEPLOYMENT_ID" \ + -H "Authorization: Bearer $JWT_TOKEN") + + echo "Status Check $i: $STATUS_RESPONSE" + + # 성공 또는 실패 상태 확인 + if echo "$STATUS_RESPONSE" | grep -q '"status":"SUCCESS"'; then + echo -e "${GREEN}✅ 배포 성공!${NC}" + break + elif echo "$STATUS_RESPONSE" | grep -q '"status":"FAILED"'; then + echo -e "${RED}❌ 배포 실패!${NC}" + break + fi + + sleep 5 + done + fi +else + echo -e "${YELLOW}Apply 테스트를 건너뜁니다.${NC}" +fi + +echo "" +echo -e "${BLUE}=== 테스트 완료 ===${NC}" +echo "" +echo -e "${YELLOW}참고:${NC}" +echo "- 실제 AWS 리소스가 생성된 경우, 나중에 destroy API를 사용하여 정리하세요" +echo "- AWS 비용이 발생할 수 있으니 주의하세요" From 757a8a054565f5c5f9a7895ff312c4f46df6379e Mon Sep 17 00:00:00 2001 From: leeeunda Date: Sun, 17 Aug 2025 16:56:08 +0900 Subject: [PATCH 09/12] docs: add AWS Set Up docs --- docs/AWS_SETUP.md | 85 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 docs/AWS_SETUP.md diff --git a/docs/AWS_SETUP.md b/docs/AWS_SETUP.md new file mode 100644 index 0000000..841cd08 --- /dev/null +++ b/docs/AWS_SETUP.md @@ -0,0 +1,85 @@ +# AWS 인증 설정 가이드 + +## 필수 환경 변수 + +Terraform으로 AWS 리소스를 생성하려면 다음 환경 변수들이 필요합니다: + +### 1. AWS Access Key 설정 +```bash +export AWS_ACCESS_KEY_ID="your-access-key-id" +export AWS_SECRET_ACCESS_KEY="your-secret-access-key" +``` + +### 2. AWS Region 설정 (선택사항) +```bash +export AWS_DEFAULT_REGION="ap-northeast-2" # 서울 리전 +``` + +### 3. AWS Profile 사용 (권장) +```bash +# AWS CLI로 프로필 설정 +aws configure --profile terraform-test + +# 환경 변수로 프로필 지정 +export AWS_PROFILE="terraform-test" +``` + +## AWS IAM 권한 + +다음 AWS 서비스에 대한 권한이 필요합니다: + +- **EC2**: 인스턴스, VPC, 서브넷, 보안 그룹 생성/삭제 +- **IAM**: 역할 및 정책 관리 (필요시) +- **VPC**: 가상 프라이빗 클라우드 관리 +- **CloudWatch**: 로그 및 모니터링 (선택사항) + +### 최소 IAM 정책 예시: +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ec2:*", + "vpc:*", + "iam:CreateRole", + "iam:AttachRolePolicy", + "iam:DetachRolePolicy", + "iam:DeleteRole" + ], + "Resource": "*" + } + ] +} +``` + +## 테스트 방법 + +### 1. 환경 변수 설정 확인 +```bash +echo $AWS_ACCESS_KEY_ID +echo $AWS_SECRET_ACCESS_KEY +echo $AWS_DEFAULT_REGION +``` + +### 2. AWS CLI 테스트 +```bash +aws sts get-caller-identity +``` + +### 3. Terraform 테스트 +```bash +# 프로젝트 루트에서 +cd test-terraform +terraform init +terraform validate +terraform plan +``` + +## 주의사항 + +⚠️ **중요**: 실제 AWS 리소스가 생성되므로 비용이 발생할 수 있습니다. +- 테스트 후 반드시 `terraform destroy`로 리소스를 정리하세요 +- t2.micro 인스턴스는 AWS Free Tier에 포함될 수 있지만, 다른 리소스는 비용이 발생할 수 있습니다 +- 프로덕션 환경에서는 더 엄격한 IAM 정책을 사용하세요 From 6c0c8db97cd04555bb05e1554df458318403dd00 Mon Sep 17 00:00:00 2001 From: leeeunda Date: Sun, 17 Aug 2025 16:57:52 +0900 Subject: [PATCH 10/12] feat: add Terraform Destroy API & execute pipeline --- .../controller/TerraformController.java | 23 +++++++ .../TerraformDestroyRequestDto.java | 17 ++++++ .../TerraformDestroyResponseDto.java | 18 ++++++ .../TerraformValidateResponseDto.java | 2 +- .../blockcloud/service/TerraformExecutor.java | 33 ++++++++++ .../blockcloud/service/TerraformService.java | 60 +++++++++++++++++++ 6 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/blockcloud/dto/RequestDto/TerraformDestroyRequestDto.java create mode 100644 src/main/java/com/blockcloud/dto/ResponseDto/TerraformDestroyResponseDto.java diff --git a/src/main/java/com/blockcloud/controller/TerraformController.java b/src/main/java/com/blockcloud/controller/TerraformController.java index 7fb3321..e7edf0c 100644 --- a/src/main/java/com/blockcloud/controller/TerraformController.java +++ b/src/main/java/com/blockcloud/controller/TerraformController.java @@ -1,11 +1,13 @@ 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; import com.blockcloud.dto.ResponseDto.DeploymentStatusResponseDto; import com.blockcloud.dto.ResponseDto.TerraformApplyResponseDto; +import com.blockcloud.dto.ResponseDto.TerraformDestroyResponseDto; import com.blockcloud.dto.ResponseDto.TerraformPlanResponseDto; import com.blockcloud.dto.ResponseDto.TerraformValidateResponseDto; import com.blockcloud.dto.common.ResponseDto; @@ -87,6 +89,27 @@ public ResponseDto applyTerraform( return ResponseDto.ok(terraformService.applyTerraform(projectId, requestDto, userDetails.getUsername())); } + /** + * Terraform 코드를 실행하여 인프라를 삭제합니다. + * + * @param projectId 프로젝트 ID + * @param requestDto Terraform 삭제 요청 + * @param authentication 인증 정보 + * @return 삭제 시작 정보가 담긴 응답 객체 + */ + @Operation( + summary = "Terraform 인프라 삭제", + description = "Terraform 코드를 실행하여 생성된 인프라를 삭제합니다. 배포는 비동기로 실행되며, 삭제 ID를 반환합니다." + ) + @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/dto/RequestDto/TerraformDestroyRequestDto.java b/src/main/java/com/blockcloud/dto/RequestDto/TerraformDestroyRequestDto.java new file mode 100644 index 0000000..f081e37 --- /dev/null +++ b/src/main/java/com/blockcloud/dto/RequestDto/TerraformDestroyRequestDto.java @@ -0,0 +1,17 @@ +package com.blockcloud.dto.RequestDto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TerraformDestroyRequestDto { + + @NotBlank(message = "Terraform 코드는 필수입니다.") + private String terraformCode; +} diff --git a/src/main/java/com/blockcloud/dto/ResponseDto/TerraformDestroyResponseDto.java b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformDestroyResponseDto.java new file mode 100644 index 0000000..de904ab --- /dev/null +++ b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformDestroyResponseDto.java @@ -0,0 +1,18 @@ +package com.blockcloud.dto.ResponseDto; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +@Builder +public class TerraformDestroyResponseDto { + + private Long deploymentId; + private String status; + private String message; + private LocalDateTime startedAt; + private String output; + private String errorMessage; +} diff --git a/src/main/java/com/blockcloud/dto/ResponseDto/TerraformValidateResponseDto.java b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformValidateResponseDto.java index 7d9a0e1..e02f9aa 100644 --- a/src/main/java/com/blockcloud/dto/ResponseDto/TerraformValidateResponseDto.java +++ b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformValidateResponseDto.java @@ -8,7 +8,7 @@ @Getter @Builder public class TerraformValidateResponseDto { - + private boolean isValid; private String output; private String errorMessage; diff --git a/src/main/java/com/blockcloud/service/TerraformExecutor.java b/src/main/java/com/blockcloud/service/TerraformExecutor.java index 7e5758a..ac25f5d 100644 --- a/src/main/java/com/blockcloud/service/TerraformExecutor.java +++ b/src/main/java/com/blockcloud/service/TerraformExecutor.java @@ -124,6 +124,39 @@ public TerraformExecutionResult apply(String terraformCode) { return executeCommand(terraformCode, "apply -auto-approve"); } + /** + * Terraform destroy를 실행하여 인프라를 삭제합니다. + * + * @param terraformCode Terraform 코드 + * @return destroy 실행 결과 + */ + public TerraformExecutionResult destroy(String terraformCode) { + return executeCommand(terraformCode, "destroy -auto-approve"); + } + + /** + * Terraform 전체 워크플로우를 실행합니다 (validate -> plan -> apply). + * + * @param terraformCode Terraform 코드 + * @return 최종 실행 결과 + */ + public TerraformExecutionResult runFullWorkflow(String terraformCode) { + // 1. Validate + TerraformExecutionResult validateResult = validate(terraformCode); + if (!validateResult.isSuccess()) { + return validateResult; + } + + // 2. Plan + TerraformExecutionResult planResult = plan(terraformCode); + if (!planResult.isSuccess()) { + return planResult; + } + + // 3. Apply + return apply(terraformCode); + } + /** * 작업 디렉토리를 정리합니다. */ diff --git a/src/main/java/com/blockcloud/service/TerraformService.java b/src/main/java/com/blockcloud/service/TerraformService.java index 78dde67..5f75b88 100644 --- a/src/main/java/com/blockcloud/service/TerraformService.java +++ b/src/main/java/com/blockcloud/service/TerraformService.java @@ -6,11 +6,13 @@ 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; import com.blockcloud.dto.ResponseDto.DeploymentStatusResponseDto; import com.blockcloud.dto.ResponseDto.TerraformApplyResponseDto; +import com.blockcloud.dto.ResponseDto.TerraformDestroyResponseDto; import com.blockcloud.dto.ResponseDto.TerraformPlanResponseDto; import com.blockcloud.dto.ResponseDto.TerraformValidateResponseDto; import com.blockcloud.exception.CommonException; @@ -107,6 +109,43 @@ public TerraformApplyResponseDto applyTerraform(Long projectId, TerraformApplyRe .build(); } + /** + * Terraform 코드를 실행하여 인프라를 삭제합니다. + */ + @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(); + } + /** * 배포 상태를 조회합니다. */ @@ -187,6 +226,27 @@ private void executeTerraformApply(Long deploymentId, Long projectId, String ter } } + private void executeTerraformDestroy(Long deploymentId, Long projectId, String terraformCode) { + try { + // 상태를 RUNNING으로 업데이트 + updateDeploymentStatus(deploymentId, DeploymentStatus.RUNNING, "인프라 삭제 실행 중"); + + // Terraform destroy 실행 + TerraformExecutor.TerraformExecutionResult result = terraformExecutor.destroy(terraformCode); + + if (result.isSuccess()) { + updateDeploymentStatus(deploymentId, DeploymentStatus.SUCCESS, "인프라 삭제 성공"); + updateDeploymentOutput(deploymentId, result.getOutput()); + } else { + updateDeploymentStatus(deploymentId, DeploymentStatus.FAILED, "인프라 삭제 실패: " + result.getError()); + updateDeploymentOutput(deploymentId, result.getError()); + } + + } catch (Exception e) { + updateDeploymentStatus(deploymentId, DeploymentStatus.FAILED, "인프라 삭제 중 오류 발생: " + e.getMessage()); + } + } + @Transactional protected void updateDeploymentStatus(Long deploymentId, DeploymentStatus status, String message) { Deployment deployment = deploymentRepository.findById(deploymentId) From 2f0019be8a18e4151bd8d170377b1be13240703f Mon Sep 17 00:00:00 2001 From: leeeunda Date: Sun, 17 Aug 2025 17:31:53 +0900 Subject: [PATCH 11/12] feat: TerraformPlan --- .../blockcloud/dto/ResponseDto/TerraformPlanResponseDto.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/blockcloud/dto/ResponseDto/TerraformPlanResponseDto.java b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformPlanResponseDto.java index 4e5d3c8..7acd022 100644 --- a/src/main/java/com/blockcloud/dto/ResponseDto/TerraformPlanResponseDto.java +++ b/src/main/java/com/blockcloud/dto/ResponseDto/TerraformPlanResponseDto.java @@ -6,7 +6,7 @@ @Getter @Builder public class TerraformPlanResponseDto { - + private boolean hasChanges; private String planOutput; private String errorMessage; From 608431713e0de54cf1f5ef13825252b29f241142 Mon Sep 17 00:00:00 2001 From: leeeunda Date: Mon, 18 Aug 2025 10:59:20 +0900 Subject: [PATCH 12/12] fix: modify Access Token expired time --- .../java/com/blockcloud/controller/TokenRestController.java | 2 +- .../com/blockcloud/exception/handler/OAuth2SuccessHandler.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/blockcloud/controller/TokenRestController.java b/src/main/java/com/blockcloud/controller/TokenRestController.java index fef1da7..bf9f4f7 100644 --- a/src/main/java/com/blockcloud/controller/TokenRestController.java +++ b/src/main/java/com/blockcloud/controller/TokenRestController.java @@ -63,7 +63,7 @@ public ResponseEntity reissue(HttpServletRequest request, HttpServletResponse String role = jwtUtil.getRole(refresh); // Create new tokens - String newAccess = jwtUtil.createJwt("access", email, role, 24 * 60 * 60 * 1000L); // 1시간 + String newAccess = jwtUtil.createJwt("access", email, role, 30 * 60 * 1000L); // 30분 String refreshToken = jwtUtil.createJwt("refresh", email, role, 24 * 60 * 60 * 1000L); // Set refresh token in cookie diff --git a/src/main/java/com/blockcloud/exception/handler/OAuth2SuccessHandler.java b/src/main/java/com/blockcloud/exception/handler/OAuth2SuccessHandler.java index 4ddc54e..19cde6d 100644 --- a/src/main/java/com/blockcloud/exception/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/blockcloud/exception/handler/OAuth2SuccessHandler.java @@ -38,7 +38,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo PrintWriter writer = response.getWriter(); try { - String accessToken = jwtUtil.createJwt("access", user.getEmail(), String.valueOf(user.getRole()), 60 * 1000L); + String accessToken = jwtUtil.createJwt("access", user.getEmail(), String.valueOf(user.getRole()), 30 * 60 * 1000L); String refreshToken = jwtUtil.createJwt("refresh", user.getEmail(), String.valueOf(user.getRole()), 24 * 60 * 60 * 1000L); Cookie refreshCookie = cookieService.createCookie( "refresh", refreshToken, 24 * 60 * 60 * 1000L);