TOC
들어가며
작업 배경
저는 깃애니몰즈라는 사이드 프로젝트에 프런트엔드 개발자로 참여하고 있습니다. 개발 기반이 궤도에 오른 상태에서 중간에 합류했는데요, 프런트엔드 프로젝트의 일부 영역에서 GitHub Actions를 활용한 자동화 구축이 되어 있었습니다.
그러던 중 이번에 새롭게 시작하는 SEO 학습 프로젝트에 GitHub Actions로 자동화를 적용해보면 좋겠다는 생각이 들었습니다. 그래서 기존 코드와 공식문서를 참고하고 보다 더 깊게 학습하며 포스트를 작성해보겠습니다.
목표
이번 포스트를 통해 이런 것들이 가능합니다.
- (PR 기능) Pull Request 추가시
개발 카테고리 Label
자동으로 달기- feature, bugfix, refactor 등
- (PR 기능) Pull Request 추가시
개발 영역(area) Label
자동으로 달기- area:ui, area:hooks 등
- (PR 기능) Pull Request 추가시
코드 변화량(diff) Label
자동으로 달기- diff:xl, diff:sm 등
- (Issue 기능) Issue 생성시
이슈 카테고리 Label
자동으로 달기- feature, bugfix 등
- (학습) labeler의 설정법
오늘 언급되는 코드는 template화하여 별도 repository로 구분했습니다. 내용 작성 중에 이 repository의 코드를 일부 인용할 예정입니다.
미리 알고 계시면 좋아요.
- 이번 포스팅은 Docker와 배포를 다루지 않습니다. Docker Image 생성이나 Deploy까지 고려하신다면 GitHub Action으로 CI/CD 구축하기 포스트를 참고하시기 바랍니다.
- CI 코드와 여러 키워드의 모든 설명은 하지 않을 예정입니다. 본 포스트의 목적은 label 자동화의 편의성 확보입니다. CI/CD 관련해서는 정확히 학습하여 기회가 된다면 별도의 포스팅으로 준비해보겠습니다.
labeler 오픈소스
label을 붙이는 작업은 여러 labeler 오픈소스가 도와줍니다.
labeler의 비교
actions/labeler
GitHub workflows에는 공식 labeler가 있습니다. 스타는 2.2K정도 되죠. 하지만 저는 srvaoa/labeler를 사용할 겁니다.
srvaoa/labeler
이름으로 아실 수 있듯이 개인 개발자의 labeler 프로젝트입니다. star가 86에 불과하는 작은 프로젝트예요. 그럼에도 왜 이 labeler를 선택했는지 설명이 필요할 것 같습니다.
labeler의 선택
공식 labeler의 적은 기능 지원
우선 공식 labeler를 살펴보시면 아시겠지만, 기능이 많지 않다고 느껴졌습니다. branch명이나 특정 파일 경로, 파일 확장자의 변경을 감지하는 게 전부죠. 하지만 srvaoa/labeler는 앞서 설명한 기능은 물론이고, 변경의 크기 비교, 제목, 본문, 작성자/작성자그룹 등 다양한 실행 조건들을 지원합니다.
빠른 속도
공식 labeler는 TypeScript로 작성된 반면, srvaoa/labeler는 Go 언어로 작성되어 빠른 속도를 자랑합니다. 프로젝트 규모와 관계 없이 보통 7초 내외로 Job이 종료되는 퍼포먼스를 보여줬습니다.
주관적인 제한점
공식 labeler의 기능이 부족하기만 한 것은 아닐 겁니다. 공식인데 이유가 있겠죠. 다만 공식 문서 페이지가 없고, README에는 제한적인 기능만 소개하고 있습니다. 그래서 제 상황에 필요한 기능이 한 눈에 봤을 때 부족했다고만 설명하겠습니다.
글을 보시는 분들은 상황에 맞춰 적절한 오픈소스를 선택하시면 됩니다. srvaoa/labeler를 사용하시겠다면 글을 이어서 봐주시면 되겠습니다.
pr_labeler.yml
본격적으로 CI 파이프라인을 구성해보겠습니다. 우선 Pull Request label 자동화부터 해보겠습니다. 프로젝트 root 위치에 .github/workflows/pr_labeler.yml
를 추가하고 내용을 이렇게 구성합니다.
출처: git-animals-client/pr_labeler.yml
# .github/workflows/pr_labeler.yml
name: "Pull Request Labeler"
permissions:
contents: read
pull-requests: write
on:
pull_request:
branches:
- "**"
types:
- opened
- reopened
- synchronize
- ready_for_review
jobs:
labeler:
runs-on: ubuntu-latest
steps:
- uses: srvaroa/labeler@v1.13.0
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
간단히 설명하면
- 모든 branch의 pull request에 대하여
- opened, …, ready_for_review 이벤트 타입일 때,
opened
: PR이 처음 생성되었을 때reopened
: 닫혔던 PR이 다시 열렸을 때synchronize
: PR branch에 새로운 커밋의 push로 업데이트될 때ready_for_review
: Draft 상태의 PR이 리뷰 준비가 완료되어 Ready for review 상태로 변경될 때
- 이 job을 실행한다.
라고 생각해주시면 됩니다. 자세한 이벤트 type에 대한 정보는 GitHub Actions - activity types를 참고해주세요.
안정성을 위한 version 고정
참고 코드는 srvaroa/labeler@master
로 되어 있지만, master 버전은 추후 언제든 바뀔 수 있으니 안정성을 고려해 버전을 v1.13.0
(현재 최신 버전)으로 고정해보았습니다.
GITHUB_TOKEN
파일에는 이런 코드가 있는데요, 어딘가에 GITHUB_TOKEN
을 넣어주어야만 할 것 같이 생겼습니다.
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
하지만 걱정하지 마세요. GitHub이 자동으로 생성해줍니다. (출처: GitHub Docs - 자동 토큰 인증)
permissions
permissions
는 GitHub Actions가 얼마나 권한을 가질 것인지를 명시적으로 지정합니다. GitHub Actions는 최소 권한 정책을 따르기에 이후에 소개될 작업들에 권한 문제가 발생할 수 있습니다. 명시적으로 지정하여 안전하게 사용하는 걸 권하기에 추가해주었습니다.
job-level에서도 지정할 수 있기 때문에 이렇게 세부 지정해줘도 됩니다.
jobs:
labeler:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
steps:
- uses: srvaroa/labeler@v1.13.0
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
issue_labeler.yml
이번에는 Pull Request가 아니라, issue에도 자동으로 label을 달아볼 겁니다. .github/workflows/issue_labeler.yml
파일을 만들고 코드를 이렇게 구성해보겠습니다.
# .github/workflows/issue_labeler.yml
name: "Issue Labeler"
permissions:
contents: read
issues: write
on:
issue:
types:
- opened
- reopened
- edited
jobs:
labeler:
runs-on: ubuntu-latest
steps:
- uses: srvaroa/labeler@v1.13.0
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
위에서 대부분 설명을 했기에 가볍게 넘어가겠습니다.
labeler 파일의 병합
왜 pr_labeler.yml
과 issue_labeler.yml
을 나눠야 하나요?
물론 이렇게 한 번에 실행할 수도 있습니다. (코드가 세로로 너무 길어지기에 activity type은 리스트 형식으로 묶어 가독성을 확보했습니다.)
# .github/workflows/pr_issue_labeler.yml
name: "Pull Request & Issue Labeler"
permissions:
contents: read
pull-requests: write
issues: write
on:
pull_request:
branches:
- "**"
types: [opened, reopened, synchronize, ready_for_review]
issue:
types: [opened, reopened, edited]
jobs:
labeler:
runs-on: ubuntu-latest
steps:
- uses: srvaroa/labeler@v1.13.0
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
파일을 구분한 이유
하지만, 제가 파일을 나눈 이유는 이렇습니다.
pull request
와issue
라는 관심사에 따라 구분할 수 있다.- job의 세부 설정이 복잡하다면 구분하는 것이 가독성의 이점이 있다.
- job이 trigger되는 상황을 구분하여 labeler의 확인 조건을 나눠 성능 이점을 가질 수 있다.
그리고 그 밖에도 Pull Request 시에 이렇게 yaml name이 표시되어 용도가 명확하지 않았죠.

