-
Notifications
You must be signed in to change notification settings - Fork 0
[Fix] prod db 마이그레이션을 위한 테라폼 동기화 작업 #187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
44 commits
Select commit
Hold shift + click to select a range
7b000e2
Merge pull request #55 from YAPP-Github/develop
lvalentine6 4ce9e64
Merge pull request #128 from YAPP-Github/develop
lvalentine6 31145cf
Merge pull request #131 from YAPP-Github/develop
lvalentine6 613c3f0
Merge pull request #160 from YAPP-Github/develop
lvalentine6 8bd14ea
fix: prod 환경 컨테이너 포트 수정
lvalentine6 e231deb
fix: 임시적으로 응원 최대 개수를 인 당 10,000개까지 허용
leegwichan 3fd45eb
Merge pull request #162 from YAPP-Github/hotfix/store-limit
leegwichan 4092edf
fix: dev 환경과 동기화
lvalentine6 539a197
feat: db 마이그레이션을 위한 테스트 환경 구현
lvalentine6 65a6c5e
feat: 람다 실행을 위한 핸들러 구현
lvalentine6 27be4f7
feat: flyway 마이그레이션 추가
lvalentine6 5e4a65d
fix: 마이그레이션 작업을 위한 output 추가
lvalentine6 c161ee2
fix: 마이그레이션 작업을 위한 라우팅 테이블 output 추가
lvalentine6 6a6773c
fix: 타입 및 인자 수정
lvalentine6 3c2ea6a
fix: rds 주소 output 추가
lvalentine6 bd211b3
feat: rds 복제 확인을 위한 점프 호스트 추가
lvalentine6 0172dcc
fix: deprecated 된 리전 명령어를 id로 교체
lvalentine6 ea93542
fix: 복제 인스턴스 사양 변경
lvalentine6 88c92e2
fix: 마이그레이션 파일명 변경
lvalentine6 6b4df4a
feat: 마이그레이션 전용 프로파일 추가
lvalentine6 8a0cffc
feat: 마이그레이션 테스트 실행, 삭제 워크플로 추가
lvalentine6 e956741
fix: 워크플로에 맞춰 람다 핸들러 수정
lvalentine6 f25da75
fix(bootstrap): ECR에 테스트 이미지 라이프 사이클 추가
lvalentine6 379b4e1
fix(migration): 마이그레이션 리소스 수정
lvalentine6 f4ffef1
fix(migration): 워크플로에서 사용할수 있도록 output 정의
lvalentine6 fe866d9
fix(migration): IAM 정책, sg 그룹 정의 추가
lvalentine6 20f90c7
fix(common): ECR에 삭제 정책 정의 추가
lvalentine6 8c0180f
fix(dev,prod): S3 output 추가
lvalentine6 bf7ad46
fix: dev가 아닌 prod를 복제하도록 수정
lvalentine6 13d6bd3
fix: 참조 환경 변경
lvalentine6 5bbc984
fix: 참조 변수명 수정
lvalentine6 7f31913
fix: dev 환경과의 충돌을 방지하기 위해 임시 삭제
lvalentine6 f2675c0
fix: 워크플로 에러 해결
lvalentine6 3ef42aa
fix: 핸들러 예외 처리 로직 강화
lvalentine6 0fbaa4e
fix: sg, iam 정책 추가
lvalentine6 fc14910
fix: 람다 의존성 목록 추가
lvalentine6 545d270
fix: 핸들러 코드에 order_index 적용
lvalentine6 05a42b9
fix: 핸들러 이름 수정, 워크플로 수정
lvalentine6 5cbac91
fix: 워크플로 이름 변경 및 참조 수정
lvalentine6 d0d6a5d
Merge branch 'develop' into fix/prod-db-migration
lvalentine6 f647e31
test: prod 환경에 맞게 테스트 비활성화
lvalentine6 83e1791
fix: 워크플로 결과 알림 추가
lvalentine6 ba539c7
fix: 커스텀 정책 분리
lvalentine6 2021c09
fix: sg 참조 수정
lvalentine6 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,246 @@ | ||
| name: 'Deploy Migration Test Environment' | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| branch: | ||
| description: '테스트할 브랜치 이름' | ||
| required: true | ||
| default: 'dev' | ||
|
|
||
| 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: ./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 | ||
|
|
||
lvalentine6 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 }}' | ||
| echo "ECR_REPOSITORY=$(echo "$TF_OUTPUTS" | jq -r '.ecr_repository_name.value')" >> $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 | ||
|
|
||
lvalentine6 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| - 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 }}" ' | ||
| .containerDefinitions[0].image = $IMAGE | | ||
| 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 ${{ env.PROD_ECS_CLUSTER_NAME }} \ | ||
| --task-definition ${{ steps.update-task-def.outputs.new_task_definition_arn }} \ | ||
| --launch-type EC2 \ | ||
| --network-configuration "awsvpcConfiguration={subnets=[${{ env.VPC_SUBNET }}],securityGroups=[${{ env.TASK_SECURITY_GROUP }}]}" \ | ||
| --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 }}" } ] } ] }' \ | ||
| --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 ${{ env.PROD_ECS_CLUSTER_NAME }} --tasks ${{ steps.ecs-run-task.outputs.task_arn }} | ||
|
|
||
lvalentine6 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| - name: Invoke Post-processing Lambda | ||
| run: | | ||
| aws lambda invoke --function-name ${{ env.LAMBDA_FUNCTION_NAME }} --payload '{"task": "postprocess"}' response.json | ||
lvalentine6 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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 }}" | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| name: 'Destroy Migration Test Environment' | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| branch: | ||
| description: '리소스를 생성했던 브랜치 이름' | ||
| required: true | ||
| default: 'dev' | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: terraform-migration-destroy | ||
| cancel-in-progress: false | ||
|
|
||
| env: | ||
| AWS_REGION: ap-northeast-2 | ||
|
|
||
| jobs: | ||
| terraform-destroy-migration: | ||
| name: Terraform Destroy MIGRATION | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v5 | ||
| with: | ||
| ref: ${{ github.event.inputs.branch }} | ||
|
|
||
| - 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_KEY_ID }} | ||
| aws-region: ${{ env.AWS_REGION }} | ||
lvalentine6 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| - name: Terraform Init (migration) | ||
| run: terraform init | ||
| working-directory: ./terraform-db-migration | ||
|
|
||
| - name: Terraform Destroy MIGRATION | ||
| run: terraform destroy -auto-approve | ||
| working-directory: ./terraform-db-migration | ||
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| logging: | ||
| level: | ||
| org.hibernate.SQL: INFO | ||
|
|
||
| spring: | ||
| cloud: | ||
| aws: | ||
| region: | ||
| static: ap-northeast-2 | ||
| s3: | ||
| bucket: ${MIGRATION_S3_BUCKET} | ||
|
|
||
| servlet: | ||
| multipart: | ||
| max-file-size: 5MB | ||
| max-request-size: 10MB | ||
|
|
||
| config: | ||
| import: "aws-parameterstore:/prod/" | ||
|
|
||
| datasource: | ||
| url: ${MIGRATION_RDS_URL} | ||
| username: ${MYSQL_USER_NAME} | ||
| password: ${MYSQL_PASSWORD} | ||
| driver-class-name: com.mysql.cj.jdbc.Driver | ||
|
|
||
| jpa: | ||
| hibernate: | ||
| ddl-auto: validate | ||
|
|
||
| flyway: | ||
| enabled: true | ||
| baseline-on-migrate: false | ||
| locations: | ||
| - classpath:db/migration | ||
| - classpath:db/seed/prod | ||
|
|
||
| jwt: | ||
| access-token-expiration: 1h | ||
| refresh-token-expiration: 14d | ||
| secret-key: ${JWT_SECRET_KEY} | ||
|
|
||
| cors: | ||
| origin: | ||
| - "https://www.eatda.net" | ||
| - "https://eatda.net" | ||
|
|
||
| oauth: | ||
| client-id: ${OAUTH_CLIENT_ID} | ||
| redirect-path: /login/callback | ||
| allowed-origins: | ||
| - "https://www.eatda.net" | ||
| - "https://eatda.net" | ||
|
|
||
| kakao: | ||
| api-key: ${KAKAO_API_KEY} |
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| locals { | ||
| default_ecr_lifecycle_policy = jsonencode({ | ||
| rules = [ | ||
| { | ||
| rulePriority = 1, | ||
| description = "7일이 지난 마이그레이션 테스트용 임시 이미지 자동 삭제", | ||
| selection = { | ||
| tagStatus = "tagged", | ||
| tagPrefixList = ["migration-test-"], | ||
| countType = "sinceImagePushed", | ||
| countUnit = "days", | ||
| countNumber = 7 | ||
| }, | ||
| action = { | ||
| type = "expire" | ||
| } | ||
| } | ||
| ] | ||
| }) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.