๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ‘จ‍๐Ÿ’ป web.dev/ops

GitHub Actions ๋ฅผ ํ™œ์šฉํ•œ release bot ๋งŒ๋“ค๊ธฐ

by HandHand 2023. 4. 24.

 

์ด๋ฒˆ์— ์‚ฌ๋‚ด์—์„œ ๋ฉ”์ผ ํ…œํ”Œ๋ฆฟ์„ ๊ด€๋ฆฌํ•˜๋Š” ํ”„๋กœ์ ํŠธ์˜ ๋ฐฐํฌ ๋ฐฉ์‹์„ ๋ณ€๊ฒฝํ•˜๋Š” ์—…๋ฌด๋ฅผ ๋งก์•„ ์ง„ํ–‰ํ•˜๋ฉฐ

Github Action (์ดํ•˜ GHA)์„ ๋‹ค๋ค„๋ณผ ๊ธฐํšŒ๊ฐ€ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

GHA๋กœ PR์—์„œ ํŠน์ • comment ๋ฅผ ๋‚จ๊ธฐ๋ฉด, ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ฐฐํฌํ•ด์ฃผ๋Š” ์ž๋™ํ™” ํ”„๋กœ์„ธ์Šค๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ์—ˆ๋Š”๋ฐ

GHA ์‚ฌ์šฉ์ด ์ฒ˜์Œ์ด๋ผ ๊ฝค๋‚˜ ๋งŽ์€ ์‚ฝ์งˆ์„ ํ•ด์„œ ๋น„์Šทํ•œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ์€ ๋ถ„๋“ค์„ ์œ„ํ•ด ๊ธฐ๋ก์„ ๋‚จ๊ธฐ๋ ค๊ณ ํ•ฉ๋‹ˆ๋‹ค.

 

๐Ÿ“Œ PR comment echo ํ…Œ์ŠคํŠธํ•˜๊ธฐ

์šฐ์„  PR์—์„œ ํŠน์ • ๋ช…๋ น์–ด๋ฅผ ๋‹ด์€ ๋Œ“๊ธ€์„ ๋‚จ๊ธฐ๋ฉด, ์ด๋ฅผ ๊ฐ์ง€ํ•˜๋Š” workflow ๋ฅผ ์ž‘์„ฑํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

release ๋ผ๋Š” comment ๋ฅผ ๋‚จ๊ธฐ๋ฉด workflow ๊ฐ€ ํ•ด๋‹น PR ์— ๋Œ“๊ธ€๋กœ ์‘๋‹ตํ•ด์ฃผ๋Š” workflow ๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

comment ์ถ”๊ฐ€๋Š” issue_comment event ๋ฅผ ํ†ตํ•ด ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

comment ์ƒ์„ฑ์€ github ์˜ REST API ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋˜๋Š”๋ฐ, curl ์„ ์ด์šฉํ•˜๋Š” ๋Œ€์‹ 

github-script action์„ ์‚ฌ์šฉํ•˜๋ฉด workflow ์—์„œ JavaScript ๋ฅผ ์ด์šฉํ•œ REST API ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

name: Pull request comment
on:
  issue_comment:
    types: [created, edited]

jobs:
  pull_request_comment:
    runs-on: ubuntu-latest
    if: github.event.issue.pull_request && contains(github.event.comment.body, 'release')
    steps:
      - name: Add comment
        if: contains(github.event.comment.body, 'release') 
        uses: actions/github-script@v6
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '๐Ÿš€ release'
            })

 

์˜๋„๋Œ€๋กœ ์ž˜ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

 

โš ๏ธ ๋งŒ์•ฝ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋‚œ๋‹ค๋ฉด..

 

 

createComment ์—์„œ ๊ถŒํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด workflow ์˜ permission ์„ ํ™•์ธํ•ด๋ณด์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

repository setting > actions > general ์—์„œ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

 

read-write ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•ด์ค๋‹ˆ๋‹ค.

 

๐Ÿ“Œ ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ release bot ์„ ๋งŒ๋“ค์–ด๋ด…์‹œ๋‹ค.

release comment ํ˜•์‹

์œ„์—์„œ ์ž‘์„ฑํ•œ workflow ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋ณธ๊ฒฉ์ ์œผ๋กœ release bot ์„ ๋งŒ๋“ค์–ด๋ด…์‹œ๋‹ค.

