Computer >> 컴퓨터 >  >> 프로그래밍 >> Bash 프로그래밍

GitHub Actions와 Pages를 활용해 GitHub 이벤트 데이터를 게시하는 방법

GitHub에서 협업하는 팀에게 이슈, 풀 리퀘스트, 댓글 등으로 기록되는 이벤트 데이터는 프로젝트를 이해하는 데 필수적인 자료입니다.

GitHub Actions가 정식 출시되면서, 우리는 프로그래밍 방식으로 GitHub 이벤트 데이터에 접근하고 이를 저장소에 보존할 수 있는 기회를 얻게 되었습니다. 데이터를 저장소 자체의 일부로 만드는 것은 GitHub 외부에 데이터를 보존하는 방법이기도 하며, GitHub Pages 같은 외부 공개 웹사이트에 데이터를 노출할 수 있게 해줍니다.

그리고 저처럼 취향이 독특하다면, GitHub 이슈 댓글을 멋진 90년대 스타일 방명록 페이지로 바꿔볼 수도 있습니다.

용도가 무엇이든 핵심 원리는 동일합니다. 단 하나의 워크플로우 파일만으로 Actions를 통해 GitHub 이벤트 데이터에 접근하고, 보존하고, 화면에 표시할 수 있습니다. 이 과정을 설명하기 위해 제 방명록을 빛나게 만들어주는 워크플로우 코드를 함께 살펴보겠습니다.

GitHub Actions의 기본 개념과 워크플로우 트리거 방식에 대한 소개가 필요하다면, 'A lightweight, tool-agnostic CI/CD flow with GitHub Actions' 문서를 참고하세요.

GitHub 이벤트 데이터 접근하기

Action 워크플로우는 몇 가지 기본 환경 변수가 설정된 환경에서 실행됩니다. 여기에는 편리한 정보가 많이 담겨 있는데, 이벤트 데이터도 포함됩니다. 이벤트 데이터에 가장 완전하게 접근하는 방법은 $GITHUB_EVENT_PATH 변수를 사용하는 것입니다. 이 변수는 전체 JSON 이벤트 페이로드가 담긴 파일의 경로를 가리킵니다.

확장된 경로는 /home/runner/work/_temp/_github_workflow/event.json 형태이며, 그 안의 데이터는 해당 웹훅 이벤트와 일치합니다. 웹훅 이벤트 데이터에 대한 문서는 GitHub REST API의 Event Types and Payloads에서 확인할 수 있습니다. JSON 데이터를 워크플로우 환경에서 사용하려면 jq 같은 도구로 이벤트 데이터를 파싱한 후 환경 변수에 담으면 됩니다.

아래는 이슈 댓글 이벤트에서 댓글 ID를 가져오는 예제입니다:

ID="$(jq '.comment.id' $GITHUB_EVENT_PATH)"

대부분의 이벤트 데이터는 JSON을 파싱하지 않고도 github.event 컨텍스트 변수를 통해 접근할 수 있습니다. 필드는 아래 예제처럼 점 표기법으로 접근하며, 역시 동일한 댓글 ID를 가져옵니다:

ID=${{ github.event.comment.id }}

제 방명록에서는 사용자 핸들과 날짜·시간을 함께 표시하고 싶었습니다. 이 이벤트 데이터는 다음과 같이 캡처할 수 있습니다:

AUTHOR=${{ github.event.comment.user.login }}
DATE=${{ github.event.comment.created_at }}

셸 변수는 데이터에 접근하기에 편리하지만, 수명이 짧다는 한계가 있습니다. 워크플로우 환경은 실행할 때마다 새로 생성되며, 한 단계에서 설정한 셸 변수조차 다른 단계로 이어지지 않습니다. 캡처한 데이터를 지속적으로 유지하려면 두 가지 선택지가 있습니다: 아티팩트를 사용하거나, 저장소에 커밋하는 것입니다.

이벤트 데이터 보존하기: 아티팩트 활용

아티팩트를 사용하면 데이터를 저장소에 커밋하지 않고도 워크플로우 작업(job) 간에 데이터를 유지할 수 있습니다. 예를 들어, 데이터를 더 영구적인 곳에 옮기기 전에 변형하거나 통합하고 싶을 때 유용합니다. 작업 간에 데이터를 지속해야 하는 이유는 다음과 같습니다:

워크플로우의 각 작업은 가상 환경의 새 인스턴스에서 실행됩니다. 작업이 완료되면 러너가 종료되면서 가상 환경 인스턴스도 삭제됩니다. (Persisting workflow data using artifacts)

