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
184 changes: 184 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
name: CI/CD Pipeline

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

# OIDC 토큰 요청 권한
permissions:
id-token: write # OIDC 토큰 요청용
contents: read # 코드 체크아웃용 (체크아웃 - Repo에 있는 코드를 Runner에 옮김)

jobs:
build:
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.vars.outputs.short_sha }}

steps:
# Step 1: 코드 가져오기
- name: Checkout code
uses: actions/checkout@v6

# Step 2: 커밋 해시 추출
- name: Set short SHA
id: vars
run: echo "short_sha=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT

# Step 3: 스프링 빌드를 위한 Java 21 설치
- name: Setup Java 21
uses: actions/setup-java@v5
with:
java-version: '21'
distribution: 'corretto'

# Step 4: Gradle 캐시
- name: Cache Gradle packages
uses: actions/cache@v5
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

# Step 5: 테스트 실행
- name: Run tests
run: ./gradlew test

# Step 6: JAR 빌드
- name: Build JAR
run: ./gradlew bootJar

# Step 7: AWS 자격증명 - OIDC
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: ${{ vars.AWS_REGION }}

# Step 8: ECR 로그인
- name: Login to ECR
uses: aws-actions/amazon-ecr-login@v2

# Step 9: Docker 빌드 및 push
- name: Build and push Docker image
run: |
SHORT_SHA=${{ steps.vars.outputs.short_sha }}
IMAGE_URI=${{ vars.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/${{ vars.ECR_REPOSITORY }}

# Docker 빌드 및 push
docker build -t $IMAGE_URI:$SHORT_SHA .
# docker build --platform linux/arm64 -t $IMAGE_URI:$SHORT_SHA .
docker push $IMAGE_URI:$SHORT_SHA

echo "✅ Pushed: $IMAGE_URI:$SHORT_SHA"

# deploy:
# needs: build
# if: github.ref == 'refs/heads/main' # main push에서만 배포 (PR에서는 CI만 실행)
# runs-on: ubuntu-latest
#
# steps:
# # Step 1: AWS 자격증명 - OIDC
# - name: Configure AWS credentials
# uses: aws-actions/configure-aws-credentials@v6
# with:
# role-to-assume: ${{ vars.AWS_ROLE_ARN }}
# aws-region: ${{ vars.AWS_REGION }}
#
# # Step 2: Launch Template 새 버전 생성 (User Data의 IMAGE_TAG를 새 커밋 SHA로 교체)
# - name: Update Launch Template
# env:
# IMAGE_TAG: ${{ needs.build.outputs.image_tag }}
# AWS_ACCOUNT_ID: ${{ vars.AWS_ACCOUNT_ID }}
# AWS_REGION: ${{ vars.AWS_REGION }}
# ECR_REPOSITORY: ${{ vars.ECR_REPOSITORY }}
# LAUNCH_TEMPLATE_ID: ${{ vars.LAUNCH_TEMPLATE_ID }}
# run: |
# ECR_URI="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY}"
#
# USER_DATA=$(cat <<'USERDATA' | sed "s|__IMAGE_TAG__|${IMAGE_TAG}|g; s|__AWS_ACCOUNT_ID__|${AWS_ACCOUNT_ID}|g; s|__AWS_REGION__|${AWS_REGION}|g; s|__ECR_REPOSITORY__|${ECR_REPOSITORY}|g" | base64 -w 0
# #!/bin/bash
# set -e
# AWS_REGION="__AWS_REGION__"
# AWS_ACCOUNT_ID="__AWS_ACCOUNT_ID__"
# ECR_REPOSITORY="__ECR_REPOSITORY__"
# IMAGE_TAG="__IMAGE_TAG__"
# ECR_URI="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY}"
# dnf update -y
# dnf install -y docker
# systemctl enable docker
# systemctl start docker
# aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
# docker pull ${ECR_URI}:${IMAGE_TAG}
# docker run -d -p 8080:8080 --restart unless-stopped --name ci-demo -e SPRING_PROFILES_ACTIVE=prod ${ECR_URI}:${IMAGE_TAG}
# USERDATA
# )
#
# NEW_VERSION=$(aws ec2 create-launch-template-version \
# --launch-template-id ${LAUNCH_TEMPLATE_ID} \
# --source-version '$Latest' \
# --launch-template-data "{\"UserData\":\"${USER_DATA}\"}" \
# --query 'LaunchTemplateVersion.VersionNumber' \
# --output text)
#
# echo "✅ Launch Template v${NEW_VERSION} 생성 완료"
#
# aws ec2 modify-launch-template \
# --launch-template-id ${LAUNCH_TEMPLATE_ID} \
# --default-version ${NEW_VERSION}
#
# echo "✅ 기본 버전 → v${NEW_VERSION} 변경 완료"
#
# # Step 3: Instance Refresh 시작
# - name: Start Instance Refresh
# id: refresh
# env:
# ASG_NAME: ${{ vars.ASG_NAME }}
# run: |
# REFRESH_ID=$(aws autoscaling start-instance-refresh \
# --auto-scaling-group-name ${ASG_NAME} \
# --preferences '{
# "MinHealthyPercentage": 100,
# "InstanceWarmup": 120
# }' \
# --query 'InstanceRefreshId' \
# --output text)
#
# echo "refresh_id=${REFRESH_ID}" >> $GITHUB_OUTPUT
# echo "✅ Instance Refresh 시작: ${REFRESH_ID}"
#
# # Step 4: Instance Refresh 완료 대기
# - name: Wait for Instance Refresh
# env:
# ASG_NAME: ${{ vars.ASG_NAME }}
# REFRESH_ID: ${{ steps.refresh.outputs.refresh_id }}
# run: |
# echo "⏳ Instance Refresh 완료 대기 중..."
#
# for i in $(seq 1 30); do
# STATUS=$(aws autoscaling describe-instance-refreshes \
# --auto-scaling-group-name ${ASG_NAME} \
# --instance-refresh-ids ${REFRESH_ID} \
# --query 'InstanceRefreshes[0].Status' \
# --output text)
#
# echo "시도 ${i}/30 - 상태: ${STATUS}"
#
# if [ "${STATUS}" = "Successful" ]; then
# echo "✅ Instance Refresh 성공! 배포 완료!"
# exit 0
# elif [ "${STATUS}" = "Failed" ] || [ "${STATUS}" = "Cancelled" ]; then
# echo "❌ Instance Refresh 실패: ${STATUS}"
# exit 1
# fi
#
# sleep 30
# done
#
# echo "❌ Instance Refresh 타임아웃 (15분 초과)"
# exit 1
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM eclipse-temurin:21-jre
WORKDIR /app

COPY build/libs/*.jar app.jar

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation "io.github.openfeign.querydsl:querydsl-jpa:${querydslVersion}"
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-actuator'

implementation platform('io.awspring.cloud:spring-cloud-aws-dependencies:4.1.0')
implementation 'io.awspring.cloud:spring-cloud-aws-starter-parameter-store'

implementation 'io.jsonwebtoken:jjwt-api:0.13.0'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.13.0'
Expand All @@ -43,6 +47,7 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test'
testImplementation 'org.springframework.boot:spring-boot-starter-security-test'
testImplementation 'org.springframework.boot:spring-boot-starter-actuator-test'
testImplementation 'org.testcontainers:testcontainers-mysql'

testCompileOnly 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
errorResponseSender.send(response, JwtErrorCode.JWT_ACCESS_DENIED))
)
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/actuator/health").permitAll()
.requestMatchers(HttpMethod.POST, "/auth/signup", "/auth/login", "/auth/reissue").permitAll()
.anyRequest().authenticated()
)
Expand Down
4 changes: 3 additions & 1 deletion src/main/resources/application-local.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ spring.config.import=optional:file:.env.local[.properties]
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

logging.level.com.dropit=DEBUG
logging.level.com.dropit=DEBUG

management.endpoint.health.show-details=always
2 changes: 2 additions & 0 deletions src/main/resources/application-prod.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
management.endpoint.health.show-details=never
spring.config.import=aws-parameterstore:/dropit-server/prod/
2 changes: 2 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ spring.data.redis.port=${REDIS_PORT}
jwt.secret=${JWT_SECRET}
jwt.expire.access=30m
jwt.expire.refresh=14d

management.endpoints.web.exposure.include=health
6 changes: 0 additions & 6 deletions src/test/java/com/dropit/DropitServerApplicationTests.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
package com.dropit;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DropitServerApplicationTests {

@Test
void contextLoads() {
}

}