Deploy Migration Test Environment #15
This file contains hidden or 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
| name: 'Deploy Migration Test Environment' | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| branch: | |
| description: '테스트할 브랜치 이름' | |
| required: true | |
| default: 'develop' | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: terraform-migration | |
| cancel-in-progress: false | |
| env: | |
| AWS_REGION: ap-northeast-2 | |
| jobs: | |
| build-lambda-handler: | |
| name: Build Lambda Handler | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.inputs.branch }} | |
| - name: Set up Python | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: '3.12' | |
| - name: Install dependencies and create zip file | |
| run: | | |
| mkdir -p build/lambda-package | |
| pip install -r ./terraform-db-migration/lambda_src/requirements.txt -t ./build/lambda-package | |
| cp ./terraform-db-migration/lambda_src/handler.py ./build/lambda-package/ | |
| cd ./build/lambda-package | |
| zip -r ../migration_lambda.zip . | |
| - name: Upload Lambda artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: lambda-build | |
| path: ./build/migration_lambda.zip | |
| terraform-apply-migration: | |
| name: Terraform Apply MIGRATION | |
| runs-on: ubuntu-latest | |
| needs: build-lambda-handler | |
| outputs: | |
| tf_outputs_json: ${{ steps.get-outputs.outputs.data }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.inputs.branch }} | |
| - name: Download Lambda artifact | |
| uses: actions/download-artifact@v5 | |
| with: | |
| name: lambda-build | |
| path: ./terraform-db-migration/build | |
| - uses: hashicorp/setup-terraform@v3 | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| 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: Terraform Init (migration) | |
| run: terraform init | |
| working-directory: ./terraform-db-migration | |
| - name: Terraform Apply MIGRATION | |
| run: terraform apply -auto-approve | |
| working-directory: ./terraform-db-migration | |
| - name: Get Terraform Outputs | |
| id: get-outputs | |
| run: | | |
| echo "data<<EOF" >> $GITHUB_OUTPUT | |
| terraform output -json >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| working-directory: ./terraform-db-migration | |
| build-and-run-migration: | |
| name: Build Image and Run Migration Sequence | |
| runs-on: ubuntu-latest | |
| needs: terraform-apply-migration | |
| steps: | |
| - name: Checkout specific branch | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.inputs.branch }} | |
| - name: Parse Terraform Outputs and Set Env | |
| id: parse-tf | |
| run: | | |
| TF_OUTPUTS='${{ needs.terraform-apply-migration.outputs.tf_outputs_json }}' | |
| ECR_REPOSITORY=$(echo "$TF_OUTPUTS" | jq -r '.ecr_repository_name.value' | awk -F/ '{print $NF}') | |
| echo "ECR_REPOSITORY=$ECR_REPOSITORY" >> $GITHUB_ENV | |
| echo "PROD_ECS_CLUSTER_NAME=$(echo "$TF_OUTPUTS" | jq -r '.prod_ecs_cluster_name.value')" >> $GITHUB_ENV | |
| echo "PROD_ECS_SERVICE_NAME=$(echo "$TF_OUTPUTS" | jq -r '.prod_ecs_api_service_name.value')" >> $GITHUB_ENV | |
| echo "LAMBDA_FUNCTION_NAME=$(echo "$TF_OUTPUTS" | jq -r '.migration_lambda_function_name.value')" >> $GITHUB_ENV | |
| echo "VPC_SUBNET=$(echo "$TF_OUTPUTS" | jq -r '.private_subnet_id.value')" >> $GITHUB_ENV | |
| echo "TASK_SECURITY_GROUP=$(echo "$TF_OUTPUTS" | jq -r '.task_security_group_id.value')" >> $GITHUB_ENV | |
| echo "CLONED_RDS_ADDRESS=$(echo "$TF_OUTPUTS" | jq -r '.cloned_rds_address.value')" >> $GITHUB_ENV | |
| echo "CLONED_S3_BUCKET_NAME=$(echo "$TF_OUTPUTS" | jq -r '.cloned_s3_bucket_name.value')" >> $GITHUB_ENV | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| 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: Set up JDK 21 | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'temurin' | |
| java-version: 21 | |
| java-package: jdk | |
| architecture: 'x64' | |
| cache: 'gradle' | |
| - name: Get TEST_JWT_SECRET_KEY from SSM | |
| id: get-test-secret | |
| run: | | |
| SECRET_VALUE=$(aws ssm get-parameter --name "/common/TEST_JWT_SECRET_KEY" --with-decryption --query "Parameter.Value" --output text) | |
| echo "TEST_JWT_SECRET_KEY=$SECRET_VALUE" >> $GITHUB_ENV | |
| - name: Build with Gradle | |
| run: | | |
| chmod +x gradlew | |
| ./gradlew clean build -Dspring.profiles.active=migration | |
| env: | |
| TEST_JWT_SECRET_KEY: ${{ env.TEST_JWT_SECRET_KEY }} | |
| - name: Login to Amazon ECR | |
| id: login-ecr | |
| uses: aws-actions/amazon-ecr-login@v2 | |
| - name: Build, tag, and push image to Amazon ECR | |
| id: build-image | |
| env: | |
| ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} | |
| run: | | |
| IMAGE_TAG="migration-test-$(date +'%Y%m%d-%H%M%S')" | |
| docker build -t $ECR_REGISTRY/${ECR_REPOSITORY}:${IMAGE_TAG} . | |
| docker push $ECR_REGISTRY/${ECR_REPOSITORY}:${IMAGE_TAG} | |
| echo "image_uri=$ECR_REGISTRY/${ECR_REPOSITORY}:${IMAGE_TAG}" >> $GITHUB_OUTPUT | |
| - name: Get Latest PROD Task Definition Info | |
| id: get-prod-info | |
| run: | | |
| TASK_DEF_ARN=$(aws ecs describe-services --cluster "${{ env.PROD_ECS_CLUSTER_NAME }}" --services "${{ env.PROD_ECS_SERVICE_NAME }}" --query "services[0].taskDefinition" --output text) | |
| CONTAINER_NAME=$(aws ecs describe-task-definition --task-definition "$TASK_DEF_ARN" --query "taskDefinition.containerDefinitions[0].name" --output text) | |
| echo "task_definition_arn=$TASK_DEF_ARN" >> $GITHUB_OUTPUT | |
| echo "container_name=$CONTAINER_NAME" >> $GITHUB_OUTPUT | |
| - name: Create New Task Definition with Updated Image | |
| id: update-task-def | |
| run: | | |
| TASK_DEF=$(aws ecs describe-task-definition \ | |
| --task-definition "${{ steps.get-prod-info.outputs.task_definition_arn }}" \ | |
| --query "taskDefinition") | |
| NEW_TASK_DEF=$(echo "$TASK_DEF" | jq --arg IMAGE "${{ steps.build-image.outputs.image_uri }}" ' | |
| .family = "api-migration" | | |
| .containerDefinitions[0].image = $IMAGE | | |
| (.containerDefinitions[0].command) |= map( | |
| if test("prod$") then sub("prod$"; "migration") else . end | |
| ) | | |
| del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .placementConstraints, | |
| .compatibilities, .registeredAt, .registeredBy) | |
| ') | |
| NEW_TASK_DEF_ARN=$(aws ecs register-task-definition \ | |
| --cli-input-json "$NEW_TASK_DEF" \ | |
| --query "taskDefinition.taskDefinitionArn" \ | |
| --output text) | |
| echo "new_task_definition_arn=$NEW_TASK_DEF_ARN" >> $GITHUB_OUTPUT | |
| - name: Invoke Pre-processing Lambda | |
| run: | | |
| META=$(aws lambda invoke \ | |
| --function-name "${{ env.LAMBDA_FUNCTION_NAME }}" \ | |
| --payload '{"task":"preprocess"}' \ | |
| --cli-binary-format raw-in-base64-out \ | |
| response.json) | |
| echo "$META" | jq . | |
| echo "$META" | jq -e '.FunctionError == null' >/dev/null || { echo "Preprocess Lambda failed"; exit 1; } | |
| - name: Run DB Migration ECS Task | |
| id: ecs-run-task | |
| run: | | |
| MIGRATION_RDS_URL="jdbc:mysql://${{ env.CLONED_RDS_ADDRESS }}/eatda?useUnicode=true&characterEncoding=UTF-8" | |
| TASK_ARN=$(aws ecs run-task \ | |
| --cluster migration-cluster \ | |
| --task-definition ${{ steps.update-task-def.outputs.new_task_definition_arn }} \ | |
| --launch-type EC2 \ | |
| --overrides '{ | |
| "containerOverrides": [ | |
| { | |
| "name": "${{ steps.get-prod-info.outputs.container_name }}", | |
| "environment": [ | |
| { "name": "SPRING_PROFILES_ACTIVE", "value": "migration" }, | |
| { "name": "MIGRATION_RDS_URL", "value": "'"${MIGRATION_RDS_URL}"'" }, | |
| { "name": "MIGRATION_S3_BUCKET", "value": "${{ env.CLONED_S3_BUCKET_NAME }}" }, | |
| { "name": "SPRING_MAIN_WEB_APPLICATION_TYPE", "value": "NONE" } | |
| ] | |
| } | |
| ] | |
| }' \ | |
| --query "tasks[0].taskArn" --output text) | |
| echo "task_arn=$TASK_ARN" >> $GITHUB_OUTPUT | |
| if [ -z "$TASK_ARN" ] || [ "$TASK_ARN" = "None" ]; then | |
| echo "Failed to start ECS task"; exit 1; | |
| fi | |
| - name: Wait for ECS Task to Complete | |
| run: | | |
| aws ecs wait tasks-stopped --cluster migration-cluster --tasks ${{ steps.ecs-run-task.outputs.task_arn }} | |
| - name: Check ECS Task Exit Code | |
| run: | | |
| TASK_ARN=${{ steps.ecs-run-task.outputs.task_arn }} | |
| EXIT_CODE=$(aws ecs describe-tasks \ | |
| --cluster migration-cluster \ | |
| --tasks $TASK_ARN \ | |
| --query "tasks[0].containers[0].exitCode" \ | |
| --output text) | |
| echo "ECS Task Exit Code: $EXIT_CODE" | |
| if [ "$EXIT_CODE" != "0" ]; then | |
| echo "Migration task failed with exit code $EXIT_CODE" | |
| exit 1 | |
| fi | |
| - name: Get DB Credentials from SSM | |
| id: get-db-creds | |
| run: | | |
| DB_USER=$(aws ssm get-parameter --name "/prod/MYSQL_USER_NAME" --with-decryption --query "Parameter.Value" --output text) | |
| DB_PASS=$(aws ssm get-parameter --name "/prod/MYSQL_PASSWORD" --with-decryption --query "Parameter.Value" --output text) | |
| echo "::add-mask::$DB_USER" | |
| echo "::add-mask::$DB_PASS" | |
| echo "DB_USER=$DB_USER" >> $GITHUB_ENV | |
| echo "DB_PASS=$DB_PASS" >> $GITHUB_ENV | |
| - name: Invoke Post-processing Lambda | |
| run: | | |
| PAYLOAD=$(jq -n \ | |
| --arg task "postprocess" \ | |
| --arg bucket "${{ env.CLONED_S3_BUCKET_NAME }}" \ | |
| --arg db_endpoint "${{ env.CLONED_RDS_ADDRESS }}" \ | |
| --arg db_username "${{ env.DB_USER }}" \ | |
| --arg db_password "${{ env.DB_PASS }}" \ | |
| '{ | |
| task: $task, | |
| target_bucket_for_realignment: $bucket, | |
| db_endpoint: $db_endpoint, | |
| db_username: $db_username, | |
| db_password: $db_password | |
| }') | |
| echo "Invoking Lambda with payload: $PAYLOAD" | |
| aws lambda invoke \ | |
| --function-name "${{ env.LAMBDA_FUNCTION_NAME }}" \ | |
| --payload "$PAYLOAD" \ | |
| --cli-binary-format raw-in-base64-out \ | |
| response.json | |
| notify: | |
| name: Send Discord Notification (Migration) | |
| runs-on: ubuntu-latest | |
| needs: build-and-run-migration | |
| if: always() | |
| steps: | |
| - name: Prepare Notification Info | |
| id: vars | |
| run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT | |
| - name: Discord Notify (Success) | |
| if: needs.build-and-run-migration.result == 'success' | |
| uses: tsickert/[email protected] | |
| with: | |
| webhook-url: ${{ secrets.DISCORD_WEBHOOK }} | |
| embed-title: "✅ Migration Test 배포 성공!" | |
| embed-color: 65280 | |
| embed-description: | | |
| 마이그레이션 테스트 환경이 성공적으로 구축되었습니다. | |
| **커밋**: [${{ steps.vars.outputs.sha_short }}](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}) | |
| **실행자**: ${{ github.actor }} | |
| embed-url: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| - name: Discord Notify (Failure) | |
| if: needs.build-and-run-migration.result != 'success' | |
| uses: tsickert/[email protected] | |
| with: | |
| webhook-url: ${{ secrets.DISCORD_WEBHOOK }} | |
| embed-title: "❌ Migration Test 배포 실패!" | |
| embed-color: 16711680 | |
| embed-description: | | |
| 마이그레이션 테스트 환경 구축중 오류가 발생했습니다. | |
| **커밋**: [${{ steps.vars.outputs.sha_short }}](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}) | |
| **실행자**: ${{ github.actor }} | |
| embed-url: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" |