아티팩트 사용을 돕는 두 가지 액션이 있습니다: upload-artifactdownload-artifact. 이 액션들을 사용하면 같은 워크플로우 내의 다른 작업에서 파일을 사용할 수 있습니다. 전체 예제는 'passing data between jobs in a workflow' 문서를 참고하세요.

upload-artifact 액션의 action.yml에는 키워드에 대한 설명이 담겨 있습니다. 업로드된 파일은 .zip 형식으로 저장되며, 같은 워크플로우 실행 중인 다른 작업이 download-artifact 액션으로 해당 데이터를 활용할 수 있습니다.

또한 저장소의 Actions 탭 아래 워크플로우 실행 페이지에서 아카이브를 직접 다운로드할 수도 있습니다.

작업 간에 워크플로우 데이터를 유지하는 것은 저장소 파일에 어떠한 변경도 가하지 않습니다. 생성된 아티팩트는 워크플로우 환경에만 존재하기 때문입니다.

개인적으로 셸 환경에서 작업하는 것이 익숙한 저는 아티팩트의 활용 범위가 좁다고 생각하지만, 언급하지 않고 넘어가면 실례일 것입니다. 작업 간 데이터 전달 외에도, 테스트 출력 데이터 같은 것을 .zip 형식 아카이브로 만드는 데 유용할 수 있습니다. 제 방명록 예제에서는 필요한 모든 단계를 하나의 작업에서 실행했기 때문에 작업 간 데이터 전달이 필요 없었습니다.

이벤트 데이터 보존하기: 워크플로우 파일을 저장소에 푸시

워크플로우에서 캡처한 데이터를 저장소 자체에 보존하려면, 해당 데이터를 Git 저장소에 추가하고 푸시해야 합니다. 워크플로우 안에서 셸 명령어를 사용해 데이터로 새 파일을 만들거나, 기존 파일에 데이터를 덧붙이는 방식으로 처리할 수 있습니다.

워크플로우에서 파일 생성하기

워크플로우에서 저장소 파일을 다루려면 먼저 checkout 액션으로 작업할 복사본을 가져와야 합니다:

- uses: actions/checkout@master
  with:
    fetch-depth: 1

방명록에 댓글을 추가하기 위해, 저는 셸 변수에 캡처된 이벤트 데이터를 적절한 파일로 변환합니다. 셸 매개변수 확장의 치환 기능을 사용해 사용자 입력을 살균(sanitize)하고 줄바꿈을 문단 태그로 변환합니다. 사용자 입력을 신중하게 다뤄야 하는 이유는 이전에 별도로 다룬 글이 있습니다.

