Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
128 changes: 128 additions & 0 deletions aws-test.tf
Original file line number Diff line number Diff line change
@@ -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
}
85 changes: 85 additions & 0 deletions docs/AWS_SETUP.md
Original file line number Diff line number Diff line change
@@ -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 정책을 사용하세요
1 change: 1 addition & 0 deletions src/main/java/com/blockcloud/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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/**"
Expand Down
152 changes: 152 additions & 0 deletions src/main/java/com/blockcloud/controller/TerraformController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
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;
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<TerraformValidateResponseDto> 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 plan 요청
* @return plan 결과가 담긴 응답 객체
*/
@Operation(
summary = "Terraform 코드 Plan",
description = "Terraform 코드를 실행했을 때 어떤 변경 사항이 발생할지 미리 확인합니다. 실제 배포는 하지 않습니다."
)
@PostMapping("/plan")
public ResponseDto<TerraformPlanResponseDto> planTerraform(
@Parameter(description = "프로젝트 ID", required = true) @PathVariable Long projectId,
@Valid @RequestBody TerraformPlanRequestDto requestDto) {
return ResponseDto.ok(terraformService.planTerraform(projectId, requestDto));
}

/**
* Terraform 코드를 적용하여 배포를 시작합니다.
*
* @param projectId 프로젝트 ID
* @param requestDto Terraform 배포 요청
* @param authentication 인증 정보
* @return 배포 시작 정보가 담긴 응답 객체
*/
@Operation(
summary = "Terraform 코드 배포",
description = "Terraform 코드를 실제 클라우드 환경에 적용하여 인프라를 배포합니다. 배포는 비동기로 실행되며, 배포 ID를 반환합니다."
)
@PostMapping("/apply")
public ResponseDto<TerraformApplyResponseDto> 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()));
}

/**
* Terraform 코드를 실행하여 인프라를 삭제합니다.
*
* @param projectId 프로젝트 ID
* @param requestDto Terraform 삭제 요청
* @param authentication 인증 정보
* @return 삭제 시작 정보가 담긴 응답 객체
*/
@Operation(
summary = "Terraform 인프라 삭제",
description = "Terraform 코드를 실행하여 생성된 인프라를 삭제합니다. 배포는 비동기로 실행되며, 삭제 ID를 반환합니다."
)
@PostMapping("/destroy")
public ResponseDto<TerraformDestroyResponseDto> 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()));
}

/**
* 특정 배포의 상태를 조회합니다.
*
* @param projectId 프로젝트 ID
* @param deploymentId 배포 ID
* @return 배포 상태 정보가 담긴 응답 객체
*/
@Operation(
summary = "배포 상태 조회",
description = "특정 배포의 현재 상태를 조회합니다. PENDING, RUNNING, SUCCESS, FAILED 상태와 상세 정보를 반환합니다."
)
@GetMapping("/deployments/{deploymentId}")
public ResponseDto<DeploymentStatusResponseDto> 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<DeploymentListResponseDto> 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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading