본문 바로가기
프로젝트/웹

(CICD) 어디 가서 내가 깃허브 액션 만들었다고 말하지 마라~

by WOOSERK 2022. 12. 15.

들어가기 전에

 

만들만 하다네요

쓸만한 소재가 생겨서 글을 또 작성하러 왔습니다. 

 

오늘 글은 깃허브 액션 마켓플레이스(Github Actions marketplace)에 커스텀 액션을 등록하는 방법입니다.

 

오늘도 봐주셔서 감사합니다.


입점 계기

최근 프로젝트 종료 날짜가 다가오는 와중에 CICD 파일 리팩토링을 좀 크게 했더니 배포에 자잘한 문제들이 많이 생겼습니다.

 

몇몇 개는 오타 수준에 불과하지만 그중 제일 큰 문제는 새로운 태그가 release되면 실서버에 배포가 되어야 하는데 그렇지 않은 것이었습니다.

 

이전까지는 잘 동작하던 기능이라 분명 새로 추가한 코드에 문제가 있을 거라 생각했습니다.

 

지난번에 paths filter 액션을 적용해서 변경된 파일의 경로에 따라 job을 분기하는 기능을 구현했었는데, 해당 기능에서 미처 생각하지 못했던 이슈를 마주하게 되었습니다.

 

name: "[CD] MAIN"
on:
  push:
    tags:
      - "v*"
  workflow_dispatch:
  
  
jobs:
  path-check:
    runs-on: ubuntu-20.04
    outputs:
      client: ${{ steps.filter.outputs.client }}
      server: ${{ steps.filter.outputs.server }}
      scheduler-server: ${{ steps.filter.outputs.scheduler-server }}
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - uses: dorny/paths-filter@v2
        id: filter
        with:
          base: ${{ github.ref }}
          ref: ${{ github.head_ref }}
          filters: |
            client:
              - 'client/**'
            server:
              - 'server/**'
            scheduler-server:
              - 'scheduler-server/**'

위 코드는 client, server, scheduler-server 각각의 경로 내에서 변경 사항이 발생했을 때 지정된 outputs 값을 true로 변경해줍니다.

 

그러면 다음 job에서 outputs의 값에 따라 실행 여부를 결정하면 됩니다.

 

이때, with 구문 안에 baseref가 있는 것을 보실 수 있습니다. 해당 구문은 실제로 몇 번 배포를 진행해보면서 문제가 생겨서 추가한 코드입니다.

어떤 문제냐면, 이 액션은 따로 설정을 하지 않으면 default branch와 변경 사항을 비교합니다. 따라서 저의 경우 매번 main 브랜치와 비교를 해서 실행할 때마다 모든 배포 액션이 동작했습니다.

 

이를 방지하기 위해 base에는 비교 대상이 되는 브랜치를 지정하고, ref는 현재 브랜치를 지정했습니다. 따라서 만약 feature/#390-post 브랜치가 dev 브랜치에 push하면 base는 dev가 되고 ref는 feature/#390-post가 됩니다.

물론 해당 코드는 태그가 push될 때만 실행되기 때문에 발생하지 않는 상황이지만요.

 

여기서 간과했던 문제가 있었습니다. 태그는 main 브랜치에서 push되기 때문에 main 브랜치와 태그의 비교가 일어날 겁니다. 하지만 main 브랜치 상태 그대로 태그를 push하기 때문에 둘은 같은 상태입니다. 따라서 변경사항이 없어 자동 배포가 일어나지 않습니다. 다음처럼요.

성공?

처음 이 문제를 발견하고 어떻게 해결할지 혼자 생각하다가 낸 결론은 '새로운 버전을 릴리즈하면 비교 없이 한 번 쫙 배포하자'였습니다. 어차피 새로운 버전이니 모든 폴더에 변경 사항이 있을 것이라는 1차원적인 생각이었습니다.

 

하지만 스스로 생각해도 근본적인 해결책은 아니어서 팀원들의 의견을 물어봤는데, 한 팀원이 해결책을 제시했습니다. 바로 '이전 태그와 비교하자'.

 

이전 태그와 비교하자

듣고 보니 완전 근본적인 해결책이어서 바로 실행에 옮기기 위해 이전 태그를 가져오는 액션을 적용했습니다.

 

그런데 적용하고 실행해보니 또 하나의 작은 문제가 발생했습니다. 

 

제가 적용한 액션은 이겁니다.

