들어가며
Github Actions란 Github Repository에서 발생하는 특정 이벤트를 트리거로 삼아서 워크플로우를 구성하고 실행할 수 있는 기능입니다.
여기서 사용되는 개념들이 궁금하다면 ➡️ Github Action을 구성할 때 알면 좋은 지식들 - archive.log
1. 담당자, 리뷰어 지정하기
이미 Github에 PR이 오픈되었을 때 리뷰어를 자동으로 지정하는 기능이 있지만, 저희 팀에서는 PR이 오픈되었을 때 진행되어야 하는 작업이 리뷰어 지정 외에도 몇가지 더 있었기 때문에 한 곳에서 모두 관리하기 위해서 워크플로우로 작성했습니다.
Github Action에서 제공하는 Toolkit을 이용해서 직접 PR에 지정해주는 방법도 있지만 review-assign-action라는 액션이 이미 마켓플레이스에 배포되어 있었기 때문에 이 액션을 이용해서 담당자와 리뷰어를 지정했습니다.
1on:2pull_request:3types: [opened, ready_for_review]45jobs:6assign:7runs-on: ubuntu-latest8permissions:9pull-requests: write10steps:11- uses: hkusu/review-assign-action@v112with:13assignees: ${{ github.actor }}14reviewers: ${{ vars.REVIEWERS }}>>
PR의 담당자는 github.actor로 이 워크플로를 트리거한 유저, 즉 이 PR을 오픈한 유저를 할당했고 리뷰어는 따로 정의한 워크플로 변수에서 후보를 가져오도록 설정했습니다.
1yoouyeon, ...
저희 팀은 팀원이 2명뿐이었기 때문에 리뷰어의 수를 따로 지정해주지 않았지만 max-num-of-reviewers
설정을 이용해서 리뷰어의 수를 지정할수도 있습니다.
2. 라벨 복사하기
저희 팀에서는 이슈와 PR을 분류할 때 라벨을 적극적으로 활용했습니다. 또한 이슈 단위로 작업하여 PR을 생성했기 때문에 대부분의 경우 연결된 이슈와 PR의 라벨이 동일하다는 특징이 있었고요. PR을 생성할 때 마다 연결된 이슈의 PR을 옮기는 작업이 번거로웠기 때문에 이를 워크플로우로 자동화했습니다.
기존에 라벨을 추가해주는 Pull Request Labeler 라는 액션이 있지만 이 액션은 특정 패턴에 맞는 PR에 라벨을 붙여주는 기능만 제공하고 있기 때문에 저희와 맞지 않아서 커스텀 액션을 만들어서 사용했습니다.
액션은 쉘 스크립트로도 정의할 수 있지만 저희는 팀원 모두가 익숙한 언어인 자바스크립트로 액션을 정의했습니다. PR이 발생한 브랜치에서 연결된 이슈를 찾고 (브랜치 이름에는 <type>/#<issue_number>-<branch_name>
의 규칙이 있습니다.) 해당 이슈에 달린 라벨과 기존 PR의 라벨을 합쳐서 새롭게 라벨을 추가해주는 로직입니다.
1import * as core from "@actions/core";2import * as github from "@actions/github";34const run = async () => {5try {6const token = core.getInput("token");7const octokit = github.getOctokit(token);89const { context } = github;10const { prNumber, branchName, owner, repo } = getPRContext(context);1112// step 1. 이슈 라벨 추출13const issueNumber = getIssueNumberFromBranch();14const issueLabels = await getIssueLabels(octokit, owner, repo, issueNumber);1516// step 2. PR에 라벨 추가17await addLabelsToPR(octokit, owner, repo, prNumber, issueLabels);18core.info("✅ PR에 라벨이 추가되었습니다.");19} catch (error) {20core.setFailed(`❌ 오류가 발생했습니다: ${error.message}`);21}22};2324const addLabelsToPR = async (octokit, owner, repo, prNumber, labels) => {25// step 1. 기존 PR 라벨 가져오기26const prData = await octokit.rest.pulls.get({27owner,28repo,29pull_number: prNumber,30});31const prLabels = prData.data.labels.map((label) => label.name);3233// step 2. 기존 PR 라벨과 이슈 라벨을 합쳐서 새로운 라벨 목록 생성34const newLabels = [...new Set([...prLabels, ...labels])];3536// step 3. PR에 라벨 추가37await octokit.rest.issues.addLabels({38owner,39repo,40issue_number: prNumber,41labels: newLabels,42});43};
이렇게 간단하지만 번거로운 작업을 워크플로우로 자동화하니 PR 내용 자체에 더 집중할 수 있었고 PR에서의 라벨 규칙도 직접 추가할 때보다 더 잘 지킬 수 있다는 효과가 있었습니다.
3. Zenhub 파이프라인과 연동하기
저희 팀에서는 이슈 관리를 Zenhub로 하고 있는데요. Zenhub에서 제공하고 있는 Automation은 이슈나 PR이 만들어지면 workspace에 추가하고 닫히면 close 파이프라인으로 이동시키는 기능만 제공하고 특정 이벤트에 따라서 파이프라인을 이동시키는 기능은 따로 제공하지 않고 있었습니다.
저희 팀에는 현재 작업중인 이슈는 In Progress 파이프라인에 두고 리뷰를 기다리는 PR과 연결된 이슈는 Review 파이프라인에 두는 규칙이 있었습니다. 하지만 작업을 시작할 때나, PR을 열 때 마다 직접 파이프라인을 옮겨주는 과정은 번거롭고 시간이 지날수록 놓치게 되는 문제가 있었습니다. 따라서 이슈를 특정 파이프라인으로 이동시켜주는 액션을 만들었습니다.
Zenhub에서 제공하고 있는 공식 API는 GraphQL API 인데요. 저희 팀 모두 GraphQL에 익숙하지 않았고 액션을 만들기 위해서 GraphQL을 공부하기에는 시간이 충분하지 않았기 때문에 일단은 Deprecated 상태인 REST API로 개발했습니다. 파이프라인 사이에 이슈를 이동하는 API를 이용해서 액션의 Input으로 전달된 목적지 파이프라인으로 이슈를 이동하는 간단한 로직입니다.
1import * as core from "@actions/core";2import * as github from "@actions/github";3import axios from "axios";45/** 메인 액션 함수 */6const run = async () => {7try {8const { token, zenhubToken, workspaceId, pipelineId } = getActionInputs();910// step 0. repo id 확인11const octokit = github.getOctokit(token);12const { owner, repo } = github.context.repo;13const repoId = await getRepoId(octokit, owner, repo);1415// step 1. 이슈 번호 추출16const issueNumber = getIssueNumberFromBranch();1718// step 2. 이슈를 이동19const requestUrl = `https://api.zenhub.com/p2/workspaces/${workspaceId}/repositories/${repoId}/issues/${issueNumber}/moves`;20const requestBody = { pipeline_id: pipelineId, position: "bottom" };21await axios.post(requestUrl, requestBody, {22headers: {23"X-Authentication-Token": zenhubToken,24"Content-Type": "application/json",25},26});27core.info(`✅ 이슈 #${issueNumber}를 이동했습니다.`);28} catch (error) {29core.setFailed(`❌ 오류가 발생했습니다: ${error.message}`);30}31};
이렇게 만든 액션은 다른 워크플로우에서 가져다 사용할 수 있습니다. 예를 들어, 브랜치가 생성되었을 때 연결된 이슈를 In Progress로 옮기는 워크플로우는 아래와 같습니다.
1name: Move Issue to In Progress Pipeline23on:4create:5branches:6- "**"78jobs:9move-issue-to-in-progress:10runs-on: ubuntu-latest11steps:12- name: Move to In Progress Pipeline13uses: ./.github/actions/zenhub-move-issue14with:15token: ${{ secrets.GITHUB_TOKEN }}16zenhub_token: ${{ secrets.ZENHUB_TOKEN }}17workspace_id: ${{ vars.ZENHUB_WORKSPACE_ID }}18pipeline_id: ${{ vars.ZENHUB_IN_PROGRESS_PIPELINE_ID }}
이 액션을 위에서 예로 든 In Progress 파이프라인으로 이동하는 워크플로우 외에도 PR이 열릴 때 실행되는 워크플로우에도 추가해서 열린 PR과 연결된 이슈를 Review 파이프라인으로 이동시키는 작업을 자동화했습니다. 번거롭고 놓치기 쉬운 작업을 자동화하니 PR 내용 자체에만 집중할 수 있을 뿐 아니라 Zenhub의 상황과 실제 Github의 상황이 잘 동기화되어서 Zenhub만으로도 태스크 상황을 명확히 확인할 수 있다는 장점이 있었습니다.
끝
요즘 번거로운 일들을 단순히 불편한 것으로 넘기지 않고 기술적으로 해결하거나 규칙 개선을 통해 해결하는 방법에 대해 관심이 많았는데요. 이번에 Github Actions 워크플로우를 활용해서 번거롭지만 놓치면 안되는 작업들을 자동화하는 식으로 개발 환경을 개선해보고 팀원에게 좋은 피드백을 받기도 해서 의미 있는 경험이 되었습니다. 저처럼 개발 환경 개선에 관심이 있는 분이라면 Github Actions를 활용해 보시기를 추천드립니다. 👍