From 50b3367aad46517c13b934184aaf6327bb503c5e Mon Sep 17 00:00:00 2001 From: imyourhopeee <144752063+imyourhopeee@users.noreply.github.com> Date: Tue, 15 Jul 2025 16:43:48 +0900 Subject: [PATCH] add CI/CD --- .github/workflows/cd.yml | 130 +++++++++++++++++++++++ .github/workflows/ci.yml | 223 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 353 insertions(+) create mode 100644 .github/workflows/cd.yml create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..0e06147 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,130 @@ +name: Terraform Apply + +on: + push: + branches: [main] # main 브랜치에 push될 때 실행 + +permissions: + contents: read # 코드 리포지토리 읽기 권한 + id-token: write # OIDC 인증을 위한 ID 토큰 발급 권한 + +jobs: + detect-changes: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set.outputs.matrix }} # 다음 job에 전달할 matrix 출력 + steps: + - name: Checkout repository + uses: actions/checkout@v3 # 현재 리포지토리 코드 체크아웃 + + - name: Filter Paths + id: filter + uses: dorny/paths-filter@v3 # 어떤 디렉토리에 변경이 있는지 필터링 + with: + filters: | + operation: + - 'operation-team-account/**' + identity: + - 'identity-team-account/**' + prod: + - 'prod-team-account/**' + dev: + - 'dev-team-account/**' + security: + - 'security-team-account/**' + stage: + - 'stage-team-account/**' + management: + - 'management-team-account/**' + + - name: Build Matrix from Filter (with subdirs) + id: set + env: + # 필터링된 결과를 환경변수로 받아옴 + FILTER_OUTPUTS_operation: ${{ steps.filter.outputs.operation }} + FILTER_OUTPUTS_identity: ${{ steps.filter.outputs.identity }} + FILTER_OUTPUTS_prod: ${{ steps.filter.outputs.prod }} + FILTER_OUTPUTS_dev: ${{ steps.filter.outputs.dev }} + FILTER_OUTPUTS_security: ${{ steps.filter.outputs.security }} + FILTER_OUTPUTS_stage: ${{ steps.filter.outputs.stage }} + FILTER_OUTPUTS_management: ${{ steps.filter.outputs.management }} + run: | + # 계정 별 IAM Role Key 매핑 + declare -A ROLE_MAP=( + ["operation"]="ROLE_ARN_OPERATION" + ["identity"]="ROLE_ARN_IDENTITY" + ["prod"]="ROLE_ARN_PROD" + ["dev"]="ROLE_ARN_DEV" + ["security"]="ROLE_ARN_SECURITY" + ["stage"]="ROLE_ARN_STAGE" + ["management"]="ROLE_ARN_MANAGEMENT" + ) + + MATRIX_ITEMS=() + + # 변경된 경로에 따라 matrix 구성 + for KEY in "${!ROLE_MAP[@]}"; do + VAR_NAME="FILTER_OUTPUTS_${KEY}" + VALUE="${!VAR_NAME}" + + if [ "$VALUE" = "true" ]; then + BASE_DIR="${KEY}-team-account" + + # 루트 디렉터리 검사 + TF_COUNT_ROOT=$(find "$BASE_DIR" -maxdepth 1 -name '*.tf' | wc -l) + if [ "$TF_COUNT_ROOT" -gt 0 ]; then + MATRIX_ITEMS+=("{\"dir\":\"$BASE_DIR\",\"role_key\":\"${ROLE_MAP[$KEY]}\"}") + fi + + # 하위 디렉터리 검사 + for DIR in $(find $BASE_DIR -type d -mindepth 1); do + if [[ "$DIR" != *".terraform"* && "$DIR" != "$BASE_DIR/modules" ]]; then + TF_COUNT=$(find "$DIR" -maxdepth 1 -name '*.tf' | wc -l) + if [ "$TF_COUNT" -gt 0 ]; then + MATRIX_ITEMS+=("{\"dir\":\"$DIR\",\"role_key\":\"${ROLE_MAP[$KEY]}\"}") + fi + fi + done + fi + done + + # 최종 matrix JSON 출력 + if [ ${#MATRIX_ITEMS[@]} -eq 0 ]; then + echo "matrix=[]" >> $GITHUB_OUTPUT + else + JSON="[$(IFS=,; echo "${MATRIX_ITEMS[*]}")]" + echo "matrix=$JSON" >> $GITHUB_OUTPUT + fi + + terraform-apply: + needs: detect-changes # detect-changes job 이후 실행 + if: ${{ needs.detect-changes.outputs.matrix != '[]' }} # 변경사항이 있을 경우에만 실행 + runs-on: ubuntu-latest + + strategy: + matrix: # matrix 기반 반복 실행 + include: ${{ fromJson(needs.detect-changes.outputs.matrix) }} + fail-fast: false # 하나 실패해도 나머지 job은 계속 진행 + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ap-northeast-2 + role-to-assume: ${{ secrets[matrix.role_key] }} # OIDC 기반으로 계정별 IAM Role Assume + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v1 + with: + terraform_version: 1.4.0 # Terraform 버전 명시 + + - name: Terraform Init + run: terraform init # Terraform 초기화: 백엔드 설정 및 provider 다운로드 + working-directory: ${{ matrix.dir }} # matrix로 전달된 디렉토리에서 실행 + + - name: Terraform Apply + run: terraform apply -auto-approve # 사용자 승인 없이 자동 적용 + working-directory: ${{ matrix.dir }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f0bf1a9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,223 @@ +name: DEV CI + +# main 브랜치로 PR이 열릴 때 특정 디렉토리 내 파일 변경이 있는 경우에만 실행 +on: + pull_request: + branches: [main] + paths: + - "operation-team-account/**" + - "identity-team-account/**" + - "prod-team-account/**" + - "dev-team-account/**" + - "security-team-account/**" + - "stage-team-account/**" + - "management-team-account/**" + +# 워크플로우 실행 시 필요한 권한 설정 +permissions: + contents: read # 저장소 콘텐츠 읽기 + pull-requests: write # PR에 댓글 작성 + id-token: write # AWS OIDC 인증에 필요 + +jobs: + # 변경된 디렉토리를 탐지하고 matrix를 구성하는 Job + detect-changes: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set.outputs.matrix }} # 다음 job에서 사용할 output matrix + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 # 전체 git history 가져오기 + + - name: Fetch origin/main + run: git fetch origin main # main 브랜치 가져오기 + + - name: Detect Changed Directories & Build Matrix + id: set + run: | + FILES=$(git diff --name-only origin/main...${{ github.sha }}) + echo "Changed files:" + echo "$FILES" + # 디렉토리명과 AWS Role Key 매핑 + declare -A ROLE_MAP=( + ["operation-team-account"]="ROLE_ARN_OPERATION" + ["identity-team-account"]="ROLE_ARN_IDENTITY" + ["prod-team-account"]="ROLE_ARN_PROD" + ["dev-team-account"]="ROLE_ARN_DEV" + ["security-team-account"]="ROLE_ARN_SECURITY" + ["stage-team-account"]="ROLE_ARN_STAGE" + ["management-team-account"]="ROLE_ARN_MANAGEMENT" + ) + + TMP_FILE=$(mktemp) + # 변경된 파일들을 돌면서 각 디렉토리의 .tf 파일이 있는 경우만 matrix 구성 + for FILE in $FILES; do + DIR=$(dirname "$FILE") + TOP_DIR=$(echo $DIR | cut -d/ -f1) + ROLE_KEY="${ROLE_MAP[$TOP_DIR]}" + + if [ -n "$ROLE_KEY" ]; then + if [ "$DIR" == "$TOP_DIR" ]; then + TF_COUNT=$(find "$DIR" -maxdepth 1 -name '*.tf' | wc -l) + if [ "$TF_COUNT" -gt 0 ]; then + echo "$DIR|$ROLE_KEY" >> $TMP_FILE + fi + else + TF_COUNT=$(find "$DIR" -maxdepth 1 -name '*.tf' | wc -l) + if [ "$TF_COUNT" -gt 0 ]; then + echo "$DIR|$ROLE_KEY" >> $TMP_FILE + fi + fi + fi + done + + # 중복 제거 및 JSON 형태의 matrix 생성 + UNIQUE_LINES=$(sort $TMP_FILE | uniq) + MATRIX_JSON="[" + FIRST=1 + + while IFS= read -r LINE; do + DIR=$(echo $LINE | cut -d"|" -f1) + ROLE_KEY=$(echo $LINE | cut -d"|" -f2) + + if [ $FIRST -eq 1 ]; then + FIRST=0 + else + MATRIX_JSON="$MATRIX_JSON," + fi + + MATRIX_JSON="$MATRIX_JSON{\"dir\":\"$DIR\",\"role_key\":\"$ROLE_KEY\"}" + done <<< "$UNIQUE_LINES" + + MATRIX_JSON="$MATRIX_JSON]" + + echo "Final JSON matrix:" + echo "$MATRIX_JSON" + + echo "matrix=$MATRIX_JSON" >> $GITHUB_OUTPUT + + # 실제 Terraform 관련 CI 작업을 수행하는 Job + terraform-ci: + needs: detect-changes + if: ${{ needs.detect-changes.outputs.matrix != '[]' }} + runs-on: ubuntu-latest + + strategy: + matrix: + include: ${{ fromJson(needs.detect-changes.outputs.matrix) }} + fail-fast: false + + env: + INFRACOST_API_KEY: ${{ secrets.INFRACOST_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + INFRACOST_TERRAFORM_CLI_WRAPPER: false + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ap-northeast-2 + role-to-assume: ${{ secrets[matrix.role_key] }} + + - name: Install Terraform + run: | + curl -LO https://releases.hashicorp.com/terraform/1.4.0/terraform_1.4.0_linux_amd64.zip + unzip terraform_1.4.0_linux_amd64.zip + sudo mv terraform /usr/local/bin/ + + - name: Install tfsec # 보안 분석 도구 설치 + run: | + curl -sSL https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash + + - name: Run tfsec (fail on HIGH+) + run: tfsec --minimum-severity HIGH --no-color ${{ matrix.dir }} + + - name: Terraform Init + run: terraform init + working-directory: ${{ matrix.dir }} + + - name: Terraform Format Check + run: terraform fmt -check -recursive + working-directory: ${{ matrix.dir }} + + - name: Terraform Validate + run: terraform validate + working-directory: ${{ matrix.dir }} + + - name: Terraform Plan + id: plan + run: | + START_TIME=$(date -u +"%Y-%m-%d %H:%M:%S UTC") + echo "START_TIME=$START_TIME" >> $GITHUB_ENV + + PLAN_FILE=tfplan.binary + PLAN_TXT=plan.txt + PLAN_JSON=plan.json + + # Plan 파일 생성 및 출력 저장 + terraform plan -no-color -out=$PLAN_FILE > /dev/null 2> plan_error.txt || echo "PLAN_FAILED=true" >> $GITHUB_ENV + terraform show -no-color $PLAN_FILE > $PLAN_TXT 2>/dev/null || echo "Plan failed" > $PLAN_TXT + + # ANSI 색상 코드 제거 + cat $PLAN_TXT | \ + sed 's/`/\\`/g' | \ + tr -d '\r' | \ + sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" \ + > cleaned_plan.txt + + PLAN_CONTENT=$(cat cleaned_plan.txt) + + # infracost 분석에 사용하기 위한 PLAN_JSON 저 + terraform show -json $PLAN_FILE > $PLAN_JSON || true + + # PR comment + { + echo "PLAN_CONTENT<> $GITHUB_OUTPUT + working-directory: ${{ matrix.dir }} + + - name: Comment Terraform Plan on PR + if: github.event.pull_request.number != '' + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + ## [Terraform Plan Summary] + | 항목 | 값 | + |-----------------|-----| + | **Status** | `${{ steps.plan.outcome }}` | + | **Directory** | `${{ matrix.dir }}` | + | **Executed At** | `${{ env.START_TIME }}` | + + --- + + ### Plan Output + ```hcl + ${{ steps.plan.outputs.PLAN_CONTENT }} + ``` + + - name: Setup Infracost + uses: infracost/actions/setup@v2 + + - name: Infracost Breakdown + run: | + infracost breakdown \ + --path=plan.json \ + --format=json \ + --out-file=infracost.json + working-directory: ${{ matrix.dir }} + + - name: Infracost Comment on Pull Request + uses: infracost/actions/comment@v1 + with: + path: ${{ matrix.dir }}/infracost.json + behavior: update