https://github.com/WyriHaximus/github-action-get-previous-tag

 

GitHub - WyriHaximus/github-action-get-previous-tag: Get the previous tag

Get the previous tag. Contribute to WyriHaximus/github-action-get-previous-tag development by creating an account on GitHub.

github.com

 

구글링 했을 때 바로 나오길래 적용해봤는데, 해당 액션은 previous tag가 아니라 latest tag를 가져왔습니다.

 

v0.1.0

v0.2.0

v0.3.0(latest)

따라서 태그 순서가 위와 같을 때 해당 액션을 적용하면 v0.2.0을 가져오고 싶었던 저의 의도와는 다르게 v0.3.0을 가져오게 됩니다.

 

다른 액션을 찾기 위해 직접 깃허브 액션 마켓플레이스에 previous tag 키워드로 검색해봤더니 스타가 달린 액션이 없어서(스타가 곧 성능이므로) 직접 만들어 사용하기로 했습니다.

 

그래서 어떻게 만드냐고

공식 문서를 참고해서 만들어보세요.

https://docs.github.com/en/actions/creating-actions/publishing-actions-in-github-marketplace

 

Publishing actions in GitHub Marketplace - GitHub Docs

You must accept the terms of service to publish actions in GitHub Marketplace. About publishing actions Before you can publish an action, you'll need to create an action in your repository. For more information, see "Creating actions." When you plan to pub

docs.github.com

가 아니라 공식 문서가 친절한 듯 친절하지 않기에 제가 만든 방법을 설명하겠습니다.

 

우선 레포지토리를 만듭니다. 이때, 반드시 public으로 만들어야 하고, README가 있어야 합니다. 

나머지는 알아서 채워 넣으시면 됩니다.

 

레포지토리를 만드셨다면 이제 마켓에 올릴 액션을 만들면 됩니다. 액션을 만드는 방법은 아래 공식 문서에 설명되어 있는데요.

https://docs.github.com/en/actions/creating-actions

 

Creating actions - GitHub Docs

Sharing actions and workflows from your private repository

docs.github.com

 

정리하면 3가지 방법이 있습니다.

  1. 도커 파일로 만들기
  2. 자바스크립트로 만들기
  3. 컴포지트 액션으로 만들기

저는 이중 제일 간단해 보이는 컴포지트 액션으로 만드는 방식을 선택했습니다. 

 

 

컴포지트 액션으로 만들기

이제 레포지토리 루트 경로에 액션을 정의한 yml 파일을 추가해야 합니다.

 

레포지토리에 쉘 스크립트 파일을 함께 넣어서 액션 안에서 실행시키는 방법도 있는데 저는 그냥 하나의 yml 파일로 만들었습니다. 처음 해보는데 어려운 방식을 선택하면 복잡하더라고요.

 

형식은 다음과 같습니다.

name: # 이름(필수. 겹치면 안됩니다!!)
description: # 설명(필수)

branding:
  icon: # 아이콘(필수)
  color: # 아이콘 배경 색깔(필수)

inputs:
  # 입력값
    
outputs:
  # 출력값
    
runs:
  using: "composite"
  steps:
    # 정의할 일련의 작업 목록

주의할 점)

  • yml 파일의 이름은 반드시 action.yml이어야 합니다.
  • 이름은 마켓플레이스에 있는 다른 액션들과 겹치면 안 됩니다.

아이콘과 색상은 모두 사전 정의되어 있으므로 아래에서 참고하여 선언하셔도 되고, 이후 나올 화면에서 직접 보면서 설정하셔도 됩니다.

https://actions-cool.github.io/github-action-branding/

 

GitHub Action Branding

 

actions-cool.github.io

 

위 형식에 맞춰 정의한 뒤 레포지토리 루트 경로에 push하면 다음과 같은 버튼이 보일 겁니다.

 

버튼을 클릭하면 아래와 같은 화면이 나옵니다. 이때 약관 동의를 요구하는데 2차 인증이 걸려 있지 않으면 2차 인증을 등록할 수 있는 QR 코드가 나올 겁니다. 모두 진행해주시면 됩니다.

저는 이전 액션 파일을 복붙 했더니 이름이 겹친다고 하네요.

 

만약 필수 형식을 몇 개 빠트렸어도 괜찮습니다. 편집 버튼을 클릭하고 바로 수정사항을 반영할 수 있습니다.

 

형식을 모두 갖췄다면 깃허브의 인정을 받을 수 있습니다.

 

