CodeRabbit Generated Unit Tests: Add unit tests for PR changes #28
Workflow file for this run
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
| # Terraform 변경 감지 Plan 파이프라인 | |
| name: PinHouse Terraform Plan 파이프라인 | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| # Terraform 디렉터리 변경만 감지 | |
| paths: | |
| - "terraform/**" | |
| # 워크플로우 실행에 필요한 최소 권한 | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| id-token: write | |
| jobs: | |
| # 변경된 Terraform 파일을 기준으로 영향받는 환경을 계산 | |
| detect-changes: | |
| name: 변경 환경 감지 | |
| runs-on: ubuntu-22.04 | |
| env: | |
| GCP_WORKLOAD_IDENTITY_PROVIDER: "" | |
| GCP_SERVICE_ACCOUNT: "" | |
| TERRAFORM_STATE_BUCKET: "" | |
| TERRAFORM_TFVARS_PROD: "" | |
| outputs: | |
| environments: ${{ steps.detect.outputs.environments || '[]' }} | |
| steps: | |
| - name: 코드 체크아웃 | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: 변경된 환경 감지 | |
| id: detect | |
| run: | | |
| # PR 기준 브랜치와 비교해 변경 파일 목록을 가져옵니다. | |
| git fetch origin ${{ github.base_ref }} | |
| CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) | |
| echo "변경된 파일 목록:" | |
| echo "$CHANGED_FILES" | |
| # 변경된 환경 목록을 배열로 수집합니다. | |
| ENVIRONMENTS=() | |
| if echo "$CHANGED_FILES" | grep -q "terraform/environments/prod/"; then | |
| ENVIRONMENTS+=("prod") | |
| fi | |
| if echo "$CHANGED_FILES" | grep -q "terraform/modules/"; then | |
| ENVIRONMENTS+=("prod") | |
| fi | |
| # 중복을 제거한 뒤 JSON 배열 문자열로 변환합니다. | |
| if [ ${#ENVIRONMENTS[@]} -eq 0 ]; then | |
| ENVIRONMENTS_JSON='[]' | |
| else | |
| ENVIRONMENTS_JSON=$(printf '%s\n' "${ENVIRONMENTS[@]}" | sort -u | jq -R . | jq -cs .) | |
| fi | |
| echo "감지된 환경: $ENVIRONMENTS_JSON" | |
| echo "environments=$ENVIRONMENTS_JSON" >> "$GITHUB_OUTPUT" | |
| # detect-changes 출력값을 바로 확인하기 위한 디버그 잡 | |
| debug-detect-changes: | |
| name: 디버그 - 감지 결과 | |
| runs-on: ubuntu-22.04 | |
| needs: detect-changes | |
| if: always() | |
| env: | |
| GCP_WORKLOAD_IDENTITY_PROVIDER: "" | |
| GCP_SERVICE_ACCOUNT: "" | |
| TERRAFORM_STATE_BUCKET: "" | |
| TERRAFORM_TFVARS_PROD: "" | |
| steps: | |
| - name: detect-changes 출력 확인 | |
| env: | |
| ENVIRONMENTS: ${{ needs.detect-changes.outputs.environments }} | |
| run: | | |
| echo "detect-changes environments 원본: $ENVIRONMENTS" | |
| printf 'detect-changes environments quoted: <%s>\n' "$ENVIRONMENTS" | |
| echo "detect-changes environments 길이: ${#ENVIRONMENTS}" | |
| echo "detect-changes environments 바이트:" | |
| printf '%s' "$ENVIRONMENTS" | od -An -tx1 | |
| echo "detect-changes environments JSON 파싱 시도:" | |
| if [ -n "$ENVIRONMENTS" ]; then | |
| printf '%s\n' "$ENVIRONMENTS" | jq -c . | |
| else | |
| echo "값이 비어 있습니다." | |
| fi | |
| # 환경별 Terraform init/fmt/validate/plan을 수행하고 결과 파일을 아티팩트로 남깁니다. | |
| terraform-plan-checks: | |
| name: 체크 - ${{ matrix.environment }} | |
| runs-on: ubuntu-22.04 | |
| needs: detect-changes | |
| if: (needs.detect-changes.outputs.environments || '[]') != '[]' # 변경된 환경이 있을 때만 실행합니다. | |
| env: | |
| GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} | |
| GCP_SERVICE_ACCOUNT: ${{ secrets.GCP_SERVICE_ACCOUNT }} | |
| TERRAFORM_STATE_BUCKET: ${{ secrets.TERRAFORM_STATE_BUCKET }} | |
| TERRAFORM_TFVARS_PROD: ${{ secrets.TERRAFORM_TFVARS_PROD }} | |
| strategy: | |
| fail-fast: false # 한 환경 실패가 다른 환경 확인을 막지 않도록 유지합니다. | |
| matrix: | |
| environment: ${{ fromJson(needs.detect-changes.outputs.environments || '[]') }} | |
| steps: | |
| - name: 코드 체크아웃 | |
| uses: actions/checkout@v4 | |
| - name: Matrix 디버그 | |
| run: | | |
| echo "matrix.environment=${{ matrix.environment }}" | |
| echo "needs.detect-changes.outputs.environments=${{ needs.detect-changes.outputs.environments }}" | |
| # 출력 파일 기반으로 결과를 다루기 위해 wrapper를 비활성화 | |
| - name: Terraform 설치 | |
| uses: hashicorp/setup-terraform@v3 | |
| with: | |
| terraform_version: 1.6.0 | |
| terraform_wrapper: false | |
| - name: Terraform 포맷 확인 | |
| id: fmt | |
| working-directory: terraform/environments/${{ matrix.environment }} | |
| run: terraform fmt -check | |
| - name: GCP 인증 | |
| uses: google-github-actions/auth@v2 | |
| with: | |
| workload_identity_provider: ${{ env.GCP_WORKLOAD_IDENTITY_PROVIDER }} | |
| service_account: ${{ env.GCP_SERVICE_ACCOUNT }} | |
| - name: State 버킷 변수 확인 | |
| id: state-bucket | |
| run: | | |
| if [ -z "${{ env.TERRAFORM_STATE_BUCKET }}" ]; then | |
| echo "오류: GitHub Secrets에 TERRAFORM_STATE_BUCKET을 설정해야 합니다." | |
| exit 1 | |
| fi | |
| - name: Terraform 초기화 | |
| id: init | |
| if: steps.state-bucket.outcome == 'success' | |
| working-directory: terraform/environments/${{ matrix.environment }} | |
| run: | | |
| terraform init \ | |
| -backend-config="bucket=${{ env.TERRAFORM_STATE_BUCKET }}" \ | |
| -backend-config="prefix=terraform/${{ matrix.environment }}/state" | |
| - name: Terraform tfvars 복원 | |
| id: tfvars | |
| if: steps.init.outcome == 'success' | |
| working-directory: terraform/environments/${{ matrix.environment }} | |
| run: | | |
| TFVARS_CONTENT="${TERRAFORM_TFVARS_PROD}" | |
| SECRET_NAME="TERRAFORM_TFVARS_PROD" | |
| if [ -z "${TFVARS_CONTENT}" ]; then | |
| echo "오류: GitHub Secret ${SECRET_NAME}를 설정해야 합니다." | |
| exit 1 | |
| fi | |
| printf '%s\n' "${TFVARS_CONTENT}" > terraform.tfvars | |
| chmod 600 terraform.tfvars | |
| - name: Terraform 유효성 검사 | |
| id: validate | |
| if: steps.init.outcome == 'success' && steps.tfvars.outcome == 'success' | |
| working-directory: terraform/environments/${{ matrix.environment }} | |
| run: terraform validate -no-color | |
| - name: Terraform Plan 실행 | |
| id: plan | |
| if: steps.init.outcome == 'success' && steps.tfvars.outcome == 'success' && steps.validate.outcome == 'success' | |
| working-directory: terraform/environments/${{ matrix.environment }} | |
| run: | | |
| terraform plan -no-color -out=tfplan | |
| terraform show -no-color tfplan > plan_output.txt | |
| - name: 결과 메타데이터 생성 | |
| if: always() | |
| working-directory: terraform/environments/${{ matrix.environment }} | |
| env: | |
| STATE_BUCKET_OUTCOME: ${{ steps.state-bucket.outcome }} | |
| INIT_OUTCOME: ${{ steps.init.outcome }} | |
| TFVARS_OUTCOME: ${{ steps.tfvars.outcome }} | |
| FMT_OUTCOME: ${{ steps.fmt.outcome }} | |
| VALIDATE_OUTCOME: ${{ steps.validate.outcome }} | |
| PLAN_OUTCOME: ${{ steps.plan.outcome }} | |
| run: | | |
| # plan 출력 파일이 없으면 댓글용 안내 문구를 생성합니다. | |
| if [ ! -f plan_output.txt ]; then | |
| echo "Plan 출력 파일이 생성되지 않았습니다." > plan_output.txt | |
| fi | |
| # plan 출력 파일 생성 | |
| cat > plan_status.json <<EOF | |
| { | |
| "environment": "${{ matrix.environment }}", | |
| "state_bucket": "${STATE_BUCKET_OUTCOME:-skipped}", | |
| "init": "${INIT_OUTCOME:-skipped}", | |
| "tfvars": "${TFVARS_OUTCOME:-skipped}", | |
| "fmt": "${FMT_OUTCOME:-skipped}", | |
| "validate": "${VALIDATE_OUTCOME:-skipped}", | |
| "plan": "${PLAN_OUTCOME:-skipped}" | |
| } | |
| EOF | |
| - name: Plan 결과 업로드 | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: tfplan-${{ matrix.environment }} | |
| path: | | |
| terraform/environments/${{ matrix.environment }}/plan_output.txt | |
| terraform/environments/${{ matrix.environment }}/plan_status.json | |
| # 업로드된 결과 파일을 읽어 PR 댓글을 환경별로 갱신 | |
| comment-plan-results: | |
| name: PR 댓글 갱신 | |
| runs-on: ubuntu-22.04 | |
| needs: | |
| - detect-changes | |
| - terraform-plan-checks | |
| if: always() && (needs.detect-changes.outputs.environments || '[]') != '[]' && github.event_name == 'pull_request' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| environment: ${{ fromJson(needs.detect-changes.outputs.environments || '[]') }} | |
| steps: | |
| - name: Plan 결과 다운로드 | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: tfplan-${{ matrix.environment }} | |
| path: artifact | |
| - name: Plan 상태 읽기 | |
| id: plan-status | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const fs = require('fs'); | |
| const status = JSON.parse(fs.readFileSync('artifact/plan_status.json', 'utf8')); | |
| core.setOutput('environment', status.environment); | |
| core.setOutput('state_bucket', status.state_bucket); | |
| core.setOutput('init', status.init); | |
| core.setOutput('tfvars', status.tfvars); | |
| core.setOutput('fmt', status.fmt); | |
| core.setOutput('validate', status.validate); | |
| core.setOutput('plan', status.plan); | |
| - name: Plan 출력 길이 조정 | |
| run: | | |
| MAX_LENGTH=65000 | |
| PLAN_FILE="artifact/plan_output.txt" | |
| TRIMMED_FILE="artifact/plan_output_trimmed.txt" | |
| if [ "$(wc -c < "$PLAN_FILE")" -gt "$MAX_LENGTH" ]; then | |
| head -c "$MAX_LENGTH" "$PLAN_FILE" > "$TRIMMED_FILE" | |
| printf '\n\n... (길이 제한으로 일부만 표시됩니다)\n' >> "$TRIMMED_FILE" | |
| else | |
| cp "$PLAN_FILE" "$TRIMMED_FILE" | |
| fi | |
| - name: 댓글 본문 생성 | |
| env: | |
| STATE_BUCKET_STATUS: ${{ steps.plan-status.outputs.state_bucket }} | |
| INIT_STATUS: ${{ steps.plan-status.outputs.init }} | |
| TFVARS_STATUS: ${{ steps.plan-status.outputs.tfvars }} | |
| FMT_STATUS: ${{ steps.plan-status.outputs.fmt }} | |
| VALIDATE_STATUS: ${{ steps.plan-status.outputs.validate }} | |
| PLAN_STATUS: ${{ steps.plan-status.outputs.plan }} | |
| run: | | |
| status_emoji() { | |
| case "$1" in | |
| success) echo "✅" ;; | |
| failure) echo "❌" ;; | |
| skipped) echo "⏭️" ;; | |
| cancelled) echo "🚫" ;; | |
| *) echo "$1" ;; | |
| esac | |
| } | |
| STATE_BUCKET_EMOJI=$(status_emoji "$STATE_BUCKET_STATUS") | |
| INIT_EMOJI=$(status_emoji "$INIT_STATUS") | |
| TFVARS_EMOJI=$(status_emoji "$TFVARS_STATUS") | |
| FMT_EMOJI=$(status_emoji "$FMT_STATUS") | |
| VALIDATE_EMOJI=$(status_emoji "$VALIDATE_STATUS") | |
| PLAN_EMOJI=$(status_emoji "$PLAN_STATUS") | |
| cat > artifact/comment_body.md <<EOF | |
| ## Terraform Plan - \`${{ steps.plan-status.outputs.environment }}\` 환경 | |
| #### State 버킷 변수 확인 📦 ${STATE_BUCKET_EMOJI} | |
| #### 초기화 결과 ⚙️ ${INIT_EMOJI} | |
| #### tfvars 복원 결과 🔐 ${TFVARS_EMOJI} | |
| #### 포맷 검사 결과 🖌 ${FMT_EMOJI} | |
| #### 유효성 검사 결과 🤖 ${VALIDATE_EMOJI} | |
| #### Plan 결과 📖 ${PLAN_EMOJI} | |
| <details><summary>Plan 상세 보기</summary> | |
| \`\`\`terraform | |
| EOF | |
| cat artifact/plan_output_trimmed.txt >> artifact/comment_body.md | |
| cat >> artifact/comment_body.md <<EOF | |
| \`\`\` | |
| </details> | |
| *실행 사용자: @${{ github.actor }}, 이벤트: \`${{ github.event_name }}\`* | |
| EOF | |
| - name: 기존 PR 댓글 조회 | |
| id: existing-comment | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const marker = `Terraform Plan - \`${{ steps.plan-status.outputs.environment }}\` 환경`; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const botComment = comments.find(comment => { | |
| return comment.user.type === 'Bot' && comment.body.includes(marker); | |
| }); | |
| core.setOutput('comment_id', botComment ? String(botComment.id) : ''); | |
| - name: PR 댓글 생성 또는 갱신 | |
| uses: actions/github-script@v7 | |
| env: | |
| COMMENT_ID: ${{ steps.existing-comment.outputs.comment_id }} | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const fs = require('fs'); | |
| const body = fs.readFileSync('artifact/comment_body.md', 'utf8'); | |
| const commentId = process.env.COMMENT_ID; | |
| if (commentId) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: Number(commentId), | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body, | |
| }); | |
| } |