이건 기호에 따라 맞춰 선택해주시면 되겠습니다.
labeler.yml
다음은 본격적으로 각 label들이 붙는 조건들을 설정해보겠습니다. 프로젝트 root 위치에 .github/labeler.yml
를 만들고 코드를 이렇게 추가합니다.
코드가 너무 길어서 내용 이해를 위한 일부 코드만 남깁니다.
코드 전문은 labeler-template - labeler.yml을 참고해주세요.
# .github/labeler.yml
version: 1
issues: True
labels:
## Issue ========================================================
### Title -------------------------------------------------------
- label: "❓question"
type: "issue"
title: "^(Q.|Question).*"
- label: "❓question"
type: "issue"
title: ".*\\?$"
### Body --------------------------------------------------------
- label: "🐞bug"
type: "issue"
body: "- \\[x\\] 버그"
## Pull Request ===================================================
### File / Directory Based (PRs only) -----------------------------
- label: "📝docs"
files:
- ".*\\.md"
- label: "✅test"
files:
- ".*\\.test\\.(ts|tsx)$"
### Branch (PRs only) ---------------------------------------------
- label: "✨feature"
branch: "(feat|feature)/.*"
### Size (PRs only) -----------------------------------------------
- label: "📏diff:XS"
size:
below: 50
...
- label: "📏diff:XL"
size:
above: 500
여기부터는 프로젝트 구조에 따라 달라집니다.
프로젝트마다 참조하는 파일의 구조가 다르거든요.
코드 설명해드릴테니 직접 구성해봅시다.
labeler 공식문서 - Conditions를 보며 설정에 필요한 요소들을 하나하나 쪼개서 해석해보겠습니다.
label의 기본 구성
labels:
- label: "(label_name)"
(condition1)
(condition2)
...
이렇게 구성되는데요, condition
이란 label이 붙는 조건에 해당합니다. 이렇게 한 label에 쌓으면 and 조건, 그리고 같은 label을 여러 문 만들면 or 조건을 만들 수 있습니다.
한 번 label condition을 알아보겠습니다.
label condition
files
변경 파일의 경로로 label을 붙입니다.
- docs: labeler - files
- 보통은 디렉토리 레벨로 설정합니다.
- 일부 경우엔 파일 레벨로 설정합니다.
labels:
- label: "📝docs"
files:
- ".*\\.md"
- label: "✅test"
files:
- ".*\\/__tests__\\/.*"
만약 monorepo를 사용하고 있다면 app과 package 레벨로 설정하여 어떤 범위가 변경되었는지 쉽게 파악할 수 있습니다.
- label: 'area:Web'
files:
- 'apps/web/*'
size
변경 라인의 숫자로 label을 붙입니다.
- docs: labeler - size
- 변경 라인은 추가된 라인과 삭제된 라인의 합으로 계산됩니다.
above
,below
조건으로 범위를 설정할 수 있습니다.
- label: "📏diff:S"
size:
above: 50
below: 150
branch
브랜치 이름으로 label을 붙입니다.
- docs: labeler - branch
- 정규표현식을 지원합니다.
- label: "✨feature"
branch: "(feat|feature)/.*"
type
label을 붙이는 action의 타입을 제한합니다.
- docs: labeler - type
- label: "🛠️bugfix"
type: "pull_request"
branch: "^fix/.*"
title
타이틀 텍스트로 label을 붙입니다.
- docs: labeler - title
- 정규표현식을 지원합니다.
- label: "❓question"
type: "issue"
title: "^(Q.|Question).*"
body
본문 텍스트로 label을 붙입니다.
- docs: labeler - body
- 정규표현식을 지원합니다.
- label: "🛠️enhancement"
type: "issue"
body: "- \\[x\\] 기능 개선"
기타 label condition
그 밖에도 쓰면 좋은 조건들이 있습니다.
negate
: 조건을 반대로 해석합니다.appendOnly
:true
로 설정하면 조건이 나중에 바뀌어도 제거하지 않습니다.- ...
작성자와 팀 단위로 label을 붙이는 등 다양한 조건들을 활용해 다채롭게 고도화할 수 있습니다. 나머지는 labeler - conditions를 참고해주세요.
정규표현식에 유의하기
사실 이 포스트를 쓰는 게 예상보다 오래 걸렸습니다. 정규표현식을 사용할 수 있다고 하는데 계속 조건이 적용되지 않았거든요.
알고보니 Go 언어를 사용한 이 GitHub Action은 정규표현식을 사용할 때 탈출문자(escape character)를 single backslash(\
)가 아니라 double backslash(\\
)로 처리해야 했습니다. labeler yaml에서 backslash를 하나 소비하고, golang에서 backslash를 하나 더 소비하기 때문이라고 하네요. (출처: srvaroa/labeler - Some regex not working in filter via property)