์ฃผ์–ด์ง„ ์˜ต์…˜์— ๋”ฐ๋ผ ๋นŒ๋“œ ์กฐ๊ฑด์„ ๋‹ค๋ฅด๊ฒŒ ํ•˜๊ณ  ์‹ถ์—ˆ๋Š”๋ฐ, ๋‹ค์Œ์€ ๊ทธ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

 

// ์˜ˆ์‹œ : release-<๋นŒ๋“œํ™˜๊ฒฝ> <์˜ต์…˜> <ํŒŒ์ผ ํ˜น์€ ๋””๋ ‰ํ† ๋ฆฌ๋ช…>

/release-staging file air-refund
/release-production directory air

 

issue comment trigger ๊ธฐ์ค€์— ์œ ์˜ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ issue comment event ๋Š” repository default branch ๋ฅผ ๊ธฐ์ค€์œผ๋กœ workflow ๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

์ด๋ฒˆ์— ๊ตฌํ˜„ํ•  release bot ์€ ํŠน์ • ์ž‘์—… ๋ธŒ๋žœ์น˜ PR์—์„œ ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ•œ ๋ชจ๋“  ํŒŒ์ผ๋“ค์„ ์กฐํšŒํ•ด์„œ

๋ฐฐํฌ๊ฐ€ ํ•„์š”ํ•œ ํ…œํ”Œ๋ฆฟ๋“ค์„ ์ถ”์ถœํ•˜๋Š” ๊ณผ์ •์ด ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

์ด๋Š” ๊ณง main ๋ธŒ๋žœ์น˜ HEAD ์™€ ํ˜„์žฌ ์ž‘์—…์ค‘์ธ PR ๋ธŒ๋žœ์น˜ HEAD ์‚ฌ์ด์˜ ๋ชจ๋“  ๋ณ€๊ฒฝํŒŒ์ผ ์กฐํšŒ๊ฐ€ ํ•„์š”ํ•œ ๊ฒƒ์ธ๋ฐ,

์ด ๋•Œ๋ฌธ์— comment ๋ฅผ ๋‚จ๊ธด ํƒ€๊ฒŸ ๋ธŒ๋žœ์น˜๋กœ checkout ํ•ด์ค˜์•ผํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ๋จผ์ € issue number ๋กœ PR ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ณ , HEAD SHA ๋ฅผ ์ด์šฉํ•ด์„œ checkout ํ•ฉ๋‹ˆ๋‹ค.

 

name: release comment
on:
  issue_comment:
    types: [created, edited]

jobs:
  release-comment:
    if: github.event.issue.pull_request && contains(github.event.comment.body, 'release-')
    runs-on: ubuntu-latest

    steps:
      - name: fetch branch
        id: fetch-branch
        uses: actions/github-script@v6
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: |
            try {
              const pr = await github.rest.pulls.get({
                owner: context.repo.owner,
                repo: context.repo.repo,
                pull_number: context.issue.number
              })

              return pr.data
            } catch (err) {
              core.setFailed(`โŒ Request failed with error ${err}`)
            }

      - name: checkout to branch
        uses: actions/checkout@v3
        with:
          ref: ${{ fromJSON(steps.fetch-branch.outputs.result).head.sha }}
          fetch-depth: 0

 

๋ช…๋ น์–ด ํŒŒ์‹ฑํ•ด์„œ ์ „๋‹ฌํ•˜๊ธฐ

์ด์ œ shell script๋กœ command line์„ ํŒŒ์‹ฑํ•˜๊ณ  ๊ทธ ๊ฒฐ๊ณผ๋ฅผ workflow ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

 

jobs:
  release-comment:
        // ...

        - name: Define release options
        run: |
          comment="${{ github.event.comment.body }}"
          args=($comment)
          PHASE=$(echo "${args[0]}" | cut -d"-" -f 2)
          TYPE=${args[1]}
          SLUG=${args[2]}
          echo "PHASE=$PHASE" >> $GITHUB_ENV
          echo "TYPE=$TYPE" >> $GITHUB_ENV
          echo "SLUG=$SLUG" >> $GITHUB_ENV

 

์ดํ›„ ๋ฐฐํฌ ์˜ต์…˜์— ๋”ฐ๋ฅธ ๋นŒ๋“œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก step ์„ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

