DevOps

[Github Actions] CI - 빌드 실패 시 Pull Request 닫고 코멘트 등록하기

zubetcha 2022. 4. 27. 00:59

 

 

얼마전 회사에서 github actions로 CI를 적용하는 일을 맡게 되었다. 이전에 프로젝트에서 자동 배포용으로 github actions를 사용해 본 적은 있지만, 부끄럽게도 문서 한번 찾아보지 않고 블로그에 나와 있는 소스를 복붙한 수준이었기 때문에 이번 기회에 하나하나 알아보며 필요한 스텝들을 붙여나갔고, 많은 공부가 되어서 정리해보고자 한다!

 

Github Actions?


 

 

Github Actions는 Github에서 제공하는 일종의 툴이다. CI/CD 파이프라인을 구성하기 위한 용도로 많이 알려져 있지만 그 외에도 개발과 관련된 다양한 워크플로우 자동화를 내 입맛에 맞게 커스텀 할 수 있도록 지원해주는 유용한 툴이다.

 

사전 준비사항


 

Github Actions 워크플로우를 작성하기에 앞서 몇 가지 알아두면 좋은 것들이 있다.

 

워크플로우는 반드시 아래와 같은 폴더 구조 안에 yml 파일에 작성되어야 하며, .github 폴더는 루트 디렉토리에 존재해야 한다.

├── .github/
│   ├──workflows/
│       └──CI.yml

 

혹시 레포지토리가 organization에 private의 형태로 있다던가, 스토리북 배포 등의 경우 SSH Key가 필요하다. 아래의 순서대로 SSH Key를 생성한 후 레포지토리에 각각의 Private Key와 Public Key를 등록해준다.

 

먼저 SSH Key를 생성하기 위해 터미널에 아래의 명령어를 입력한다.

ssh-keygen -t rsa -b 4096 -C "[Git 이메일 주소]"

 

위의 명령어를 입력한 후 엔터를 누르면 아래와 같이 SSH Key가 생성된 것을 확인할 수 있다.

 

 

id_rsa가 Private Key이고, id_rsa.pub이 Public Key이다.

 

터미널에서 아래의 명령어를 입력하면 Private Key와 Public Key의 전체 값을 확인할 수 있다.

vi ~/.ssh

 

우선 id_rsa.pub 으로 들어가서 ssh-rsa로 시작하는 Public Key를 복사한 후 레포지토리의 Deploy Key로 등록해준다.

 

 

그 후 다시 Public Key인 id_rsa로 들어가서 전체를 복사한 후 레포지토리의 Actions Secrets에 등록해준다. 이 때 주의할 점은 -----BEGIN OPENSSH PRIVATE KEY----- 부터 -----END OPENSSH PRIVATE KEY----- 까지 모두 포함되어야 한다는 점이다.

 

 

여기까지 하면 Github Actions runner가 등록된 SSH Key로 Private한 레포지토리에도 접근할 수 있게 된다.

 

checkout은 Github Actions runner가 우리의 레포지토리에 접근할 수 있게 해주는 스텝이기 때문에 반드시 필요한 스텝이며, 일반적으로 첫 번째로 들어간다. 

 

Github actions 간단하게 알아보기


 

 

name - (optional) workflow 이름

on - workflow 트리거로, 1) 특정 브랜치에서만, 2) 특정 파일에 변경 사항이 있는 경우에만, 3) push, PR, issue comment 등의 이벤트가 발생했을 때 등 다양하고 상세하게 설정할 수 있다.

jobs - 하나의 workflow를 아우르는 그룹

check-bats-version - jobs의 명시적인 이름으로, 원하는 대로 설정할 수 있다.

runs-on - Github Actions Runnerworkflow 실행할 환경으로, Github가 호스팅한 가상 서버 (상황에 따라 다른 OS로 설정 가능하며, 여러 OS에서 실행하고 싶으면 배열로 설정할 수 있다.)

steps - check-bats-version을 실행할 단계들의 집합으로, 각 단계들은 steps 아래에 있어야 한다.

