詩と創作・思索のひろば

ドキドキギュンギュンダイアリーです!!!

Fork me on GitHub

GitHub Actions から Cloud Datastore エミュレータを利用する

盆栽のテストを書くのに Cloud Datastore エミュレータ が必要になったので、GitHub Actions で利用してみた。

大きく分けて方法は2つある。

ジョブ中で明示的に gcloud beta emulators datastore start する

setup-gcloud というアクションで gcloud コマンドをインストールできる。これを使う方法。

このままでは必要なコンポーネントはついてこないので、加えて gcloud components install する必要がある。

また、エミュレータが起動したのを待つために、curl を --retry-connrefused つきで利用する、というわけで以下のようになる(全景はこちら)。

    - run: |
        gcloud components install cloud-datastore-emulator --quiet
        gcloud components install beta --quiet
    - run: |
        gcloud beta emulators datastore start --project prchecklist-test &
        curl http://localhost:8081 --silent --retry 30 --retry-connrefused --retry-delay 1
    - run: |
        eval $(gcloud beta emulators datastore env-init)
        ...

サービスコンテナを使う

上記の方法でもいいんだけど、本質的でないことに行数を費やしてしまっている気がしてしまってどうもよろしくない。

GitHub Actions にはサービスコンテナという概念があって、ジョブで利用したいサービスを宣言的に指定できるらしい。MySQL とか Redis とか使うならこれだろうから、Datastore もこれにしたい。

サービスコンテナは Docker イメージを起動するかたち。オフィシャルに google/cloud-sdk ってイメージがあって、これで gcloud コマンドを利用できるんだけど、見るかぎり GitHub Actions のサービスコンテナでは今のところ CMD を外から指定できないようなので、エミュレータを起動する CMD をもったイメージを作る必要がある。

雑に検索してみた感じだと、ベースのバージョンに追従しつつ信用できそうな(=リポジトリと連携して automated build が有効になってそうな)やつがないので自作する。ただ書いてみたかっただけかもしれない。

https://hub.docker.com/r/motemen/datastore-emulator

google/cloud-sdk に CMD と HEALTHCHECK を足しただけ。これを使うと、以下のように書けてスッキリした(全景)。

    services:
      datastore-emulator:
        image: motemen/datastore-emulator
        ports:
          - '8081:8081'
        env:
          CLOUDSDK_CORE_PROJECT: ...

そして

書いていたらまったく同じ課題を解決しようとしている記事をはっけんしました……(これを読んで自分の actions 設定を手直しした)。

budougumi0617.github.io

GitHub ActionsでRustプロジェクトをクロスビルドしてリリースする

前のエントリではバイナリを GitHub のリリースとして配布しているけれど、これは GitHub Actions でビルドとリリースのアップロードを行っている。いろいろ試行錯誤した結果なのでメモを残しておく。

ビルドに成功してからリリースを作成する、とした違いはあるけど、基本的に以下のエントリを参考にしている。

【GitHub Actions】Go言語の自動テストからリリースまでを作ってみた - Qiita

できあがりの workflow はこういう感じ。

https://github.com/motemen/slack-stream-json/blob/97d3745dcc8931a1d75217573d5ca60705be632f/.github/workflows/release.yml

一度 CI をセットアップしてしまうと、そのあと触ることは全然ないので、時間をあけて完全に忘れてしまった状態でも、問題なくメンテできるようになっていてほしい。そういう観点では、公式っぽいアクションだけで構成できたのはよかった。

ジョブはこういう流れになっている。

  • Build (matrix)
  • Create-release
  • Upload-release (matrix)

Build

  build:
    strategy:
      matrix:
        target:
          - x86_64-unknown-linux-gnu
          - x86_64-pc-windows-gnu
          - x86_64-apple-darwin
        include:
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
          - target: x86_64-pc-windows-gnu
            os: ubuntu-latest
          - target: x86_64-apple-darwin
            os: macos-latest

    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v1

      # https://github.com/actions/cache/blob/master/examples.md#rust---cargo
      - name: Cache cargo registry
        uses: actions/cache@v1
        with:
          path: ~/.cargo/registry
          key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
      - name: Cache cargo index
        uses: actions/cache@v1
        with:
          path: ~/.cargo/git
          key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
      - name: Cache cargo build
        uses: actions/cache@v1
        with:
          path: target
          key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}

      - uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          override: true
      - uses: actions-rs/cargo@v1.0.1
        with:
          command: build
          args: --release --target=${{ matrix.target }}
          use-cross: true

      - run: |
          zip --junk-paths slack-stream-json-${{ matrix.target }} target/${{ matrix.target }}/release/slack-stream-json{,.exe}
      - uses: actions/upload-artifact@v1
        with:
          name: build-${{ matrix.target }}
          path: slack-stream-json-${{ matrix.target }}.zip

Linux、macOS、Windows それぞれでビルドしたいので、matrix を使う。なんか cross というのを使えば(actions-rs/cargo のオプションにある)1ホスト上で全部クロスビルドできそうだったんだけど、x86_64-apple-darwin 向けのビルドで "error: failed to run custom build command for backtrace-sys v0.1.32" などと言われてしまい簡単にはできなそうだった。このへんの仕組みは何も知らないので、OSごと切り替えられるのであればそちらのほうがシンプルだろうと、そのようにした。

x86_64-pc-windows-gnu 向けは Linux でビルドできた。ここが Windows でしかビルドできなかったら、zip コマンドを使い変えないといけなくて大変だったのではないだろうか……。

actions/cache も Rust/Cargo に対応している

ビルドしたバイナリは、あとでリリースに上げたいので actions/upload-artifact しておく。