기타 꿀팁
자동 할당(auto_assign)
개요
- 공식문서: https://github.com/marketplace/actions/auto-assign-action
- 주요 기능
- PR 리뷰어(reviewer) 자동 설정
- PR 담당자(assignee) 자동 설정
.github/auto-assign.yml
파일에서 코드를 전개합니다.
중급 설정
공식 문서에서는 CI레벨의 job까지 소개해주는데, 보통은 CI까지 필요 없이 이 정도면 될 것 같네요.
(출처: Auto Assign Action > Single Reviewers List)
# .github/auto-assign.yml
addReviewers: true
addAssignees: false
reviewers:
- reviewerA
- reviewerB
- reviewerC
numberOfReviewers: 0
초급 설정
더더욱 일반적으로는 PR을 올린 본인이 담당자가 되기 때문에 “작성자(author) 본인을 담당자로 해줘.”라는 설정도 있습니다. 이렇게요.
(출처: Auto Assign Action > Assign Author as Assignee)
# .github/auto-assign.yml
addAssignees: author
분량을 고려해서 각 keyword와 고급 기능은 생략했는데요, 자세한 내용은 Auto Assign Action 공식문서를 참고해주세요.
GitHub API로 Label 생성
job이 실행되면 labeler.yml
파일에 정의된 label은 자동으로 생성됩니다. 하지만 label은 색상이나 설명 등 부가 정보들이 있고, 이걸 명시적으로 만들고 싶을 수 있겠죠.
그런데 이게 하나하나 만들면 상당히 귀찮습니다. 그래서 shell script로 만들어보려 합니다.
환경은 MacOS + brew를 사용합니다.
GitHub CLI 설치 및 로그인
# GitHub CLI 설치
brew install gh
# GitHub 로그인
gh auth login
저는 Web UI를 사용해 로그인하는 게 편하기 때문에 아래와 같은 옵션을 선택해 로그인 했습니다.
> gh auth login
? Where do you use GitHub? GitHub.com
? What is your preferred protocol for Git operations on this host? HTTPS
? Authenticate Git with your GitHub credentials? Yes
? How would you like to authenticate GitHub CLI? Login with a web browser
! First copy your one-time code: 6E83-3946
Press Enter to open https://github.com/login/device in your browser...
✓ Authentication complete.
- gh config set -h github.com git_protocol https
✓ Configured git protocol
✓ Logged in as Orchemi
shell script 생성
저는 root 경로에서 /scripts
폴더를 하나 만들고, create-labels.sh
라는 이름의 shell script 파일을 만들었습니다. 그리고 이렇게 코드를 구성했습니다.
#!/bin/bash
# GitHub repository 정보
REPO_OWNER="username"
REPO_NAME="repo-name"
# 라벨 정보를 배열로 정의
LABELS=(
# Common
"60A5FA / ✨feature / 기능 신규 추가"
"3B82F6 / 🛠️enhancement / 기존 기능 개선"
"6B7280 / ⚙️chore / 기타 작업"
# Issue
"DC2626 / 🐞bug / 오류"
"CA8A04 / ❓question / 질문"
# Pull Request
"F5F5F5 / 📝docs / 문서 변경"
"F87171 / 🛠️bugfix / 오류 수정"
"A78BFA / 🧹refactor / 코드 리팩토링"
"34D399 / ✅test / 테스트 코드 추가"
## Size
"F3F4F6 / 📏diff:XS / 50라인 이내 변경"
"E5E7EB / 📏diff:S / 50~150라인 변경"
"D1D5DB / 📏diff:M / 150~250라인 변경"
"9CA3AF / 📏diff:L / 250~500라인 변경"
"6B7280 / 📏diff:XL / 500라인 초과 변경"
)
# 각 라벨 생성
for LABEL_INFO in "${LABELS[@]}"; do
IFS=' / ' read -r LABEL_COLOR LABEL_NAME LABEL_DESCRIPTION <<< "$LABEL_INFO"
echo "Creating label: $LABEL_NAME with color $LABEL_COLOR"
gh api repos/$REPO_OWNER/$REPO_NAME/labels -f name="$LABEL_NAME" -f color="$LABEL_COLOR" -f description="$LABEL_DESCRIPTION"
done
echo "All labels have been created successfully!"
우선 이런 식으로 구성됩니다.
REPO_OWNER
와REPO_NAME
의 정보를 기반으로 하여 repository를 찾아갑니다.색상 / 이름 / 설명
으로 구성된 label 요소들의 배열LABELS
을 만듭니다.LABELS
의 label 정보를 순회하며, 각각/
로 쪼개어 변수로 지정합니다.- 위의 모든 정보를 조합해 github label 생성 api를 보냅니다.
구분점이나 순서 등 정보들은 원하시는대로 바꿔서 구성하시면 됩니다.
참고로 첫 줄의 주석 #!/bin/bash
은 shebang(샤뱅)이라고 하는데, OS가 어떤 프로그램으로 이 shell script를 실행할지 알게 해주는 역할을 합니다. 꼭 넣어주세요.
shell script 실행
# 실행권한 부여
chmod +x scripts/create-labels.sh
# 코드 실행
./scripts/create-labels.sh
이렇게 shell script를 통해 쉽게 디테일한 label을 만들어 봤습니다.
(안 익숙하신 분들을 위해) 참고로 script를 실행하면 이런 게 나올텐데 이 상태를 나가는 단축키는 q
입니다.
{
"id": ...,
"node_id": "...",
"url": "https://api.github.com/repos/Orchemi/huns-seo/labels/✨feature",
"name": "✨feature",
"color": "60A5FA",
"default": false,
"description": "기능 신규 추가"
}
(END)
GitHub Issue Template 활용
issue labeler는 불필요?
body condition을 언급하며, issue body에 포함된 문자열 확인을 통해 label을 붙이는 예시를 들었습니다. 하지만 issue의 경우 GitHub Issue Template을 활용하면 label의 지정도, default title, author, body 등도 사전에 설정할 수 있습니다. 한 마디로 issue는 굳이 labeler가 필요하지 않을 수 있다는 것이죠. Repository Issue 템플릿 구성 문서를 참고해주세요.
issue form
추가로 Issue Form을 사용하면 yaml 구조로 더 고도화된 Issue Template을 만들 수 있으니 참고하시면 좋습니다.

