태그 보관물: workflow

Smoke test를 위한 GitHub Self-hosted Runner 설정

Unittest가 개발 과정에서 논리를 점검하는데 유용하기는 하지만 이세상에는 integration test가 담당해야 하는 부분도 엄연히 존재한다. 각각의 컴포넌트들이 잘 작성되었다 할지라도 이것을 통합했을 때의 동작을 보장하는 것은 또 다른 이야기 이기 때문이다.

이 부분을 커버하기 위한 목적으로 가상의 데이터를 생성하고 프로그램의 처음 시작 부분부터 마지막 출력 부분까지(E2E) 점검하는 스크립트를 만들어 두었다. 문제는 unittest 만큼이나 이 스크립트를 자주 돌리지 않을 뿐 아니라 종종 커밋할 때 까먹는 다는 것이다.

가만, 이런 귀찮을 걸 안까먹으려고 CI를 설정해 두지 않았던가!

몇몇 리소스들을 찾아보니 GitHub workflow에서 제공하는 VM에서 docker를 돌리는 것에는 문제가 없다고 한다. 그래서 기존에 Lint와 Unitest를 수행하던 CI script에 추가로 E2E를 위한 smoke test script를 수행하는 부분을 추가하고 돌려봤더니 안타깝게도 docker instance 생성 후에 pip로 패키지들을 설치하다고 공간이 모자라서 오류가 나고 있었다.

VM에서 띄우는 docker 자체는 동작하지만 디스크 공간 같은 리소스는 충분히 주어지지 않는 것이다. 보다 높은 데이터 공간을 위해 비싼 요금제를 사용하는 것도 고려해 볼 수 있겠지만, 매번 CI를 수행할 때마다 docker instacne들을 모두 재생성 한다면 시간도 오래 걸릴 것이어서 별로 합리적인 해결 방안은 아닐 것 같다.

그래서 생각한 결론은 Self-hosted runner를 살리는 것이다. 이것 용도로 컴퓨터를 설정해야 하고 항상 네트워크에 물려 있도록 해야 하는 귀찮음은 있지만 비싼 GitHub 요금제나 instance 생성에 걸리는 시간은 아낄 수 있을것 이라 생각해서 이다.

Self-hosted runner를 설정하는 과정은 어렵지 않다. GitHub의 Actions 항목에서 Runners -> Self-hosted runner를 선택하고 “New runner” 버튼을 누르면 자세한 설정 명령어가 나오는데, 그대로 복사 붙여넣기만 하면 된다.

기존의 Lint / Unittest를 수행하는 CI는 그대로 GitHub에서 수행하고, 새로 추가된 smoke test만 self-hosted runner에서 돌리도록 해서 총 6분정도가 소요된다. 이 정도면 쓸만하다.

결론

GitHub 요금제를 올리는 방법도 있지만, 스스로 유지보수가 가능하다면 self-hosted runner를 사용해서 docker instance의 재생성을 막고 부가적인 환경 설정을 자유롭게 하는 방법도 고려해 볼만 하다.

GitHub release에 바이너리 첨부 자동화

GitHub에서 release를 생성하면 source code의 snapshot이 zip과 tar.gz로 저장된다. 여기에 추가해서 컴파일된 결과가 자동으로 추가하도록 한다면, 간단히 source code와 연계된 바이너리도 함께 배포할 수 있을 것이다.

이 글에서는 안드로이드 프로젝트를 가정해서 release를 생성할 때 안드로이드 APK를 빌드하고 source code와 함께 배포하는 간단한 workflow를 설명한다.

전체 코드

Event trigger

on:
  release:
    types: [published]

Release에서만 동작하므로 event trigger는 release – published이다. 이 event는 web상에서 새로운 release package를 publish 할때 trigger 된다.

환경변수

env:
  TAG: ${{ github.ref_name }}
  ASSET_FILE_PATH: "./prebuilt-${{ github.ref_name }}.zip"

