-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
[feat] CI/CD 구현
- Loading branch information
Showing
4 changed files
with
266 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
name: Java CI with Gradle | ||
|
||
on: | ||
push: | ||
branches: [ "develop" ] | ||
pull_request: | ||
branches: [ "develop" ] | ||
|
||
permissions: | ||
contents: read | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Checkout the repository | ||
uses: actions/checkout@v3 | ||
|
||
- name: Set up JDK 17 | ||
uses: actions/setup-java@v3 | ||
with: | ||
java-version: '17' | ||
distribution: 'temurin' | ||
|
||
# gradle caching - 빌드 시간 향상 | ||
- name: Gradle Caching | ||
uses: actions/cache@v3 | ||
with: | ||
path: | | ||
~/.gradle/caches | ||
~/.gradle/wrapper | ||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | ||
restore-keys: | | ||
${{ runner.os }}-gradle- | ||
# 환경별 yml 파일 생성(1) - application.yml | ||
- name: make application.yml | ||
if: | | ||
contains(github.ref, 'develop') || | ||
(github.event_name == 'pull_request') | ||
run: | | ||
mkdir ./src/main/resources # resources 폴더 생성 | ||
cd ./src/main/resources # resources 폴더로 이동 | ||
touch ./application.yml # application.yml 생성 | ||
echo "${{ secrets.YML }}" > ./application.yml # github actions에서 설정한 값을 application.yml 파일에 쓰기 | ||
shell: bash | ||
|
||
# 환경별 yml 파일 생성(2) - dev | ||
- name: make application-dev.yml | ||
if: | | ||
contains(github.ref, 'develop') || | ||
(github.event_name == 'pull_request') | ||
run: | | ||
cd ./src/main/resources | ||
touch ./application-dev.yml | ||
echo "${{ secrets.YML_DEV }}" > ./application-dev.yml | ||
shell: bash | ||
|
||
# 환경별 yml 파일 생성(3) - bucket | ||
- name: make application-bucket.yml | ||
if: | | ||
contains(github.ref, 'develop') || | ||
(github.event_name == 'pull_request') | ||
run: | | ||
cd ./src/main/resources | ||
touch ./application-dev.yml | ||
echo "${{ secrets.YML_BUCKET }}" > ./application-bucket.yml | ||
shell: bash | ||
|
||
# 환경별 yml 파일 생성(3) - oauth | ||
#- name: make application-oauth.yml | ||
# if: | | ||
# contains(github.ref, 'develop') || | ||
# (github.event_name == 'pull_request') | ||
# run: | | ||
# cd ./src/main/resources | ||
# touch ./application-oauth.yml | ||
# echo "${{ secrets.YML_OAUTH }}" > ./application-oauth.yml | ||
# shell: bash | ||
|
||
# 환경별 yml 파일 생성(3) - jwt | ||
#- name: make application-jwt.yml | ||
# if: | | ||
# contains(github.ref, 'develop') || | ||
# (github.event_name == 'pull_request') | ||
# run: | | ||
# cd ./src/main/resources | ||
# touch ./application-jwt.yml | ||
# echo "${{ secrets.YML_JWT }}" > ./application-jwt.yml | ||
# shell: bash | ||
|
||
# Gradle Build (test는 제외) | ||
- name: Build with Gradle | ||
run: | | ||
chmod +x gradlew | ||
./gradlew build -x test | ||
# 도커 허브에 로그인 | ||
- name: Docker Hub Login | ||
uses: docker/login-action@v2 | ||
with: | ||
username: ${{ secrets.DOCKER_USER_NAME }} | ||
password: ${{ secrets.DOCKER_PASSWORD }} | ||
|
||
- name: Build Docker image | ||
run: docker build -t ${{ secrets.DOCKER_USER_NAME }}/wedit . | ||
|
||
- name: Push Docker image | ||
run: docker push ${{ secrets.DOCKER_USER_NAME }}/wedit | ||
|
||
deploy: | ||
runs-on: ubuntu-latest | ||
needs: build | ||
if: github.event_name == 'push' | ||
|
||
steps: | ||
- name: Deploy to EC2 | ||
uses: appleboy/[email protected] | ||
with: | ||
host: ${{ secrets.HOST }} | ||
username: ${{ secrets.EC2_USER }} | ||
key: ${{ secrets.SSH_PRIVATE_KEY }} | ||
port: 22 | ||
script: | | ||
EXISTING_CONTAINER_ID=$(sudo docker ps -q -f "publish=8080" -f "status=running") | ||
if [ ! -z "$EXISTING_CONTAINER_ID" ]; then | ||
sudo docker stop $EXISTING_CONTAINER_ID | ||
sudo docker rm $EXISTING_CONTAINER_ID | ||
fi | ||
EXISTING_CONTAINER_ID=$(sudo docker ps -q -f "status=exited") | ||
if [ ! -z "$EXISTING_CONTAINER_ID" ]; then | ||
sudo docker rm $EXISTING_CONTAINER_ID | ||
fi | ||
sudo docker pull ${{ secrets.DOCKER_USER_NAME }}/wedit | ||
sudo docker run --name spring -d -p 8080:8080 -e TZ=Asia/Seoul ${{ secrets.DOCKER_USER_NAME }}/wedit | ||
sudo docker image prune -a -f | ||
debug: true # Enable debugging output |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# open jdk 17 버전의 환경을 구성 | ||
FROM openjdk:17-alpine | ||
|
||
# build가 되는 시점에 JAR_FILE이라는 변수 명에 build/libs/*.jar 선언 | ||
# build/libs - gradle로 빌드했을 때 jar 파일이 생성되는 경로 | ||
ARG JAR_FILE=build/libs/*.jar | ||
|
||
# JAR_FILE을 app.jar로 복사 | ||
COPY ${JAR_FILE} app.jar | ||
|
||
# 운영 및 개발에서 사용되는 환경 설정을 분리 | ||
ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=dev, bucket", "/app.jar"] |
80 changes: 80 additions & 0 deletions
80
src/main/java/com/wedit/weditapp/domain/shared/S3Service.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package com.wedit.weditapp.domain.shared; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.util.UUID; | ||
|
||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
import com.amazonaws.services.s3.AmazonS3Client; | ||
import com.amazonaws.services.s3.model.ObjectMetadata; | ||
import com.amazonaws.services.s3.model.PutObjectRequest; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
@Slf4j | ||
@RequiredArgsConstructor | ||
@Service | ||
public class S3Service { | ||
private final AmazonS3Client amazonS3Client; | ||
|
||
@Value("${cloud.aws.s3.bucket}") | ||
private String bucket; | ||
|
||
@Value("${cloud.aws.s3.path.image}") | ||
private String imageFolder; | ||
|
||
public String upload(MultipartFile multipartFile) { | ||
String fileName = imageFolder + multipartFile.getOriginalFilename(); | ||
|
||
if (!(fileName.endsWith(".png") || fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") || fileName.endsWith( | ||
".gif") || fileName.endsWith(".bmp"))) { | ||
throw new RuntimeException(); | ||
} | ||
|
||
ObjectMetadata metadata = new ObjectMetadata(); | ||
metadata.setContentType(multipartFile.getContentType()); | ||
metadata.setContentLength(multipartFile.getSize()); | ||
String uploadImageUrl = putS3(multipartFile, fileName, metadata); | ||
|
||
return uploadImageUrl; // 업로드된 파일의 S3 URL 주소 반환 | ||
} | ||
|
||
private String putS3(MultipartFile multipartFile, String fileName, ObjectMetadata metadata) { | ||
try (InputStream inputStream = multipartFile.getInputStream()) { | ||
amazonS3Client.putObject( | ||
new PutObjectRequest(bucket, fileName, inputStream, metadata) | ||
); | ||
} catch (IOException e) { | ||
throw new RuntimeException("파일 업로드 중 오류 발생", e); | ||
} | ||
return amazonS3Client.getUrl(bucket, fileName).toString(); | ||
} | ||
|
||
//이미지 가져오기(url) => public access 걸어놔서 가져올 수 있을 것임 | ||
public String getImageFileUrl(String fileName) { | ||
String filePath = imageFolder + fileName; | ||
return amazonS3Client.getUrl(bucket, filePath).toString(); | ||
} | ||
|
||
//파일 이름 중복 방지 코드 | ||
private String generateUniqueFileName(String originalFilename) { | ||
String extension = originalFilename.substring(originalFilename.lastIndexOf(".")); | ||
return UUID.randomUUID() + extension; // 고유 파일 이름 생성 | ||
} | ||
//검증로직 필요하면 사용 가능 | ||
private void validateFileFormat(MultipartFile multipartFile) { | ||
String contentType = multipartFile.getContentType(); | ||
if (contentType == null || !contentType.startsWith("image/")) { | ||
throw new IllegalArgumentException("지원하지 않는 파일 형식입니다."); | ||
} | ||
} | ||
//S3 파일 삭제가 필요한 경우 | ||
public void removeFile(String fileUrl) { | ||
String fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1); | ||
amazonS3Client.deleteObject(bucket, imageFolder + fileName); | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
src/main/java/com/wedit/weditapp/global/config/AwsConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.wedit.weditapp.global.config; | ||
|
||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
import com.amazonaws.auth.AWSStaticCredentialsProvider; | ||
import com.amazonaws.auth.BasicAWSCredentials; | ||
import com.amazonaws.services.s3.AmazonS3Client; | ||
import com.amazonaws.services.s3.AmazonS3ClientBuilder; | ||
|
||
@Configuration | ||
public class AwsConfig { | ||
@Value("${cloud.aws.credentials.access-key}") | ||
private String accessKey; | ||
|
||
// S3를 등록한 사람이 전달받은 접속하기 위한 secret key 값 | ||
@Value("${cloud.aws.credentials.secret-key}") | ||
private String secretKey; | ||
|
||
// S3를 등록한 사람이 S3를 사용할 지역 | ||
@Value("${cloud.aws.region.static}") | ||
private String region; | ||
|
||
// 전달받은 Accesskey 와 SecretKey 로 아마존 서비스 실행 준비 | ||
@Bean | ||
public AmazonS3Client amazonS3Client() { | ||
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey); | ||
return (AmazonS3Client)AmazonS3ClientBuilder.standard() | ||
.withRegion(region) | ||
.withCredentials(new AWSStaticCredentialsProvider(awsCreds)) | ||
.build(); | ||
} | ||
} |