Create-release

  create-release:
    needs: [build]
    runs-on: ubuntu-latest
    steps:
      - id: create-release
        uses: actions/create-release@v1.0.0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release ${{ github.ref }}
          draft: false
          prerelease: false
      - run: |
          echo '${{ steps.create-release.outputs.upload_url }}' > release_upload_url.txt
      - uses: actions/upload-artifact@v1
        with:
          name: create-release
          path: release_upload_url.txt

actions/create-release でタグに対応するリリースを作成する。リリースを作るのは一度しかできない(エラーになる。ここでちょっとハマった)ので、matrix から外して、build とは別のジョブとして立てる。

リリースのアップロード用 URL を、これも後で使うので upload-artifact しておく。

Upload-release

  upload-release:
    strategy:
      matrix:
        target:
          - x86_64-unknown-linux-gnu
          - x86_64-pc-windows-gnu
          - x86_64-apple-darwin
    needs: [create-release]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v1
        with:
          name: create-release
      - id: upload-url
        run: |
          echo "::set-output name=url::$(cat create-release/release_upload_url.txt)"
      - uses: actions/download-artifact@v1
        with:
          name: build-${{ matrix.target }}
      - uses: actions/upload-release-asset@v1.0.1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.upload-url.outputs.url }}
          asset_path: ./build-${{ matrix.target }}/slack-stream-json-${{ matrix.target }}.zip
          asset_name: slack-stream-json-${{ matrix.target }}.zip
          asset_content_type: application/zip

これが最後のフェーズ。あまりそうする必要もないけど、build と合わせるため matrix を使う。actions/download-artifact で前の2つのジョブの結果を受け取って、actions/upload-release-asset する。これで各 OS 分のファイルをリリースに添付できる。

標準出力に ::set-output と送ることで、ステップの output を設定できるらしい。おおう。ステップ名には - も使えるので、今回はケバブケースを採用してみた。

オレ流 Pull Request 作業フロー

チームで作業する同じリポジトリの中で Pull Request を送り合うのではなく、オープンソースプロジェクトに外部から PR がやってくる場合の話です。

最近のフロー

送られてきた PR に対しては、大まかには仕様の話、実装方針の話、具体的な実装の話を詰めながらマージできるように持っていくわけだけれど、それがほとんど満足いく状態になっていてマージしたいと思うタイミングになっても、変数の名前付けだとか、ちょっとした処理の書き方だとかで、相手にお願いするよりは自分で手を加えてからマージした方が手っ取り早いことがある。そういう時は PR 元のブランチを手元にチェックアウトして、そのブランチを自分の変更で進めた上で master にマージするようにすると、push 時に PR も閉じられて便利です。

motemen/lgtm.sh#1 の例。分かりにくいれど、PR にさらに 1 コミット足してからマージしてる。

この際コードは fork された外部のリポジトリにあるから、たんに git checkout すればいいわけではなく予め git fetch なりする必要がある。

で、最近いろいろ試行錯誤した結果、以下のようにするとよさそうだった。

1. フォークされたリポジトリを、そのオーナー名で remote に登録する

git remote add -f jwerle https://github.com/jwerle/lgtm.sh.git

-f オプションで、登録後 git fetch する。

2. PR 元のリモートブランチを、ローカルにチェックアウトする

git checkout -b '#1-jwerle/master' --track remotes/jwerle/master

この際 #xxx をブランチ名に入れておくと、コミットメッセージなどにブランチ名が入った時に自動的にリンクになって便利(ありがたいことに # はブランチ名に使える)。また、このブランチで git pull すると、PR が更新されたときに最新のコードを取得できる。

3. ローカルにチェックアウトされたブランチの push 先を自分のリポジトリに変更

git config --local 'branch.#1-jwerle/master.pushremote' origin

これで、このブランチを変更したときに、自分のリポジトリに push することができる。この設定後も依然として git pull は相手のリポジトリから行われる。

hub-pr

一度以上のようにしてしまうと結構便利なわけだけど、セットアップに少し面倒がある。最近 PR を受け取ることが重なったこともあり、このへんの作業はもうフローが決まってるのでコマンド化することにした。ホントはちょっとしたシェルスクリプトに収めるつもりだったのだけど、なぜか色気を出してしまった……。yak shaving だけが人生だ。

motemen/hub-pr · GitHub

go get github.com/motemen/hub-pr で入手できます。

使い方

hub-pr checkout PULL_REQUEST_NUMBER

PR 番号を与えると、以上に書いたフローでブランチを作ってくれる。zsh 用の補完を用意してるので以外と簡単なのです。

f:id:motemen:20150324003457g:plain

あとは普通にマージしてやればいいのだけど、手元でマージするとあの GitHub おなじみのマージメッセージにならないので、それと同じものを生成するコマンドも用意しておいた。

hub-pr merge BRANCH

これであたかもマージボタンを押したかのようなコミットメッセージでもってマージされます。なんか便利ですね。ほかにも hub-pr browse っていう便利なのもありますが、オマケです。コンソールから PR やイシューを扱うなら、ghi ってのが圧倒的に便利。


このフローで作業したいと思う人がいるかどうかは分かりませんが……、どうぞご利用下さい。verbose output 的なオプションがないのでわりとブラックボックス感が高い。

Go で GitHub 用のツールを書く時は github.com/github/hub 以下のパッケージを使うとなかなか快適に行えるようでした。まあそりゃそうか。

GitHub実践入門 ~Pull Requestによる開発の変革 (WEB+DB PRESS plus)

GitHub実践入門 ~Pull Requestによる開発の変革 (WEB+DB PRESS plus)

はてなで一緒に働きませんか?