๋นŒ๋“œ ๊ฒฐ๊ณผ๋กœ ๋ฐœ์ƒํ•œ ๋กœ๊ทธ๋“ค์„ output text file ๋กœ ํ•จ๊ป˜ ์ถœ๋ ฅํ•ด์ฃผ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

 

- name: Release single template
  if: env.TYPE == 'file'
  run: |
    npm run release:${{ env.PHASE }} -- -t FILE -s ${{ env.SLUG }} | tee output.txt

// ... other options

 

comment ์ƒ์„ฑํ•˜๊ธฐ

์ด์ œ ๋กœ๊ทธ ํŒŒ์ผ์„ ํ•œ์ค„์”ฉ ์ฝ์–ด์„œ ํ…œํ”Œ๋ฆฟ ๋ฐฐํฌ ๊ด€๋ จ ๋กœ๊ทธ๋“ค๋งŒ ์ถ”์ถœํ•ด์„œ , ๋กœ ๊ตฌ๋ถ„๋œ ๋ฌธ์ž์—ด์„ ๋งŒ๋“ค์–ด์ฃผ๊ณ ,

์ดํ›„ step ์—์„œ ์ด ๊ฒฐ๊ณผ๊ฐ’์„ ์ด์šฉํ•ด์„œ ํ…Œ์ด๋ธ” ํ˜•์‹์˜ report comment ๋ฅผ ์ƒ์„ฑํ•ด์ค๋‹ˆ๋‹ค.

 

- name: Read report
  id: read-report
  run: |
    messages=()
    // ... "output.txt" ๋กœ๋ถ€ํ„ฐ ํŒŒ์ผ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด messages ์— ๋ฐฐ์—ด๋กœ ํ• ๋‹นํ•˜๋Š” ์‰˜ ์Šคํฌ๋ฆฝํŠธ

    REPORT="$(printf "%s," "${messages[@]}")"
    echo "REPORT="$REPORT"" >> $GITHUB_OUTPUT

- name: Notify release success
  if: success()
  uses: actions/github-script@v6
    with:
    script: |
            try {
            function createReport() {
              const reports = '${{ steps.read-report.outputs.REPORT }}'.split(',')
              // ... reports ๋กœ comment body ๋ฅผ ํ˜•์‹์— ๋งž๊ฒŒ ์ƒ์„ฑํ•ด์„œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
            }

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: createReport()
            })
          } catch (err) {
            core.setFailed(`โŒ Request failed with error ${err}`)
          }

 

์ž˜ ๋™์ž‘ํ•˜๋„ค์š”!

 

HEAD ๋น„๊ตํ•ด์„œ ๋ณ€๊ฒฝ๋œ ํŒŒ์ผ ์กฐํšŒํ•˜๊ธฐ

์•ž์„œ PR ์—์„œ ๋ณ€๊ฒฝ๋œ ํ…œํ”Œ๋ฆฟ๋งŒ ์ถ”์ถœํ•ด์„œ ๋ฐฐํฌํ•˜๋Š” ์˜ต์…˜๋„ ์ถ”๊ฐ€ํ–ˆ๋‹ค๊ณ  ํ–ˆ๋Š”๋ฐ, ์ด๋ฅผ ์œ„ํ•ด์„œ

์‹ค์ œ ์„œ๋น„์Šค ์ฝ”๋“œ์—์„œ๋Š” changed-files ๋ฅผ ์ด์šฉํ•ด์„œ ๋ณ€๊ฒฝ๋œ ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ ๋ชฉ๋ก์„ ์กฐํšŒํ•˜๊ณ 

ํ•ด๋‹น ํ…œํ”Œ๋ฆฟ ์ฝ”๋“œ๋ฅผ ํŒŒ์‹ฑํ•ด ์˜์กด์„ฑ์„ ํŒŒ์•…ํ•œ ๋’ค ์‹ค์ œ ๋ฐฐํฌ ๋Œ€์ƒ ํ…œํ”Œ๋ฆฟ์„ ํŒŒ์•…ํ•˜๋Š” ๋ชจ๋“ˆ์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ“Œ ์ฐธ๊ณ ์ž๋ฃŒ

์›Œํฌํ”Œ๋กœ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ์ด๋ฒคํŠธ - GitHub Docs

https://github.com/actions/checkout/issues/331

๋ฐ˜์‘ํ˜•

๐Ÿ’ฌ ๋Œ“๊ธ€