이제 아래 형식에 맞춰 액션 소개글을 작성해주신 뒤 제출하면 깃허브 액션이 만들어집니다.

 

이제 당신도 Github actions 오너!

 

이제 레포지토리로 가면 아래와 같은 화면이 반겨줍니다.

 

클릭하면 마켓플레이스에서 자신의 액션을 볼 수 있습니다.

이제 여기저기 가져다 쓰면 됩니다.

 

제가 직접 느낀 커스텀 액션의 장점은 다음과 같습니다.

  1. 자신에게 필요한 액션을 직접 정의하여 사용할 수 있다.
  2. 액션에 자기 아이디가 박혀있어 간지난다.
  3. 밖에 나갈 때마다 사람들이 깃허브 액션 오너임을 알아보고 악수 요청을 한다.
  4. 통장에 무수히 찍히는 0(앞자리도 0)

등이 있습니다.

 

너는 어떻게 썼나요?

저의 목적은 이전 태그를 가져오는 것이었습니다. 그래서 기존의 실서버 배포 코드를 다음처럼 수정했습니다.

name: "[CD] MAIN"
on:
  push:
    tags:
      - "v*"
  workflow_dispatch:

jobs:
  path-check:
    runs-on: ubuntu-20.04
    outputs:
      client: ${{ steps.filter.outputs.client }}
      server: ${{ steps.filter.outputs.server }}
      scheduler-server: ${{ steps.filter.outputs.scheduler-server }}
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Get Prev Tag
        id: prev-tag-finder
        uses: WOOSERK/get-prev-tag-action@v0.1.1

  # 다음 jobs...

 

제 커스텀 액션의 must have item이었던 checkout 액션의 fetch-depth 옵션을 정의해줍니다. 해당 옵션을 0으로 놓으면 모든 태그와 브랜치의 정보를 가져옵니다.

 

그 후 제 이름이 박힌 액션을 호출합니다. 이 액션은 다음과 같은 작업을 합니다.

outputs:
  prev-tag:
    description: "Previous Git Tag"
    value: ${{ steps.prev-tag-finder.outputs.prev-tag }}

runs:
  using: "composite"
  steps:
    - id: tag-count
      run: echo tag-count=$(git tag | tail -n 2 | wc -l) >> $GITHUB_OUTPUT
      shell: bash

    - name: prev-tag-finder
      id: prev-tag-finder
      run: |
        if [ ${{steps.tag-count.outputs.tag-count }} -lt 2 ]; then
          echo "You have less than 2 tags. So prev-tag is set to v1.0.0"
          echo prev-tag=v1.0.0 >> $GITHUB_OUTPUT
        else
          echo prev-tag=$(git tag | tail -n 2 | head -n 1) >> $GITHUB_OUTPUT
        fi
      shell: bash

 

우선 레포지토리의 태그 개수를 세고, 개수가 2개 미만이면 v1.0.0으로 세팅해줍니다.

2개 이상이라면 두 번째 태그를 출력 값으로 내보냅니다.

 

끝입니다. 다른 예외 상황들을 처리해줘야 하지만 우선 가장 기본적인 기능만 하도록 구현했습니다.

 

그 후 step은 paths filter에 태그 이름을 넘겨줘서 이전 태그와 비교하게 합니다.

      - name: Get Prev Tag
        id: prev-tag-finder
        uses: WOOSERK/get-prev-tag-action@v0.1.1
        
      - uses: dorny/paths-filter@v2
        id: filter
        with:
          base: ${{ steps.prev-tag-finder.outputs.prev-tag }}
          ref: ${{ github.head_ref }}
          filters: |
            client:
              - 'client/**'
            server:
              - 'server/**'
            scheduler-server:
              - 'scheduler-server/**'

  # 다음 jobs...

 

이제 비교가 끝나면 변경된 파일이 있을 시 각각 배포를 진행합니다.

 

적용 결과

저의 master piece가 태그 비교를 완벽하게 해내고 있는 것을 볼 수 있습니다.

 

이번에도 요약해주세요

네. 깃허브 액션을 직접 만드는 방법을 소개했습니다.

  1. README가 있는 공개 레포지토리를 만든다.
  2. 메타데이터가 포함된 액션 파일 action.yml 을 레포지토리 루트 경로에 생성한다.
  3. 등록 절차를 거친다.
  4. 생성 완료

여러분도 맞춤형 액션을 하나 장만해보세요.