diff --git a/.github/workflows/ecsCd.yml b/.github/workflows/ecsCd.yml new file mode 100644 index 00000000..287a3358 --- /dev/null +++ b/.github/workflows/ecsCd.yml @@ -0,0 +1,85 @@ +name: Deploy Spring Boot to ECS (Fargate) + +on: + push: + branches: + - mainDeploy + +env: + AWS_REGION: ap-northeast-2 + ECR_REPOSITORY: spring-app + ECS_CLUSTER: prod-cluster + ECS_SERVICE: spring-service + IMAGE_TAG: latest + CONTAINER_NAME: spring-container + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Create resources folder if not exists + run: mkdir -p ./src/main/resources + + - name: Create application.yml from GitHub Secret + run: echo "${{ secrets.APPLICATION_YML }}" > ./src/main/resources/application.yml + + - name: Set up JDK 23 + uses: actions/setup-java@v3 + with: + java-version: '23' + + - name: Build Spring Boot app + run: ./gradlew clean build -x test + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Log in to Amazon ECR + uses: aws-actions/amazon-ecr-login@v1 + + - name: Get AWS Account ID + id: aws-account + run: | + ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) + echo "ACCOUNT_ID=$ACCOUNT_ID" >> $GITHUB_ENV + echo "::set-output name=account_id::$ACCOUNT_ID" + + - name: Build and push Docker image to ECR + run: | + IMAGE_URI=${{ steps.aws-account.outputs.account_id }}.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com/${{ env.ECR_REPOSITORY }} + docker build -t $IMAGE_URI:$IMAGE_TAG . + docker push $IMAGE_URI:$IMAGE_TAG + echo "IMAGE_URI=$IMAGE_URI:$IMAGE_TAG" >> $GITHUB_ENV + echo "::set-output name=image_uri::$IMAGE_URI:$IMAGE_TAG" + + - name: Replace placeholders in ecs-task-def.json + run: | + sed -i "s||${{ steps.aws-account.outputs.account_id }}|g" ecs-task-def.json + sed -i "s||${{ steps.build-and-push.outputs.image_uri }}|g" ecs-task-def.json + cat ecs-task-def.json + + - name: Register new ECS task definition + id: register-task + run: | + TASK_DEF_ARN=$(aws ecs register-task-definition \ + --cli-input-json file://ecs-task-def.json \ + --query "taskDefinition.taskDefinitionArn" \ + --output text) + echo "TASK_DEF_ARN=$TASK_DEF_ARN" + echo "::set-output name=task_definition_arn::$TASK_DEF_ARN" + + - name: Deploy to ECS (Fargate) + uses: aws-actions/amazon-ecs-deploy-task-definition@v1 + with: + cluster: ${{ env.ECS_CLUSTER }} + service: ${{ env.ECS_SERVICE }} + task-definition: ${{ steps.register-task.outputs.task_definition_arn }} + wait-for-service-stability: true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..fc9e50f9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM eclipse-temurin:23-jdk-jammy as build +WORKDIR /app +COPY . . +RUN ./gradlew clean build -x test +FROM eclipse-temurin:23-jre-jammy +WORKDIR /app +COPY --from=build /app/build/libs/*.jar app.jar +COPY src/main/resources/application.yml ./src/main/resources/application.yml +EXPOSE 8080 +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/ecs-task-def.json b/ecs-task-def.json new file mode 100644 index 00000000..8de411b3 --- /dev/null +++ b/ecs-task-def.json @@ -0,0 +1,29 @@ +{ + "family": "spring-app", + "networkMode": "awsvpc", + "requiresCompatibilities": ["FARGATE"], + "cpu": "1024", + "memory": "2048", + "executionRoleArn": "arn:aws:iam:::role/ecsTaskExecutionRole", + "containerDefinitions": [ + { + "name": "spring-container", + "image": "", + "essential": true, + "portMappings": [ + { + "containerPort": 8080, + "protocol": "tcp" + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/spring-app", + "awslogs-region": "ap-northeast-2", + "awslogs-stream-prefix": "ecs" + } + } + } + ] +} diff --git a/src/main/java/goorm/back/zo6/conference/application/shared/ConferenceFactory.java b/src/main/java/goorm/back/zo6/conference/application/shared/ConferenceFactory.java index b388a107..4e4cb29b 100644 --- a/src/main/java/goorm/back/zo6/conference/application/shared/ConferenceFactory.java +++ b/src/main/java/goorm/back/zo6/conference/application/shared/ConferenceFactory.java @@ -9,6 +9,8 @@ public class ConferenceFactory { public Conference createConference(ConferenceCreateRequest request) { + String imageKey = parseS3ImageKeyFromUrl(request.imageUrl()); + return Conference.builder() .name(request.name()) .description(request.description()) @@ -16,9 +18,16 @@ public Conference createConference(ConferenceCreateRequest request) { .location(request.location()) .startTime(request.startTime()) .endTime(request.endTime()) - .imageKey(request.imageUrl()) + .imageKey(imageKey) .isActive(true) .hasSessions(request.hasSessions()) .build(); } + + private String parseS3ImageKeyFromUrl(String imageUrl) { + if (imageUrl == null || !imageUrl.contains("/conference/images/")) { + throw new IllegalArgumentException("Invalid image url"); + } + return imageUrl.substring(imageUrl.indexOf("conference/images")); + } } diff --git a/src/main/java/goorm/back/zo6/conference/application/shared/SessionFactory.java b/src/main/java/goorm/back/zo6/conference/application/shared/SessionFactory.java index cea4ecd5..a7d96c60 100644 --- a/src/main/java/goorm/back/zo6/conference/application/shared/SessionFactory.java +++ b/src/main/java/goorm/back/zo6/conference/application/shared/SessionFactory.java @@ -30,6 +30,8 @@ public Set createSessionDtos(List reservations) { public Session createSession(SessionCreateRequest request, Conference conference) { + String speakerImageKey = parseSpeakerImageKey(request.speakerImage()); + return Session.builder() .name(request.name()) .capacity(request.capacity()) @@ -40,8 +42,15 @@ public Session createSession(SessionCreateRequest request, Conference conference .speakerName(request.speakerName()) .speakerOrganization(request.speakerOrganization()) .isActive(true) - .speakerImageKey(request.speakerImage()) + .speakerImageKey(speakerImageKey) .conference(conference) .build(); } + + private String parseSpeakerImageKey(String speakerImage) { + if (speakerImage == null || !speakerImage.contains("/conference/speaker/")) { + throw new IllegalArgumentException("Invalid speaker image url"); + } + return speakerImage.substring(speakerImage.indexOf("conference/speaker/")); + } }