diff --git a/.github/workflows/auto-assign-merge.yml b/.github/workflows/auto-assign-merge.yml index d6591f7c..443e16cf 100644 --- a/.github/workflows/auto-assign-merge.yml +++ b/.github/workflows/auto-assign-merge.yml @@ -1,193 +1,193 @@ name: Auto Assign, Review, and Merge on: - pull_request: - types: - - opened - - labeled - - unlabeled - - review_requested - - review_request_removed - pull_request_review: - types: - - submitted + pull_request: + types: + - opened + - labeled + - unlabeled + - review_requested + - review_request_removed + pull_request_review: + types: + - submitted jobs: - auto-assign: - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Set up Node.js - uses: actions/setup-node@v2 - with: - node-version: "16" - - - name: Assign PR creator as Assignee - uses: actions/github-script@v6 - with: - script: | - const prNumber = context.payload.pull_request.number; - const currentAssignees = context.payload.pull_request.assignees.map(a => a.login); - - // PR 작성자가 이미 담당자로 지정되어 있지 않은 경우에만 할당 - if (!currentAssignees.includes(context.actor)) { - await github.rest.issues.addAssignees({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - assignees: [context.actor] - }); - } - - auto-reviewers: - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - needs: auto-assign - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Set up Node.js - uses: actions/setup-node@v2 - with: - node-version: "16" - - - name: Add reviewers based on labels - uses: actions/github-script@v6 - with: - script: | - const prNumber = context.payload.pull_request.number; - const prAuthor = context.payload.pull_request.user.login; - const assignees = context.payload.pull_request.assignees.map(a => a.login); - - // 현재 리뷰어 목록 가져오기 - const currentReviewers = context.payload.pull_request.requested_reviewers - ? context.payload.pull_request.requested_reviewers.map(r => r.login) - : []; - - const BE_reviewers = ['summersummerwhy', 'ezcolin2']; - const FE_reviewers = ['baegyeong', 'pkh0106']; - const doc_reviewers = ['summersummerwhy', 'ezcolin2', 'baegyeong', 'pkh0106']; - - // Function to filter out assignees, PR author, and current reviewers - const filterReviewers = (reviewers) => { - return reviewers.filter(r => - !assignees.includes(r) && - r !== prAuthor && - !currentReviewers.includes(r) - ); - }; - - // Check the labels on the PR and assign appropriate reviewers - const labels = context.payload.pull_request.labels.map(label => label.name); - let reviewersToAdd = []; - - if (labels.includes('🐧🚀😶‍🌫️ BE')) { - reviewersToAdd.push(...filterReviewers(BE_reviewers)); - } - if (labels.includes('🐳🐣 FE')) { - reviewersToAdd.push(...filterReviewers(FE_reviewers)); - } - if (labels.includes('📚 Documentation')) { - reviewersToAdd.push(...filterReviewers(doc_reviewers)); - } - - // Remove duplicates and limit the number of reviewers - reviewersToAdd = [...new Set(reviewersToAdd)]; - const maxReviewers = 4; // 최대 리뷰어 수 제한 - reviewersToAdd = reviewersToAdd.slice(0, maxReviewers); - - // Request reviewers only if there are new reviewers to add - if (reviewersToAdd.length > 0) { - try { - await github.rest.pulls.requestReviewers({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber, - reviewers: reviewersToAdd - }); - console.log(`Added reviewers: ${reviewersToAdd.join(', ')}`); - } catch (error) { - console.error('Failed to add reviewers:', error); - // 실패해도 워크플로우는 계속 진행 - } - } else { - console.log('No new reviewers to add'); - } - - auto-merge: - if: github.event_name == 'pull_request_review' && github.event.review.state == 'approved' - runs-on: ubuntu-latest - permissions: - pull-requests: write - contents: write - steps: - - name: Merge and Close PR if Approved - uses: actions/github-script@v6 - with: - script: | - const prNumber = context.payload.pull_request.number || context.payload.review.pull_request_number; - - try { - // PR 정보 가져오기 - const pr = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber - }); - - if (pr.data.merged) { - console.log('PR이 이미 머지되었습니다.'); - return; - } - - // 머지 가능 상태 확인 - if (!pr.data.mergeable) { - console.log('PR에 충돌이 있습니다. 수동 확인이 필요합니다.'); - core.setFailed('PR에 충돌이 있어 자동 머지를 진행할 수 없습니다.'); - return; - } - - // 모든 리뷰 확인 - const reviews = await github.rest.pulls.listReviews({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber - }); - - // 각 리뷰어의 최신 리뷰 상태만 확인 - const latestReviews = new Map(); - reviews.data.forEach(review => { - latestReviews.set(review.user.login, review.state); - }); - - const hasRejection = Array.from(latestReviews.values()).includes('CHANGES_REQUESTED'); - const approvalCount = Array.from(latestReviews.values()).filter(state => state === 'APPROVED').length; - - if (!hasRejection && approvalCount >= 1) { - console.log('PR 머지를 시도합니다...'); - await github.rest.pulls.merge({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber - }); - - // 브랜치 삭제 - const branchName = pr.data.head.ref; - if (branchName !== 'main' && branchName !== 'master') { - await github.rest.git.deleteRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `heads/${branchName}` - }); - } - } else { - console.log('머지 조건이 충족되지 않았습니다.'); - } - } catch (error) { - console.error('Error:', error); - core.setFailed(error.message); - } + auto-assign: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: "16" + + - name: Assign PR creator as Assignee + uses: actions/github-script@v6 + with: + script: | + const prNumber = context.payload.pull_request.number; + const currentAssignees = context.payload.pull_request.assignees.map(a => a.login); + + // PR 작성자가 이미 담당자로 지정되어 있지 않은 경우에만 할당 + if (!currentAssignees.includes(context.actor)) { + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + assignees: [context.actor] + }); + } + + auto-reviewers: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + needs: auto-assign + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: "16" + + - name: Add reviewers based on labels + uses: actions/github-script@v6 + with: + script: | + const prNumber = context.payload.pull_request.number; + const prAuthor = context.payload.pull_request.user.login; + const assignees = context.payload.pull_request.assignees.map(a => a.login); + + // 현재 리뷰어 목록 가져오기 + const currentReviewers = context.payload.pull_request.requested_reviewers + ? context.payload.pull_request.requested_reviewers.map(r => r.login) + : []; + + const BE_reviewers = ['growth123', 'ezcolin2']; + const FE_reviewers = ['baegyeong', 'pkh0106']; + const doc_reviewers = ['growth123', 'ezcolin2', 'baegyeong', 'pkh0106']; + + // Function to filter out assignees, PR author, and current reviewers + const filterReviewers = (reviewers) => { + return reviewers.filter(r => + !assignees.includes(r) && + r !== prAuthor && + !currentReviewers.includes(r) + ); + }; + + // Check the labels on the PR and assign appropriate reviewers + const labels = context.payload.pull_request.labels.map(label => label.name); + let reviewersToAdd = []; + + if (labels.includes('🐧🚀😶‍🌫️ BE')) { + reviewersToAdd.push(...filterReviewers(BE_reviewers)); + } + if (labels.includes('🐳🐣 FE')) { + reviewersToAdd.push(...filterReviewers(FE_reviewers)); + } + if (labels.includes('📚 Documentation')) { + reviewersToAdd.push(...filterReviewers(doc_reviewers)); + } + + // Remove duplicates and limit the number of reviewers + reviewersToAdd = [...new Set(reviewersToAdd)]; + const maxReviewers = 4; // 최대 리뷰어 수 제한 + reviewersToAdd = reviewersToAdd.slice(0, maxReviewers); + + // Request reviewers only if there are new reviewers to add + if (reviewersToAdd.length > 0) { + try { + await github.rest.pulls.requestReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + reviewers: reviewersToAdd + }); + console.log(`Added reviewers: ${reviewersToAdd.join(', ')}`); + } catch (error) { + console.error('Failed to add reviewers:', error); + // 실패해도 워크플로우는 계속 진행 + } + } else { + console.log('No new reviewers to add'); + } + + auto-merge: + if: github.event_name == 'pull_request_review' && github.event.review.state == 'approved' + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: write + steps: + - name: Merge and Close PR if Approved + uses: actions/github-script@v6 + with: + script: | + const prNumber = context.payload.pull_request.number || context.payload.review.pull_request_number; + + try { + // PR 정보 가져오기 + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber + }); + + if (pr.data.merged) { + console.log('PR이 이미 머지되었습니다.'); + return; + } + + // 머지 가능 상태 확인 + if (!pr.data.mergeable) { + console.log('PR에 충돌이 있습니다. 수동 확인이 필요합니다.'); + core.setFailed('PR에 충돌이 있어 자동 머지를 진행할 수 없습니다.'); + return; + } + + // 모든 리뷰 확인 + const reviews = await github.rest.pulls.listReviews({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber + }); + + // 각 리뷰어의 최신 리뷰 상태만 확인 + const latestReviews = new Map(); + reviews.data.forEach(review => { + latestReviews.set(review.user.login, review.state); + }); + + const hasRejection = Array.from(latestReviews.values()).includes('CHANGES_REQUESTED'); + const approvalCount = Array.from(latestReviews.values()).filter(state => state === 'APPROVED').length; + + if (!hasRejection && approvalCount >= 1) { + console.log('PR 머지를 시도합니다...'); + await github.rest.pulls.merge({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber + }); + + // 브랜치 삭제 + const branchName = pr.data.head.ref; + if (branchName !== 'main' && branchName !== 'master') { + await github.rest.git.deleteRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `heads/${branchName}` + }); + } + } else { + console.log('머지 조건이 충족되지 않았습니다.'); + } + } catch (error) { + console.error('Error:', error); + core.setFailed(error.message); + } diff --git a/.github/workflows/cd-pipeline.yml b/.github/workflows/cd-pipeline.yml index e3fde18a..1610ca17 100644 --- a/.github/workflows/cd-pipeline.yml +++ b/.github/workflows/cd-pipeline.yml @@ -34,35 +34,35 @@ jobs: # 그리고 layer 수가 너무 많으면 node:20-alpine 버전 사용 (50개 이상) - name: package docker image build and push run: | - STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://hub.docker.com/v2/repositories/summersummerwhy/octodocs-modules) + STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://hub.docker.com/v2/repositories/growth123/octodocs-modules) if [ "$STATUS" -eq 404 ]; then echo "octodocs-modules not found" - docker build -f ./services/module/Dockerfile.init -t summersummerwhy/octodocs-modules . + docker build -f ./services/module/Dockerfile.init -t growth123/octodocs-modules . else echo "octodocs-modules found" - docker build -f ./services/module/Dockerfile -t summersummerwhy/octodocs-modules . - LAYERS=$(docker inspect --format '{{len .RootFS.Layers}}' summersummerwhy/octodocs-modules) + docker build -f ./services/module/Dockerfile -t growth123/octodocs-modules . + LAYERS=$(docker inspect --format '{{len .RootFS.Layers}}' growth123/octodocs-modules) if [ $LAYERS -gt 50 ]; then echo "too many layers" - docker build -f ./services/module/Dockerfile.init -t summersummerwhy/octodocs-modules . + docker build -f ./services/module/Dockerfile.init -t growth123/octodocs-modules . fi fi # Docker 이미지 빌드 - name: docker image build run: | - docker build -f ./services/backend/Dockerfile.prod -t summersummerwhy/octodocs-backend . & - docker build -f ./services/nginx/Dockerfile.prod -t summersummerwhy/octodocs-nginx . & - docker build -f ./services/websocket/Dockerfile.prod -t summersummerwhy/octodocs-websocket . & + docker build -f ./services/backend/Dockerfile.prod -t growth123/octodocs-backend . & + docker build -f ./services/nginx/Dockerfile.prod -t growth123/octodocs-nginx . & + docker build -f ./services/websocket/Dockerfile.prod -t growth123/octodocs-websocket . & wait # Docker 이미지 푸시 - name: docker image push run: | - docker push summersummerwhy/octodocs-modules & - docker push summersummerwhy/octodocs-backend & - docker push summersummerwhy/octodocs-nginx & - docker push summersummerwhy/octodocs-websocket & + docker push growth123/octodocs-modules & + docker push growth123/octodocs-backend & + docker push growth123/octodocs-nginx & + docker push growth123/octodocs-websocket & wait deploy: @@ -85,7 +85,7 @@ jobs: ssh -o StrictHostKeyChecking=no $REMOTE_USER@$REMOTE_HOST << 'EOF' cd /root/octodocs - docker pull summersummerwhy/octodocs-modules + docker pull growth123/octodocs-modules docker-compose -f compose.prod.yml pull - name: deploy env: diff --git a/compose.prod.override.yml b/compose.prod.override.yml index 6ac4ab23..e26361e6 100644 --- a/compose.prod.override.yml +++ b/compose.prod.override.yml @@ -5,16 +5,16 @@ services: build: context: . dockerfile: ./services/nginx/Dockerfile.prod - image: summersummerwhy/octodocs-nginx + image: growth123/octodocs-nginx backend: build: context: . dockerfile: ./services/backend/Dockerfile.prod - image: summersummerwhy/octodocs-backend + image: growth123/octodocs-backend websocket: build: context: . dockerfile: ./services/websocket/Dockerfile.prod - image: summersummerwhy/octodocs-websocket + image: growth123/octodocs-websocket diff --git a/compose.prod.yml b/compose.prod.yml index 1bd8ca41..e2ce3917 100644 --- a/compose.prod.yml +++ b/compose.prod.yml @@ -2,7 +2,7 @@ version: "3.8" services: nginx: - image: summersummerwhy/octodocs-nginx:latest + image: growth123/octodocs-nginx:latest env_file: - .env.server restart: always @@ -21,7 +21,7 @@ services: condition: service_healthy backend: - image: summersummerwhy/octodocs-backend:latest + image: growth123/octodocs-backend:latest env_file: - .env.server expose: @@ -49,7 +49,7 @@ services: restart: always websocket: - image: summersummerwhy/octodocs-websocket:latest + image: growth123/octodocs-websocket:latest env_file: - .env.server expose: diff --git a/services/backend/Dockerfile.prod b/services/backend/Dockerfile.prod index f9485016..82f6438f 100644 --- a/services/backend/Dockerfile.prod +++ b/services/backend/Dockerfile.prod @@ -1,4 +1,4 @@ -FROM summersummerwhy/octodocs-modules:latest +FROM growth123/octodocs-modules:latest # 소스 코드 복사 COPY . . diff --git a/services/module/Dockerfile b/services/module/Dockerfile index 3771ba0c..f031259b 100644 --- a/services/module/Dockerfile +++ b/services/module/Dockerfile @@ -14,7 +14,7 @@ COPY apps/websocket/package.json ./apps/websocket/ # node_modules를 가지고 있는 이미지 # 이 이미지를 기반으로 각 workspace 별 이미지를 만들면 # yarn install 레이어를 공유하게 된다. -FROM summersummerwhy/octodocs-modules:latest +FROM growth123/octodocs-modules:latest # 호이스팅을 위해 COPY --from=builder /app/apps /app/apps diff --git a/services/nginx/Dockerfile.prod b/services/nginx/Dockerfile.prod index 8cec117b..97c629cb 100644 --- a/services/nginx/Dockerfile.prod +++ b/services/nginx/Dockerfile.prod @@ -1,5 +1,5 @@ # 빌드 스테이지 -FROM summersummerwhy/octodocs-modules:latest as builder +FROM growth123/octodocs-modules:latest as builder # 소스 코드 복사 COPY . . diff --git a/services/websocket/Dockerfile.prod b/services/websocket/Dockerfile.prod index 0712f887..a3192aaf 100644 --- a/services/websocket/Dockerfile.prod +++ b/services/websocket/Dockerfile.prod @@ -1,4 +1,4 @@ -FROM summersummerwhy/octodocs-modules:latest +FROM growth123/octodocs-modules:latest # 소스 코드 복사 COPY . .