Release package를 생성할 때 넣는 version의 이름은 github.ref_name으로 참조 된다. 첨부되는 파일의 이름은 prebuilt-<version_tag>.zip으로 설정한다. 참고로 GitHub에서는 release에 추가되는 소스코드외의 파일들을 “Asset”이라 부른다.

빌드 수행 및 Asset 생성

    # Checkout source code and build Android APKs.
    - name: Checkout
      uses: actions/checkout@v3
    - name: Setup JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'
        cache: gradle
    - name: Build Android APK and zip
      run: |
        sh ./gradlew assemble
        zip ${{ env.ASSET_FILE_PATH }} \
          ./app/build/outputs/apk/debug/app-debug.apk \
          ./app/build/outputs/apk/release/app-release*.apk

Android build를 위한 JAVA를 설정하고 Gradle의 assemble target을 설정하여 APK file에 대한 빌드를 수행한다. Assemble은 debug와 release용 두개의 apk를 성성하므로 이들을 하나의 zip파일로 만들어서 prebuilt-<version_tag>.zip에 추가한다.

Release에 Asset 추가

Release에 asset을 추가해주는 GitHub action은 보이질 않는다. 그래서 GitHub script를 이용해서 release에 asset을 추가하는 동작을 다음과 같이 정의했다. 이는 두 개의 동작으로 이루어지는데 하나는 주어진 tag로 release 정보를 찾아오는 것이고, 다른 하나는 가져온 release 정보에 파일을 업로드하는 것이다.

동작 1. Tag로 release 가져오기

GitHub script project의 README.md에 따르면 GitHub script상에서 github객체는 사전인증된(pre-authenticated) Octokit client라고 한다. 따라서 새롭게 instance를 만들지 않고 바로 Octokit의 API를 사용할 수 있다. getReleaseByTag()에 tag를 넘겨주면 통해 release에 대한 객체가 반환된다. 그 중에 release를 구분하기 위한 ID만 사용한다.

    // Get a release for given tag.
    const release = await github.rest.repos.getReleaseByTag({
       owner: context.repo.owner,
       repo: context.repo.repo,
       tag: process.env.TAG
    });
    const release_id = release.data.id;
    console.log("Release id for the tag " + process.env.TAG + ": " + release_id);

동작 2. Release에 파일 업로드 하기

앞의 과정에서 만든 업로드할 zip파일과 release를 구분할 ID 정보를 uploadReleaseAsset()에 넘겨 주면 해당 파일이 asset으로 등록된다.

    // Upload release assets.
    const fs = require("fs");
    const filename = process.env.ASSET_FILE_PATH.replace(/^.*[\\/]/, "");
    var uploaded = await github.rest.repos.uploadReleaseAsset({
       owner: context.repo.owner,
       repo: context.repo.repo,
       release_id: release_id,
       name: filename,
       data: await fs.readFileSync(process.env.ASSET_FILE_PATH),
    });
    console.log(
       process.env.ASSET_FILE_PATH
       + " has been uploaded as " + filename
       + " to the release " + process.env.TAG);

주의: upload 동작은 release를 수정하는 것이므로 workflow가 repository에 대한 write permission이 있어야 하므로 settings 항목에서 에서 write permission이 허가되어 있는지 확인한다. 이 부분이 안되어 있다면 403 error가 날 것이다.

Release workflow 실행

GitHub repo의 Code 탭에서 Tags -> Releases -> Tag -> Draft a new release를 선택한 후 Choose a tag를 눌러서 나오는 입력창에 새로 생성할 version tag를 입력해 Create new tag를 선택한 다음 Publish release 버튼을 누른다.

이 때 만들어지는 release에는 소스코드만 들어 있지만, 정상적으로 동작했다면 Actions tab에 release workflow가 등록되어 수행되는 것이 보일 것이다. Workflow가 정상 종료된 후 해당 release로 다시 가보면 asset이 등록되어 있는 것을 볼 수 있다.