くりにっき

フルスタックキュアエンジニアです

RubyKaigi 2020のCFPに採択されました

昨年 に続き、今年も登壇させてもらえることになりました!

rubykaigi.org

今回は「Ruby on CI」というタイトルで、CIマニアがRuby製のgemやアプリのCIについて延々と話します。

https://rubykaigi.org/2020/presentations/sue445.html#apr10

CFPはここにありますがネタバレ情報満載なので現段階では限定公開にしておいて発表後に全公開します。

www.pixiv.net

2020/6/4 20:00追記

RubyKaigiが中止になったのでCFP全公開しています(´;ω;`)

circleci-ruby-orbs v1.5.0をリリースした

https://circleci.com/orbs/registry/orb/sue445/ruby-orbs

リリースノート

https://github.com/sue445/circleci-ruby-orbs/blob/master/CHANGELOG.md#v150

Ruby 2.7でbundler 2.1.2が入りましたが、bundler 2.1で bundle install --path などの引数がdeprecate*1されてcircleci-ruby-orbsの中でdeprecate warningが出ていたのを対応しています。

ついでに bundle_withoutbundle_with などの引数も追加しています。

1/28 21:00追記

リリースしたもののキャッシュがうまく効かない現象があったのでrevertしたのを1.5.2としてリリースしてますorz

go-mod-tidy-prを作った

GitHub Actions上でgo mod tidyしてPRを作るActionです

github.com

github.com

モチベーション

dependabotでgoのモジュールを定期的にアップデートしていると go.sum に古いバージョンのゴミが残り続けます。

github.com/aws/aws-lambda-go v1.11.1 h1:wuOnhS5aqzPOWns71FO35PtbtBKHr4MYsPVt5qXLSfI=
github.com/aws/aws-lambda-go v1.11.1/go.mod h1:Rr2SMTLeSMKgD45uep9V/NP8tnbCcySgu04cx0k/6cw=
github.com/aws/aws-lambda-go v1.12.0 h1:CgKAMdFIWExd4U6c9DUE+ax8N0fsmkYirqcfmReRCeo=
github.com/aws/aws-lambda-go v1.12.0/go.mod h1:050MeYvnG0NozqUw+ljHH9x0SwxeBnbxHVhcjn9nJFA=
github.com/aws/aws-lambda-go v1.12.1 h1:rMToYOcPFYDixQ7VNNPg78LmiqPgWD5f8zdLL+EsDAk=
github.com/aws/aws-lambda-go v1.12.1/go.mod h1:z4ywteZ5WwbIEzG0tXizIAUlUwkTNNknX4upd5Z5XJM=
github.com/aws/aws-lambda-go v1.13.0 h1:yjvZBGAxmrVQnakZ6/SE2S6L7Iwyx4CkJEcCQCc7WtU=
github.com/aws/aws-lambda-go v1.13.0/go.mod h1:z4ywteZ5WwbIEzG0tXizIAUlUwkTNNknX4upd5Z5XJM=
github.com/aws/aws-lambda-go v1.13.1 h1:qVIOD3UrEUo4amwgEBu6AI0CfnBsp71XJEYU05RbQ1k=
github.com/aws/aws-lambda-go v1.13.1/go.mod h1:z4ywteZ5WwbIEzG0tXizIAUlUwkTNNknX4upd5Z5XJM=
github.com/aws/aws-lambda-go v1.13.2 h1:8lYuRVn6rESoUNZXdbCmtGB4bBk4vcVYojiHjE4mMrM=
github.com/aws/aws-lambda-go v1.13.2/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-lambda-go v1.13.3 h1:SuCy7H3NLyp+1Mrfp+m80jcbi9KYWAs9/BXwppwRDzY=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=

go mod tidy すればゴミは消えるのですが、ローカルで定期的に叩くのも面倒です。

本来ならこの手の機能はdependabotで欲しくて実際に要望は上がってるんですが、dependabot側の対応は割と大変そうな感じがしました。

github.com

そのためGitHub Actionsの勉強がてら自分で作りました。

使い方

下記のようなymlをリポジトリに置くだけです。

パラメータの詳しい説明はMarketplaceかリポジトリを見てください。

# .github/workflows/go-mod-tidy-pr.yml
name: go-mod-tidy-pr

on:
  schedule:
    - cron: "0 0 * * 1" # Weekly build

jobs:
  go-mod-tidy-pr:
    name: go-mod-tidy-pr

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Run go-mod-tidy-pr
        uses: sue445/go-mod-tidy-pr@master
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          git_user_name: GitHub Actions
          git_user_email: github-actions@example.cpm
          # reviewer: foo
          # assign: foo
          # milestone: some_milestone
          # labels: go-mod-tidy
          # draft: "true"
          # go_mod_directory: "/dir/to/go-mod"
          # debug: "true"
          # duplicate: "true"

技術的なこと

なぜGitHub Actionsを使ったか?

API経由でPullRequestを作るにはトークンが必要なのですが、GitHub Actionsであればパーソナルアクセストークンの発行が不要なので色々便利だからです。

これはCircleCIとかにはないメリットなので、CI上でGitHubAPIを使いたい場合にはGitHub Actions一択だと思います。

余談ですがGitHubのパーソナルアクセストークンはリポジトリ単位の権限の設定がないため発行しないにこしたことはないです。(自分のパーソナルアクセストークンが漏れると自分がアクセス可能なリポジトリが全て第三者からもアクセス可能になるので大惨事)

aptで入るhubが古くてGitHub Actionsで動かなくてハマった

GitHub Actionsで hub pull-request すると下記のようなエラーが出てしばらくハマってました。

> GET https://api.github.com/user
> Authorization: token [REDACTED]
> Accept: application/vnd.github.v3+json;charset=utf-8
< HTTP 403
{"message":"Resource not accessible by integration","documentation_url":"https://developer.github.com/v3/users/#get-the-authenticated-user"}
Error getting current user: Forbidden (HTTP 403)
Resource not accessible by integration

https://github.com/github/hub/releases/tag/v2.12.3 でこの現象の修正が入っているのですが、aptで入るhubが2.7.0なのでaptを使わずにhubを入れる必要があったのがちょっとしたハマりでした

$ apt-cache show hub | grep Version
Version: 2.7.0~ds1-1+b10

使った図

チリツモのゴミが消えてスッキリしました

https://github.com/sue445/gitpanda/pull/186/files

GitHub ActionsでTravis CIのallow_failures的なやつをやりたい

いわゆるジョブが失敗しても成功扱いしたい的なやつ

tl;dr;

jobs:
  test:
    runs-on: ubuntu-latest

    container: ${{ matrix.ruby }}

    strategy:
      fail-fast: false

      matrix:
        ruby:
          - ruby:2.2
          - ruby:2.3
          - ruby:2.4
          - ruby:2.5
          - ruby:2.6
          - ruby:2.7
          - rubylang/ruby:master-nightly-bionic
        include:
          - ruby: rubylang/ruby:master-nightly-bionic
            allow_failures: "true"

    steps:
      - uses: actions/checkout@v2

      - name: Cache vendor/bundle
        uses: actions/cache@v1
        id: cache_gem
        with:
          path: vendor/bundle
          key: v1-gem-${{ runner.os }}-${{ matrix.ruby }}-${{ github.sha }}
          restore-keys: |
            v1-gem-${{ runner.os }}-${{ matrix.ruby }}-
        continue-on-error: ${{ matrix.allow_failures == 'true' }}

https://github.com/sue445/rubicure/blob/c32525010361837a1afe05e1d1217a0f9e4321e0/.github/workflows/test.yml#L12-L45

解説

  • include で任意のmatrixに変数を突っ込むことができる
    • 上の例だと ruby:ubylang/ruby:master-nightly-bionic の時だけ matrix.allow_failures"true" で設定される *1
    • ちゃんと調べていないけど include で明示的に変数セットされてない場合はエラーにならない(空文字で評価されてる?)
  • continue-on-error: true にしてると中で失敗(赤色)しても成功扱い(緑色)にしてくれる
  • fail-fast: false はなくてもいいです
    • 僕はデフォルトの fail-fast: true (どれか1つでもジョブが失敗したら他のジョブを全部キャンセルする)が嫌いなので無効にしてます

*1:trueはyaml特殊文字なのでダブルクォーテーションで囲まないと多分ダメ

plant_erd v0.2.0をリリースした

sue445.hatenablog.com

github.com

リリースノート

https://github.com/sue445/plant_erd/blob/master/CHANGELOG.md#v020

v0.2.0のトピックスはOracle対応です。ブコメとかでちょいちょいOracle対応してほしいっていうのは観測してたんですが、なんやかんやで作り切るのに1ヶ月くらいかかってました

ただし実行に Oracle Instant Client のsoファイルやDLLファイルが必要になる関係で、Oracle専用の実行ファイル( plant_erd-oracle)を用意しています

苦労点

https://github.com/sue445/plant_erd/pull/50 からかいつまんで説明。

実はOracle対応だけなら3日くらいで実装できたのですが、実装以外の部分で大半の時間を使いました。

ロスコンパイルができない

Oracle Instant Clientを含んだ実行ファイルのビルドにはOracle Instant Clientが必要になります。

しかし https://www.oracle.com/database/technologies/instant-client/downloads.html を見てもらえれば分かるようにOracle Instant Clientは対応OSが限定されているため、さすがのGoでもUbuntu上でWindowsMacの実行ファイルはビルドできませんでした。*1

幸いにもGitHub ActionsではUbuntu, Windows, MacのRunnerが提供されていたため、それぞれのOS上でビルドすることができて非常に助かりました。

初めてのWindows上でのCI

UbuntuMacであればビルドスクリプトbashでいいのですが、Windows上でのCIは今回初めてだったので試行錯誤しました。

https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsrun にあるようにWindowsだとPowerShell, cmd, Git Bashが使えます。

最初Git Bashでビルドスクリプトを書いてたもののWindows固有の問題にぶつかってうまく動かずにPowerShellで書き直しつつ、PowerShellでもうまくいかなくて結局Git Bashでビルドスクリプトで書きました。

ここのWindowsビルドだけで1〜2週間くらいかかったと思います。

途中で実行ファイルを分けた

Oracle対応ができていざ動作確認をしようとしたところ、plant_erd mysqlplant_erd postgresql のようにOracleと関係ない機能を使うだけでもInstant Clientが必要なことが判明したので苦肉の策でOracleだけ実行ファイルを分けました。

ライセンスをどうするか

Oracle Instant Clientは OTNライセンス です。

Oracle Instant Clientは再配布可能 *2 なので実行ファイルに含めて配布することは問題ありません。

しかしその場合リポジトリ上でのライセンスの表記を

  1. MITライセンスのままでいい
  2. MITライセンスをやめてOTNライセンスにする
  3. MITライセンスとOTNライセンスのデュアルライセンスにする

のどれにすればいいかで悩みました。

OTNのサポートに問い合わせたところ、MITライセンスとInstant Clientに付随するOTNライセンスは別に扱ってほしいとの回答をもらったのでREADMEにも「The program is available as open source under the terms of the MIT License. But plant_erd-oracle contains Oracle Instant Client. Oracle Instant Client is under OTN License.」(本ソフトウェアにはMITライセンスが適用されるが、実行ファイルに内包されているInstant ClientにはOTNライセンスが適用される)のような書き方になりました。

*1:余談ですがここに気づくまでに数日かかりました

*2:https://www.oracle.com/technetwork/jp/database/features/ic-faq-094177-ja.html#A4379

capistrano-itamaeのCIをDockerizeした

GitHub Actions移行時についでにやったやつ

github.com

sue445.hatenablog.com

新旧構成

モチベーション

capistrano-itamaeではインテグレーションテストのためにsshできるVMが必要だったのでVagrantとDigitalOceanを使ってました。

しかし下記のような理由でCIのDockerizeをしようと考えました

  • VM作成にはDigitalOceanのアカウントが必要なので、第三者がPRを送る時にローカルで手軽に動作確認できない。(CIでは僕のアカウントでDigitalOceanが使えるようにしてます)
  • リモートにVMを作るのでローカルにVMを作る場合と比べてオーバーヘッドがある
  • 不慮の事故でリモートのVMが残り続けると課金が発生するので定期的にcronでチェックする必要がある
  • CIで毎回Vagrantのインストールするのに時間かかる。(30秒程度)
    • CIで起動されたDockerコンテナから別のDockerコンテナを起動するのはだいたいのCIサービスで対応してる
  • Werckerが今の自分の中ではレガシー構成

実際の差分

github.com

Dockerコンテナに対してcap deployするのは下記エントリが参考になりました。

qiita.com

ちなみに最初はCircleCIを使う予定だったのですが、GitHub Actionsはローカルにコンテナを作るのに対してCircleCIだとリモートにコンテナを作る関係で

docker image build . -t ssh_server
docker run -d -p 10000:22 ssh_server

のような感じでコンテナを起動した時に、CircleCIだと10000番ポートに通信できなかったためボツになりました。 *1

ボツPR

github.com

まとめ

  • Dockerizeしたことによりモダンな構成になった
  • CIからVagrant依存をはがせた
  • リモートにコンテナを作るかローカルにコンテナを作るかの挙動の違いもCI選択の理由になるという学びがあった

*1: https://circleci.com/docs/ja/2.0/building-docker-images/ にも「ジョブとリモート Dockerはそれぞれ異なる隔離された環境内で実行されます。 そのため、Docker コンテナはリモート Docker 内で稼働しているコンテナと直接やりとりすることはできません。」って書いてる

個人gemのいくつかでRuby 2.4以下のサポートを切った

下記エントリの続き

sue445.hatenablog.com

Ruby 2.7で

require "open-uri"

open(url).read

のように書くと

warning: calling URI.open via Kernel#open is deprecated, call URI.open directly or use URI#open

のようなdeprecation warningが出ます。

しかし URI.openRuby 2.5以降にしか存在しないためRuby 2.4以下だとエラーになります。

Ruby 2.4以下とRuby 2.5以上の両方で動かすこともできたのですが、この機会にRuby 2.4以下のサポートを切ることにしました。

具体的には下記gemです