마치며
럭키비키잖아
사실 최근 회사 GitLab CI/CD를 구축하고 모듈화하면서 CI 코드에 대해 많이 공부하게 되었는데요, 이번에 GitHub CI 코드를 학습하고 적용할 때 큰 도움이 되었습니다. 방식이 크게 다르진 않더라고요. 새로운 학습을 할 때 배경지식은 역시 중요한가 봅니다.
생각보다 오래 걸렸다
간단히 내용 정리만 하려 했는데 생각보다 R&D 시간이 길어졌습니다. 이런 이유 때문이었죠.
- Golang에서 정규표현식을 다루는 방식이 익숙치 않았어요.
- labeler가 제 예상보다 정말 많은 기능들을 지원했어요. 다 훑어보느라 좀 걸렸어요.
- 참고했던 코드에서도 오류가 조금 있었어요. 정규표현식에서요.
제 주말을... 많이 썼지만 많이 알아가고 내용도 잘 정리해서 뿌듯합니다.
피드백은 언제나 환영
모르는 걸 공부하면서 보기 쉽게 정리하는 과정이었고, 저는 최선을 다했습니다. 하지만 더 좋은 방법이 있거나, 어쩌면 제 설명에 오류가 있을 수도 있겠죠. 그런 점이 있다면 피드백은 언제든지, 얼마든지 환영이니 댓글이나 이메일로 알려주세요😁😁
정말 마치며
재미있는 연구였습니다. 많은 도움이 되시길 바랄게요. 감사합니다!