Haskell AntennaのCI/CDをGitHub Actionsに移行する

Posted by Nobutada MATSUBARA(@matsubara0507) on December 12, 2020Tags: Antenna, GitHub Actions

Haskell-jpのコンテンツの一つとしてHaskell AntennaというWebページの開発・運用をしております。

バイナリのビルドやDockerイメージのビルドにTravisCIを、バイナリを実行してページの更新をするのにDroneCIを使っていました。 しかし、長らく放置していてちゃんと動作しているか怪しかったので、メンテナンスをするついでに昨今はやり(要出典)のGitHub Actionsにこれらを移行することにしました

Link to
here
Stackプロジェクトのビルド

まずはバイナリのビルドを行うように設定します。 Haskell AntennaのプログラムはHaskell Stackを利用しているので、stack buildが実行できれば良いです。

name: Build Application
on:
  pull_request: null
  push:
    branches:
    - master
jobs:
  build:
    name: ${{ matrix.os }}
    runs-on: ubuntu-18.04
    strategy:
      fail-fast: false
      matrix:
        ghc: ["8.8.4"]
    steps:
    - uses: actions/checkout@v2
    - name: Cache .stack
      id: cache-stack
      uses: actions/cache@v2
      with:
        path: ~/.stack
        key: "\
          ${{ runner.os }}-stack\
          -${{ hashFiles('**/stack.yaml.lock') }}\
          -${{ hashFiles('**/package.yaml') }}\
        "
        restore-keys: |
          ${{ runner.os }}-stack-
    - uses: haskell/actions/setup@main
      name: Setup Haskell
      with:
        ghc-version: ${{ matrix.ghc }}
        enable-stack: true
        stack-version: 'latest'
    - name: Install dependencies
      run: stack --system-ghc test --only-dependencies
    - name: Build and Test
      run: stack --system-ghc test --copy-bins --local-bin-path=./bin

これは、PRが作られたときやmasterがプッシュされたときに実行されることを想定しています。

GitHub ActionsHaskellHaskell Stackを使うには、公式が提供しているactions/setup-haskell haskell/actions/setup を利用します。 元々はactions/haskell-setupがありましたが、どうやらメンテナンスする人がいなくなったっぽくアーカイブされてしまいました。 この記事を書いている時点では移行したばかりでちゃんとタグが切られていないため、mainブランチを指定しています。 ちなみに、StackプロジェクトのGHCバージョンをhaskell/actions/setupでインストールして、stack --system-ghcをすることでキャッシュサイズを減らすことができます。

これまた余談ですが、actions/setup-haskellの方を使っていて次のようなエラーが出る場合はactions/setup-haskellのバージョンが古いです(最新では修正済みです)。haskell/actions/setupの方を使いましょう。

Installing ghc version 8.8.4
  Error: Unable to process command '::add-path::/opt/ghc/8.8.4/bin' successfully.
  Error: The `add-path` command is disabled. Please upgrade to using Environment Files or opt into unsecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_COMMANDS` environment variable to `true`. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/

Link to
here
Dockerイメージのビルドとプッシュ

antennaプログラムはDockerイメージにしてDocker Hubに置いてあります(これもGitHub Container Registryに移行したいですね)。 なので、masterの更新に合わせてDockerイメージをビルドしてプッシュするジョブを設定します。 Dockerイメージのビルドとプッシュにはdocker/build-push-actionを使います。

# さっきと同じ設定ファイルです
name: Build Application
on:
  pull_request: null
  push:
    branches:
    - master
jobs:
  build:
    name: ${{ matrix.os }}
    runs-on: ubuntu-18.04
    strategy:
      fail-fast: false
      matrix:
        ghc: ["8.8.4"]
    steps:
    ... # 割愛
    - name: Build and Test
      run: stack --system-ghc test --copy-bins --local-bin-path=./bin
    # Build and Push Docker Image
    - name: Setup QEMU
      uses: docker/setup-qemu-action@master
      with:
        platforms: all
    - name: Setup Docker Buildx
      id: buildx
      uses: docker/setup-buildx-action@master
      with:
        version: latest
    - name: Login to DockerHub
      uses: docker/login-action@v1
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}
    - name: Build and push
      uses: docker/build-push-action@v2
      with:
        context: .
        builder: ${{ steps.buildx.outputs.name }}
        tags: haskelljp/antenna:latest
        push: ${{ github.event_name != 'pull_request' }}
        build-args: local_bin_path=./bin

masterブランチへのプッシュのときにだけDockerイメージのプッシュをして欲しいので、push:github.event_name != 'pull_request' を設定しています。 また、Haskell Stackでビルドされたバイナリファイルは--local-bin-path=./binオプションで./binに置いてあります。 これをDockerfileでコピーするようにしている(下記参照)ので、docker buildの引数にlocal_bin_path=./binというのを与える必要がありました。

FROM matsubara0507/ubuntu-for-haskell:git
ARG local_bin_path
RUN mkdir -p /root/.local/bin && mkdir -p /work
ENV PATH /root/.local/bin:$PATH
WORKDIR /work
COPY ${local_bin_path} /root/.local/bin

このように前のstepまでの結果を利用するには context: . を指定する必要があります(デフォルトではgit-contextというのを使うからです)。

Link to
here
antennaプログラムの実行

最後に、masterの更新があったときにantennaプログラムを実行してHaskell Antennaページを更新するような設定をします。 日毎のスケジュール実行も設定したいので、新しいワークフローを切りました。

name: Update Antenna page
on:
  schedule:
  - cron: '0 8 * * *'
  push:
    branches:
    - master
    paths-ignore:
    - 'README.md'
    - 'CHANGELOG.md'
    - 'LICENSE'
    - '.gitignore'
jobs:
  update:
    name: ${{ matrix.os }}
    runs-on: ubuntu-18.04
    strategy:
      fail-fast: false
      matrix:
        ghc: ["8.8.4"]
    steps:
    ... # Install dependenciesまでは一緒なので割愛
    - name: Build
      run: stack --system-ghc build

    - uses: actions/checkout@v2
      with:
        ref: 'gh-pages'
        path: 'temp'
    - name: Exec Application
      run: |
        cp sites.yaml temp/sites.yaml
        cp -r image/* temp/image
        cd temp && stack exec -- antenna sites.yaml
    - name: Push changes
      env:
        COMMIT_MESSAGE: Update haskell antenna. See https://haskell.jp/antenna/ for new entries!
      run: |
        git config --local user.email "[email protected]"
        git config --local user.name "Bot"
        git status
        git add -A
        git diff --staged --quiet || git commit -m "$COMMIT_MESSAGE"
        git push origin gh-pages
      working-directory: ./temp

バイナリをビルドするところまでは一緒です。 Haskell Antennaは同じリポジトリのgh-pagesブランチに置いて、GitHub Pagesを使って公開しています。 なので、同じリポジトリのgh-pagesブランチをgit cloneしなおしてサブディレクトリに置き、そこでantennaプログラムを実行して、更新があった場合にのみプッシュしています。 同じリポジトリであれば、特に設定することなくプッシュできるのがGitHub Actionsのメリットですね。

Link to
here
おまけ:Zennを追加しました!

ついでに最近のアップデートによって、ZennHaskell Antennaに載せるサイトへ追加しましたigrep氏がしてくれました、ありがとうございます)。 アイコンの利用規約などがわからなかったのですが、GitHubPR上で直接聞いてみたところ、問題ないという回答をいただきました。 突然だったのにありがとうございます。