- name: Turn comment into file
  run: |
    ID=${{ github.event.comment.id }}
    AUTHOR=${{ github.event.comment.user.login }}
    DATE=${{ github.event.comment.created_at }}
    COMMENT=$(echo "${{ github.event.comment.body }}")
    NO_TAGS=${COMMENT//[<>]/\`}
    FOLDER=comments

    printf '%b\n' "<div class=\"comment\"><p>${AUTHOR} says:</p><p>${NO_TAGS//$'\n'/\<\/p\>\<p\>}</p><p>${DATE}</p></div>\r\n" > ${FOLDER}/${ID}.html

printf를 사용하고 출력을 >로 새 파일에 리디렉션하면, 이벤트 데이터가 댓글 ID 번호로 이름 지어진 HTML 파일로 변환됩니다. 서식을 정리하면 다음과 같습니다:

<div class="comment">
  <p>victoriadrake says:</p>
  <p>This is a comment!</p>
  <p>2019-11-04T00:28:36Z</p>
</div>

댓글을 다룰 때 댓글 ID로 파일 이름을 짓는 한 가지 효과는, 동일한 ID를 가진 새 파일이 기존 파일을 덮어쓴다는 점입니다. 방명록에는 이것이 오히려 편리한데, 댓글 수정 시 원본 댓글 파일을 대체할 수 있기 때문입니다.

Hugo 같은 정적 사이트 생성기를 사용한다면 Markdown 형식의 파일을 만들어 content/ 폴더에 넣기만 하면, 일반적인 사이트 빌드가 나머지를 알아서 처리해줍니다.

제 소박한 방명록의 경우, 개별 댓글 파일들을 하나의 페이지로 통합하는 추가 단계가 있습니다. 실행될 때마다 기존 index.htmlheader.html 부분으로 덮어쓰고(>), 내림차순으로 정렬된 모든 댓글 파일의 내용을 찾아 덧붙이고(>>), 마지막으로 footer.html 부분을 덧붙여 페이지를 마무리합니다.

- name: Assemble page
  run: |
    cat header.html > index.html
    find comments/ -name "*.html" | sort -r | xargs -I % cat % >> index.html
    cat footer.html >> index.html

변경 사항을 저장소에 커밋하기

checkout 액션은 저장소를 클론하는 것과 완전히 동일하지 않으므로, 글을 작성하는 시점 기준으로 몇 가지 우회해야 할 문제가 남아 있습니다. 변경 사항을 master 브랜치로 성공적으로 pull, checkout, push하려면 몇 단계가 더 필요하지만, 셸에서 아주 간단히 처리할 수 있습니다.

아래는 워크플로우가 만든 변경 사항을 추가하고, 커밋하고, 저장소의 master 브랜치로 푸시하는 단계입니다.

- name: Push changes to repo
  run: |
    REMOTE=https://${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
    git config user.email "${{ github.actor }}@users.noreply.github.com"
    git config user.name "${{ github.actor }}"

    git pull ${REMOTE}
    git checkout master
    git add .
    git status
    git commit -am "Add new comment"
    git push ${REMOTE} master

원격 저장소, 즉 우리의 저장소는 github.repository 컨텍스트 변수로 지정합니다. 워크플로우가 master 브랜치에 푸시할 수 있도록 허용받으려면 secrets.GITHUB_TOKEN 변수를 사용합니다.

워크플로우 환경은 매번 새롭게 태어나므로 Git 설정이 필요합니다. 위 예제에서는 github.actor 컨텍스트 변수로 워크플로우를 시작한 계정의 사용자 이름을 입력했습니다. 이메일 역시 GitHub의 기본 noreply 이메일 주소 형식으로 설정했습니다.

이벤트 데이터 표시하기

2019년 11월 6일 수정: GitHub Actions가 Pages 사이트 빌드를 트리거하려면 개인 액세스 토큰(Personal Access Token)이 필요합니다.

GitHub Pages를 기본 secrets.GITHUB_TOKEN 변수와 함께 사용 중이고 사이트 생성기가 없다면, 워크플로우에서 저장소로 변경 사항을 푸시해도 저장소 파일만 업데이트됩니다. GitHub Pages 빌드는 실패하며, "Your site is having problems building: Page build failed."라는 오류가 발생합니다.

Actions가 Pages 사이트 빌드를 트리거할 수 있게 하려면 개인 액세스 토큰을 생성해야 합니다. 이 토큰은 저장소 설정에서 시크릿(secret)으로 저장할 수 있으며, 기본 secrets.GITHUB_TOKEN 변수 대신 워크플로우에 전달할 수 있습니다. Actions 환경과 변수에 대해서는 별도 포스트에서 더 자세히 다루었습니다.

개인 액세스 토큰을 사용하면 Actions 워크플로우가 시작한 푸시도 Pages 사이트를 업데이트합니다. 제 방명록에 댓글을 달아 직접 확인해보세요! 댓글 생성 이벤트가 워크플로우를 트리거하고, 워크플로우는 약 30초에서 1분 정도 실행된 후 방명록 페이지를 업데이트합니다.

Hugo처럼 변경 사항 게시를 위해 사이트 빌드가 필요한 경우에도 Action이 이를 처리할 수 있습니다. 다만 의도치 않은 무한 루프를 피하기 위해, 하나의 Action 워크플로우가 다른 워크플로우를 트리거하지는 않습니다. 대신 Makefile로 사이트 빌드 과정을 처리하는 것이 매우 편리한데, 어떤 워크플로우든 Makefile을 실행할 수 있기 때문입니다. 워크플로우 작업의 마지막 단계로 Makefile 실행을 추가하고, 필요한 곳에 저장소 토큰을 넣어주기만 하면 됩니다:

- name: Run Makefile
  env:
    TOKEN: ${{ secrets.GITHUB_TOKEN }}
  run: make all

이렇게 하면 워크플로우의 마지막 단계에서 업데이트된 사이트가 빌드되고 배포됩니다.

이벤트 데이터의 무한한 가능성

GitHub Actions는 이벤트 데이터를 캡처하고 활용하는 깔끔한 방법을 제공하여, 데이터가 GitHub 안에만 머물지 않도록 해줍니다. 가능성은 상상력에 의해서만 제한됩니다! 이 기술로 만들 수 있는 몇 가지 아이디어를 소개합니다:

  1. GitHub 계정이 없는 고객도 프로젝트 이슈를 열람하고 피드백을 남길 수 있는 공개형 이슈 보드
  2. 모든 저장소의 새 이슈, 댓글, PR을 자동으로 업데이트하는 RSS 피드
  3. GitHub 이슈 댓글을 입력 방식으로 활용하는 정적 사이트용 댓글 시스템
  4. 그리고 물론, 멋진 90년대 스타일 방명록 페이지

90년대 방명록 페이지를 만들었다고 말씀드렸나요? 내면의 GeoCities 덕후가 조금 흥분하고 있습니다.