uses - 마켓플레이스 등에 오픈 소스로 제공되고 있는 action을 사용하는 경우 uses 키워드를 사용한다.

run - 위와 같은 별도의 action을 사용하는 게 아닌 직접 명령어로 실행하고 싶은 경우 run 키워드를 사용한다.

 

CI 워크플로우


name: 'test'

# DESCRIBE: main branch에서 pull request가 발생했을 때 하위의 액션들을 실행한다.
on:
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    
	# DESCRIBE: matrix 키워드를 사용하면 같은 워크플로우 파일 내에서 변수처럼 사용할 수 있다.
    strategy:
      matrix:
        node-version: [16.x] 

    steps:
      # DESCRIBE: 해당 레포지토리로 checkout 하는 step
      - name: Checkout
        uses: actions/checkout@v3

      # DESCRIBE: Private 레포에 접근할 수 있도록 SSH 비밀키를 설정해주는 step
      # github secrets에 SSH PRIVATE KEY를 환경 변수로 등록하여 사용한다.
      - name: Set up SSH Agent
        uses: webfactory/ssh-agent@v0.5.4
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

      # DESCRIBE: node 16버전으로 환경 설정해주는 step
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}

      # DESCRIBE: node modules를 캐시해주는 step
      - name: Cache node modules
        id: node-cache
        uses: actions/cache@v3
        with: 
          path: '**/node_modules'
          key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn-lock') }}
          restore-keys: |
            ${{ runner.os }}-modules
            ${{ runner.os }}-

      # DESCRIBE: 디펜던시를 설치해주는 step
      - name: Install dependencies
        if: steps.node-cache.outputs.cache-hit != 'true'
        run: yarn install
      
      # DESCRIBE: 프로젝트를 빌드해주는 step
      - name: Build
        run: yarn build

      # DESCRIBE: 빌드 실패한 경우에만 실행되는 step
      - name: If build fail
        # 이전 step이 실패한 경우에만 이 step을 실행시키는 syntax
        if: ${{ failure() }}
        uses: actions/github-script@v6
        with:
          github-token: ${{ github.token }}
          script: |
            const pull_number = ${{ github.event.pull_request.number }}
            const updated_title = `[BUILD FAIL] ${{ github.event.pull_request.title }}`
            await github.rest.pulls.createReview({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: pull_number,
              body: '빌드에 실패했습니다.',
              event: 'REQUEST_CHANGES'
            })
            await github.rest.pulls.update({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: pull_number,
              title: updated_title,
              state: 'closed'
            })

 

matrix strategy

matrix는 변수를 이용해 하나의 job 안에서 자동으로 여러 조건의 job을 실행할 수 있게 해주는 키워드이다. 만약 다양한 node 버전이나 OS에서 테스트를 실행하고 싶다면 아래와 같이 배열 안에 해당 조건들을 요소로 넣어주면 된다.

 

 

if

if 키워드를 사용하면 if 키워드가 붙은 job을 실행할 조건을 설정할 수 있다. 상세한 조건을 직접 설정할 수도 있고, 아래와 같이 기본적으로 제공하는 조건들도 있다. 나는 if 조건문 직전의 스텝에 build 를 설정하여 build에 실패하면 if 조건문의 스텝이 실행되도록 설정하였다.

 

if: ${{ success() }} - 직전 step 이 실패하거나 취소되지 않았을 때만 지금의 step을 실행한다.

if: ${{ failure() }} - 직전 step이 실패하면 지금의 step을 실행한다.

if: ${{ always() }} - 항상 지금의 step을 실행하지만 소스를 가져오지 못하는 등의 심각한 문제가 발생한 경우에는 중지한다.

if: ${{ canceled() }} - workflow가 취소됐을 때 지금의 step을 실행한다.

 

이렇게 워크플로우 파일을 작성하여 main 브랜치에 둔 후 임의로 build를 실패하도록 짜서 PR을 보내면,

 

 

자동으로 PR 타이틀에 [BUILD FAIL]이 붙고, github actions bot이 빌드에 실패했다고 코멘트를 달아준 후 PR이 자동으로 닫히게 된다!